LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_tile.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2374 2572 92.3 %
Date: 2025-08-01 10:10:57 Functions: 66 73 90.4 %

          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_json.h"
      17             : #include "cpl_mem_cache.h"
      18             : #include "cpl_spawn.h"
      19             : #include "cpl_time.h"
      20             : #include "cpl_worker_thread_pool.h"
      21             : #include "gdal_alg_priv.h"
      22             : #include "gdal_priv.h"
      23             : #include "gdalgetgdalpath.h"
      24             : #include "gdalwarper.h"
      25             : #include "gdal_utils.h"
      26             : #include "ogr_spatialref.h"
      27             : #include "memdataset.h"
      28             : #include "tilematrixset.hpp"
      29             : #include "ogr_p.h"
      30             : 
      31             : #include <algorithm>
      32             : #include <array>
      33             : #include <atomic>
      34             : #include <cinttypes>
      35             : #include <cmath>
      36             : #include <mutex>
      37             : #include <utility>
      38             : #include <thread>
      39             : 
      40             : #ifndef _WIN32
      41             : #define FORK_ALLOWED
      42             : #endif
      43             : 
      44             : //! @cond Doxygen_Suppress
      45             : 
      46             : #ifndef _
      47             : #define _(x) (x)
      48             : #endif
      49             : 
      50             : // Unlikely substring to appear in stdout. We do that in case some GDAL
      51             : // driver would output on stdout.
      52             : constexpr const char PROGRESS_MARKER[] = {'!', '.', 'x'};
      53             : constexpr const char END_MARKER[] = {'?', 'E', '?', 'N', '?', 'D', '?'};
      54             : 
      55             : constexpr const char *STOP_MARKER = "STOP\n";
      56             : 
      57             : namespace
      58             : {
      59             : struct BandMetadata
      60             : {
      61             :     std::string osDescription{};
      62             :     GDALDataType eDT{};
      63             :     GDALColorInterp eColorInterp{};
      64             :     std::string osCenterWaveLength{};
      65             :     std::string osFWHM{};
      66             : };
      67             : }  // namespace
      68             : 
      69             : /************************************************************************/
      70             : /*                       GetThresholdMinTilesPerJob()                   */
      71             : /************************************************************************/
      72             : 
      73          13 : static int GetThresholdMinThreadsForSpawn()
      74             : {
      75             :     // Minimum number of threads for automatic switch to spawning
      76          13 :     constexpr int THRESHOLD_MIN_THREADS_FOR_SPAWN = 8;
      77             : 
      78             :     // Config option for test only
      79          13 :     return std::max(1, atoi(CPLGetConfigOption(
      80             :                            "GDAL_THRESHOLD_MIN_THREADS_FOR_SPAWN",
      81          13 :                            CPLSPrintf("%d", THRESHOLD_MIN_THREADS_FOR_SPAWN))));
      82             : }
      83             : 
      84             : /************************************************************************/
      85             : /*                        GetThresholdMinTilesPerJob()                  */
      86             : /************************************************************************/
      87             : 
      88         178 : static int GetThresholdMinTilesPerJob()
      89             : {
      90             :     // Minimum number of tiles per job to decide for automatic switch to spawning
      91         178 :     constexpr int THRESHOLD_TILES_PER_JOB = 100;
      92             : 
      93             :     // Config option for test only
      94             :     return std::max(
      95         178 :         1, atoi(CPLGetConfigOption("GDAL_THRESHOLD_MIN_TILES_PER_JOB",
      96         178 :                                    CPLSPrintf("%d", THRESHOLD_TILES_PER_JOB))));
      97             : }
      98             : 
      99             : /************************************************************************/
     100             : /*           GDALRasterTileAlgorithm::GDALRasterTileAlgorithm()         */
     101             : /************************************************************************/
     102             : 
     103         140 : GDALRasterTileAlgorithm::GDALRasterTileAlgorithm()
     104         140 :     : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
     105             : {
     106         140 :     AddProgressArg();
     107             :     AddArg("spawned", 0, _("Whether this is a spawned worker"),
     108         280 :            &m_spawned)
     109         140 :         .SetHidden();  // Used in spawn mode
     110             : #ifdef FORK_ALLOWED
     111             :     AddArg("forked", 0, _("Whether this is a forked worker"),
     112         280 :            &m_forked)
     113         140 :         .SetHidden();  // Used in forked mode
     114             : #else
     115             :     CPL_IGNORE_RET_VAL(m_forked);
     116             : #endif
     117         280 :     AddArg("config-options-in-stdin", 0, _(""), &m_dummy)
     118         140 :         .SetHidden();  // Used in spawn mode
     119             :     AddArg("ovr-zoom-level", 0, _("Overview zoom level to compute"),
     120         280 :            &m_ovrZoomLevel)
     121         140 :         .SetMinValueIncluded(0)
     122         140 :         .SetHidden();  // Used in spawn mode
     123         280 :     AddArg("ovr-min-x", 0, _("Minimum tile X coordinate"), &m_minOvrTileX)
     124         140 :         .SetMinValueIncluded(0)
     125         140 :         .SetHidden();  // Used in spawn mode
     126         280 :     AddArg("ovr-max-x", 0, _("Maximum tile X coordinate"), &m_maxOvrTileX)
     127         140 :         .SetMinValueIncluded(0)
     128         140 :         .SetHidden();  // Used in spawn mode
     129         280 :     AddArg("ovr-min-y", 0, _("Minimum tile Y coordinate"), &m_minOvrTileY)
     130         140 :         .SetMinValueIncluded(0)
     131         140 :         .SetHidden();  // Used in spawn mode
     132         280 :     AddArg("ovr-max-y", 0, _("Maximum tile Y coordinate"), &m_maxOvrTileY)
     133         140 :         .SetMinValueIncluded(0)
     134         140 :         .SetHidden();  // Used in spawn mode
     135             : 
     136         140 :     AddOpenOptionsArg(&m_openOptions);
     137         140 :     AddInputFormatsArg(&m_inputFormats)
     138         280 :         .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_RASTER});
     139         140 :     AddInputDatasetArg(&m_dataset, GDAL_OF_RASTER);
     140         140 :     AddOutputFormatArg(&m_outputFormat)
     141         140 :         .SetDefault(m_outputFormat)
     142             :         .AddMetadataItem(
     143             :             GAAMDI_REQUIRED_CAPABILITIES,
     144         700 :             {GDAL_DCAP_RASTER, GDAL_DCAP_CREATECOPY, GDAL_DMD_EXTENSIONS})
     145         280 :         .AddMetadataItem(GAAMDI_VRT_COMPATIBLE, {"false"});
     146         140 :     AddCreationOptionsArg(&m_creationOptions);
     147             : 
     148         280 :     AddArg("output", 'o', _("Output directory"), &m_outputDirectory)
     149         140 :         .SetRequired()
     150         140 :         .SetMinCharCount(1)
     151         140 :         .SetPositional();
     152             : 
     153         560 :     std::vector<std::string> tilingSchemes{"raster"};
     154        1260 :     for (const std::string &scheme :
     155        2660 :          gdal::TileMatrixSet::listPredefinedTileMatrixSets())
     156             :     {
     157        2520 :         auto poTMS = gdal::TileMatrixSet::parse(scheme.c_str());
     158        2520 :         OGRSpatialReference oSRS_TMS;
     159        2520 :         if (poTMS && !poTMS->hasVariableMatrixWidth() &&
     160        1260 :             oSRS_TMS.SetFromUserInput(poTMS->crs().c_str()) == OGRERR_NONE)
     161             :         {
     162        1260 :             std::string identifier = scheme == "GoogleMapsCompatible"
     163             :                                          ? "WebMercatorQuad"
     164        2520 :                                          : poTMS->identifier();
     165        1260 :             m_mapTileMatrixIdentifierToScheme[identifier] = scheme;
     166        1260 :             tilingSchemes.push_back(std::move(identifier));
     167             :         }
     168             :     }
     169         280 :     AddArg("tiling-scheme", 0, _("Tiling scheme"), &m_tilingScheme)
     170         140 :         .SetDefault("WebMercatorQuad")
     171         140 :         .SetChoices(tilingSchemes)
     172             :         .SetHiddenChoices(
     173             :             "GoogleMapsCompatible",  // equivalent of WebMercatorQuad
     174             :             "mercator",              // gdal2tiles equivalent of WebMercatorQuad
     175             :             "geodetic"  // gdal2tiles (not totally) equivalent of WorldCRS84Quad
     176         140 :         );
     177             : 
     178         280 :     AddArg("min-zoom", 0, _("Minimum zoom level"), &m_minZoomLevel)
     179         140 :         .SetMinValueIncluded(0);
     180         280 :     AddArg("max-zoom", 0, _("Maximum zoom level"), &m_maxZoomLevel)
     181         140 :         .SetMinValueIncluded(0);
     182             : 
     183         280 :     AddArg("min-x", 0, _("Minimum tile X coordinate"), &m_minTileX)
     184         140 :         .SetMinValueIncluded(0);
     185         280 :     AddArg("max-x", 0, _("Maximum tile X coordinate"), &m_maxTileX)
     186         140 :         .SetMinValueIncluded(0);
     187         280 :     AddArg("min-y", 0, _("Minimum tile Y coordinate"), &m_minTileY)
     188         140 :         .SetMinValueIncluded(0);
     189         280 :     AddArg("max-y", 0, _("Maximum tile Y coordinate"), &m_maxTileY)
     190         140 :         .SetMinValueIncluded(0);
     191             :     AddArg("no-intersection-ok", 0,
     192             :            _("Whether dataset extent not intersecting tile matrix is only a "
     193             :              "warning"),
     194         140 :            &m_noIntersectionIsOK);
     195             : 
     196             :     AddArg("resampling", 'r', _("Resampling method for max zoom"),
     197         280 :            &m_resampling)
     198             :         .SetChoices("nearest", "bilinear", "cubic", "cubicspline", "lanczos",
     199             :                     "average", "rms", "mode", "min", "max", "med", "q1", "q3",
     200         140 :                     "sum")
     201         140 :         .SetDefault("cubic")
     202         140 :         .SetHiddenChoices("near");
     203             :     AddArg("overview-resampling", 0, _("Resampling method for overviews"),
     204         280 :            &m_overviewResampling)
     205             :         .SetChoices("nearest", "bilinear", "cubic", "cubicspline", "lanczos",
     206             :                     "average", "rms", "mode", "min", "max", "med", "q1", "q3",
     207         140 :                     "sum")
     208         140 :         .SetHiddenChoices("near");
     209             : 
     210             :     AddArg("convention", 0,
     211             :            _("Tile numbering convention: xyz (from top) or tms (from bottom)"),
     212         280 :            &m_convention)
     213         140 :         .SetDefault(m_convention)
     214         140 :         .SetChoices("xyz", "tms");
     215         280 :     AddArg("tile-size", 0, _("Override default tile size"), &m_tileSize)
     216         140 :         .SetMinValueIncluded(64)
     217         140 :         .SetMaxValueIncluded(32768);
     218             :     AddArg("add-alpha", 0, _("Whether to force adding an alpha channel"),
     219         280 :            &m_addalpha)
     220         140 :         .SetMutualExclusionGroup("alpha");
     221             :     AddArg("no-alpha", 0, _("Whether to disable adding an alpha channel"),
     222         280 :            &m_noalpha)
     223         140 :         .SetMutualExclusionGroup("alpha");
     224             :     auto &dstNoDataArg =
     225         140 :         AddArg("dst-nodata", 0, _("Destination nodata value"), &m_dstNoData);
     226         140 :     AddArg("skip-blank", 0, _("Do not generate blank tiles"), &m_skipBlank);
     227             : 
     228             :     {
     229             :         auto &arg = AddArg("metadata", 0,
     230         280 :                            _("Add metadata item to output tiles"), &m_metadata)
     231         280 :                         .SetMetaVar("<KEY>=<VALUE>")
     232         140 :                         .SetPackedValuesAllowed(false);
     233          17 :         arg.AddValidationAction([this, &arg]()
     234         157 :                                 { return ParseAndValidateKeyValue(arg); });
     235         140 :         arg.AddHiddenAlias("mo");
     236             :     }
     237             :     AddArg("copy-src-metadata", 0,
     238             :            _("Whether to copy metadata from source dataset"),
     239         140 :            &m_copySrcMetadata);
     240             : 
     241             :     AddArg("aux-xml", 0, _("Generate .aux.xml sidecar files when needed"),
     242         140 :            &m_auxXML);
     243         140 :     AddArg("kml", 0, _("Generate KML files"), &m_kml);
     244         140 :     AddArg("resume", 0, _("Generate only missing files"), &m_resume);
     245             : 
     246         140 :     AddNumThreadsArg(&m_numThreads, &m_numThreadsStr);
     247             :     AddArg("parallel-method", 0,
     248             : #ifdef FORK_ALLOWED
     249             :            _("Parallelization method (thread, spawn, fork)")
     250             : #else
     251             :            _("Parallelization method (thread / spawn)")
     252             : #endif
     253             :                ,
     254         280 :            &m_parallelMethod)
     255             :         .SetChoices("thread", "spawn"
     256             : #ifdef FORK_ALLOWED
     257             :                     ,
     258             :                     "fork"
     259             : #endif
     260         140 :         );
     261             : 
     262         140 :     constexpr const char *ADVANCED_RESAMPLING_CATEGORY = "Advanced Resampling";
     263             :     auto &excludedValuesArg =
     264             :         AddArg("excluded-values", 0,
     265             :                _("Tuples of values (e.g. <R>,<G>,<B> or (<R1>,<G1>,<B1>),"
     266             :                  "(<R2>,<G2>,<B2>)) that must beignored as contributing source "
     267             :                  "pixels during (average) resampling"),
     268         280 :                &m_excludedValues)
     269         140 :             .SetCategory(ADVANCED_RESAMPLING_CATEGORY);
     270             :     auto &excludedValuesPctThresholdArg =
     271             :         AddArg(
     272             :             "excluded-values-pct-threshold", 0,
     273             :             _("Minimum percentage of source pixels that must be set at one of "
     274             :               "the --excluded-values to cause the excluded value to be used as "
     275             :               "the target pixel value"),
     276         280 :             &m_excludedValuesPctThreshold)
     277         140 :             .SetDefault(m_excludedValuesPctThreshold)
     278         140 :             .SetMinValueIncluded(0)
     279         140 :             .SetMaxValueIncluded(100)
     280         140 :             .SetCategory(ADVANCED_RESAMPLING_CATEGORY);
     281             :     auto &nodataValuesPctThresholdArg =
     282             :         AddArg(
     283             :             "nodata-values-pct-threshold", 0,
     284             :             _("Minimum percentage of source pixels that must be set at one of "
     285             :               "nodata (or alpha=0 or any other way to express transparent pixel"
     286             :               "to cause the target pixel value to be transparent"),
     287         280 :             &m_nodataValuesPctThreshold)
     288         140 :             .SetDefault(m_nodataValuesPctThreshold)
     289         140 :             .SetMinValueIncluded(0)
     290         140 :             .SetMaxValueIncluded(100)
     291         140 :             .SetCategory(ADVANCED_RESAMPLING_CATEGORY);
     292             : 
     293         140 :     constexpr const char *PUBLICATION_CATEGORY = "Publication";
     294         280 :     AddArg("webviewer", 0, _("Web viewer to generate"), &m_webviewers)
     295         140 :         .SetDefault("all")
     296         140 :         .SetChoices("none", "all", "leaflet", "openlayers", "mapml", "stac")
     297         140 :         .SetCategory(PUBLICATION_CATEGORY);
     298             :     AddArg("url", 0,
     299             :            _("URL address where the generated tiles are going to be published"),
     300         280 :            &m_url)
     301         140 :         .SetCategory(PUBLICATION_CATEGORY);
     302         280 :     AddArg("title", 0, _("Title of the map"), &m_title)
     303         140 :         .SetCategory(PUBLICATION_CATEGORY);
     304         280 :     AddArg("copyright", 0, _("Copyright for the map"), &m_copyright)
     305         140 :         .SetCategory(PUBLICATION_CATEGORY);
     306             :     AddArg("mapml-template", 0,
     307             :            _("Filename of a template mapml file where variables will be "
     308             :              "substituted"),
     309         280 :            &m_mapmlTemplate)
     310         140 :         .SetMinCharCount(1)
     311         140 :         .SetCategory(PUBLICATION_CATEGORY);
     312             : 
     313         140 :     AddValidationAction(
     314         161 :         [this, &dstNoDataArg, &excludedValuesArg,
     315        1064 :          &excludedValuesPctThresholdArg, &nodataValuesPctThresholdArg]()
     316             :         {
     317         161 :             if (m_minTileX >= 0 && m_maxTileX >= 0 && m_minTileX > m_maxTileX)
     318             :             {
     319           1 :                 ReportError(CE_Failure, CPLE_IllegalArg,
     320             :                             "'min-x' must be lesser or equal to 'max-x'");
     321           1 :                 return false;
     322             :             }
     323             : 
     324         160 :             if (m_minTileY >= 0 && m_maxTileY >= 0 && m_minTileY > m_maxTileY)
     325             :             {
     326           1 :                 ReportError(CE_Failure, CPLE_IllegalArg,
     327             :                             "'min-y' must be lesser or equal to 'max-y'");
     328           1 :                 return false;
     329             :             }
     330             : 
     331         159 :             if (m_minZoomLevel >= 0 && m_maxZoomLevel >= 0 &&
     332          55 :                 m_minZoomLevel > m_maxZoomLevel)
     333             :             {
     334           1 :                 ReportError(CE_Failure, CPLE_IllegalArg,
     335             :                             "'min-zoom' must be lesser or equal to 'max-zoom'");
     336           1 :                 return false;
     337             :             }
     338             : 
     339         158 :             if (m_addalpha && dstNoDataArg.IsExplicitlySet())
     340             :             {
     341           1 :                 ReportError(
     342             :                     CE_Failure, CPLE_IllegalArg,
     343             :                     "'add-alpha' and 'dst-nodata' are mutually exclusive");
     344           1 :                 return false;
     345             :             }
     346             : 
     347         465 :             for (const auto *arg :
     348             :                  {&excludedValuesArg, &excludedValuesPctThresholdArg,
     349         622 :                   &nodataValuesPctThresholdArg})
     350             :             {
     351         467 :                 if (arg->IsExplicitlySet() && m_resampling != "average")
     352             :                 {
     353           1 :                     ReportError(CE_Failure, CPLE_AppDefined,
     354             :                                 "'%s' can only be specified if 'resampling' is "
     355             :                                 "set to 'average'",
     356           1 :                                 arg->GetName().c_str());
     357           2 :                     return false;
     358             :                 }
     359         467 :                 if (arg->IsExplicitlySet() && !m_overviewResampling.empty() &&
     360           1 :                     m_overviewResampling != "average")
     361             :                 {
     362           1 :                     ReportError(CE_Failure, CPLE_AppDefined,
     363             :                                 "'%s' can only be specified if "
     364             :                                 "'overview-resampling' is set to 'average'",
     365           1 :                                 arg->GetName().c_str());
     366           1 :                     return false;
     367             :                 }
     368             :             }
     369             : 
     370         155 :             return true;
     371             :         });
     372         140 : }
     373             : 
     374             : /************************************************************************/
     375             : /*                          GetTileIndices()                            */
     376             : /************************************************************************/
     377             : 
     378         427 : static bool GetTileIndices(gdal::TileMatrixSet::TileMatrix &tileMatrix,
     379             :                            bool bInvertAxisTMS, int tileSize,
     380             :                            const double adfExtent[4], int &nMinTileX,
     381             :                            int &nMinTileY, int &nMaxTileX, int &nMaxTileY,
     382             :                            bool noIntersectionIsOK, bool &bIntersects,
     383             :                            bool checkRasterOverflow = true)
     384             : {
     385         427 :     if (tileSize > 0)
     386             :     {
     387          32 :         tileMatrix.mResX *=
     388          32 :             static_cast<double>(tileMatrix.mTileWidth) / tileSize;
     389          32 :         tileMatrix.mResY *=
     390          32 :             static_cast<double>(tileMatrix.mTileHeight) / tileSize;
     391          32 :         tileMatrix.mTileWidth = tileSize;
     392          32 :         tileMatrix.mTileHeight = tileSize;
     393             :     }
     394             : 
     395         427 :     if (bInvertAxisTMS)
     396           2 :         std::swap(tileMatrix.mTopLeftX, tileMatrix.mTopLeftY);
     397             : 
     398         427 :     const double dfTileWidth = tileMatrix.mResX * tileMatrix.mTileWidth;
     399         427 :     const double dfTileHeight = tileMatrix.mResY * tileMatrix.mTileHeight;
     400             : 
     401         427 :     constexpr double EPSILON = 1e-3;
     402         427 :     const double dfMinTileX =
     403         427 :         (adfExtent[0] - tileMatrix.mTopLeftX) / dfTileWidth;
     404         427 :     nMinTileX = static_cast<int>(
     405         854 :         std::clamp(std::floor(dfMinTileX + EPSILON), 0.0,
     406         427 :                    static_cast<double>(tileMatrix.mMatrixWidth - 1)));
     407         427 :     const double dfMinTileY =
     408         427 :         (tileMatrix.mTopLeftY - adfExtent[3]) / dfTileHeight;
     409         427 :     nMinTileY = static_cast<int>(
     410         854 :         std::clamp(std::floor(dfMinTileY + EPSILON), 0.0,
     411         427 :                    static_cast<double>(tileMatrix.mMatrixHeight - 1)));
     412         427 :     const double dfMaxTileX =
     413         427 :         (adfExtent[2] - tileMatrix.mTopLeftX) / dfTileWidth;
     414         427 :     nMaxTileX = static_cast<int>(
     415         854 :         std::clamp(std::floor(dfMaxTileX + EPSILON), 0.0,
     416         427 :                    static_cast<double>(tileMatrix.mMatrixWidth - 1)));
     417         427 :     const double dfMaxTileY =
     418         427 :         (tileMatrix.mTopLeftY - adfExtent[1]) / dfTileHeight;
     419         427 :     nMaxTileY = static_cast<int>(
     420         854 :         std::clamp(std::floor(dfMaxTileY + EPSILON), 0.0,
     421         427 :                    static_cast<double>(tileMatrix.mMatrixHeight - 1)));
     422             : 
     423         427 :     bIntersects = (dfMinTileX <= tileMatrix.mMatrixWidth && dfMaxTileX >= 0 &&
     424         854 :                    dfMinTileY <= tileMatrix.mMatrixHeight && dfMaxTileY >= 0);
     425         427 :     if (!bIntersects)
     426             :     {
     427           2 :         CPLDebug("gdal_raster_tile",
     428             :                  "dfMinTileX=%g dfMinTileY=%g dfMaxTileX=%g dfMaxTileY=%g",
     429             :                  dfMinTileX, dfMinTileY, dfMaxTileX, dfMaxTileY);
     430           2 :         CPLError(noIntersectionIsOK ? CE_Warning : CE_Failure, CPLE_AppDefined,
     431             :                  "Extent of source dataset is not compatible with extent of "
     432             :                  "tile matrix %s",
     433             :                  tileMatrix.mId.c_str());
     434           2 :         return noIntersectionIsOK;
     435             :     }
     436         425 :     if (checkRasterOverflow)
     437             :     {
     438         320 :         if (nMaxTileX - nMinTileX + 1 > INT_MAX / tileMatrix.mTileWidth ||
     439         320 :             nMaxTileY - nMinTileY + 1 > INT_MAX / tileMatrix.mTileHeight)
     440             :         {
     441           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Too large zoom level");
     442           0 :             return false;
     443             :         }
     444             :     }
     445         425 :     return true;
     446             : }
     447             : 
     448             : /************************************************************************/
     449             : /*                           GetFileY()                                 */
     450             : /************************************************************************/
     451             : 
     452        4253 : static int GetFileY(int iY, const gdal::TileMatrixSet::TileMatrix &tileMatrix,
     453             :                     const std::string &convention)
     454             : {
     455        4253 :     return convention == "xyz" ? iY : tileMatrix.mMatrixHeight - 1 - iY;
     456             : }
     457             : 
     458             : /************************************************************************/
     459             : /*                          GenerateTile()                              */
     460             : /************************************************************************/
     461             : 
     462         522 : static bool GenerateTile(
     463             :     GDALDataset *poSrcDS, GDALDriver *m_poDstDriver, const char *pszExtension,
     464             :     CSLConstList creationOptions, GDALWarpOperation &oWO,
     465             :     const OGRSpatialReference &oSRS_TMS, GDALDataType eWorkingDataType,
     466             :     const gdal::TileMatrixSet::TileMatrix &tileMatrix,
     467             :     const std::string &outputDirectory, int nBands, const double *pdfDstNoData,
     468             :     int nZoomLevel, int iX, int iY, const std::string &convention,
     469             :     int nMinTileX, int nMinTileY, bool bSkipBlank, bool bUserAskedForAlpha,
     470             :     bool bAuxXML, bool bResume, const std::vector<std::string> &metadata,
     471             :     const GDALColorTable *poColorTable, std::vector<GByte> &dstBuffer)
     472             : {
     473             :     const std::string osDirZ = CPLFormFilenameSafe(
     474        1044 :         outputDirectory.c_str(), CPLSPrintf("%d", nZoomLevel), nullptr);
     475             :     const std::string osDirX =
     476        1044 :         CPLFormFilenameSafe(osDirZ.c_str(), CPLSPrintf("%d", iX), nullptr);
     477         522 :     const int iFileY = GetFileY(iY, tileMatrix, convention);
     478             :     const std::string osFilename = CPLFormFilenameSafe(
     479        1044 :         osDirX.c_str(), CPLSPrintf("%d", iFileY), pszExtension);
     480             : 
     481         522 :     if (bResume)
     482             :     {
     483             :         VSIStatBufL sStat;
     484           5 :         if (VSIStatL(osFilename.c_str(), &sStat) == 0)
     485           5 :             return true;
     486             :     }
     487             : 
     488         517 :     const int nDstXOff = (iX - nMinTileX) * tileMatrix.mTileWidth;
     489         517 :     const int nDstYOff = (iY - nMinTileY) * tileMatrix.mTileHeight;
     490         517 :     memset(dstBuffer.data(), 0, dstBuffer.size());
     491        1034 :     const CPLErr eErr = oWO.WarpRegionToBuffer(
     492         517 :         nDstXOff, nDstYOff, tileMatrix.mTileWidth, tileMatrix.mTileHeight,
     493         517 :         dstBuffer.data(), eWorkingDataType);
     494         517 :     if (eErr != CE_None)
     495           2 :         return false;
     496             : 
     497             :     const bool bDstHasAlpha =
     498         544 :         nBands > poSrcDS->GetRasterCount() ||
     499          29 :         (nBands == poSrcDS->GetRasterCount() &&
     500          28 :          poSrcDS->GetRasterBand(nBands)->GetColorInterpretation() ==
     501         515 :              GCI_AlphaBand);
     502         515 :     const size_t nBytesPerBand = static_cast<size_t>(tileMatrix.mTileWidth) *
     503         515 :                                  tileMatrix.mTileHeight *
     504         515 :                                  GDALGetDataTypeSizeBytes(eWorkingDataType);
     505         515 :     if (bDstHasAlpha && bSkipBlank)
     506             :     {
     507           9 :         bool bBlank = true;
     508      327706 :         for (size_t i = 0; i < nBytesPerBand && bBlank; ++i)
     509             :         {
     510      327697 :             bBlank = (dstBuffer[(nBands - 1) * nBytesPerBand + i] == 0);
     511             :         }
     512           9 :         if (bBlank)
     513           5 :             return true;
     514             :     }
     515         510 :     if (bDstHasAlpha && !bUserAskedForAlpha)
     516             :     {
     517         495 :         bool bAllOpaque = true;
     518    27927700 :         for (size_t i = 0; i < nBytesPerBand && bAllOpaque; ++i)
     519             :         {
     520    27949200 :             bAllOpaque = (dstBuffer[(nBands - 1) * nBytesPerBand + i] == 255);
     521             :         }
     522           0 :         if (bAllOpaque)
     523         420 :             nBands--;
     524             :     }
     525             : 
     526           0 :     VSIMkdir(osDirZ.c_str(), 0755);
     527         510 :     VSIMkdir(osDirX.c_str(), 0755);
     528             : 
     529             :     auto memDS = std::unique_ptr<GDALDataset>(
     530         510 :         MEMDataset::Create("", tileMatrix.mTileWidth, tileMatrix.mTileHeight, 0,
     531        1020 :                            eWorkingDataType, nullptr));
     532        2051 :     for (int i = 0; i < nBands; ++i)
     533             :     {
     534        1541 :         char szBuffer[32] = {'\0'};
     535        3082 :         int nRet = CPLPrintPointer(
     536        1541 :             szBuffer, dstBuffer.data() + i * nBytesPerBand, sizeof(szBuffer));
     537        1541 :         szBuffer[nRet] = 0;
     538             : 
     539        1541 :         char szOption[64] = {'\0'};
     540        1541 :         snprintf(szOption, sizeof(szOption), "DATAPOINTER=%s", szBuffer);
     541             : 
     542        1541 :         char *apszOptions[] = {szOption, nullptr};
     543             : 
     544        1541 :         memDS->AddBand(eWorkingDataType, apszOptions);
     545        1541 :         auto poDstBand = memDS->GetRasterBand(i + 1);
     546        1541 :         if (i + 1 <= poSrcDS->GetRasterCount())
     547        1466 :             poDstBand->SetColorInterpretation(
     548        1466 :                 poSrcDS->GetRasterBand(i + 1)->GetColorInterpretation());
     549             :         else
     550          75 :             poDstBand->SetColorInterpretation(GCI_AlphaBand);
     551        1541 :         if (pdfDstNoData)
     552           9 :             poDstBand->SetNoDataValue(*pdfDstNoData);
     553        1541 :         if (i == 0 && poColorTable)
     554           1 :             poDstBand->SetColorTable(
     555           1 :                 const_cast<GDALColorTable *>(poColorTable));
     556             :     }
     557        1020 :     const CPLStringList aosMD(metadata);
     558         582 :     for (const auto [key, value] : cpl::IterateNameValue(aosMD))
     559             :     {
     560          72 :         memDS->SetMetadataItem(key, value);
     561             :     }
     562             : 
     563         510 :     GDALGeoTransform gt;
     564        1020 :     gt[0] =
     565         510 :         tileMatrix.mTopLeftX + iX * tileMatrix.mResX * tileMatrix.mTileWidth;
     566         510 :     gt[1] = tileMatrix.mResX;
     567         510 :     gt[2] = 0;
     568        1020 :     gt[3] =
     569         510 :         tileMatrix.mTopLeftY - iY * tileMatrix.mResY * tileMatrix.mTileHeight;
     570         510 :     gt[4] = 0;
     571         510 :     gt[5] = -tileMatrix.mResY;
     572         510 :     memDS->SetGeoTransform(gt);
     573             : 
     574         510 :     memDS->SetSpatialRef(&oSRS_TMS);
     575             : 
     576             :     CPLConfigOptionSetter oSetter("GDAL_PAM_ENABLED", bAuxXML ? "YES" : "NO",
     577        1020 :                                   false);
     578             :     CPLConfigOptionSetter oSetter2("GDAL_DISABLE_READDIR_ON_OPEN", "YES",
     579        1020 :                                    false);
     580             : 
     581         510 :     std::unique_ptr<CPLConfigOptionSetter> poSetter;
     582             :     // No need to reopen the dataset at end of CreateCopy() (for PNG
     583             :     // and JPEG) if we don't need to generate .aux.xml
     584         510 :     if (!bAuxXML)
     585         506 :         poSetter = std::make_unique<CPLConfigOptionSetter>(
     586        1012 :             "GDAL_OPEN_AFTER_COPY", "NO", false);
     587         510 :     CPL_IGNORE_RET_VAL(poSetter);
     588             : 
     589             :     const bool bSupportsCreateOnlyVisibleAtCloseTime =
     590        1020 :         m_poDstDriver->GetMetadataItem(
     591         510 :             GDAL_DCAP_CREATE_ONLY_VISIBLE_AT_CLOSE_TIME) != nullptr;
     592             : 
     593             :     const std::string osTmpFilename = bSupportsCreateOnlyVisibleAtCloseTime
     594             :                                           ? osFilename
     595        1020 :                                           : osFilename + ".tmp." + pszExtension;
     596             : 
     597        1020 :     CPLStringList aosCreationOptions(creationOptions);
     598         510 :     if (bSupportsCreateOnlyVisibleAtCloseTime)
     599             :         aosCreationOptions.SetNameValue("@CREATE_ONLY_VISIBLE_AT_CLOSE_TIME",
     600         508 :                                         "YES");
     601             :     std::unique_ptr<GDALDataset> poOutDS(
     602             :         m_poDstDriver->CreateCopy(osTmpFilename.c_str(), memDS.get(), false,
     603         510 :                                   aosCreationOptions.List(), nullptr, nullptr));
     604         510 :     bool bRet = poOutDS && poOutDS->Close() == CE_None;
     605         510 :     poOutDS.reset();
     606         510 :     if (bRet)
     607             :     {
     608         508 :         if (!bSupportsCreateOnlyVisibleAtCloseTime)
     609             :         {
     610           0 :             bRet = VSIRename(osTmpFilename.c_str(), osFilename.c_str()) == 0;
     611           0 :             if (bAuxXML)
     612             :             {
     613           0 :                 VSIRename((osTmpFilename + ".aux.xml").c_str(),
     614           0 :                           (osFilename + ".aux.xml").c_str());
     615             :             }
     616             :         }
     617             :     }
     618             :     else
     619             :     {
     620           2 :         VSIUnlink(osTmpFilename.c_str());
     621             :     }
     622         510 :     return bRet;
     623             : }
     624             : 
     625             : /************************************************************************/
     626             : /*                    GenerateOverviewTile()                            */
     627             : /************************************************************************/
     628             : 
     629             : static bool
     630         157 : GenerateOverviewTile(GDALDataset &oSrcDS, GDALDriver *m_poDstDriver,
     631             :                      const std::string &outputFormat, const char *pszExtension,
     632             :                      CSLConstList creationOptions,
     633             :                      CSLConstList papszWarpOptions,
     634             :                      const std::string &resampling,
     635             :                      const gdal::TileMatrixSet::TileMatrix &tileMatrix,
     636             :                      const std::string &outputDirectory, int nZoomLevel, int iX,
     637             :                      int iY, const std::string &convention, bool bSkipBlank,
     638             :                      bool bUserAskedForAlpha, bool bAuxXML, bool bResume)
     639             : {
     640             :     const std::string osDirZ = CPLFormFilenameSafe(
     641         314 :         outputDirectory.c_str(), CPLSPrintf("%d", nZoomLevel), nullptr);
     642             :     const std::string osDirX =
     643         314 :         CPLFormFilenameSafe(osDirZ.c_str(), CPLSPrintf("%d", iX), nullptr);
     644             : 
     645         157 :     const int iFileY = GetFileY(iY, tileMatrix, convention);
     646             :     const std::string osFilename = CPLFormFilenameSafe(
     647         314 :         osDirX.c_str(), CPLSPrintf("%d", iFileY), pszExtension);
     648             : 
     649         157 :     if (bResume)
     650             :     {
     651             :         VSIStatBufL sStat;
     652           2 :         if (VSIStatL(osFilename.c_str(), &sStat) == 0)
     653           1 :             return true;
     654             :     }
     655             : 
     656         156 :     VSIMkdir(osDirZ.c_str(), 0755);
     657         156 :     VSIMkdir(osDirX.c_str(), 0755);
     658             : 
     659             :     const bool bSupportsCreateOnlyVisibleAtCloseTime =
     660         312 :         m_poDstDriver->GetMetadataItem(
     661         156 :             GDAL_DCAP_CREATE_ONLY_VISIBLE_AT_CLOSE_TIME) != nullptr;
     662             : 
     663         312 :     CPLStringList aosOptions;
     664             : 
     665         156 :     aosOptions.AddString("-of");
     666         156 :     aosOptions.AddString(outputFormat.c_str());
     667             : 
     668         180 :     for (const char *pszCO : cpl::Iterate(creationOptions))
     669             :     {
     670          24 :         aosOptions.AddString("-co");
     671          24 :         aosOptions.AddString(pszCO);
     672             :     }
     673         156 :     if (bSupportsCreateOnlyVisibleAtCloseTime)
     674             :     {
     675         156 :         aosOptions.AddString("-co");
     676         156 :         aosOptions.AddString("@CREATE_ONLY_VISIBLE_AT_CLOSE_TIME=YES");
     677             :     }
     678             : 
     679             :     CPLConfigOptionSetter oSetter("GDAL_PAM_ENABLED", bAuxXML ? "YES" : "NO",
     680         312 :                                   false);
     681             :     CPLConfigOptionSetter oSetter2("GDAL_DISABLE_READDIR_ON_OPEN", "YES",
     682         312 :                                    false);
     683             : 
     684         156 :     aosOptions.AddString("-r");
     685         156 :     aosOptions.AddString(resampling.c_str());
     686             : 
     687         156 :     std::unique_ptr<GDALDataset> poOutDS;
     688         156 :     const double dfMinX =
     689         156 :         tileMatrix.mTopLeftX + iX * tileMatrix.mResX * tileMatrix.mTileWidth;
     690         156 :     const double dfMaxY =
     691         156 :         tileMatrix.mTopLeftY - iY * tileMatrix.mResY * tileMatrix.mTileHeight;
     692         156 :     const double dfMaxX = dfMinX + tileMatrix.mResX * tileMatrix.mTileWidth;
     693         156 :     const double dfMinY = dfMaxY - tileMatrix.mResY * tileMatrix.mTileHeight;
     694             : 
     695             :     const bool resamplingCompatibleOfTranslate =
     696         457 :         papszWarpOptions == nullptr &&
     697         448 :         (resampling == "nearest" || resampling == "average" ||
     698         297 :          resampling == "bilinear" || resampling == "cubic" ||
     699           9 :          resampling == "cubicspline" || resampling == "lanczos" ||
     700           3 :          resampling == "mode");
     701             : 
     702             :     const std::string osTmpFilename = bSupportsCreateOnlyVisibleAtCloseTime
     703             :                                           ? osFilename
     704         312 :                                           : osFilename + ".tmp." + pszExtension;
     705             : 
     706         156 :     if (resamplingCompatibleOfTranslate)
     707             :     {
     708         150 :         GDALGeoTransform upperGT;
     709         150 :         oSrcDS.GetGeoTransform(upperGT);
     710         150 :         const double dfMinXUpper = upperGT[0];
     711             :         const double dfMaxXUpper =
     712         150 :             dfMinXUpper + upperGT[1] * oSrcDS.GetRasterXSize();
     713         150 :         const double dfMaxYUpper = upperGT[3];
     714             :         const double dfMinYUpper =
     715         150 :             dfMaxYUpper + upperGT[5] * oSrcDS.GetRasterYSize();
     716         150 :         if (dfMinX >= dfMinXUpper && dfMaxX <= dfMaxXUpper &&
     717         134 :             dfMinY >= dfMinYUpper && dfMaxY <= dfMaxYUpper)
     718             :         {
     719             :             // If the overview tile is fully within the extent of the
     720             :             // upper zoom level, we can use GDALDataset::RasterIO() directly.
     721             : 
     722         134 :             const auto eDT = oSrcDS.GetRasterBand(1)->GetRasterDataType();
     723             :             const size_t nBytesPerBand =
     724         134 :                 static_cast<size_t>(tileMatrix.mTileWidth) *
     725         134 :                 tileMatrix.mTileHeight * GDALGetDataTypeSizeBytes(eDT);
     726             :             std::vector<GByte> dstBuffer(nBytesPerBand *
     727         134 :                                          oSrcDS.GetRasterCount());
     728             : 
     729         134 :             const double dfXOff = (dfMinX - dfMinXUpper) / upperGT[1];
     730         134 :             const double dfYOff = (dfMaxYUpper - dfMaxY) / -upperGT[5];
     731         134 :             const double dfXSize = (dfMaxX - dfMinX) / upperGT[1];
     732         134 :             const double dfYSize = (dfMaxY - dfMinY) / -upperGT[5];
     733             :             GDALRasterIOExtraArg sExtraArg;
     734         133 :             INIT_RASTERIO_EXTRA_ARG(sExtraArg);
     735         133 :             CPL_IGNORE_RET_VAL(sExtraArg.eResampleAlg);
     736         133 :             sExtraArg.eResampleAlg =
     737         133 :                 GDALRasterIOGetResampleAlg(resampling.c_str());
     738         133 :             sExtraArg.dfXOff = dfXOff;
     739         133 :             sExtraArg.dfYOff = dfYOff;
     740         133 :             sExtraArg.dfXSize = dfXSize;
     741         133 :             sExtraArg.dfYSize = dfYSize;
     742         133 :             sExtraArg.bFloatingPointWindowValidity =
     743         133 :                 sExtraArg.eResampleAlg != GRIORA_NearestNeighbour;
     744         133 :             constexpr double EPSILON = 1e-3;
     745         133 :             if (oSrcDS.RasterIO(GF_Read, static_cast<int>(dfXOff + EPSILON),
     746         133 :                                 static_cast<int>(dfYOff + EPSILON),
     747         133 :                                 static_cast<int>(dfXSize + 0.5),
     748         133 :                                 static_cast<int>(dfYSize + 0.5),
     749         133 :                                 dstBuffer.data(), tileMatrix.mTileWidth,
     750         133 :                                 tileMatrix.mTileHeight, eDT,
     751             :                                 oSrcDS.GetRasterCount(), nullptr, 0, 0, 0,
     752         134 :                                 &sExtraArg) == CE_None)
     753             :             {
     754         133 :                 int nDstBands = oSrcDS.GetRasterCount();
     755             :                 const bool bDstHasAlpha =
     756         133 :                     oSrcDS.GetRasterBand(nDstBands)->GetColorInterpretation() ==
     757         133 :                     GCI_AlphaBand;
     758         133 :                 if (bDstHasAlpha && bSkipBlank)
     759             :                 {
     760           2 :                     bool bBlank = true;
     761       65545 :                     for (size_t i = 0; i < nBytesPerBand && bBlank; ++i)
     762             :                     {
     763       65543 :                         bBlank =
     764       65543 :                             (dstBuffer[(nDstBands - 1) * nBytesPerBand + i] ==
     765             :                              0);
     766             :                     }
     767           2 :                     if (bBlank)
     768           1 :                         return true;
     769           1 :                     bSkipBlank = false;
     770             :                 }
     771         132 :                 if (bDstHasAlpha && !bUserAskedForAlpha)
     772             :                 {
     773         132 :                     bool bAllOpaque = true;
     774     8454280 :                     for (size_t i = 0; i < nBytesPerBand && bAllOpaque; ++i)
     775             :                     {
     776     8454150 :                         bAllOpaque =
     777     8454150 :                             (dstBuffer[(nDstBands - 1) * nBytesPerBand + i] ==
     778             :                              255);
     779             :                     }
     780         132 :                     if (bAllOpaque)
     781         129 :                         nDstBands--;
     782             :                 }
     783             : 
     784         264 :                 auto memDS = std::unique_ptr<GDALDataset>(MEMDataset::Create(
     785         132 :                     "", tileMatrix.mTileWidth, tileMatrix.mTileHeight, 0, eDT,
     786         264 :                     nullptr));
     787         531 :                 for (int i = 0; i < nDstBands; ++i)
     788             :                 {
     789         399 :                     char szBuffer[32] = {'\0'};
     790         798 :                     int nRet = CPLPrintPointer(
     791         399 :                         szBuffer, dstBuffer.data() + i * nBytesPerBand,
     792             :                         sizeof(szBuffer));
     793         399 :                     szBuffer[nRet] = 0;
     794             : 
     795         399 :                     char szOption[64] = {'\0'};
     796         399 :                     snprintf(szOption, sizeof(szOption), "DATAPOINTER=%s",
     797             :                              szBuffer);
     798             : 
     799         399 :                     char *apszOptions[] = {szOption, nullptr};
     800             : 
     801         399 :                     memDS->AddBand(eDT, apszOptions);
     802         399 :                     auto poSrcBand = oSrcDS.GetRasterBand(i + 1);
     803         399 :                     auto poDstBand = memDS->GetRasterBand(i + 1);
     804         399 :                     poDstBand->SetColorInterpretation(
     805         399 :                         poSrcBand->GetColorInterpretation());
     806         399 :                     int bHasNoData = false;
     807             :                     const double dfNoData =
     808         399 :                         poSrcBand->GetNoDataValue(&bHasNoData);
     809         399 :                     if (bHasNoData)
     810           0 :                         poDstBand->SetNoDataValue(dfNoData);
     811         399 :                     if (auto poCT = poSrcBand->GetColorTable())
     812           0 :                         poDstBand->SetColorTable(poCT);
     813             :                 }
     814         132 :                 memDS->SetMetadata(oSrcDS.GetMetadata());
     815         264 :                 memDS->SetGeoTransform(GDALGeoTransform(
     816         132 :                     dfMinX, tileMatrix.mResX, 0, dfMaxY, 0, -tileMatrix.mResY));
     817             : 
     818         132 :                 memDS->SetSpatialRef(oSrcDS.GetSpatialRef());
     819             : 
     820         132 :                 std::unique_ptr<CPLConfigOptionSetter> poSetter;
     821             :                 // No need to reopen the dataset at end of CreateCopy() (for PNG
     822             :                 // and JPEG) if we don't need to generate .aux.xml
     823         132 :                 if (!bAuxXML)
     824         132 :                     poSetter = std::make_unique<CPLConfigOptionSetter>(
     825         264 :                         "GDAL_OPEN_AFTER_COPY", "NO", false);
     826         132 :                 CPL_IGNORE_RET_VAL(poSetter);
     827             : 
     828         264 :                 CPLStringList aosCreationOptions(creationOptions);
     829         132 :                 if (bSupportsCreateOnlyVisibleAtCloseTime)
     830             :                     aosCreationOptions.SetNameValue(
     831         132 :                         "@CREATE_ONLY_VISIBLE_AT_CLOSE_TIME", "YES");
     832         132 :                 poOutDS.reset(m_poDstDriver->CreateCopy(
     833             :                     osTmpFilename.c_str(), memDS.get(), false,
     834         132 :                     aosCreationOptions.List(), nullptr, nullptr));
     835         133 :             }
     836             :         }
     837             :         else
     838             :         {
     839             :             // If the overview tile is not fully within the extent of the
     840             :             // upper zoom level, use GDALTranslate() to use VRT padding
     841             : 
     842          16 :             aosOptions.AddString("-q");
     843             : 
     844          16 :             aosOptions.AddString("-projwin");
     845          16 :             aosOptions.AddString(CPLSPrintf("%.17g", dfMinX));
     846          16 :             aosOptions.AddString(CPLSPrintf("%.17g", dfMaxY));
     847          16 :             aosOptions.AddString(CPLSPrintf("%.17g", dfMaxX));
     848          16 :             aosOptions.AddString(CPLSPrintf("%.17g", dfMinY));
     849             : 
     850          16 :             aosOptions.AddString("-outsize");
     851          16 :             aosOptions.AddString(CPLSPrintf("%d", tileMatrix.mTileWidth));
     852          16 :             aosOptions.AddString(CPLSPrintf("%d", tileMatrix.mTileHeight));
     853             : 
     854             :             GDALTranslateOptions *psOptions =
     855          16 :                 GDALTranslateOptionsNew(aosOptions.List(), nullptr);
     856          16 :             poOutDS.reset(GDALDataset::FromHandle(GDALTranslate(
     857             :                 osTmpFilename.c_str(), GDALDataset::ToHandle(&oSrcDS),
     858             :                 psOptions, nullptr)));
     859          16 :             GDALTranslateOptionsFree(psOptions);
     860             :         }
     861             :     }
     862             :     else
     863             :     {
     864           6 :         aosOptions.AddString("-te");
     865           6 :         aosOptions.AddString(CPLSPrintf("%.17g", dfMinX));
     866           6 :         aosOptions.AddString(CPLSPrintf("%.17g", dfMinY));
     867           6 :         aosOptions.AddString(CPLSPrintf("%.17g", dfMaxX));
     868           6 :         aosOptions.AddString(CPLSPrintf("%.17g", dfMaxY));
     869             : 
     870           6 :         aosOptions.AddString("-ts");
     871           6 :         aosOptions.AddString(CPLSPrintf("%d", tileMatrix.mTileWidth));
     872           6 :         aosOptions.AddString(CPLSPrintf("%d", tileMatrix.mTileHeight));
     873             : 
     874          11 :         for (int i = 0; papszWarpOptions && papszWarpOptions[i]; ++i)
     875             :         {
     876           5 :             aosOptions.AddString("-wo");
     877           5 :             aosOptions.AddString(papszWarpOptions[i]);
     878             :         }
     879             : 
     880             :         GDALWarpAppOptions *psOptions =
     881           6 :             GDALWarpAppOptionsNew(aosOptions.List(), nullptr);
     882           6 :         GDALDatasetH hSrcDS = GDALDataset::ToHandle(&oSrcDS);
     883           6 :         poOutDS.reset(GDALDataset::FromHandle(GDALWarp(
     884             :             osTmpFilename.c_str(), nullptr, 1, &hSrcDS, psOptions, nullptr)));
     885           6 :         GDALWarpAppOptionsFree(psOptions);
     886             :     }
     887             : 
     888         155 :     bool bRet = poOutDS != nullptr;
     889         155 :     if (bRet && bSkipBlank)
     890             :     {
     891          10 :         auto poLastBand = poOutDS->GetRasterBand(poOutDS->GetRasterCount());
     892          10 :         if (poLastBand->GetColorInterpretation() == GCI_AlphaBand)
     893             :         {
     894             :             std::vector<GByte> buffer(
     895           1 :                 static_cast<size_t>(tileMatrix.mTileWidth) *
     896           1 :                 tileMatrix.mTileHeight *
     897           1 :                 GDALGetDataTypeSizeBytes(poLastBand->GetRasterDataType()));
     898           2 :             CPL_IGNORE_RET_VAL(poLastBand->RasterIO(
     899           1 :                 GF_Read, 0, 0, tileMatrix.mTileWidth, tileMatrix.mTileHeight,
     900           1 :                 buffer.data(), tileMatrix.mTileWidth, tileMatrix.mTileHeight,
     901             :                 poLastBand->GetRasterDataType(), 0, 0, nullptr));
     902           1 :             bool bBlank = true;
     903       65537 :             for (size_t i = 0; i < buffer.size() && bBlank; ++i)
     904             :             {
     905       65536 :                 bBlank = (buffer[i] == 0);
     906             :             }
     907           1 :             if (bBlank)
     908             :             {
     909           1 :                 poOutDS.reset();
     910           1 :                 VSIUnlink(osTmpFilename.c_str());
     911           1 :                 if (bAuxXML)
     912           0 :                     VSIUnlink((osTmpFilename + ".aux.xml").c_str());
     913           1 :                 return true;
     914             :             }
     915             :         }
     916             :     }
     917         154 :     bRet = bRet && poOutDS->Close() == CE_None;
     918         154 :     poOutDS.reset();
     919         154 :     if (bRet)
     920             :     {
     921         153 :         if (!bSupportsCreateOnlyVisibleAtCloseTime)
     922             :         {
     923           0 :             bRet = VSIRename(osTmpFilename.c_str(), osFilename.c_str()) == 0;
     924           0 :             if (bAuxXML)
     925             :             {
     926           0 :                 VSIRename((osTmpFilename + ".aux.xml").c_str(),
     927           0 :                           (osFilename + ".aux.xml").c_str());
     928             :             }
     929             :         }
     930             :     }
     931             :     else
     932             :     {
     933           1 :         VSIUnlink(osTmpFilename.c_str());
     934             :     }
     935         154 :     return bRet;
     936             : }
     937             : 
     938             : namespace
     939             : {
     940             : 
     941             : /************************************************************************/
     942             : /*                     FakeMaxZoomRasterBand                            */
     943             : /************************************************************************/
     944             : 
     945             : class FakeMaxZoomRasterBand : public GDALRasterBand
     946             : {
     947             :     void *m_pDstBuffer = nullptr;
     948             :     CPL_DISALLOW_COPY_ASSIGN(FakeMaxZoomRasterBand)
     949             : 
     950             :   public:
     951         413 :     FakeMaxZoomRasterBand(int nBandIn, int nWidth, int nHeight,
     952             :                           int nBlockXSizeIn, int nBlockYSizeIn,
     953             :                           GDALDataType eDT, void *pDstBuffer)
     954         413 :         : m_pDstBuffer(pDstBuffer)
     955             :     {
     956         413 :         nBand = nBandIn;
     957         413 :         nRasterXSize = nWidth;
     958         413 :         nRasterYSize = nHeight;
     959         413 :         nBlockXSize = nBlockXSizeIn;
     960         413 :         nBlockYSize = nBlockYSizeIn;
     961         413 :         eDataType = eDT;
     962         413 :     }
     963             : 
     964           0 :     CPLErr IReadBlock(int, int, void *) override
     965             :     {
     966           0 :         CPLAssert(false);
     967             :         return CE_Failure;
     968             :     }
     969             : 
     970             : #ifdef DEBUG
     971           0 :     CPLErr IWriteBlock(int, int, void *) override
     972             :     {
     973           0 :         CPLAssert(false);
     974             :         return CE_Failure;
     975             :     }
     976             : #endif
     977             : 
     978        1004 :     CPLErr IRasterIO(GDALRWFlag eRWFlag, [[maybe_unused]] int nXOff,
     979             :                      [[maybe_unused]] int nYOff, [[maybe_unused]] int nXSize,
     980             :                      [[maybe_unused]] int nYSize, void *pData,
     981             :                      [[maybe_unused]] int nBufXSize,
     982             :                      [[maybe_unused]] int nBufYSize, GDALDataType eBufType,
     983             :                      GSpacing nPixelSpace, [[maybe_unused]] GSpacing nLineSpace,
     984             :                      GDALRasterIOExtraArg *) override
     985             :     {
     986             :         // For sake of implementation simplicity, check various assumptions of
     987             :         // how GDALAlphaMask code does I/O
     988        1004 :         CPLAssert((nXOff % nBlockXSize) == 0);
     989        1004 :         CPLAssert((nYOff % nBlockYSize) == 0);
     990        1004 :         CPLAssert(nXSize == nBufXSize);
     991        1004 :         CPLAssert(nXSize == nBlockXSize);
     992        1004 :         CPLAssert(nYSize == nBufYSize);
     993        1004 :         CPLAssert(nYSize == nBlockYSize);
     994        1004 :         CPLAssert(nLineSpace == nBlockXSize * nPixelSpace);
     995        1004 :         CPLAssert(
     996             :             nBand ==
     997             :             poDS->GetRasterCount());  // only alpha band is accessed this way
     998        1004 :         if (eRWFlag == GF_Read)
     999             :         {
    1000         502 :             double dfZero = 0;
    1001         502 :             GDALCopyWords64(&dfZero, GDT_Float64, 0, pData, eBufType,
    1002             :                             static_cast<int>(nPixelSpace),
    1003         502 :                             static_cast<size_t>(nBlockXSize) * nBlockYSize);
    1004             :         }
    1005             :         else
    1006             :         {
    1007         502 :             GDALCopyWords64(pData, eBufType, static_cast<int>(nPixelSpace),
    1008             :                             m_pDstBuffer, eDataType,
    1009             :                             GDALGetDataTypeSizeBytes(eDataType),
    1010         502 :                             static_cast<size_t>(nBlockXSize) * nBlockYSize);
    1011             :         }
    1012        1004 :         return CE_None;
    1013             :     }
    1014             : };
    1015             : 
    1016             : /************************************************************************/
    1017             : /*                       FakeMaxZoomDataset                             */
    1018             : /************************************************************************/
    1019             : 
    1020             : // This class is used to create a fake output dataset for GDALWarpOperation.
    1021             : // In particular we need to implement GDALRasterBand::IRasterIO(GF_Write, ...)
    1022             : // to catch writes (of one single tile) to the alpha band and redirect them
    1023             : // to the dstBuffer passed to FakeMaxZoomDataset constructor.
    1024             : 
    1025             : class FakeMaxZoomDataset : public GDALDataset
    1026             : {
    1027             :     const int m_nBlockXSize;
    1028             :     const int m_nBlockYSize;
    1029             :     const OGRSpatialReference m_oSRS;
    1030             :     const GDALGeoTransform m_gt{};
    1031             : 
    1032             :   public:
    1033         121 :     FakeMaxZoomDataset(int nWidth, int nHeight, int nBandsIn, int nBlockXSize,
    1034             :                        int nBlockYSize, GDALDataType eDT,
    1035             :                        const GDALGeoTransform &gt,
    1036             :                        const OGRSpatialReference &oSRS,
    1037             :                        std::vector<GByte> &dstBuffer)
    1038         121 :         : m_nBlockXSize(nBlockXSize), m_nBlockYSize(nBlockYSize), m_oSRS(oSRS),
    1039         121 :           m_gt(gt)
    1040             :     {
    1041         121 :         eAccess = GA_Update;
    1042         121 :         nRasterXSize = nWidth;
    1043         121 :         nRasterYSize = nHeight;
    1044         534 :         for (int i = 1; i <= nBandsIn; ++i)
    1045             :         {
    1046         413 :             SetBand(i,
    1047             :                     new FakeMaxZoomRasterBand(
    1048             :                         i, nWidth, nHeight, nBlockXSize, nBlockYSize, eDT,
    1049         413 :                         dstBuffer.data() + static_cast<size_t>(i - 1) *
    1050         826 :                                                nBlockXSize * nBlockYSize *
    1051         413 :                                                GDALGetDataTypeSizeBytes(eDT)));
    1052             :         }
    1053         121 :     }
    1054             : 
    1055         308 :     const OGRSpatialReference *GetSpatialRef() const override
    1056             :     {
    1057         308 :         return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
    1058             :     }
    1059             : 
    1060          97 :     CPLErr GetGeoTransform(GDALGeoTransform &gt) const override
    1061             :     {
    1062          97 :         gt = m_gt;
    1063          97 :         return CE_None;
    1064             :     }
    1065             : 
    1066             :     using GDALDataset::Clone;
    1067             : 
    1068             :     std::unique_ptr<FakeMaxZoomDataset>
    1069          24 :     Clone(std::vector<GByte> &dstBuffer) const
    1070             :     {
    1071             :         return std::make_unique<FakeMaxZoomDataset>(
    1072          24 :             nRasterXSize, nRasterYSize, nBands, m_nBlockXSize, m_nBlockYSize,
    1073          48 :             GetRasterBand(1)->GetRasterDataType(), m_gt, m_oSRS, dstBuffer);
    1074             :     }
    1075             : };
    1076             : 
    1077             : /************************************************************************/
    1078             : /*                          MosaicRasterBand                            */
    1079             : /************************************************************************/
    1080             : 
    1081             : class MosaicRasterBand : public GDALRasterBand
    1082             : {
    1083             :     const int m_tileMinX;
    1084             :     const int m_tileMinY;
    1085             :     const GDALColorInterp m_eColorInterp;
    1086             :     const gdal::TileMatrixSet::TileMatrix m_oTM;
    1087             :     const std::string m_convention;
    1088             :     const std::string m_directory;
    1089             :     const std::string m_extension;
    1090             :     const bool m_hasNoData;
    1091             :     const double m_noData;
    1092             :     std::unique_ptr<GDALColorTable> m_poColorTable{};
    1093             : 
    1094             :   public:
    1095         294 :     MosaicRasterBand(GDALDataset *poDSIn, int nBandIn, int nWidth, int nHeight,
    1096             :                      int nBlockXSizeIn, int nBlockYSizeIn, GDALDataType eDT,
    1097             :                      GDALColorInterp eColorInterp, int nTileMinX, int nTileMinY,
    1098             :                      const gdal::TileMatrixSet::TileMatrix &oTM,
    1099             :                      const std::string &convention,
    1100             :                      const std::string &directory, const std::string &extension,
    1101             :                      const double *pdfDstNoData,
    1102             :                      const GDALColorTable *poColorTable)
    1103         294 :         : m_tileMinX(nTileMinX), m_tileMinY(nTileMinY),
    1104             :           m_eColorInterp(eColorInterp), m_oTM(oTM), m_convention(convention),
    1105             :           m_directory(directory), m_extension(extension),
    1106         294 :           m_hasNoData(pdfDstNoData != nullptr),
    1107         294 :           m_noData(pdfDstNoData ? *pdfDstNoData : 0),
    1108         588 :           m_poColorTable(poColorTable ? poColorTable->Clone() : nullptr)
    1109             :     {
    1110         294 :         poDS = poDSIn;
    1111         294 :         nBand = nBandIn;
    1112         294 :         nRasterXSize = nWidth;
    1113         294 :         nRasterYSize = nHeight;
    1114         294 :         nBlockXSize = nBlockXSizeIn;
    1115         294 :         nBlockYSize = nBlockYSizeIn;
    1116         294 :         eDataType = eDT;
    1117         294 :     }
    1118             : 
    1119             :     CPLErr IReadBlock(int nXBlock, int nYBlock, void *pData) override;
    1120             : 
    1121        2798 :     GDALColorTable *GetColorTable() override
    1122             :     {
    1123        2798 :         return m_poColorTable.get();
    1124             :     }
    1125             : 
    1126         765 :     GDALColorInterp GetColorInterpretation() override
    1127             :     {
    1128         765 :         return m_eColorInterp;
    1129             :     }
    1130             : 
    1131        2277 :     double GetNoDataValue(int *pbHasNoData) override
    1132             :     {
    1133        2277 :         if (pbHasNoData)
    1134        2268 :             *pbHasNoData = m_hasNoData;
    1135        2277 :         return m_noData;
    1136             :     }
    1137             : };
    1138             : 
    1139             : /************************************************************************/
    1140             : /*                         MosaicDataset                                */
    1141             : /************************************************************************/
    1142             : 
    1143             : // This class is to expose the tiles of a given level as a mosaic that
    1144             : // can be used as a source to generate the immediately below zoom level.
    1145             : 
    1146             : class MosaicDataset : public GDALDataset
    1147             : {
    1148             :     friend class MosaicRasterBand;
    1149             : 
    1150             :     const std::string m_directory;
    1151             :     const std::string m_extension;
    1152             :     const std::string m_format;
    1153             :     const std::vector<GDALColorInterp> m_aeColorInterp;
    1154             :     const gdal::TileMatrixSet::TileMatrix &m_oTM;
    1155             :     const OGRSpatialReference m_oSRS;
    1156             :     const int m_nTileMinX;
    1157             :     const int m_nTileMinY;
    1158             :     const int m_nTileMaxX;
    1159             :     const int m_nTileMaxY;
    1160             :     const std::string m_convention;
    1161             :     const GDALDataType m_eDT;
    1162             :     const double *const m_pdfDstNoData;
    1163             :     const std::vector<std::string> &m_metadata;
    1164             :     const GDALColorTable *const m_poCT;
    1165             : 
    1166             :     GDALGeoTransform m_gt{};
    1167             :     const int m_nMaxCacheTileSize;
    1168             :     lru11::Cache<std::string, std::shared_ptr<GDALDataset>> m_oCacheTile;
    1169             : 
    1170             :     CPL_DISALLOW_COPY_ASSIGN(MosaicDataset)
    1171             : 
    1172             :   public:
    1173          85 :     MosaicDataset(const std::string &directory, const std::string &extension,
    1174             :                   const std::string &format,
    1175             :                   const std::vector<GDALColorInterp> &aeColorInterp,
    1176             :                   const gdal::TileMatrixSet::TileMatrix &oTM,
    1177             :                   const OGRSpatialReference &oSRS, int nTileMinX, int nTileMinY,
    1178             :                   int nTileMaxX, int nTileMaxY, const std::string &convention,
    1179             :                   int nBandsIn, GDALDataType eDT, const double *pdfDstNoData,
    1180             :                   const std::vector<std::string> &metadata,
    1181             :                   const GDALColorTable *poCT, int maxCacheTileSize)
    1182          85 :         : m_directory(directory), m_extension(extension), m_format(format),
    1183             :           m_aeColorInterp(aeColorInterp), m_oTM(oTM), m_oSRS(oSRS),
    1184             :           m_nTileMinX(nTileMinX), m_nTileMinY(nTileMinY),
    1185             :           m_nTileMaxX(nTileMaxX), m_nTileMaxY(nTileMaxY),
    1186             :           m_convention(convention), m_eDT(eDT), m_pdfDstNoData(pdfDstNoData),
    1187             :           m_metadata(metadata), m_poCT(poCT),
    1188             :           m_nMaxCacheTileSize(maxCacheTileSize),
    1189          85 :           m_oCacheTile(/* max_size = */ maxCacheTileSize, /* elasticity = */ 0)
    1190             :     {
    1191          85 :         nRasterXSize = (nTileMaxX - nTileMinX + 1) * oTM.mTileWidth;
    1192          85 :         nRasterYSize = (nTileMaxY - nTileMinY + 1) * oTM.mTileHeight;
    1193          85 :         m_gt[0] = oTM.mTopLeftX + nTileMinX * oTM.mResX * oTM.mTileWidth;
    1194          85 :         m_gt[1] = oTM.mResX;
    1195          85 :         m_gt[2] = 0;
    1196          85 :         m_gt[3] = oTM.mTopLeftY - nTileMinY * oTM.mResY * oTM.mTileHeight;
    1197          85 :         m_gt[4] = 0;
    1198          85 :         m_gt[5] = -oTM.mResY;
    1199         379 :         for (int i = 1; i <= nBandsIn; ++i)
    1200             :         {
    1201             :             const GDALColorInterp eColorInterp =
    1202         294 :                 (i <= static_cast<int>(m_aeColorInterp.size()))
    1203         294 :                     ? m_aeColorInterp[i - 1]
    1204         294 :                     : GCI_AlphaBand;
    1205         294 :             SetBand(i, new MosaicRasterBand(
    1206         294 :                            this, i, nRasterXSize, nRasterYSize, oTM.mTileWidth,
    1207         294 :                            oTM.mTileHeight, eDT, eColorInterp, nTileMinX,
    1208             :                            nTileMinY, oTM, convention, directory, extension,
    1209         294 :                            pdfDstNoData, poCT));
    1210             :         }
    1211          85 :         SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
    1212         170 :         const CPLStringList aosMD(metadata);
    1213         102 :         for (const auto [key, value] : cpl::IterateNameValue(aosMD))
    1214             :         {
    1215          17 :             SetMetadataItem(key, value);
    1216             :         }
    1217          85 :     }
    1218             : 
    1219         178 :     const OGRSpatialReference *GetSpatialRef() const override
    1220             :     {
    1221         178 :         return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
    1222             :     }
    1223             : 
    1224         188 :     CPLErr GetGeoTransform(GDALGeoTransform &gt) const override
    1225             :     {
    1226         188 :         gt = m_gt;
    1227         188 :         return CE_None;
    1228             :     }
    1229             : 
    1230             :     using GDALDataset::Clone;
    1231             : 
    1232          24 :     std::unique_ptr<MosaicDataset> Clone() const
    1233             :     {
    1234             :         return std::make_unique<MosaicDataset>(
    1235          24 :             m_directory, m_extension, m_format, m_aeColorInterp, m_oTM, m_oSRS,
    1236          24 :             m_nTileMinX, m_nTileMinY, m_nTileMaxX, m_nTileMaxY, m_convention,
    1237          24 :             nBands, m_eDT, m_pdfDstNoData, m_metadata, m_poCT,
    1238          24 :             m_nMaxCacheTileSize);
    1239             :     }
    1240             : };
    1241             : 
    1242             : /************************************************************************/
    1243             : /*                   MosaicRasterBand::IReadBlock()                     */
    1244             : /************************************************************************/
    1245             : 
    1246        3559 : CPLErr MosaicRasterBand::IReadBlock(int nXBlock, int nYBlock, void *pData)
    1247             : {
    1248        3559 :     auto poThisDS = cpl::down_cast<MosaicDataset *>(poDS);
    1249             :     std::string filename = CPLFormFilenameSafe(
    1250        7111 :         m_directory.c_str(), CPLSPrintf("%d", m_tileMinX + nXBlock), nullptr);
    1251        3556 :     const int iFileY = GetFileY(m_tileMinY + nYBlock, m_oTM, m_convention);
    1252        7115 :     filename = CPLFormFilenameSafe(filename.c_str(), CPLSPrintf("%d", iFileY),
    1253        3560 :                                    m_extension.c_str());
    1254             : 
    1255        3559 :     std::shared_ptr<GDALDataset> poTileDS;
    1256        3560 :     if (!poThisDS->m_oCacheTile.tryGet(filename, poTileDS))
    1257             :     {
    1258        1025 :         const char *const apszAllowedDrivers[] = {poThisDS->m_format.c_str(),
    1259        1024 :                                                   nullptr};
    1260        1024 :         const char *const apszAllowedDriversForCOG[] = {"GTiff", "LIBERTIFF",
    1261             :                                                         nullptr};
    1262             :         // CPLDebugOnly("gdal_raster_tile", "Opening %s", filename.c_str());
    1263        1024 :         poTileDS.reset(GDALDataset::Open(
    1264             :             filename.c_str(), GDAL_OF_RASTER | GDAL_OF_INTERNAL,
    1265        1024 :             EQUAL(poThisDS->m_format.c_str(), "COG") ? apszAllowedDriversForCOG
    1266             :                                                      : apszAllowedDrivers));
    1267        1021 :         if (!poTileDS)
    1268             :         {
    1269             :             VSIStatBufL sStat;
    1270           6 :             if (VSIStatL(filename.c_str(), &sStat) == 0)
    1271             :             {
    1272           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1273             :                          "File %s exists but cannot be opened with %s driver",
    1274             :                          filename.c_str(), poThisDS->m_format.c_str());
    1275           1 :                 return CE_Failure;
    1276             :             }
    1277             :         }
    1278        1016 :         poThisDS->m_oCacheTile.insert(filename, poTileDS);
    1279             :     }
    1280        3558 :     if (!poTileDS || nBand > poTileDS->GetRasterCount())
    1281             :     {
    1282        1726 :         memset(pData,
    1283         859 :                (poTileDS && (nBand == poTileDS->GetRasterCount() + 1)) ? 255
    1284             :                                                                        : 0,
    1285         862 :                static_cast<size_t>(nBlockXSize) * nBlockYSize *
    1286         862 :                    GDALGetDataTypeSizeBytes(eDataType));
    1287         867 :         return CE_None;
    1288             :     }
    1289             :     else
    1290             :     {
    1291        2691 :         return poTileDS->GetRasterBand(nBand)->RasterIO(
    1292             :             GF_Read, 0, 0, nBlockXSize, nBlockYSize, pData, nBlockXSize,
    1293        2691 :             nBlockYSize, eDataType, 0, 0, nullptr);
    1294             :     }
    1295             : }
    1296             : 
    1297             : }  // namespace
    1298             : 
    1299             : /************************************************************************/
    1300             : /*                         ApplySubstitutions()                         */
    1301             : /************************************************************************/
    1302             : 
    1303          77 : static void ApplySubstitutions(CPLString &s,
    1304             :                                const std::map<std::string, std::string> &substs)
    1305             : {
    1306        1208 :     for (const auto &[key, value] : substs)
    1307             :     {
    1308        1131 :         s.replaceAll("%(" + key + ")s", value);
    1309        1131 :         s.replaceAll("%(" + key + ")d", value);
    1310        1131 :         s.replaceAll("%(" + key + ")f", value);
    1311        1131 :         s.replaceAll("${" + key + "}", value);
    1312             :     }
    1313          77 : }
    1314             : 
    1315             : /************************************************************************/
    1316             : /*                           GenerateLeaflet()                          */
    1317             : /************************************************************************/
    1318             : 
    1319          19 : static void GenerateLeaflet(const std::string &osDirectory,
    1320             :                             const std::string &osTitle, double dfSouthLat,
    1321             :                             double dfWestLon, double dfNorthLat,
    1322             :                             double dfEastLon, int nMinZoom, int nMaxZoom,
    1323             :                             int nTileSize, const std::string &osExtension,
    1324             :                             const std::string &osURL,
    1325             :                             const std::string &osCopyright, bool bXYZ)
    1326             : {
    1327          19 :     if (const char *pszTemplate = CPLFindFile("gdal", "leaflet_template.html"))
    1328             :     {
    1329          38 :         const std::string osFilename(pszTemplate);
    1330          38 :         std::map<std::string, std::string> substs;
    1331             : 
    1332             :         // For tests
    1333             :         const char *pszFmt =
    1334          19 :             atoi(CPLGetConfigOption("GDAL_RASTER_TILE_HTML_PREC", "17")) == 10
    1335             :                 ? "%.10g"
    1336          19 :                 : "%.17g";
    1337             : 
    1338          38 :         substs["double_quote_escaped_title"] =
    1339          57 :             CPLString(osTitle).replaceAll('"', "\\\"");
    1340          19 :         char *pszStr = CPLEscapeString(osTitle.c_str(), -1, CPLES_XML);
    1341          19 :         substs["xml_escaped_title"] = pszStr;
    1342          19 :         CPLFree(pszStr);
    1343          19 :         substs["south"] = CPLSPrintf(pszFmt, dfSouthLat);
    1344          19 :         substs["west"] = CPLSPrintf(pszFmt, dfWestLon);
    1345          19 :         substs["north"] = CPLSPrintf(pszFmt, dfNorthLat);
    1346          19 :         substs["east"] = CPLSPrintf(pszFmt, dfEastLon);
    1347          19 :         substs["centerlon"] = CPLSPrintf(pszFmt, (dfNorthLat + dfSouthLat) / 2);
    1348          19 :         substs["centerlat"] = CPLSPrintf(pszFmt, (dfWestLon + dfEastLon) / 2);
    1349          19 :         substs["minzoom"] = CPLSPrintf("%d", nMinZoom);
    1350          19 :         substs["maxzoom"] = CPLSPrintf("%d", nMaxZoom);
    1351          19 :         substs["beginzoom"] = CPLSPrintf("%d", nMaxZoom);
    1352          19 :         substs["tile_size"] = CPLSPrintf("%d", nTileSize);  // not used
    1353          19 :         substs["tileformat"] = osExtension;
    1354          19 :         substs["publishurl"] = osURL;  // not used
    1355          19 :         substs["copyright"] = CPLString(osCopyright).replaceAll('"', "\\\"");
    1356          19 :         substs["tms"] = bXYZ ? "0" : "1";
    1357             : 
    1358          19 :         GByte *pabyRet = nullptr;
    1359          19 :         CPL_IGNORE_RET_VAL(VSIIngestFile(nullptr, osFilename.c_str(), &pabyRet,
    1360             :                                          nullptr, 10 * 1024 * 1024));
    1361          19 :         if (pabyRet)
    1362             :         {
    1363          38 :             CPLString osHTML(reinterpret_cast<char *>(pabyRet));
    1364          19 :             CPLFree(pabyRet);
    1365             : 
    1366          19 :             ApplySubstitutions(osHTML, substs);
    1367             : 
    1368          19 :             VSILFILE *f = VSIFOpenL(CPLFormFilenameSafe(osDirectory.c_str(),
    1369             :                                                         "leaflet.html", nullptr)
    1370             :                                         .c_str(),
    1371             :                                     "wb");
    1372          19 :             if (f)
    1373             :             {
    1374          19 :                 VSIFWriteL(osHTML.data(), 1, osHTML.size(), f);
    1375          19 :                 VSIFCloseL(f);
    1376             :             }
    1377             :         }
    1378             :     }
    1379          19 : }
    1380             : 
    1381             : /************************************************************************/
    1382             : /*                           GenerateMapML()                            */
    1383             : /************************************************************************/
    1384             : 
    1385             : static void
    1386          20 : GenerateMapML(const std::string &osDirectory, const std::string &mapmlTemplate,
    1387             :               const std::string &osTitle, int nMinTileX, int nMinTileY,
    1388             :               int nMaxTileX, int nMaxTileY, int nMinZoom, int nMaxZoom,
    1389             :               const std::string &osExtension, const std::string &osURL,
    1390             :               const std::string &osCopyright, const gdal::TileMatrixSet &tms)
    1391             : {
    1392          20 :     if (const char *pszTemplate =
    1393          20 :             (mapmlTemplate.empty() ? CPLFindFile("gdal", "template_tiles.mapml")
    1394          20 :                                    : mapmlTemplate.c_str()))
    1395             :     {
    1396          40 :         const std::string osFilename(pszTemplate);
    1397          40 :         std::map<std::string, std::string> substs;
    1398             : 
    1399          20 :         if (tms.identifier() == "GoogleMapsCompatible")
    1400          19 :             substs["TILING_SCHEME"] = "OSMTILE";
    1401           1 :         else if (tms.identifier() == "WorldCRS84Quad")
    1402           1 :             substs["TILING_SCHEME"] = "WGS84";
    1403             :         else
    1404           0 :             substs["TILING_SCHEME"] = tms.identifier();
    1405             : 
    1406          20 :         substs["URL"] = osURL.empty() ? "./" : osURL;
    1407          20 :         substs["MINTILEX"] = CPLSPrintf("%d", nMinTileX);
    1408          20 :         substs["MINTILEY"] = CPLSPrintf("%d", nMinTileY);
    1409          20 :         substs["MAXTILEX"] = CPLSPrintf("%d", nMaxTileX);
    1410          20 :         substs["MAXTILEY"] = CPLSPrintf("%d", nMaxTileY);
    1411          20 :         substs["CURZOOM"] = CPLSPrintf("%d", nMaxZoom);
    1412          20 :         substs["MINZOOM"] = CPLSPrintf("%d", nMinZoom);
    1413          20 :         substs["MAXZOOM"] = CPLSPrintf("%d", nMaxZoom);
    1414          20 :         substs["TILEEXT"] = osExtension;
    1415          20 :         char *pszStr = CPLEscapeString(osTitle.c_str(), -1, CPLES_XML);
    1416          20 :         substs["TITLE"] = pszStr;
    1417          20 :         CPLFree(pszStr);
    1418          20 :         substs["COPYRIGHT"] = osCopyright;
    1419             : 
    1420          20 :         GByte *pabyRet = nullptr;
    1421          20 :         CPL_IGNORE_RET_VAL(VSIIngestFile(nullptr, osFilename.c_str(), &pabyRet,
    1422             :                                          nullptr, 10 * 1024 * 1024));
    1423          20 :         if (pabyRet)
    1424             :         {
    1425          40 :             CPLString osMAPML(reinterpret_cast<char *>(pabyRet));
    1426          20 :             CPLFree(pabyRet);
    1427             : 
    1428          20 :             ApplySubstitutions(osMAPML, substs);
    1429             : 
    1430          20 :             VSILFILE *f = VSIFOpenL(
    1431          40 :                 CPLFormFilenameSafe(osDirectory.c_str(), "mapml.mapml", nullptr)
    1432             :                     .c_str(),
    1433             :                 "wb");
    1434          20 :             if (f)
    1435             :             {
    1436          20 :                 VSIFWriteL(osMAPML.data(), 1, osMAPML.size(), f);
    1437          20 :                 VSIFCloseL(f);
    1438             :             }
    1439             :         }
    1440             :     }
    1441          20 : }
    1442             : 
    1443             : /************************************************************************/
    1444             : /*                            GenerateSTAC()                            */
    1445             : /************************************************************************/
    1446             : 
    1447             : static void
    1448          24 : GenerateSTAC(const std::string &osDirectory, const std::string &osTitle,
    1449             :              double dfWestLon, double dfSouthLat, double dfEastLon,
    1450             :              double dfNorthLat, const std::vector<std::string> &metadata,
    1451             :              const std::vector<BandMetadata> &aoBandMetadata, int nMinZoom,
    1452             :              int nMaxZoom, const std::string &osExtension,
    1453             :              const std::string &osFormat, const std::string &osURL,
    1454             :              const std::string &osCopyright, const OGRSpatialReference &oSRS,
    1455             :              const gdal::TileMatrixSet &tms, bool bInvertAxisTMS, int tileSize,
    1456             :              const double adfExtent[4], const GDALArgDatasetValue &dataset)
    1457             : {
    1458          48 :     CPLJSONObject oRoot;
    1459          24 :     oRoot["stac_version"] = "1.1.0";
    1460          48 :     CPLJSONArray oExtensions;
    1461          24 :     oRoot["stac_extensions"] = oExtensions;
    1462          24 :     oRoot["id"] = osTitle;
    1463          24 :     oRoot["type"] = "Feature";
    1464          24 :     oRoot["bbox"] = {dfWestLon, dfSouthLat, dfEastLon, dfNorthLat};
    1465          48 :     CPLJSONObject oGeometry;
    1466             : 
    1467          24 :     const auto BuildPolygon = [](double x1, double y1, double x2, double y2)
    1468             :     {
    1469             :         return CPLJSONArray::Build({CPLJSONArray::Build(
    1470             :             {CPLJSONArray::Build({x1, y1}), CPLJSONArray::Build({x1, y2}),
    1471             :              CPLJSONArray::Build({x2, y2}), CPLJSONArray::Build({x2, y1}),
    1472         168 :              CPLJSONArray::Build({x1, y1})})});
    1473             :     };
    1474             : 
    1475          24 :     if (dfWestLon <= dfEastLon)
    1476             :     {
    1477          24 :         oGeometry["type"] = "Polygon";
    1478             :         oGeometry["coordinates"] =
    1479          24 :             BuildPolygon(dfWestLon, dfSouthLat, dfEastLon, dfNorthLat);
    1480             :     }
    1481             :     else
    1482             :     {
    1483           0 :         oGeometry["type"] = "MultiPolygon";
    1484           0 :         oGeometry["coordinates"] = {
    1485             :             BuildPolygon(dfWestLon, dfSouthLat, 180.0, dfNorthLat),
    1486           0 :             BuildPolygon(-180.0, dfSouthLat, dfEastLon, dfNorthLat)};
    1487             :     }
    1488          24 :     oRoot["geometry"] = std::move(oGeometry);
    1489             : 
    1490          48 :     CPLJSONObject oProperties;
    1491          24 :     oRoot["properties"] = oProperties;
    1492          48 :     const CPLStringList aosMD(metadata);
    1493          48 :     std::string osDateTime = "1970-01-01T00:00:00.000Z";
    1494          24 :     if (!dataset.GetName().empty())
    1495             :     {
    1496             :         VSIStatBufL sStat;
    1497          24 :         if (VSIStatL(dataset.GetName().c_str(), &sStat) == 0 &&
    1498          12 :             sStat.st_mtime != 0)
    1499             :         {
    1500             :             struct tm tm;
    1501          12 :             CPLUnixTimeToYMDHMS(sStat.st_mtime, &tm);
    1502             :             osDateTime = CPLSPrintf(
    1503          12 :                 "%04d-%02d-%02dT%02d:%02d:%02dZ", tm.tm_year + 1900,
    1504          12 :                 tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
    1505             :         }
    1506             :     }
    1507          48 :     std::string osStartDateTime = "0001-01-01T00:00:00.000Z";
    1508          48 :     std::string osEndDateTime = "9999-12-31T23:59:59.999Z";
    1509             : 
    1510           0 :     const auto GetDateTimeAsISO8211 = [](const char *pszInput)
    1511             :     {
    1512           0 :         std::string osRet;
    1513             :         OGRField sField;
    1514           0 :         if (OGRParseDate(pszInput, &sField, 0))
    1515             :         {
    1516           0 :             char *pszDT = OGRGetXMLDateTime(&sField);
    1517           0 :             if (pszDT)
    1518           0 :                 osRet = pszDT;
    1519           0 :             CPLFree(pszDT);
    1520             :         }
    1521           0 :         return osRet;
    1522             :     };
    1523             : 
    1524          25 :     for (const auto &[key, value] : cpl::IterateNameValue(aosMD))
    1525             :     {
    1526           1 :         if (EQUAL(key, "datetime"))
    1527             :         {
    1528           0 :             std::string osTmp = GetDateTimeAsISO8211(value);
    1529           0 :             if (!osTmp.empty())
    1530             :             {
    1531           0 :                 osDateTime = std::move(osTmp);
    1532           0 :                 continue;
    1533             :             }
    1534             :         }
    1535           1 :         else if (EQUAL(key, "start_datetime"))
    1536             :         {
    1537           0 :             std::string osTmp = GetDateTimeAsISO8211(value);
    1538           0 :             if (!osTmp.empty())
    1539             :             {
    1540           0 :                 osStartDateTime = std::move(osTmp);
    1541           0 :                 continue;
    1542             :             }
    1543             :         }
    1544           1 :         else if (EQUAL(key, "end_datetime"))
    1545             :         {
    1546           0 :             std::string osTmp = GetDateTimeAsISO8211(value);
    1547           0 :             if (!osTmp.empty())
    1548             :             {
    1549           0 :                 osEndDateTime = std::move(osTmp);
    1550           0 :                 continue;
    1551             :             }
    1552             :         }
    1553           1 :         else if (EQUAL(key, "TIFFTAG_DATETIME"))
    1554             :         {
    1555             :             int nYear, nMonth, nDay, nHour, nMin, nSec;
    1556           0 :             if (sscanf(value, "%04d:%02d:%02d %02d:%02d:%02d", &nYear, &nMonth,
    1557           0 :                        &nDay, &nHour, &nMin, &nSec) == 6)
    1558             :             {
    1559             :                 osDateTime = CPLSPrintf("%04d-%02d-%02dT%02d:%02d:%02dZ", nYear,
    1560           0 :                                         nMonth, nDay, nHour, nMin, nSec);
    1561           0 :                 continue;
    1562             :             }
    1563             :         }
    1564             : 
    1565           1 :         oProperties[key] = value;
    1566             :     }
    1567          24 :     oProperties["datetime"] = osDateTime;
    1568          24 :     oProperties["start_datetime"] = osStartDateTime;
    1569          24 :     oProperties["end_datetime"] = osEndDateTime;
    1570          24 :     if (!osCopyright.empty())
    1571           0 :         oProperties["copyright"] = osCopyright;
    1572             : 
    1573             :     // Just keep the tile matrix zoom levels we use
    1574          48 :     gdal::TileMatrixSet tmsLimitedToZoomLevelUsed(tms);
    1575          24 :     auto &tileMatrixList = tmsLimitedToZoomLevelUsed.tileMatrixList();
    1576          24 :     tileMatrixList.erase(tileMatrixList.begin() + nMaxZoom + 1,
    1577          48 :                          tileMatrixList.end());
    1578          24 :     tileMatrixList.erase(tileMatrixList.begin(),
    1579          48 :                          tileMatrixList.begin() + nMinZoom);
    1580             : 
    1581          48 :     CPLJSONObject oLimits;
    1582             :     // Patch their definition with the potentially overridden tileSize.
    1583          81 :     for (auto &tm : tileMatrixList)
    1584             :     {
    1585          57 :         int nOvrMinTileX = 0;
    1586          57 :         int nOvrMinTileY = 0;
    1587          57 :         int nOvrMaxTileX = 0;
    1588          57 :         int nOvrMaxTileY = 0;
    1589          57 :         bool bIntersects = false;
    1590          57 :         CPL_IGNORE_RET_VAL(GetTileIndices(
    1591             :             tm, bInvertAxisTMS, tileSize, adfExtent, nOvrMinTileX, nOvrMinTileY,
    1592             :             nOvrMaxTileX, nOvrMaxTileY, /* noIntersectionIsOK = */ true,
    1593             :             bIntersects));
    1594             : 
    1595          57 :         CPLJSONObject oLimit;
    1596          57 :         oLimit["min_tile_col"] = nOvrMinTileX;
    1597          57 :         oLimit["max_tile_col"] = nOvrMaxTileX;
    1598          57 :         oLimit["min_tile_row"] = nOvrMinTileY;
    1599          57 :         oLimit["max_tile_row"] = nOvrMaxTileY;
    1600          57 :         oLimits[tm.mId] = std::move(oLimit);
    1601             :     }
    1602             : 
    1603          48 :     CPLJSONObject oTilesTileMatrixSets;
    1604             :     {
    1605          24 :         CPLJSONDocument oDoc;
    1606          24 :         CPL_IGNORE_RET_VAL(
    1607          24 :             oDoc.LoadMemory(tmsLimitedToZoomLevelUsed.exportToTMSJsonV1()));
    1608             :         oTilesTileMatrixSets[tmsLimitedToZoomLevelUsed.identifier()] =
    1609          24 :             oDoc.GetRoot();
    1610             :     }
    1611          24 :     oProperties["tiles:tile_matrix_sets"] = std::move(oTilesTileMatrixSets);
    1612             : 
    1613          48 :     CPLJSONObject oTilesTileMatrixLinks;
    1614          48 :     CPLJSONObject oTilesTileMatrixLink;
    1615             :     oTilesTileMatrixLink["url"] =
    1616          24 :         std::string("#").append(tmsLimitedToZoomLevelUsed.identifier());
    1617          24 :     oTilesTileMatrixLink["limits"] = std::move(oLimits);
    1618             :     oTilesTileMatrixLinks[tmsLimitedToZoomLevelUsed.identifier()] =
    1619          24 :         std::move(oTilesTileMatrixLink);
    1620          24 :     oProperties["tiles:tile_matrix_links"] = std::move(oTilesTileMatrixLinks);
    1621             : 
    1622          24 :     const char *pszAuthName = oSRS.GetAuthorityName(nullptr);
    1623          24 :     const char *pszAuthCode = oSRS.GetAuthorityCode(nullptr);
    1624          24 :     if (pszAuthName && pszAuthCode)
    1625             :     {
    1626          23 :         oProperties["proj:code"] =
    1627          23 :             std::string(pszAuthName).append(":").append(pszAuthCode);
    1628             :     }
    1629             :     else
    1630             :     {
    1631           1 :         char *pszPROJJSON = nullptr;
    1632           1 :         CPL_IGNORE_RET_VAL(oSRS.exportToPROJJSON(&pszPROJJSON, nullptr));
    1633           1 :         if (pszPROJJSON)
    1634             :         {
    1635           0 :             CPLJSONDocument oDoc;
    1636           0 :             CPL_IGNORE_RET_VAL(oDoc.LoadMemory(pszPROJJSON));
    1637           0 :             CPLFree(pszPROJJSON);
    1638           0 :             oProperties["proj:projjson"] = oDoc.GetRoot();
    1639             :         }
    1640             :     }
    1641             :     {
    1642          24 :         auto ovrTileMatrix = tms.tileMatrixList()[nMaxZoom];
    1643          24 :         int nOvrMinTileX = 0;
    1644          24 :         int nOvrMinTileY = 0;
    1645          24 :         int nOvrMaxTileX = 0;
    1646          24 :         int nOvrMaxTileY = 0;
    1647          24 :         bool bIntersects = false;
    1648          24 :         CPL_IGNORE_RET_VAL(GetTileIndices(
    1649             :             ovrTileMatrix, bInvertAxisTMS, tileSize, adfExtent, nOvrMinTileX,
    1650             :             nOvrMinTileY, nOvrMaxTileX, nOvrMaxTileY,
    1651             :             /* noIntersectionIsOK = */ true, bIntersects));
    1652           0 :         oProperties["proj:shape"] = {
    1653          24 :             (nOvrMaxTileY - nOvrMinTileY + 1) * ovrTileMatrix.mTileHeight,
    1654          24 :             (nOvrMaxTileX - nOvrMinTileX + 1) * ovrTileMatrix.mTileWidth};
    1655             : 
    1656           0 :         oProperties["proj:transform"] = {
    1657          24 :             ovrTileMatrix.mResX,
    1658             :             0.0,
    1659          24 :             ovrTileMatrix.mTopLeftX +
    1660          24 :                 nOvrMinTileX * ovrTileMatrix.mTileWidth * ovrTileMatrix.mResX,
    1661             :             0.0,
    1662          24 :             -ovrTileMatrix.mResY,
    1663          24 :             ovrTileMatrix.mTopLeftY +
    1664          24 :                 nOvrMinTileY * ovrTileMatrix.mTileHeight * ovrTileMatrix.mResY,
    1665             :             0.0,
    1666             :             0.0,
    1667          24 :             0.0};
    1668             :     }
    1669             : 
    1670          24 :     constexpr const char *ASSET_NAME = "bands";
    1671             : 
    1672          48 :     CPLJSONObject oAssetTemplates;
    1673          24 :     oRoot["asset_templates"] = oAssetTemplates;
    1674             : 
    1675          48 :     CPLJSONObject oAssetTemplate;
    1676          24 :     oAssetTemplates[ASSET_NAME] = oAssetTemplate;
    1677             : 
    1678          48 :     std::string osHref = (osURL.empty() ? std::string(".") : std::string(osURL))
    1679          24 :                              .append("/{TileMatrix}/{TileCol}/{TileRow}.")
    1680          48 :                              .append(osExtension);
    1681             : 
    1682             :     const std::map<std::string, std::string> oMapVSIToURIPrefix = {
    1683             :         {"vsis3", "s3://"},
    1684             :         {"vsigs", "gs://"},
    1685             :         {"vsiaz", "az://"},  // Not universally recognized
    1686         144 :     };
    1687             : 
    1688             :     const CPLStringList aosSplitHref(
    1689          48 :         CSLTokenizeString2(osHref.c_str(), "/", 0));
    1690          24 :     if (!aosSplitHref.empty())
    1691             :     {
    1692          24 :         const auto oIter = oMapVSIToURIPrefix.find(aosSplitHref[0]);
    1693          24 :         if (oIter != oMapVSIToURIPrefix.end())
    1694             :         {
    1695             :             // +2 because of 2 slash characters
    1696           0 :             osHref = std::string(oIter->second)
    1697           0 :                          .append(osHref.c_str() + strlen(aosSplitHref[0]) + 2);
    1698             :         }
    1699             :     }
    1700          24 :     oAssetTemplate["href"] = osHref;
    1701             : 
    1702          24 :     if (EQUAL(osFormat.c_str(), "COG"))
    1703             :         oAssetTemplate["type"] =
    1704           0 :             "image/tiff; application=geotiff; profile=cloud-optimized";
    1705          24 :     else if (osExtension == "tif")
    1706           2 :         oAssetTemplate["type"] = "image/tiff; application=geotiff";
    1707          22 :     else if (osExtension == "png")
    1708          22 :         oAssetTemplate["type"] = "image/png";
    1709           0 :     else if (osExtension == "jpg")
    1710           0 :         oAssetTemplate["type"] = "image/jpeg";
    1711           0 :     else if (osExtension == "webp")
    1712           0 :         oAssetTemplate["type"] = "image/webp";
    1713             : 
    1714             :     const std::map<GDALDataType, const char *> oMapDTToStac = {
    1715             :         {GDT_Int8, "int8"},
    1716             :         {GDT_Int16, "int16"},
    1717             :         {GDT_Int32, "int32"},
    1718             :         {GDT_Int64, "int64"},
    1719             :         {GDT_Byte, "uint8"},
    1720             :         {GDT_UInt16, "uint16"},
    1721             :         {GDT_UInt32, "uint32"},
    1722             :         {GDT_UInt64, "uint64"},
    1723             :         // float16: 16-bit float; unhandled
    1724             :         {GDT_Float32, "float32"},
    1725             :         {GDT_Float64, "float64"},
    1726             :         {GDT_CInt16, "cint16"},
    1727             :         {GDT_CInt32, "cint32"},
    1728             :         // cfloat16: complex 16-bit float; unhandled
    1729             :         {GDT_CFloat32, "cfloat32"},
    1730             :         {GDT_CFloat64, "cfloat64"},
    1731          48 :     };
    1732             : 
    1733          48 :     CPLJSONArray oBands;
    1734          24 :     int iBand = 1;
    1735          24 :     bool bEOExtensionUsed = false;
    1736          78 :     for (const auto &bandMD : aoBandMetadata)
    1737             :     {
    1738         108 :         CPLJSONObject oBand;
    1739          54 :         oBand["name"] = bandMD.osDescription.empty()
    1740         108 :                             ? std::string(CPLSPrintf("Band%d", iBand))
    1741          54 :                             : bandMD.osDescription;
    1742             : 
    1743          54 :         const auto oIter = oMapDTToStac.find(bandMD.eDT);
    1744          54 :         if (oIter != oMapDTToStac.end())
    1745          54 :             oBand["data_type"] = oIter->second;
    1746             : 
    1747          54 :         if (const char *pszCommonName =
    1748          54 :                 GDALGetSTACCommonNameFromColorInterp(bandMD.eColorInterp))
    1749             :         {
    1750          33 :             bEOExtensionUsed = true;
    1751          33 :             oBand["eo:common_name"] = pszCommonName;
    1752             :         }
    1753          54 :         if (!bandMD.osCenterWaveLength.empty() && !bandMD.osFWHM.empty())
    1754             :         {
    1755           0 :             bEOExtensionUsed = true;
    1756             :             oBand["eo:center_wavelength"] =
    1757           0 :                 CPLAtof(bandMD.osCenterWaveLength.c_str());
    1758           0 :             oBand["eo:full_width_half_max"] = CPLAtof(bandMD.osFWHM.c_str());
    1759             :         }
    1760          54 :         ++iBand;
    1761          54 :         oBands.Add(oBand);
    1762             :     }
    1763          24 :     oAssetTemplate["bands"] = oBands;
    1764             : 
    1765          24 :     oRoot.Add("assets", CPLJSONObject());
    1766          24 :     oRoot.Add("links", CPLJSONArray());
    1767             : 
    1768          24 :     oExtensions.Add(
    1769             :         "https://stac-extensions.github.io/tiled-assets/v1.0.0/schema.json");
    1770          24 :     oExtensions.Add(
    1771             :         "https://stac-extensions.github.io/projection/v2.0.0/schema.json");
    1772          24 :     if (bEOExtensionUsed)
    1773          11 :         oExtensions.Add(
    1774             :             "https://stac-extensions.github.io/eo/v2.0.0/schema.json");
    1775             : 
    1776             :     // Serialize JSON document to file
    1777             :     const std::string osJSON =
    1778          48 :         CPLString(oRoot.Format(CPLJSONObject::PrettyFormat::Pretty))
    1779          72 :             .replaceAll("\\/", '/');
    1780          24 :     VSILFILE *f = VSIFOpenL(
    1781          48 :         CPLFormFilenameSafe(osDirectory.c_str(), "stacta.json", nullptr)
    1782             :             .c_str(),
    1783             :         "wb");
    1784          24 :     if (f)
    1785             :     {
    1786          24 :         VSIFWriteL(osJSON.data(), 1, osJSON.size(), f);
    1787          24 :         VSIFCloseL(f);
    1788             :     }
    1789          24 : }
    1790             : 
    1791             : /************************************************************************/
    1792             : /*                           GenerateOpenLayers()                       */
    1793             : /************************************************************************/
    1794             : 
    1795          27 : static void GenerateOpenLayers(
    1796             :     const std::string &osDirectory, const std::string &osTitle, double dfMinX,
    1797             :     double dfMinY, double dfMaxX, double dfMaxY, int nMinZoom, int nMaxZoom,
    1798             :     int nTileSize, const std::string &osExtension, const std::string &osURL,
    1799             :     const std::string &osCopyright, const gdal::TileMatrixSet &tms,
    1800             :     bool bInvertAxisTMS, const OGRSpatialReference &oSRS_TMS, bool bXYZ)
    1801             : {
    1802          54 :     std::map<std::string, std::string> substs;
    1803             : 
    1804             :     // For tests
    1805             :     const char *pszFmt =
    1806          27 :         atoi(CPLGetConfigOption("GDAL_RASTER_TILE_HTML_PREC", "17")) == 10
    1807             :             ? "%.10g"
    1808          27 :             : "%.17g";
    1809             : 
    1810          27 :     char *pszStr = CPLEscapeString(osTitle.c_str(), -1, CPLES_XML);
    1811          27 :     substs["xml_escaped_title"] = pszStr;
    1812          27 :     CPLFree(pszStr);
    1813          27 :     substs["ominx"] = CPLSPrintf(pszFmt, dfMinX);
    1814          27 :     substs["ominy"] = CPLSPrintf(pszFmt, dfMinY);
    1815          27 :     substs["omaxx"] = CPLSPrintf(pszFmt, dfMaxX);
    1816          27 :     substs["omaxy"] = CPLSPrintf(pszFmt, dfMaxY);
    1817          27 :     substs["center_x"] = CPLSPrintf(pszFmt, (dfMinX + dfMaxX) / 2);
    1818          27 :     substs["center_y"] = CPLSPrintf(pszFmt, (dfMinY + dfMaxY) / 2);
    1819          27 :     substs["minzoom"] = CPLSPrintf("%d", nMinZoom);
    1820          27 :     substs["maxzoom"] = CPLSPrintf("%d", nMaxZoom);
    1821          27 :     substs["tile_size"] = CPLSPrintf("%d", nTileSize);
    1822          27 :     substs["tileformat"] = osExtension;
    1823          27 :     substs["publishurl"] = osURL;
    1824          27 :     substs["copyright"] = osCopyright;
    1825          27 :     substs["sign_y"] = bXYZ ? "" : "-";
    1826             : 
    1827             :     CPLString s(R"raw(<!DOCTYPE html>
    1828             : <html>
    1829             : <head>
    1830             :     <title>%(xml_escaped_title)s</title>
    1831             :     <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
    1832             :     <meta http-equiv='imagetoolbar' content='no'/>
    1833             :     <style type="text/css"> v\:* {behavior:url(#default#VML);}
    1834             :         html, body { overflow: hidden; padding: 0; height: 100%; width: 100%; font-family: 'Lucida Grande',Geneva,Arial,Verdana,sans-serif; }
    1835             :         body { margin: 10px; background: #fff; }
    1836             :         h1 { margin: 0; padding: 6px; border:0; font-size: 20pt; }
    1837             :         #header { height: 43px; padding: 0; background-color: #eee; border: 1px solid #888; }
    1838             :         #subheader { height: 12px; text-align: right; font-size: 10px; color: #555;}
    1839             :         #map { height: 90%; border: 1px solid #888; }
    1840             :     </style>
    1841             :     <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">
    1842             :     <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@main/dist/en/v7.0.0/legacy/ol.js"></script>
    1843             :     <script src="https://unpkg.com/ol-layerswitcher@4.1.1"></script>
    1844             :     <link rel="stylesheet" href="https://unpkg.com/ol-layerswitcher@4.1.1/src/ol-layerswitcher.css" />
    1845             : </head>
    1846             : <body>
    1847             :     <div id="header"><h1>%(xml_escaped_title)s</h1></div>
    1848             :     <div id="subheader">Generated by <a href="https://gdal.org/programs/gdal_raster_tile.html">gdal raster tile</a>&nbsp;&nbsp;&nbsp;&nbsp;</div>
    1849             :     <div id="map" class="map"></div>
    1850             :     <div id="mouse-position"></div>
    1851             :     <script type="text/javascript">
    1852             :         var mousePositionControl = new ol.control.MousePosition({
    1853             :             className: 'custom-mouse-position',
    1854             :             target: document.getElementById('mouse-position'),
    1855             :             undefinedHTML: '&nbsp;'
    1856             :         });
    1857             :         var map = new ol.Map({
    1858             :             controls: ol.control.defaults.defaults().extend([mousePositionControl]),
    1859          54 :             target: 'map',)raw");
    1860             : 
    1861          35 :     if (tms.identifier() == "GoogleMapsCompatible" ||
    1862           8 :         tms.identifier() == "WorldCRS84Quad")
    1863             :     {
    1864          21 :         s += R"raw(
    1865             :             layers: [
    1866             :                 new ol.layer.Group({
    1867             :                         title: 'Base maps',
    1868             :                         layers: [
    1869             :                             new ol.layer.Tile({
    1870             :                                 title: 'OpenStreetMap',
    1871             :                                 type: 'base',
    1872             :                                 visible: true,
    1873             :                                 source: new ol.source.OSM()
    1874             :                             }),
    1875             :                         ]
    1876             :                 }),)raw";
    1877             :     }
    1878             : 
    1879          27 :     if (tms.identifier() == "GoogleMapsCompatible")
    1880             :     {
    1881          19 :         s += R"raw(new ol.layer.Group({
    1882             :                     title: 'Overlay',
    1883             :                     layers: [
    1884             :                         new ol.layer.Tile({
    1885             :                             title: 'Overlay',
    1886             :                             // opacity: 0.7,
    1887             :                             extent: [%(ominx)f, %(ominy)f,%(omaxx)f, %(omaxy)f],
    1888             :                             source: new ol.source.XYZ({
    1889             :                                 attributions: '%(copyright)s',
    1890             :                                 minZoom: %(minzoom)d,
    1891             :                                 maxZoom: %(maxzoom)d,
    1892             :                                 url: './{z}/{x}/{%(sign_y)sy}.%(tileformat)s',
    1893             :                                 tileSize: [%(tile_size)d, %(tile_size)d]
    1894             :                             })
    1895             :                         }),
    1896             :                     ]
    1897             :                 }),)raw";
    1898             :     }
    1899           8 :     else if (tms.identifier() == "WorldCRS84Quad")
    1900             :     {
    1901           2 :         const double base_res = 180.0 / nTileSize;
    1902           4 :         std::string resolutions = "[";
    1903           4 :         for (int i = 0; i <= nMaxZoom; ++i)
    1904             :         {
    1905           2 :             if (i > 0)
    1906           0 :                 resolutions += ",";
    1907           2 :             resolutions += CPLSPrintf(pszFmt, base_res / (1 << i));
    1908             :         }
    1909           2 :         resolutions += "]";
    1910           2 :         substs["resolutions"] = std::move(resolutions);
    1911             : 
    1912           2 :         if (bXYZ)
    1913             :         {
    1914           1 :             substs["origin"] = "[-180,90]";
    1915           1 :             substs["y_formula"] = "tileCoord[2]";
    1916             :         }
    1917             :         else
    1918             :         {
    1919           1 :             substs["origin"] = "[-180,-90]";
    1920           1 :             substs["y_formula"] = "- 1 - tileCoord[2]";
    1921             :         }
    1922             : 
    1923           2 :         s += R"raw(
    1924             :                 new ol.layer.Group({
    1925             :                     title: 'Overlay',
    1926             :                     layers: [
    1927             :                         new ol.layer.Tile({
    1928             :                             title: 'Overlay',
    1929             :                             // opacity: 0.7,
    1930             :                             extent: [%(ominx)f, %(ominy)f,%(omaxx)f, %(omaxy)f],
    1931             :                             source: new ol.source.TileImage({
    1932             :                                 attributions: '%(copyright)s',
    1933             :                                 projection: 'EPSG:4326',
    1934             :                                 minZoom: %(minzoom)d,
    1935             :                                 maxZoom: %(maxzoom)d,
    1936             :                                 tileGrid: new ol.tilegrid.TileGrid({
    1937             :                                     extent: [-180,-90,180,90],
    1938             :                                     origin: %(origin)s,
    1939             :                                     resolutions: %(resolutions)s,
    1940             :                                     tileSize: [%(tile_size)d, %(tile_size)d]
    1941             :                                 }),
    1942             :                                 tileUrlFunction: function(tileCoord) {
    1943             :                                     return ('./{z}/{x}/{y}.%(tileformat)s'
    1944             :                                         .replace('{z}', String(tileCoord[0]))
    1945             :                                         .replace('{x}', String(tileCoord[1]))
    1946             :                                         .replace('{y}', String(%(y_formula)s)));
    1947             :                                 },
    1948             :                             })
    1949             :                         }),
    1950             :                     ]
    1951             :                 }),)raw";
    1952             :     }
    1953             :     else
    1954             :     {
    1955          12 :         substs["maxres"] =
    1956          12 :             CPLSPrintf(pszFmt, tms.tileMatrixList()[nMinZoom].mResX);
    1957          12 :         std::string resolutions = "[";
    1958          16 :         for (int i = 0; i <= nMaxZoom; ++i)
    1959             :         {
    1960          10 :             if (i > 0)
    1961           4 :                 resolutions += ",";
    1962          10 :             resolutions += CPLSPrintf(pszFmt, tms.tileMatrixList()[i].mResX);
    1963             :         }
    1964           6 :         resolutions += "]";
    1965           6 :         substs["resolutions"] = std::move(resolutions);
    1966             : 
    1967          12 :         std::string matrixsizes = "[";
    1968          16 :         for (int i = 0; i <= nMaxZoom; ++i)
    1969             :         {
    1970          10 :             if (i > 0)
    1971           4 :                 matrixsizes += ",";
    1972             :             matrixsizes +=
    1973          10 :                 CPLSPrintf("[%d,%d]", tms.tileMatrixList()[i].mMatrixWidth,
    1974          20 :                            tms.tileMatrixList()[i].mMatrixHeight);
    1975             :         }
    1976           6 :         matrixsizes += "]";
    1977           6 :         substs["matrixsizes"] = std::move(matrixsizes);
    1978             : 
    1979           6 :         double dfTopLeftX = tms.tileMatrixList()[0].mTopLeftX;
    1980           6 :         double dfTopLeftY = tms.tileMatrixList()[0].mTopLeftY;
    1981           6 :         if (bInvertAxisTMS)
    1982           0 :             std::swap(dfTopLeftX, dfTopLeftY);
    1983             : 
    1984           6 :         if (bXYZ)
    1985             :         {
    1986          10 :             substs["origin"] =
    1987          10 :                 CPLSPrintf("[%.17g,%.17g]", dfTopLeftX, dfTopLeftY);
    1988           5 :             substs["y_formula"] = "tileCoord[2]";
    1989             :         }
    1990             :         else
    1991             :         {
    1992           2 :             substs["origin"] = CPLSPrintf(
    1993             :                 "[%.17g,%.17g]", dfTopLeftX,
    1994           1 :                 dfTopLeftY - tms.tileMatrixList()[0].mResY *
    1995           3 :                                  tms.tileMatrixList()[0].mTileHeight);
    1996           1 :             substs["y_formula"] = "- 1 - tileCoord[2]";
    1997             :         }
    1998             : 
    1999          12 :         substs["tilegrid_extent"] =
    2000             :             CPLSPrintf("[%.17g,%.17g,%.17g,%.17g]", dfTopLeftX,
    2001           6 :                        dfTopLeftY - tms.tileMatrixList()[0].mMatrixHeight *
    2002           6 :                                         tms.tileMatrixList()[0].mResY *
    2003           6 :                                         tms.tileMatrixList()[0].mTileHeight,
    2004           6 :                        dfTopLeftX + tms.tileMatrixList()[0].mMatrixWidth *
    2005           6 :                                         tms.tileMatrixList()[0].mResX *
    2006           6 :                                         tms.tileMatrixList()[0].mTileWidth,
    2007          24 :                        dfTopLeftY);
    2008             : 
    2009           6 :         s += R"raw(
    2010             :             layers: [
    2011             :                 new ol.layer.Group({
    2012             :                     title: 'Overlay',
    2013             :                     layers: [
    2014             :                         new ol.layer.Tile({
    2015             :                             title: 'Overlay',
    2016             :                             // opacity: 0.7,
    2017             :                             extent: [%(ominx)f, %(ominy)f,%(omaxx)f, %(omaxy)f],
    2018             :                             source: new ol.source.TileImage({
    2019             :                                 attributions: '%(copyright)s',
    2020             :                                 minZoom: %(minzoom)d,
    2021             :                                 maxZoom: %(maxzoom)d,
    2022             :                                 tileGrid: new ol.tilegrid.TileGrid({
    2023             :                                     extent: %(tilegrid_extent)s,
    2024             :                                     origin: %(origin)s,
    2025             :                                     resolutions: %(resolutions)s,
    2026             :                                     sizes: %(matrixsizes)s,
    2027             :                                     tileSize: [%(tile_size)d, %(tile_size)d]
    2028             :                                 }),
    2029             :                                 tileUrlFunction: function(tileCoord) {
    2030             :                                     return ('./{z}/{x}/{y}.%(tileformat)s'
    2031             :                                         .replace('{z}', String(tileCoord[0]))
    2032             :                                         .replace('{x}', String(tileCoord[1]))
    2033             :                                         .replace('{y}', String(%(y_formula)s)));
    2034             :                                 },
    2035             :                             })
    2036             :                         }),
    2037             :                     ]
    2038             :                 }),)raw";
    2039             :     }
    2040             : 
    2041          27 :     s += R"raw(
    2042             :             ],
    2043             :             view: new ol.View({
    2044             :                 center: [%(center_x)f, %(center_y)f],)raw";
    2045             : 
    2046          35 :     if (tms.identifier() == "GoogleMapsCompatible" ||
    2047           8 :         tms.identifier() == "WorldCRS84Quad")
    2048             :     {
    2049          21 :         substs["view_zoom"] = substs["minzoom"];
    2050          21 :         if (tms.identifier() == "WorldCRS84Quad")
    2051             :         {
    2052           2 :             substs["view_zoom"] = CPLSPrintf("%d", nMinZoom + 1);
    2053             :         }
    2054             : 
    2055          21 :         s += R"raw(
    2056             :                 zoom: %(view_zoom)d,)raw";
    2057             :     }
    2058             :     else
    2059             :     {
    2060           6 :         s += R"raw(
    2061             :                 resolution: %(maxres)f,)raw";
    2062             :     }
    2063             : 
    2064          27 :     if (tms.identifier() == "WorldCRS84Quad")
    2065             :     {
    2066           2 :         s += R"raw(
    2067             :                 projection: 'EPSG:4326',)raw";
    2068             :     }
    2069          25 :     else if (!oSRS_TMS.IsEmpty() && tms.identifier() != "GoogleMapsCompatible")
    2070             :     {
    2071           5 :         const char *pszAuthName = oSRS_TMS.GetAuthorityName(nullptr);
    2072           5 :         const char *pszAuthCode = oSRS_TMS.GetAuthorityCode(nullptr);
    2073           5 :         if (pszAuthName && pszAuthCode && EQUAL(pszAuthName, "EPSG"))
    2074             :         {
    2075           3 :             substs["epsg_code"] = pszAuthCode;
    2076           3 :             if (oSRS_TMS.IsGeographic())
    2077             :             {
    2078           1 :                 substs["units"] = "deg";
    2079             :             }
    2080             :             else
    2081             :             {
    2082           2 :                 const char *pszUnits = "";
    2083           2 :                 if (oSRS_TMS.GetLinearUnits(&pszUnits) == 1.0)
    2084           2 :                     substs["units"] = "m";
    2085             :                 else
    2086           0 :                     substs["units"] = pszUnits;
    2087             :             }
    2088           3 :             s += R"raw(
    2089             :                 projection: new ol.proj.Projection({code: 'EPSG:%(epsg_code)s', units:'%(units)s'}),)raw";
    2090             :         }
    2091             :     }
    2092             : 
    2093          27 :     s += R"raw(
    2094             :             })
    2095             :         });)raw";
    2096             : 
    2097          35 :     if (tms.identifier() == "GoogleMapsCompatible" ||
    2098           8 :         tms.identifier() == "WorldCRS84Quad")
    2099             :     {
    2100          21 :         s += R"raw(
    2101             :         map.addControl(new ol.control.LayerSwitcher());)raw";
    2102             :     }
    2103             : 
    2104          27 :     s += R"raw(
    2105             :     </script>
    2106             : </body>
    2107             : </html>)raw";
    2108             : 
    2109          27 :     ApplySubstitutions(s, substs);
    2110             : 
    2111          27 :     VSILFILE *f = VSIFOpenL(
    2112          54 :         CPLFormFilenameSafe(osDirectory.c_str(), "openlayers.html", nullptr)
    2113             :             .c_str(),
    2114             :         "wb");
    2115          27 :     if (f)
    2116             :     {
    2117          27 :         VSIFWriteL(s.data(), 1, s.size(), f);
    2118          27 :         VSIFCloseL(f);
    2119             :     }
    2120          27 : }
    2121             : 
    2122             : /************************************************************************/
    2123             : /*                           GetTileBoundingBox()                       */
    2124             : /************************************************************************/
    2125             : 
    2126           6 : static void GetTileBoundingBox(int nTileX, int nTileY, int nTileZ,
    2127             :                                const gdal::TileMatrixSet *poTMS,
    2128             :                                bool bInvertAxisTMS,
    2129             :                                OGRCoordinateTransformation *poCTToWGS84,
    2130             :                                double &dfTLX, double &dfTLY, double &dfTRX,
    2131             :                                double &dfTRY, double &dfLLX, double &dfLLY,
    2132             :                                double &dfLRX, double &dfLRY)
    2133             : {
    2134             :     gdal::TileMatrixSet::TileMatrix tileMatrix =
    2135          12 :         poTMS->tileMatrixList()[nTileZ];
    2136           6 :     if (bInvertAxisTMS)
    2137           0 :         std::swap(tileMatrix.mTopLeftX, tileMatrix.mTopLeftY);
    2138             : 
    2139           6 :     dfTLX = tileMatrix.mTopLeftX +
    2140           6 :             nTileX * tileMatrix.mResX * tileMatrix.mTileWidth;
    2141           6 :     dfTLY = tileMatrix.mTopLeftY -
    2142           6 :             nTileY * tileMatrix.mResY * tileMatrix.mTileHeight;
    2143           6 :     poCTToWGS84->Transform(1, &dfTLX, &dfTLY);
    2144             : 
    2145           6 :     dfTRX = tileMatrix.mTopLeftX +
    2146           6 :             (nTileX + 1) * tileMatrix.mResX * tileMatrix.mTileWidth;
    2147           6 :     dfTRY = tileMatrix.mTopLeftY -
    2148           6 :             nTileY * tileMatrix.mResY * tileMatrix.mTileHeight;
    2149           6 :     poCTToWGS84->Transform(1, &dfTRX, &dfTRY);
    2150             : 
    2151           6 :     dfLLX = tileMatrix.mTopLeftX +
    2152           6 :             nTileX * tileMatrix.mResX * tileMatrix.mTileWidth;
    2153           6 :     dfLLY = tileMatrix.mTopLeftY -
    2154           6 :             (nTileY + 1) * tileMatrix.mResY * tileMatrix.mTileHeight;
    2155           6 :     poCTToWGS84->Transform(1, &dfLLX, &dfLLY);
    2156             : 
    2157           6 :     dfLRX = tileMatrix.mTopLeftX +
    2158           6 :             (nTileX + 1) * tileMatrix.mResX * tileMatrix.mTileWidth;
    2159           6 :     dfLRY = tileMatrix.mTopLeftY -
    2160           6 :             (nTileY + 1) * tileMatrix.mResY * tileMatrix.mTileHeight;
    2161           6 :     poCTToWGS84->Transform(1, &dfLRX, &dfLRY);
    2162           6 : }
    2163             : 
    2164             : /************************************************************************/
    2165             : /*                           GenerateKML()                              */
    2166             : /************************************************************************/
    2167             : 
    2168             : namespace
    2169             : {
    2170             : struct TileCoordinates
    2171             : {
    2172             :     int nTileX = 0;
    2173             :     int nTileY = 0;
    2174             :     int nTileZ = 0;
    2175             : };
    2176             : }  // namespace
    2177             : 
    2178           5 : static void GenerateKML(const std::string &osDirectory,
    2179             :                         const std::string &osTitle, int nTileX, int nTileY,
    2180             :                         int nTileZ, int nTileSize,
    2181             :                         const std::string &osExtension,
    2182             :                         const std::string &osURL,
    2183             :                         const gdal::TileMatrixSet *poTMS, bool bInvertAxisTMS,
    2184             :                         const std::string &convention,
    2185             :                         OGRCoordinateTransformation *poCTToWGS84,
    2186             :                         const std::vector<TileCoordinates> &children)
    2187             : {
    2188          10 :     std::map<std::string, std::string> substs;
    2189             : 
    2190           5 :     const bool bIsTileKML = nTileX >= 0;
    2191             : 
    2192             :     // For tests
    2193             :     const char *pszFmt =
    2194           5 :         atoi(CPLGetConfigOption("GDAL_RASTER_TILE_KML_PREC", "14")) == 10
    2195             :             ? "%.10f"
    2196           5 :             : "%.14f";
    2197             : 
    2198           5 :     substs["tx"] = CPLSPrintf("%d", nTileX);
    2199           5 :     substs["tz"] = CPLSPrintf("%d", nTileZ);
    2200           5 :     substs["tileformat"] = osExtension;
    2201           5 :     substs["minlodpixels"] = CPLSPrintf("%d", nTileSize / 2);
    2202          10 :     substs["maxlodpixels"] =
    2203          10 :         children.empty() ? "-1" : CPLSPrintf("%d", nTileSize * 8);
    2204             : 
    2205           5 :     double dfTLX = 0;
    2206           5 :     double dfTLY = 0;
    2207           5 :     double dfTRX = 0;
    2208           5 :     double dfTRY = 0;
    2209           5 :     double dfLLX = 0;
    2210           5 :     double dfLLY = 0;
    2211           5 :     double dfLRX = 0;
    2212           5 :     double dfLRY = 0;
    2213             : 
    2214           5 :     int nFileY = -1;
    2215           5 :     if (!bIsTileKML)
    2216             :     {
    2217           2 :         char *pszStr = CPLEscapeString(osTitle.c_str(), -1, CPLES_XML);
    2218           2 :         substs["xml_escaped_title"] = pszStr;
    2219           2 :         CPLFree(pszStr);
    2220             :     }
    2221             :     else
    2222             :     {
    2223           3 :         nFileY = GetFileY(nTileY, poTMS->tileMatrixList()[nTileZ], convention);
    2224           3 :         substs["realtiley"] = CPLSPrintf("%d", nFileY);
    2225           6 :         substs["xml_escaped_title"] =
    2226           6 :             CPLSPrintf("%d/%d/%d.kml", nTileZ, nTileX, nFileY);
    2227             : 
    2228           3 :         GetTileBoundingBox(nTileX, nTileY, nTileZ, poTMS, bInvertAxisTMS,
    2229             :                            poCTToWGS84, dfTLX, dfTLY, dfTRX, dfTRY, dfLLX,
    2230             :                            dfLLY, dfLRX, dfLRY);
    2231             :     }
    2232             : 
    2233          10 :     substs["drawOrder"] = CPLSPrintf("%d", nTileX == 0  ? 2 * nTileZ + 1
    2234           4 :                                            : nTileX > 0 ? 2 * nTileZ
    2235          14 :                                                         : 0);
    2236             : 
    2237           5 :     substs["url"] = osURL.empty() && bIsTileKML ? "../../" : "";
    2238             : 
    2239           5 :     const bool bIsRectangle =
    2240           5 :         (dfTLX == dfLLX && dfTRX == dfLRX && dfTLY == dfTRY && dfLLY == dfLRY);
    2241           5 :     const bool bUseGXNamespace = bIsTileKML && !bIsRectangle;
    2242             : 
    2243          10 :     substs["xmlns_gx"] = bUseGXNamespace
    2244             :                              ? " xmlns:gx=\"http://www.google.com/kml/ext/2.2\""
    2245          10 :                              : "";
    2246             : 
    2247             :     CPLString s(R"raw(<?xml version="1.0" encoding="utf-8"?>
    2248             : <kml xmlns="http://www.opengis.net/kml/2.2"%(xmlns_gx)s>
    2249             :   <Document>
    2250             :     <name>%(xml_escaped_title)s</name>
    2251             :     <description></description>
    2252             :     <Style>
    2253             :       <ListStyle id="hideChildren">
    2254             :         <listItemType>checkHideChildren</listItemType>
    2255             :       </ListStyle>
    2256             :     </Style>
    2257          10 : )raw");
    2258           5 :     ApplySubstitutions(s, substs);
    2259             : 
    2260           5 :     if (bIsTileKML)
    2261             :     {
    2262             :         CPLString s2(R"raw(    <Region>
    2263             :       <LatLonAltBox>
    2264             :         <north>%(north)f</north>
    2265             :         <south>%(south)f</south>
    2266             :         <east>%(east)f</east>
    2267             :         <west>%(west)f</west>
    2268             :       </LatLonAltBox>
    2269             :       <Lod>
    2270             :         <minLodPixels>%(minlodpixels)d</minLodPixels>
    2271             :         <maxLodPixels>%(maxlodpixels)d</maxLodPixels>
    2272             :       </Lod>
    2273             :     </Region>
    2274             :     <GroundOverlay>
    2275             :       <drawOrder>%(drawOrder)d</drawOrder>
    2276             :       <Icon>
    2277             :         <href>%(realtiley)d.%(tileformat)s</href>
    2278             :       </Icon>
    2279             :       <LatLonBox>
    2280             :         <north>%(north)f</north>
    2281             :         <south>%(south)f</south>
    2282             :         <east>%(east)f</east>
    2283             :         <west>%(west)f</west>
    2284             :       </LatLonBox>
    2285           6 : )raw");
    2286             : 
    2287           3 :         if (!bIsRectangle)
    2288             :         {
    2289             :             s2 +=
    2290           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>
    2291             : )raw";
    2292             :         }
    2293             : 
    2294           3 :         s2 += R"raw(    </GroundOverlay>
    2295             : )raw";
    2296           3 :         substs["north"] = CPLSPrintf(pszFmt, std::max(dfTLY, dfTRY));
    2297           3 :         substs["south"] = CPLSPrintf(pszFmt, std::min(dfLLY, dfLRY));
    2298           3 :         substs["east"] = CPLSPrintf(pszFmt, std::max(dfTRX, dfLRX));
    2299           3 :         substs["west"] = CPLSPrintf(pszFmt, std::min(dfLLX, dfTLX));
    2300             : 
    2301           3 :         if (!bIsRectangle)
    2302             :         {
    2303           1 :             substs["TLX"] = CPLSPrintf(pszFmt, dfTLX);
    2304           1 :             substs["TLY"] = CPLSPrintf(pszFmt, dfTLY);
    2305           1 :             substs["TRX"] = CPLSPrintf(pszFmt, dfTRX);
    2306           1 :             substs["TRY"] = CPLSPrintf(pszFmt, dfTRY);
    2307           1 :             substs["LRX"] = CPLSPrintf(pszFmt, dfLRX);
    2308           1 :             substs["LRY"] = CPLSPrintf(pszFmt, dfLRY);
    2309           1 :             substs["LLX"] = CPLSPrintf(pszFmt, dfLLX);
    2310           1 :             substs["LLY"] = CPLSPrintf(pszFmt, dfLLY);
    2311             :         }
    2312             : 
    2313           3 :         ApplySubstitutions(s2, substs);
    2314           3 :         s += s2;
    2315             :     }
    2316             : 
    2317           8 :     for (const auto &child : children)
    2318             :     {
    2319           3 :         substs["tx"] = CPLSPrintf("%d", child.nTileX);
    2320           3 :         substs["tz"] = CPLSPrintf("%d", child.nTileZ);
    2321           6 :         substs["realtiley"] = CPLSPrintf(
    2322           3 :             "%d", GetFileY(child.nTileY, poTMS->tileMatrixList()[child.nTileZ],
    2323           6 :                            convention));
    2324             : 
    2325           3 :         GetTileBoundingBox(child.nTileX, child.nTileY, child.nTileZ, poTMS,
    2326             :                            bInvertAxisTMS, poCTToWGS84, dfTLX, dfTLY, dfTRX,
    2327             :                            dfTRY, dfLLX, dfLLY, dfLRX, dfLRY);
    2328             : 
    2329             :         CPLString s2(R"raw(    <NetworkLink>
    2330             :       <name>%(tz)d/%(tx)d/%(realtiley)d.%(tileformat)s</name>
    2331             :       <Region>
    2332             :         <LatLonAltBox>
    2333             :           <north>%(north)f</north>
    2334             :           <south>%(south)f</south>
    2335             :           <east>%(east)f</east>
    2336             :           <west>%(west)f</west>
    2337             :         </LatLonAltBox>
    2338             :         <Lod>
    2339             :           <minLodPixels>%(minlodpixels)d</minLodPixels>
    2340             :           <maxLodPixels>-1</maxLodPixels>
    2341             :         </Lod>
    2342             :       </Region>
    2343             :       <Link>
    2344             :         <href>%(url)s%(tz)d/%(tx)d/%(realtiley)d.kml</href>
    2345             :         <viewRefreshMode>onRegion</viewRefreshMode>
    2346             :         <viewFormat/>
    2347             :       </Link>
    2348             :     </NetworkLink>
    2349           6 : )raw");
    2350           3 :         substs["north"] = CPLSPrintf(pszFmt, std::max(dfTLY, dfTRY));
    2351           3 :         substs["south"] = CPLSPrintf(pszFmt, std::min(dfLLY, dfLRY));
    2352           3 :         substs["east"] = CPLSPrintf(pszFmt, std::max(dfTRX, dfLRX));
    2353           3 :         substs["west"] = CPLSPrintf(pszFmt, std::min(dfLLX, dfTLX));
    2354           3 :         ApplySubstitutions(s2, substs);
    2355           3 :         s += s2;
    2356             :     }
    2357             : 
    2358           5 :     s += R"raw(</Document>
    2359             : </kml>)raw";
    2360             : 
    2361          10 :     std::string osFilename(osDirectory);
    2362           5 :     if (!bIsTileKML)
    2363             :     {
    2364             :         osFilename =
    2365           2 :             CPLFormFilenameSafe(osFilename.c_str(), "doc.kml", nullptr);
    2366             :     }
    2367             :     else
    2368             :     {
    2369           6 :         osFilename = CPLFormFilenameSafe(osFilename.c_str(),
    2370           3 :                                          CPLSPrintf("%d", nTileZ), nullptr);
    2371           6 :         osFilename = CPLFormFilenameSafe(osFilename.c_str(),
    2372           3 :                                          CPLSPrintf("%d", nTileX), nullptr);
    2373           6 :         osFilename = CPLFormFilenameSafe(osFilename.c_str(),
    2374           3 :                                          CPLSPrintf("%d.kml", nFileY), nullptr);
    2375             :     }
    2376             : 
    2377           5 :     VSILFILE *f = VSIFOpenL(osFilename.c_str(), "wb");
    2378           5 :     if (f)
    2379             :     {
    2380           5 :         VSIFWriteL(s.data(), 1, s.size(), f);
    2381           5 :         VSIFCloseL(f);
    2382             :     }
    2383           5 : }
    2384             : 
    2385             : namespace
    2386             : {
    2387             : 
    2388             : /************************************************************************/
    2389             : /*                            ResourceManager                           */
    2390             : /************************************************************************/
    2391             : 
    2392             : // Generic cache managing resources
    2393             : template <class Resource> class ResourceManager /* non final */
    2394             : {
    2395             :   public:
    2396         136 :     virtual ~ResourceManager() = default;
    2397             : 
    2398          48 :     std::unique_ptr<Resource> AcquireResources()
    2399             :     {
    2400          96 :         std::lock_guard oLock(m_oMutex);
    2401          48 :         if (!m_oResources.empty())
    2402             :         {
    2403           0 :             auto ret = std::move(m_oResources.back());
    2404           0 :             m_oResources.pop_back();
    2405           0 :             return ret;
    2406             :         }
    2407             : 
    2408          48 :         return CreateResources();
    2409             :     }
    2410             : 
    2411          48 :     void ReleaseResources(std::unique_ptr<Resource> resources)
    2412             :     {
    2413          96 :         std::lock_guard oLock(m_oMutex);
    2414          48 :         m_oResources.push_back(std::move(resources));
    2415          48 :     }
    2416             : 
    2417           0 :     void SetError()
    2418             :     {
    2419           0 :         std::lock_guard oLock(m_oMutex);
    2420           0 :         if (m_errorMsg.empty())
    2421           0 :             m_errorMsg = CPLGetLastErrorMsg();
    2422           0 :     }
    2423             : 
    2424          12 :     const std::string &GetErrorMsg() const
    2425             :     {
    2426          12 :         std::lock_guard oLock(m_oMutex);
    2427          24 :         return m_errorMsg;
    2428             :     }
    2429             : 
    2430             :   protected:
    2431             :     virtual std::unique_ptr<Resource> CreateResources() = 0;
    2432             : 
    2433             :   private:
    2434             :     mutable std::mutex m_oMutex{};
    2435             :     std::vector<std::unique_ptr<Resource>> m_oResources{};
    2436             :     std::string m_errorMsg{};
    2437             : };
    2438             : 
    2439             : /************************************************************************/
    2440             : /*                         PerThreadMaxZoomResources                    */
    2441             : /************************************************************************/
    2442             : 
    2443             : // Per-thread resources for generation of tiles at full resolution
    2444             : struct PerThreadMaxZoomResources
    2445             : {
    2446             :     struct GDALDatasetReleaser
    2447             :     {
    2448          24 :         void operator()(GDALDataset *poDS)
    2449             :         {
    2450          24 :             if (poDS)
    2451          24 :                 poDS->ReleaseRef();
    2452          24 :         }
    2453             :     };
    2454             : 
    2455             :     std::unique_ptr<GDALDataset, GDALDatasetReleaser> poSrcDS{};
    2456             :     std::vector<GByte> dstBuffer{};
    2457             :     std::unique_ptr<FakeMaxZoomDataset> poFakeMaxZoomDS{};
    2458             :     std::unique_ptr<void, decltype(&GDALDestroyTransformer)> poTransformer{
    2459             :         nullptr, GDALDestroyTransformer};
    2460             :     std::unique_ptr<GDALWarpOperation> poWO{};
    2461             : };
    2462             : 
    2463             : /************************************************************************/
    2464             : /*                      PerThreadMaxZoomResourceManager                 */
    2465             : /************************************************************************/
    2466             : 
    2467             : // Manage a cache of PerThreadMaxZoomResources instances
    2468             : class PerThreadMaxZoomResourceManager final
    2469             :     : public ResourceManager<PerThreadMaxZoomResources>
    2470             : {
    2471             :   public:
    2472          75 :     PerThreadMaxZoomResourceManager(GDALDataset *poSrcDS,
    2473             :                                     const GDALWarpOptions *psWO,
    2474             :                                     void *pTransformerArg,
    2475             :                                     const FakeMaxZoomDataset &oFakeMaxZoomDS,
    2476             :                                     size_t nBufferSize)
    2477          75 :         : m_poSrcDS(poSrcDS), m_psWOSource(psWO),
    2478             :           m_pTransformerArg(pTransformerArg), m_oFakeMaxZoomDS(oFakeMaxZoomDS),
    2479          75 :           m_nBufferSize(nBufferSize)
    2480             :     {
    2481          75 :     }
    2482             : 
    2483             :   protected:
    2484          24 :     std::unique_ptr<PerThreadMaxZoomResources> CreateResources() override
    2485             :     {
    2486          48 :         auto ret = std::make_unique<PerThreadMaxZoomResources>();
    2487             : 
    2488          24 :         ret->poSrcDS.reset(GDALGetThreadSafeDataset(m_poSrcDS, GDAL_OF_RASTER));
    2489          24 :         if (!ret->poSrcDS)
    2490           0 :             return nullptr;
    2491             : 
    2492             :         try
    2493             :         {
    2494          24 :             ret->dstBuffer.resize(m_nBufferSize);
    2495             :         }
    2496           0 :         catch (const std::exception &)
    2497             :         {
    2498           0 :             CPLError(CE_Failure, CPLE_OutOfMemory,
    2499             :                      "Out of memory allocating temporary buffer");
    2500           0 :             return nullptr;
    2501             :         }
    2502             : 
    2503          24 :         ret->poFakeMaxZoomDS = m_oFakeMaxZoomDS.Clone(ret->dstBuffer);
    2504             : 
    2505          24 :         ret->poTransformer.reset(GDALCloneTransformer(m_pTransformerArg));
    2506          24 :         if (!ret->poTransformer)
    2507           0 :             return nullptr;
    2508             : 
    2509             :         auto psWO =
    2510             :             std::unique_ptr<GDALWarpOptions, decltype(&GDALDestroyWarpOptions)>(
    2511          48 :                 GDALCloneWarpOptions(m_psWOSource), GDALDestroyWarpOptions);
    2512          24 :         if (!psWO)
    2513           0 :             return nullptr;
    2514             : 
    2515          24 :         psWO->hSrcDS = GDALDataset::ToHandle(ret->poSrcDS.get());
    2516          24 :         psWO->hDstDS = GDALDataset::ToHandle(ret->poFakeMaxZoomDS.get());
    2517          24 :         psWO->pTransformerArg = ret->poTransformer.get();
    2518          24 :         psWO->pfnTransformer = m_psWOSource->pfnTransformer;
    2519             : 
    2520          24 :         ret->poWO = std::make_unique<GDALWarpOperation>();
    2521          24 :         if (ret->poWO->Initialize(psWO.get()) != CE_None)
    2522           0 :             return nullptr;
    2523             : 
    2524          24 :         return ret;
    2525             :     }
    2526             : 
    2527             :   private:
    2528             :     GDALDataset *const m_poSrcDS;
    2529             :     const GDALWarpOptions *const m_psWOSource;
    2530             :     void *const m_pTransformerArg;
    2531             :     const FakeMaxZoomDataset &m_oFakeMaxZoomDS;
    2532             :     const size_t m_nBufferSize;
    2533             : 
    2534             :     CPL_DISALLOW_COPY_ASSIGN(PerThreadMaxZoomResourceManager)
    2535             : };
    2536             : 
    2537             : /************************************************************************/
    2538             : /*                       PerThreadLowerZoomResources                    */
    2539             : /************************************************************************/
    2540             : 
    2541             : // Per-thread resources for generation of tiles at zoom level < max
    2542             : struct PerThreadLowerZoomResources
    2543             : {
    2544             :     std::unique_ptr<GDALDataset> poSrcDS{};
    2545             : };
    2546             : 
    2547             : /************************************************************************/
    2548             : /*                   PerThreadLowerZoomResourceManager                  */
    2549             : /************************************************************************/
    2550             : 
    2551             : // Manage a cache of PerThreadLowerZoomResources instances
    2552             : class PerThreadLowerZoomResourceManager final
    2553             :     : public ResourceManager<PerThreadLowerZoomResources>
    2554             : {
    2555             :   public:
    2556          61 :     explicit PerThreadLowerZoomResourceManager(const MosaicDataset &oSrcDS)
    2557          61 :         : m_oSrcDS(oSrcDS)
    2558             :     {
    2559          61 :     }
    2560             : 
    2561             :   protected:
    2562          24 :     std::unique_ptr<PerThreadLowerZoomResources> CreateResources() override
    2563             :     {
    2564          24 :         auto ret = std::make_unique<PerThreadLowerZoomResources>();
    2565          24 :         ret->poSrcDS = m_oSrcDS.Clone();
    2566          24 :         return ret;
    2567             :     }
    2568             : 
    2569             :   private:
    2570             :     const MosaicDataset &m_oSrcDS;
    2571             : };
    2572             : 
    2573             : }  // namespace
    2574             : 
    2575             : /************************************************************************/
    2576             : /*            GDALRasterTileAlgorithm::ValidateOutputFormat()           */
    2577             : /************************************************************************/
    2578             : 
    2579         121 : bool GDALRasterTileAlgorithm::ValidateOutputFormat(GDALDataType eSrcDT) const
    2580             : {
    2581         121 :     if (m_outputFormat == "PNG")
    2582             :     {
    2583          97 :         if (m_poSrcDS->GetRasterCount() > 4)
    2584             :         {
    2585           1 :             ReportError(CE_Failure, CPLE_NotSupported,
    2586             :                         "Only up to 4 bands supported for PNG.");
    2587           1 :             return false;
    2588             :         }
    2589          96 :         if (eSrcDT != GDT_Byte && eSrcDT != GDT_UInt16)
    2590             :         {
    2591           1 :             ReportError(CE_Failure, CPLE_NotSupported,
    2592             :                         "Only Byte and UInt16 data types supported for PNG.");
    2593           1 :             return false;
    2594             :         }
    2595             :     }
    2596          24 :     else if (m_outputFormat == "JPEG")
    2597             :     {
    2598           5 :         if (m_poSrcDS->GetRasterCount() > 4)
    2599             :         {
    2600           1 :             ReportError(
    2601             :                 CE_Failure, CPLE_NotSupported,
    2602             :                 "Only up to 4 bands supported for JPEG (with alpha ignored).");
    2603           1 :             return false;
    2604             :         }
    2605             :         const bool bUInt16Supported =
    2606           4 :             strstr(m_poDstDriver->GetMetadataItem(GDAL_DMD_CREATIONDATATYPES),
    2607           4 :                    "UInt16");
    2608           4 :         if (eSrcDT != GDT_Byte && !(eSrcDT == GDT_UInt16 && bUInt16Supported))
    2609             :         {
    2610           1 :             ReportError(
    2611             :                 CE_Failure, CPLE_NotSupported,
    2612             :                 bUInt16Supported
    2613             :                     ? "Only Byte and UInt16 data types supported for JPEG."
    2614             :                     : "Only Byte data type supported for JPEG.");
    2615           1 :             return false;
    2616             :         }
    2617           3 :         if (eSrcDT == GDT_UInt16)
    2618             :         {
    2619           3 :             if (const char *pszNBITS =
    2620           6 :                     m_poSrcDS->GetRasterBand(1)->GetMetadataItem(
    2621           3 :                         "NBITS", "IMAGE_STRUCTURE"))
    2622             :             {
    2623           1 :                 if (atoi(pszNBITS) > 12)
    2624             :                 {
    2625           1 :                     ReportError(CE_Failure, CPLE_NotSupported,
    2626             :                                 "JPEG output only supported up to 12 bits");
    2627           1 :                     return false;
    2628             :                 }
    2629             :             }
    2630             :             else
    2631             :             {
    2632           2 :                 double adfMinMax[2] = {0, 0};
    2633           2 :                 m_poSrcDS->GetRasterBand(1)->ComputeRasterMinMax(
    2634           2 :                     /* bApproxOK = */ true, adfMinMax);
    2635           2 :                 if (adfMinMax[1] >= (1 << 12))
    2636             :                 {
    2637           1 :                     ReportError(CE_Failure, CPLE_NotSupported,
    2638             :                                 "JPEG output only supported up to 12 bits");
    2639           1 :                     return false;
    2640             :                 }
    2641             :             }
    2642             :         }
    2643             :     }
    2644          19 :     else if (m_outputFormat == "WEBP")
    2645             :     {
    2646           3 :         if (m_poSrcDS->GetRasterCount() != 3 &&
    2647           1 :             m_poSrcDS->GetRasterCount() != 4)
    2648             :         {
    2649           1 :             ReportError(CE_Failure, CPLE_NotSupported,
    2650             :                         "Only 3 or 4 bands supported for WEBP.");
    2651           1 :             return false;
    2652             :         }
    2653           1 :         if (eSrcDT != GDT_Byte)
    2654             :         {
    2655           1 :             ReportError(CE_Failure, CPLE_NotSupported,
    2656             :                         "Only Byte data type supported for WEBP.");
    2657           1 :             return false;
    2658             :         }
    2659             :     }
    2660         113 :     return true;
    2661             : }
    2662             : 
    2663             : /************************************************************************/
    2664             : /*            GDALRasterTileAlgorithm::ComputeJobChunkSize()            */
    2665             : /************************************************************************/
    2666             : 
    2667             : // Given a number of tiles in the Y dimension being nTilesPerCol and
    2668             : // in the X dimension being nTilesPerRow, compute the (upper bound of)
    2669             : // number of jobs needed to be nYOuterIterations x nXOuterIterations,
    2670             : // with each job processing in average dfTilesYPerJob x dfTilesXPerJob
    2671             : // tiles.
    2672             : /* static */
    2673          25 : void GDALRasterTileAlgorithm::ComputeJobChunkSize(
    2674             :     int nMaxJobCount, int nTilesPerCol, int nTilesPerRow,
    2675             :     double &dfTilesYPerJob, int &nYOuterIterations, double &dfTilesXPerJob,
    2676             :     int &nXOuterIterations)
    2677             : {
    2678          25 :     CPLAssert(nMaxJobCount >= 1);
    2679          25 :     dfTilesYPerJob = static_cast<double>(nTilesPerCol) / nMaxJobCount;
    2680          25 :     nYOuterIterations = dfTilesYPerJob >= 1 ? nMaxJobCount : 1;
    2681             : 
    2682          50 :     dfTilesXPerJob = dfTilesYPerJob >= 1
    2683          25 :                          ? nTilesPerRow
    2684           7 :                          : static_cast<double>(nTilesPerRow) / nMaxJobCount;
    2685          25 :     nXOuterIterations = dfTilesYPerJob >= 1 ? 1 : nMaxJobCount;
    2686             : 
    2687          25 :     if (dfTilesYPerJob < 1 && dfTilesXPerJob < 1 &&
    2688           7 :         nTilesPerCol <= nMaxJobCount / nTilesPerRow)
    2689             :     {
    2690           7 :         dfTilesYPerJob = 1;
    2691           7 :         dfTilesXPerJob = 1;
    2692           7 :         nYOuterIterations = nTilesPerCol;
    2693           7 :         nXOuterIterations = nTilesPerRow;
    2694             :     }
    2695          25 : }
    2696             : 
    2697             : /************************************************************************/
    2698             : /*               GDALRasterTileAlgorithm::AddArgToArgv()                */
    2699             : /************************************************************************/
    2700             : 
    2701         144 : bool GDALRasterTileAlgorithm::AddArgToArgv(const GDALAlgorithmArg *arg,
    2702             :                                            CPLStringList &aosArgv) const
    2703             : {
    2704         144 :     aosArgv.push_back(CPLSPrintf("--%s", arg->GetName().c_str()));
    2705         144 :     if (arg->GetType() == GAAT_STRING)
    2706             :     {
    2707          50 :         aosArgv.push_back(arg->Get<std::string>().c_str());
    2708             :     }
    2709          94 :     else if (arg->GetType() == GAAT_STRING_LIST)
    2710             :     {
    2711          12 :         bool bFirst = true;
    2712          24 :         for (const std::string &s : arg->Get<std::vector<std::string>>())
    2713             :         {
    2714          12 :             if (!bFirst)
    2715             :             {
    2716           0 :                 aosArgv.push_back(CPLSPrintf("--%s", arg->GetName().c_str()));
    2717             :             }
    2718          12 :             bFirst = false;
    2719          12 :             aosArgv.push_back(s.c_str());
    2720             :         }
    2721             :     }
    2722          82 :     else if (arg->GetType() == GAAT_REAL)
    2723             :     {
    2724           0 :         aosArgv.push_back(CPLSPrintf("%.17g", arg->Get<double>()));
    2725             :     }
    2726          82 :     else if (arg->GetType() == GAAT_INTEGER)
    2727             :     {
    2728          82 :         aosArgv.push_back(CPLSPrintf("%d", arg->Get<int>()));
    2729             :     }
    2730           0 :     else if (arg->GetType() != GAAT_BOOLEAN)
    2731             :     {
    2732           0 :         ReportError(CE_Failure, CPLE_AppDefined,
    2733             :                     "Bug: argument of type %d not handled "
    2734             :                     "by gdal raster tile!",
    2735           0 :                     static_cast<int>(arg->GetType()));
    2736           0 :         return false;
    2737             :     }
    2738         144 :     return true;
    2739             : }
    2740             : 
    2741             : /************************************************************************/
    2742             : /*            GDALRasterTileAlgorithm::IsCompatibleOfSpawn()            */
    2743             : /************************************************************************/
    2744             : 
    2745          13 : bool GDALRasterTileAlgorithm::IsCompatibleOfSpawn(const char *&pszErrorMsg)
    2746             : {
    2747          13 :     pszErrorMsg = "";
    2748          13 :     if (!m_bIsNamedNonMemSrcDS)
    2749             :     {
    2750           1 :         pszErrorMsg = "Unnamed or memory dataset sources are not supported "
    2751             :                       "with spawn parallelization method";
    2752           1 :         return false;
    2753             :     }
    2754          12 :     if (cpl::starts_with(m_outputDirectory, "/vsimem/"))
    2755             :     {
    2756           4 :         pszErrorMsg = "/vsimem/ output directory not supported with spawn "
    2757             :                       "parallelization method";
    2758           4 :         return false;
    2759             :     }
    2760             : 
    2761           8 :     if (m_osGDALPath.empty())
    2762           8 :         m_osGDALPath = GDALGetGDALPath();
    2763           8 :     return !(m_osGDALPath.empty());
    2764             : }
    2765             : 
    2766             : /************************************************************************/
    2767             : /*                      GetProgressForChildProcesses()                  */
    2768             : /************************************************************************/
    2769             : 
    2770          13 : static void GetProgressForChildProcesses(
    2771             :     bool &bRet, std::vector<CPLSpawnedProcess *> &ahSpawnedProcesses,
    2772             :     std::vector<uint64_t> &anRemainingTilesForProcess, uint64_t &nCurTile,
    2773             :     uint64_t nTotalTiles, GDALProgressFunc pfnProgress, void *pProgressData)
    2774             : {
    2775          13 :     std::vector<unsigned int> anProgressState(ahSpawnedProcesses.size(), 0);
    2776          13 :     std::vector<unsigned int> anEndState(ahSpawnedProcesses.size(), 0);
    2777          13 :     std::vector<bool> abFinished(ahSpawnedProcesses.size(), false);
    2778             : 
    2779         367 :     while (bRet)
    2780             :     {
    2781         367 :         size_t iProcess = 0;
    2782         367 :         size_t nFinished = 0;
    2783        1815 :         for (CPLSpawnedProcess *hSpawnedProcess : ahSpawnedProcesses)
    2784             :         {
    2785        1448 :             char ch = 0;
    2786        4344 :             if (abFinished[iProcess] ||
    2787        1448 :                 !CPLPipeRead(CPLSpawnAsyncGetInputFileHandle(hSpawnedProcess),
    2788        1448 :                              &ch, 1))
    2789             :             {
    2790           0 :                 ++nFinished;
    2791             :             }
    2792        1448 :             else if (ch == PROGRESS_MARKER[anProgressState[iProcess]])
    2793             :             {
    2794        1014 :                 ++anProgressState[iProcess];
    2795        1014 :                 if (anProgressState[iProcess] == sizeof(PROGRESS_MARKER))
    2796             :                 {
    2797         338 :                     anProgressState[iProcess] = 0;
    2798         338 :                     --anRemainingTilesForProcess[iProcess];
    2799         338 :                     ++nCurTile;
    2800         338 :                     if (bRet && pfnProgress)
    2801             :                     {
    2802          84 :                         if (!pfnProgress(static_cast<double>(nCurTile) /
    2803          84 :                                              static_cast<double>(nTotalTiles),
    2804             :                                          "", pProgressData))
    2805             :                         {
    2806           0 :                             CPLError(CE_Failure, CPLE_UserInterrupt,
    2807             :                                      "Process interrupted by user");
    2808           0 :                             bRet = false;
    2809           0 :                             return;
    2810             :                         }
    2811             :                     }
    2812             :                 }
    2813             :             }
    2814         434 :             else if (ch == END_MARKER[anEndState[iProcess]])
    2815             :             {
    2816         350 :                 ++anEndState[iProcess];
    2817         350 :                 if (anEndState[iProcess] == sizeof(END_MARKER))
    2818             :                 {
    2819          50 :                     anEndState[iProcess] = 0;
    2820          50 :                     abFinished[iProcess] = true;
    2821          50 :                     ++nFinished;
    2822             :                 }
    2823             :             }
    2824             :             else
    2825             :             {
    2826          84 :                 CPLErrorOnce(
    2827             :                     CE_Warning, CPLE_AppDefined,
    2828             :                     "Spurious character detected on stdout of child process");
    2829          84 :                 anProgressState[iProcess] = 0;
    2830          84 :                 if (ch == PROGRESS_MARKER[anProgressState[iProcess]])
    2831             :                 {
    2832          84 :                     ++anProgressState[iProcess];
    2833             :                 }
    2834             :             }
    2835        1448 :             ++iProcess;
    2836             :         }
    2837         367 :         if (!bRet || nFinished == ahSpawnedProcesses.size())
    2838          13 :             break;
    2839             :     }
    2840             : }
    2841             : 
    2842             : /************************************************************************/
    2843             : /*                       WaitForSpawnedProcesses()                      */
    2844             : /************************************************************************/
    2845             : 
    2846          13 : void GDALRasterTileAlgorithm::WaitForSpawnedProcesses(
    2847             :     bool &bRet, const std::vector<std::string> &asCommandLines,
    2848             :     std::vector<CPLSpawnedProcess *> &ahSpawnedProcesses) const
    2849             : {
    2850          13 :     size_t iProcess = 0;
    2851          63 :     for (CPLSpawnedProcess *hSpawnedProcess : ahSpawnedProcesses)
    2852             :     {
    2853          50 :         CPL_IGNORE_RET_VAL(
    2854          50 :             CPLPipeWrite(CPLSpawnAsyncGetOutputFileHandle(hSpawnedProcess),
    2855             :                          STOP_MARKER, static_cast<int>(strlen(STOP_MARKER))));
    2856             : 
    2857          50 :         char ch = 0;
    2858          50 :         std::string errorMsg;
    2859         519 :         while (CPLPipeRead(CPLSpawnAsyncGetErrorFileHandle(hSpawnedProcess),
    2860         519 :                            &ch, 1))
    2861             :         {
    2862         469 :             if (ch == '\n')
    2863             :             {
    2864           6 :                 if (!errorMsg.empty())
    2865             :                 {
    2866           6 :                     if (cpl::starts_with(errorMsg, "ERROR "))
    2867             :                     {
    2868           6 :                         const auto nPos = errorMsg.find(": ");
    2869           6 :                         if (nPos != std::string::npos)
    2870           6 :                             errorMsg = errorMsg.substr(nPos + 1);
    2871           6 :                         ReportError(CE_Failure, CPLE_AppDefined, "%s",
    2872             :                                     errorMsg.c_str());
    2873             :                     }
    2874             :                     else
    2875             :                     {
    2876           0 :                         std::string osComp = "GDAL";
    2877           0 :                         const auto nPos = errorMsg.find(": ");
    2878           0 :                         if (nPos != std::string::npos)
    2879             :                         {
    2880           0 :                             osComp = errorMsg.substr(0, nPos);
    2881           0 :                             errorMsg = errorMsg.substr(nPos + 1);
    2882             :                         }
    2883           0 :                         CPLDebug(osComp.c_str(), "%s", errorMsg.c_str());
    2884             :                     }
    2885           6 :                     errorMsg.clear();
    2886             :                 }
    2887             :             }
    2888             :             else
    2889             :             {
    2890         463 :                 errorMsg += ch;
    2891             :             }
    2892             :         }
    2893             : 
    2894          50 :         if (CPLSpawnAsyncFinish(hSpawnedProcess, /* bWait = */ true,
    2895          50 :                                 /* bKill = */ false) != 0)
    2896             :         {
    2897           2 :             bRet = false;
    2898           2 :             ReportError(CE_Failure, CPLE_AppDefined,
    2899             :                         "Child process '%s' failed",
    2900           2 :                         asCommandLines[iProcess].c_str());
    2901             :         }
    2902          50 :         ++iProcess;
    2903             :     }
    2904          13 : }
    2905             : 
    2906             : /************************************************************************/
    2907             : /*               GDALRasterTileAlgorithm::GetMaxChildCount()            */
    2908             : /**********************************f**************************************/
    2909             : 
    2910          13 : int GDALRasterTileAlgorithm::GetMaxChildCount(int nMaxJobCount) const
    2911             : {
    2912             : #ifndef _WIN32
    2913             :     // Limit the number of jobs compared to how many file descriptors we have
    2914             :     // left
    2915             :     const int remainingFileDescriptorCount =
    2916          13 :         CPLGetRemainingFileDescriptorCount();
    2917          13 :     constexpr int SOME_MARGIN = 3;
    2918          13 :     constexpr int FD_PER_CHILD = 3; /* stdin, stdout and stderr */
    2919          13 :     if (FD_PER_CHILD * nMaxJobCount + SOME_MARGIN >
    2920             :         remainingFileDescriptorCount)
    2921             :     {
    2922           0 :         nMaxJobCount = std::max(
    2923           0 :             1, (remainingFileDescriptorCount - SOME_MARGIN) / FD_PER_CHILD);
    2924           0 :         ReportError(
    2925             :             CE_Warning, CPLE_AppDefined,
    2926             :             "Limiting the number of child workers to %d (instead of %d), "
    2927             :             "because there are not enough file descriptors left (%d)",
    2928           0 :             nMaxJobCount, m_numThreads, remainingFileDescriptorCount);
    2929             :     }
    2930             : #endif
    2931          13 :     return nMaxJobCount;
    2932             : }
    2933             : 
    2934             : /************************************************************************/
    2935             : /*                           SendConfigOptions()                        */
    2936             : /************************************************************************/
    2937             : 
    2938          26 : static void SendConfigOptions(CPLSpawnedProcess *hSpawnedProcess, bool &bRet)
    2939             : {
    2940             :     // Send most config options through pipe, to avoid leaking
    2941             :     // secrets when listing processes
    2942          26 :     auto handle = CPLSpawnAsyncGetOutputFileHandle(hSpawnedProcess);
    2943          78 :     for (auto pfnFunc : {&CPLGetConfigOptions, &CPLGetThreadLocalConfigOptions})
    2944             :     {
    2945         104 :         CPLStringList aosConfigOptions((*pfnFunc)());
    2946         126 :         for (const char *pszNameValue : aosConfigOptions)
    2947             :         {
    2948          74 :             if (!STARTS_WITH(pszNameValue, "GDAL_CACHEMAX") &&
    2949          74 :                 !STARTS_WITH(pszNameValue, "GDAL_NUM_THREADS"))
    2950             :             {
    2951          74 :                 constexpr const char *CONFIG_MARKER = "--config\n";
    2952          74 :                 bRet &= CPL_TO_BOOL(
    2953             :                     CPLPipeWrite(handle, CONFIG_MARKER,
    2954          74 :                                  static_cast<int>(strlen(CONFIG_MARKER))));
    2955          74 :                 char *pszEscaped = CPLEscapeString(pszNameValue, -1, CPLES_URL);
    2956          74 :                 bRet &= CPL_TO_BOOL(CPLPipeWrite(
    2957          74 :                     handle, pszEscaped, static_cast<int>(strlen(pszEscaped))));
    2958          74 :                 CPLFree(pszEscaped);
    2959          74 :                 bRet &= CPL_TO_BOOL(CPLPipeWrite(handle, "\n", 1));
    2960             :             }
    2961             :         }
    2962             :     }
    2963          26 :     constexpr const char *END_CONFIG_MARKER = "END_CONFIG\n";
    2964          26 :     bRet &=
    2965          26 :         CPL_TO_BOOL(CPLPipeWrite(handle, END_CONFIG_MARKER,
    2966          26 :                                  static_cast<int>(strlen(END_CONFIG_MARKER))));
    2967          26 : }
    2968             : 
    2969             : /************************************************************************/
    2970             : /*                        GenerateTilesForkMethod()                     */
    2971             : /************************************************************************/
    2972             : 
    2973             : #ifdef FORK_ALLOWED
    2974             : 
    2975             : namespace
    2976             : {
    2977             : struct ForkWorkStructure
    2978             : {
    2979             :     uint64_t nCacheMaxPerProcess = 0;
    2980             :     CPLStringList aosArgv{};
    2981             :     GDALDataset *poMemSrcDS{};
    2982             : };
    2983             : }  // namespace
    2984             : 
    2985             : static CPL_FILE_HANDLE pipeIn = CPL_FILE_INVALID_HANDLE;
    2986             : static CPL_FILE_HANDLE pipeOut = CPL_FILE_INVALID_HANDLE;
    2987             : 
    2988           0 : static int GenerateTilesForkMethod(CPL_FILE_HANDLE in, CPL_FILE_HANDLE out)
    2989             : {
    2990           0 :     pipeIn = in;
    2991           0 :     pipeOut = out;
    2992             : 
    2993           0 :     const ForkWorkStructure *pWorkStructure = nullptr;
    2994           0 :     CPLPipeRead(in, &pWorkStructure, sizeof(pWorkStructure));
    2995             : 
    2996           0 :     CPLSetConfigOption("GDAL_NUM_THREADS", "1");
    2997           0 :     GDALSetCacheMax64(pWorkStructure->nCacheMaxPerProcess);
    2998             : 
    2999           0 :     GDALRasterTileAlgorithm alg;
    3000           0 :     if (pWorkStructure->poMemSrcDS)
    3001           0 :         alg.GetArg(GDAL_ARG_NAME_INPUT)->Set(pWorkStructure->poMemSrcDS);
    3002           0 :     return alg.ParseCommandLineArguments(pWorkStructure->aosArgv) && alg.Run()
    3003           0 :                ? 0
    3004           0 :                : 1;
    3005             : }
    3006             : 
    3007             : #endif  // FORK_ALLOWED
    3008             : 
    3009             : /************************************************************************/
    3010             : /*          GDALRasterTileAlgorithm::GenerateBaseTilesSpawnMethod()     */
    3011             : /************************************************************************/
    3012             : 
    3013           5 : bool GDALRasterTileAlgorithm::GenerateBaseTilesSpawnMethod(
    3014             :     int nBaseTilesPerCol, int nBaseTilesPerRow, int nMinTileX, int nMinTileY,
    3015             :     int nMaxTileX, int nMaxTileY, uint64_t nTotalTiles, uint64_t nBaseTiles,
    3016             :     GDALProgressFunc pfnProgress, void *pProgressData)
    3017             : {
    3018           5 :     if (m_parallelMethod == "spawn")
    3019             :     {
    3020           3 :         CPLAssert(!m_osGDALPath.empty());
    3021             :     }
    3022             : 
    3023           5 :     const int nMaxJobCount = GetMaxChildCount(std::max(
    3024           0 :         1, static_cast<int>(std::min<uint64_t>(
    3025           5 :                m_numThreads, nBaseTiles / GetThresholdMinTilesPerJob()))));
    3026             : 
    3027             :     double dfTilesYPerJob;
    3028             :     int nYOuterIterations;
    3029             :     double dfTilesXPerJob;
    3030             :     int nXOuterIterations;
    3031           5 :     ComputeJobChunkSize(nMaxJobCount, nBaseTilesPerCol, nBaseTilesPerRow,
    3032             :                         dfTilesYPerJob, nYOuterIterations, dfTilesXPerJob,
    3033             :                         nXOuterIterations);
    3034             : 
    3035           5 :     CPLDebugOnly("gdal_raster_tile",
    3036             :                  "nYOuterIterations=%d, dfTilesYPerJob=%g, "
    3037             :                  "nXOuterIterations=%d, dfTilesXPerJob=%g",
    3038             :                  nYOuterIterations, dfTilesYPerJob, nXOuterIterations,
    3039             :                  dfTilesXPerJob);
    3040             : 
    3041          10 :     std::vector<std::string> asCommandLines;
    3042          10 :     std::vector<CPLSpawnedProcess *> ahSpawnedProcesses;
    3043          10 :     std::vector<uint64_t> anRemainingTilesForProcess;
    3044             : 
    3045           5 :     const uint64_t nCacheMaxPerProcess = GDALGetCacheMax64() / nMaxJobCount;
    3046             : 
    3047           5 :     const auto poSrcDriver = m_poSrcDS->GetDriver();
    3048             :     const bool bIsMEMSource =
    3049           5 :         poSrcDriver && EQUAL(poSrcDriver->GetDescription(), "MEM");
    3050             : 
    3051           5 :     int nLastYEndIncluded = nMinTileY - 1;
    3052             : 
    3053             : #ifdef FORK_ALLOWED
    3054          10 :     std::vector<std::unique_ptr<ForkWorkStructure>> forkWorkStructures;
    3055             : #endif
    3056             : 
    3057           5 :     bool bRet = true;
    3058          23 :     for (int iYOuterIter = 0; bRet && iYOuterIter < nYOuterIterations &&
    3059          18 :                               nLastYEndIncluded < nMaxTileY;
    3060             :          ++iYOuterIter)
    3061             :     {
    3062          18 :         const int iYStart = nLastYEndIncluded + 1;
    3063             :         const int iYEndIncluded =
    3064          18 :             iYOuterIter + 1 == nYOuterIterations
    3065          31 :                 ? nMaxTileY
    3066             :                 : std::max(
    3067             :                       iYStart,
    3068          31 :                       static_cast<int>(std::floor(
    3069          13 :                           nMinTileY + (iYOuterIter + 1) * dfTilesYPerJob - 1)));
    3070             : 
    3071          18 :         nLastYEndIncluded = iYEndIncluded;
    3072             : 
    3073          18 :         int nLastXEndIncluded = nMinTileX - 1;
    3074          36 :         for (int iXOuterIter = 0; bRet && iXOuterIter < nXOuterIterations &&
    3075          18 :                                   nLastXEndIncluded < nMaxTileX;
    3076             :              ++iXOuterIter)
    3077             :         {
    3078          18 :             const int iXStart = nLastXEndIncluded + 1;
    3079             :             const int iXEndIncluded =
    3080          18 :                 iXOuterIter + 1 == nXOuterIterations
    3081          18 :                     ? nMaxTileX
    3082             :                     : std::max(iXStart,
    3083          18 :                                static_cast<int>(std::floor(
    3084           0 :                                    nMinTileX +
    3085           0 :                                    (iXOuterIter + 1) * dfTilesXPerJob - 1)));
    3086             : 
    3087          18 :             nLastXEndIncluded = iXEndIncluded;
    3088             : 
    3089          18 :             anRemainingTilesForProcess.push_back(
    3090           0 :                 static_cast<uint64_t>(iYEndIncluded - iYStart + 1) *
    3091          18 :                 (iXEndIncluded - iXStart + 1));
    3092             : 
    3093          18 :             CPLStringList aosArgv;
    3094          18 :             if (m_parallelMethod == "spawn")
    3095             :             {
    3096          10 :                 aosArgv.push_back(m_osGDALPath.c_str());
    3097          10 :                 aosArgv.push_back("raster");
    3098          10 :                 aosArgv.push_back("tile");
    3099          10 :                 aosArgv.push_back("--config-options-in-stdin");
    3100          10 :                 aosArgv.push_back("--config");
    3101          10 :                 aosArgv.push_back("GDAL_NUM_THREADS=1");
    3102          10 :                 aosArgv.push_back("--config");
    3103          10 :                 aosArgv.push_back(
    3104             :                     CPLSPrintf("GDAL_CACHEMAX=%" PRIu64, nCacheMaxPerProcess));
    3105             :             }
    3106          18 :             aosArgv.push_back("--num-threads");
    3107          18 :             aosArgv.push_back("1");
    3108          18 :             aosArgv.push_back("--min-x");
    3109          18 :             aosArgv.push_back(CPLSPrintf("%d", iXStart));
    3110          18 :             aosArgv.push_back("--max-x");
    3111          18 :             aosArgv.push_back(CPLSPrintf("%d", iXEndIncluded));
    3112          18 :             aosArgv.push_back("--min-y");
    3113          18 :             aosArgv.push_back(CPLSPrintf("%d", iYStart));
    3114          18 :             aosArgv.push_back("--max-y");
    3115          18 :             aosArgv.push_back(CPLSPrintf("%d", iYEndIncluded));
    3116          18 :             aosArgv.push_back("--webviewer");
    3117          18 :             aosArgv.push_back("none");
    3118          18 :             aosArgv.push_back(m_parallelMethod == "spawn" ? "--spawned"
    3119             :                                                           : "--forked");
    3120          18 :             if (!bIsMEMSource)
    3121             :             {
    3122          14 :                 aosArgv.push_back("--input");
    3123          14 :                 aosArgv.push_back(m_poSrcDS->GetDescription());
    3124             :             }
    3125         936 :             for (const auto &arg : GetArgs())
    3126             :             {
    3127        1090 :                 if (arg->IsExplicitlySet() && arg->GetName() != "min-x" &&
    3128         258 :                     arg->GetName() != "min-y" && arg->GetName() != "max-x" &&
    3129         242 :                     arg->GetName() != "max-y" && arg->GetName() != "min-zoom" &&
    3130         140 :                     arg->GetName() != "progress" &&
    3131         140 :                     arg->GetName() != "progress-forked" &&
    3132         122 :                     arg->GetName() != "input" &&
    3133         102 :                     arg->GetName() != "num-threads" &&
    3134        1054 :                     arg->GetName() != "webviewer" &&
    3135          50 :                     arg->GetName() != "parallel-method")
    3136             :                 {
    3137          40 :                     if (!AddArgToArgv(arg.get(), aosArgv))
    3138           0 :                         return false;
    3139             :                 }
    3140             :             }
    3141             : 
    3142          18 :             std::string cmdLine;
    3143         440 :             for (const char *arg : aosArgv)
    3144             :             {
    3145         422 :                 if (!cmdLine.empty())
    3146         404 :                     cmdLine += ' ';
    3147         422 :                 cmdLine += arg;
    3148             :             }
    3149          18 :             CPLDebugOnly("gdal_raster_tile", "%s %s",
    3150             :                          m_parallelMethod == "spawn" ? "Spawning" : "Forking",
    3151             :                          cmdLine.c_str());
    3152          18 :             asCommandLines.push_back(std::move(cmdLine));
    3153             : 
    3154             : #ifdef FORK_ALLOWED
    3155          18 :             if (m_parallelMethod == "fork")
    3156             :             {
    3157           8 :                 forkWorkStructures.push_back(
    3158          16 :                     std::make_unique<ForkWorkStructure>());
    3159           8 :                 ForkWorkStructure *pData = forkWorkStructures.back().get();
    3160           8 :                 pData->nCacheMaxPerProcess = nCacheMaxPerProcess;
    3161           8 :                 pData->aosArgv = aosArgv;
    3162           8 :                 if (bIsMEMSource)
    3163           4 :                     pData->poMemSrcDS = m_poSrcDS;
    3164             :             }
    3165          18 :             CPL_IGNORE_RET_VAL(aosArgv);
    3166             : #endif
    3167             : 
    3168          36 :             CPLSpawnedProcess *hSpawnedProcess = CPLSpawnAsync(
    3169             : #ifdef FORK_ALLOWED
    3170          18 :                 m_parallelMethod == "fork" ? GenerateTilesForkMethod :
    3171             : #endif
    3172             :                                            nullptr,
    3173          28 :                 m_parallelMethod == "fork" ? nullptr : aosArgv.List(),
    3174             :                 /* bCreateInputPipe = */ true,
    3175             :                 /* bCreateOutputPipe = */ true,
    3176          18 :                 /* bCreateErrorPipe = */ true, nullptr);
    3177          18 :             if (!hSpawnedProcess)
    3178             :             {
    3179           0 :                 ReportError(CE_Failure, CPLE_AppDefined,
    3180             :                             "Spawning child gdal process '%s' failed",
    3181           0 :                             asCommandLines.back().c_str());
    3182           0 :                 bRet = false;
    3183           0 :                 break;
    3184             :             }
    3185             : 
    3186          18 :             CPLDebugOnly("gdal_raster_tile",
    3187             :                          "Job for y in [%d,%d] and x in [%d,%d], "
    3188             :                          "run by process %" PRIu64,
    3189             :                          iYStart, iYEndIncluded, iXStart, iXEndIncluded,
    3190             :                          static_cast<uint64_t>(
    3191             :                              CPLSpawnAsyncGetChildProcessId(hSpawnedProcess)));
    3192             : 
    3193          18 :             ahSpawnedProcesses.push_back(hSpawnedProcess);
    3194             : 
    3195          18 :             if (m_parallelMethod == "spawn")
    3196             :             {
    3197          10 :                 SendConfigOptions(hSpawnedProcess, bRet);
    3198             :             }
    3199             : 
    3200             : #ifdef FORK_ALLOWED
    3201             :             else
    3202             :             {
    3203           8 :                 ForkWorkStructure *pData = forkWorkStructures.back().get();
    3204           8 :                 auto handle = CPLSpawnAsyncGetOutputFileHandle(hSpawnedProcess);
    3205           8 :                 bRet &= CPL_TO_BOOL(CPLPipeWrite(
    3206           8 :                     handle, &pData, static_cast<int>(sizeof(pData))));
    3207             :             }
    3208             : #endif
    3209             : 
    3210          18 :             if (!bRet)
    3211             :             {
    3212           0 :                 ReportError(CE_Failure, CPLE_AppDefined,
    3213             :                             "Could not transmit config options to child gdal "
    3214             :                             "process '%s'",
    3215           0 :                             asCommandLines.back().c_str());
    3216           0 :                 break;
    3217             :             }
    3218             :         }
    3219             :     }
    3220             : 
    3221           5 :     uint64_t nCurTile = 0;
    3222           5 :     GetProgressForChildProcesses(bRet, ahSpawnedProcesses,
    3223             :                                  anRemainingTilesForProcess, nCurTile,
    3224             :                                  nTotalTiles, pfnProgress, pProgressData);
    3225             : 
    3226           5 :     WaitForSpawnedProcesses(bRet, asCommandLines, ahSpawnedProcesses);
    3227             : 
    3228           5 :     if (bRet && nCurTile != nBaseTiles)
    3229             :     {
    3230           0 :         bRet = false;
    3231           0 :         ReportError(CE_Failure, CPLE_AppDefined,
    3232             :                     "Not all tiles at max zoom level have been "
    3233             :                     "generated. Got %" PRIu64 ", expected %" PRIu64,
    3234             :                     nCurTile, nBaseTiles);
    3235             :     }
    3236             : 
    3237           5 :     return bRet;
    3238             : }
    3239             : 
    3240             : /************************************************************************/
    3241             : /*      GDALRasterTileAlgorithm::GenerateOverviewTilesSpawnMethod()     */
    3242             : /************************************************************************/
    3243             : 
    3244           8 : bool GDALRasterTileAlgorithm::GenerateOverviewTilesSpawnMethod(
    3245             :     int iZ, int nOvrMinTileX, int nOvrMinTileY, int nOvrMaxTileX,
    3246             :     int nOvrMaxTileY, std::atomic<uint64_t> &nCurTile, uint64_t nTotalTiles,
    3247             :     GDALProgressFunc pfnProgress, void *pProgressData)
    3248             : {
    3249           8 :     if (m_parallelMethod == "spawn")
    3250             :     {
    3251           4 :         CPLAssert(!m_osGDALPath.empty());
    3252             :     }
    3253             : 
    3254           8 :     const int nOvrTilesPerCol = nOvrMaxTileY - nOvrMinTileY + 1;
    3255           8 :     const int nOvrTilesPerRow = nOvrMaxTileX - nOvrMinTileX + 1;
    3256           8 :     const uint64_t nExpectedOvrTileCount =
    3257           8 :         static_cast<uint64_t>(nOvrTilesPerCol) * nOvrTilesPerRow;
    3258             : 
    3259           8 :     const int nMaxJobCount = GetMaxChildCount(
    3260           0 :         std::max(1, static_cast<int>(std::min<uint64_t>(
    3261           0 :                         m_numThreads, nExpectedOvrTileCount /
    3262           8 :                                           GetThresholdMinTilesPerJob()))));
    3263             : 
    3264             :     double dfTilesYPerJob;
    3265             :     int nYOuterIterations;
    3266             :     double dfTilesXPerJob;
    3267             :     int nXOuterIterations;
    3268           8 :     ComputeJobChunkSize(nMaxJobCount, nOvrTilesPerCol, nOvrTilesPerRow,
    3269             :                         dfTilesYPerJob, nYOuterIterations, dfTilesXPerJob,
    3270             :                         nXOuterIterations);
    3271             : 
    3272           8 :     CPLDebugOnly("gdal_raster_tile",
    3273             :                  "z=%d, nYOuterIterations=%d, dfTilesYPerJob=%g, "
    3274             :                  "nXOuterIterations=%d, dfTilesXPerJob=%g",
    3275             :                  iZ, nYOuterIterations, dfTilesYPerJob, nXOuterIterations,
    3276             :                  dfTilesXPerJob);
    3277             : 
    3278          16 :     std::vector<std::string> asCommandLines;
    3279          16 :     std::vector<CPLSpawnedProcess *> ahSpawnedProcesses;
    3280          16 :     std::vector<uint64_t> anRemainingTilesForProcess;
    3281             : 
    3282             : #ifdef FORK_ALLOWED
    3283          16 :     std::vector<std::unique_ptr<ForkWorkStructure>> forkWorkStructures;
    3284             : #endif
    3285             : 
    3286           8 :     const uint64_t nCacheMaxPerProcess = GDALGetCacheMax64() / nMaxJobCount;
    3287             : 
    3288           8 :     const auto poSrcDriver = m_poSrcDS ? m_poSrcDS->GetDriver() : nullptr;
    3289             :     const bool bIsMEMSource =
    3290           8 :         poSrcDriver && EQUAL(poSrcDriver->GetDescription(), "MEM");
    3291             : 
    3292           8 :     int nLastYEndIncluded = nOvrMinTileY - 1;
    3293           8 :     bool bRet = true;
    3294          32 :     for (int iYOuterIter = 0; bRet && iYOuterIter < nYOuterIterations &&
    3295          24 :                               nLastYEndIncluded < nOvrMaxTileY;
    3296             :          ++iYOuterIter)
    3297             :     {
    3298          24 :         const int iYStart = nLastYEndIncluded + 1;
    3299             :         const int iYEndIncluded =
    3300          24 :             iYOuterIter + 1 == nYOuterIterations
    3301          40 :                 ? nOvrMaxTileY
    3302             :                 : std::max(iYStart,
    3303          40 :                            static_cast<int>(std::floor(
    3304          16 :                                nOvrMinTileY +
    3305          16 :                                (iYOuterIter + 1) * dfTilesYPerJob - 1)));
    3306             : 
    3307          24 :         nLastYEndIncluded = iYEndIncluded;
    3308             : 
    3309          24 :         int nLastXEndIncluded = nOvrMinTileX - 1;
    3310          56 :         for (int iXOuterIter = 0; bRet && iXOuterIter < nXOuterIterations &&
    3311          32 :                                   nLastXEndIncluded < nOvrMaxTileX;
    3312             :              ++iXOuterIter)
    3313             :         {
    3314          32 :             const int iXStart = nLastXEndIncluded + 1;
    3315             :             const int iXEndIncluded =
    3316          32 :                 iXOuterIter + 1 == nXOuterIterations
    3317          40 :                     ? nOvrMaxTileX
    3318             :                     : std::max(iXStart,
    3319          40 :                                static_cast<int>(std::floor(
    3320           8 :                                    nOvrMinTileX +
    3321           8 :                                    (iXOuterIter + 1) * dfTilesXPerJob - 1)));
    3322             : 
    3323          32 :             nLastXEndIncluded = iXEndIncluded;
    3324             : 
    3325          32 :             anRemainingTilesForProcess.push_back(
    3326           0 :                 static_cast<uint64_t>(iYEndIncluded - iYStart + 1) *
    3327          32 :                 (iXEndIncluded - iXStart + 1));
    3328             : 
    3329          32 :             CPLStringList aosArgv;
    3330          32 :             if (m_parallelMethod == "spawn")
    3331             :             {
    3332          16 :                 aosArgv.push_back(m_osGDALPath.c_str());
    3333          16 :                 aosArgv.push_back("raster");
    3334          16 :                 aosArgv.push_back("tile");
    3335          16 :                 aosArgv.push_back("--config-options-in-stdin");
    3336          16 :                 aosArgv.push_back("--config");
    3337          16 :                 aosArgv.push_back("GDAL_NUM_THREADS=1");
    3338          16 :                 aosArgv.push_back("--config");
    3339          16 :                 aosArgv.push_back(
    3340             :                     CPLSPrintf("GDAL_CACHEMAX=%" PRIu64, nCacheMaxPerProcess));
    3341             :             }
    3342          32 :             aosArgv.push_back("--num-threads");
    3343          32 :             aosArgv.push_back("1");
    3344          32 :             aosArgv.push_back("--ovr-zoom-level");
    3345          32 :             aosArgv.push_back(CPLSPrintf("%d", iZ));
    3346          32 :             aosArgv.push_back("--ovr-min-x");
    3347          32 :             aosArgv.push_back(CPLSPrintf("%d", iXStart));
    3348          32 :             aosArgv.push_back("--ovr-max-x");
    3349          32 :             aosArgv.push_back(CPLSPrintf("%d", iXEndIncluded));
    3350          32 :             aosArgv.push_back("--ovr-min-y");
    3351          32 :             aosArgv.push_back(CPLSPrintf("%d", iYStart));
    3352          32 :             aosArgv.push_back("--ovr-max-y");
    3353          32 :             aosArgv.push_back(CPLSPrintf("%d", iYEndIncluded));
    3354          32 :             aosArgv.push_back("--webviewer");
    3355          32 :             aosArgv.push_back("none");
    3356          32 :             aosArgv.push_back(m_parallelMethod == "spawn" ? "--spawned"
    3357             :                                                           : "--forked");
    3358          32 :             if (!bIsMEMSource)
    3359             :             {
    3360          24 :                 aosArgv.push_back("--input");
    3361          24 :                 aosArgv.push_back(m_dataset.GetName().c_str());
    3362             :             }
    3363        1664 :             for (const auto &arg : GetArgs())
    3364             :             {
    3365        1936 :                 if (arg->IsExplicitlySet() && arg->GetName() != "progress" &&
    3366         304 :                     arg->GetName() != "progress-forked" &&
    3367         272 :                     arg->GetName() != "input" &&
    3368         240 :                     arg->GetName() != "num-threads" &&
    3369        1904 :                     arg->GetName() != "webviewer" &&
    3370         120 :                     arg->GetName() != "parallel-method")
    3371             :                 {
    3372         104 :                     if (!AddArgToArgv(arg.get(), aosArgv))
    3373           0 :                         return false;
    3374             :                 }
    3375             :             }
    3376             : 
    3377          32 :             std::string cmdLine;
    3378         896 :             for (const char *arg : aosArgv)
    3379             :             {
    3380         864 :                 if (!cmdLine.empty())
    3381         832 :                     cmdLine += ' ';
    3382         864 :                 cmdLine += arg;
    3383             :             }
    3384          32 :             CPLDebugOnly("gdal_raster_tile", "%s %s",
    3385             :                          m_parallelMethod == "spawn" ? "Spawning" : "Forking",
    3386             :                          cmdLine.c_str());
    3387          32 :             asCommandLines.push_back(std::move(cmdLine));
    3388             : 
    3389             : #ifdef FORK_ALLOWED
    3390          32 :             if (m_parallelMethod == "fork")
    3391             :             {
    3392          16 :                 forkWorkStructures.push_back(
    3393          32 :                     std::make_unique<ForkWorkStructure>());
    3394          16 :                 ForkWorkStructure *pData = forkWorkStructures.back().get();
    3395          16 :                 pData->nCacheMaxPerProcess = nCacheMaxPerProcess;
    3396          16 :                 pData->aosArgv = aosArgv;
    3397          16 :                 if (bIsMEMSource)
    3398           8 :                     pData->poMemSrcDS = m_poSrcDS;
    3399             :             }
    3400          32 :             CPL_IGNORE_RET_VAL(aosArgv);
    3401             : #endif
    3402             : 
    3403          64 :             CPLSpawnedProcess *hSpawnedProcess = CPLSpawnAsync(
    3404             : #ifdef FORK_ALLOWED
    3405          32 :                 m_parallelMethod == "fork" ? GenerateTilesForkMethod :
    3406             : #endif
    3407             :                                            nullptr,
    3408          48 :                 m_parallelMethod == "fork" ? nullptr : aosArgv.List(),
    3409             :                 /* bCreateInputPipe = */ true,
    3410             :                 /* bCreateOutputPipe = */ true,
    3411          32 :                 /* bCreateErrorPipe = */ true, nullptr);
    3412          32 :             if (!hSpawnedProcess)
    3413             :             {
    3414           0 :                 ReportError(CE_Failure, CPLE_AppDefined,
    3415             :                             "Spawning child gdal process '%s' failed",
    3416           0 :                             asCommandLines.back().c_str());
    3417           0 :                 bRet = false;
    3418           0 :                 break;
    3419             :             }
    3420             : 
    3421          32 :             CPLDebugOnly("gdal_raster_tile",
    3422             :                          "Job for z = %d, y in [%d,%d] and x in [%d,%d], "
    3423             :                          "run by process %" PRIu64,
    3424             :                          iZ, iYStart, iYEndIncluded, iXStart, iXEndIncluded,
    3425             :                          static_cast<uint64_t>(
    3426             :                              CPLSpawnAsyncGetChildProcessId(hSpawnedProcess)));
    3427             : 
    3428          32 :             ahSpawnedProcesses.push_back(hSpawnedProcess);
    3429             : 
    3430          32 :             if (m_parallelMethod == "spawn")
    3431             :             {
    3432          16 :                 SendConfigOptions(hSpawnedProcess, bRet);
    3433             :             }
    3434             : 
    3435             : #ifdef FORK_ALLOWED
    3436             :             else
    3437             :             {
    3438          16 :                 ForkWorkStructure *pData = forkWorkStructures.back().get();
    3439          16 :                 auto handle = CPLSpawnAsyncGetOutputFileHandle(hSpawnedProcess);
    3440          16 :                 bRet &= CPL_TO_BOOL(CPLPipeWrite(
    3441          16 :                     handle, &pData, static_cast<int>(sizeof(pData))));
    3442             :             }
    3443             : #endif
    3444          32 :             if (!bRet)
    3445             :             {
    3446           0 :                 ReportError(CE_Failure, CPLE_AppDefined,
    3447             :                             "Could not transmit config options to child gdal "
    3448             :                             "process '%s'",
    3449           0 :                             asCommandLines.back().c_str());
    3450           0 :                 break;
    3451             :             }
    3452             :         }
    3453             :     }
    3454             : 
    3455           8 :     uint64_t nCurTileLocal = nCurTile;
    3456           8 :     GetProgressForChildProcesses(bRet, ahSpawnedProcesses,
    3457             :                                  anRemainingTilesForProcess, nCurTileLocal,
    3458             :                                  nTotalTiles, pfnProgress, pProgressData);
    3459             : 
    3460           8 :     WaitForSpawnedProcesses(bRet, asCommandLines, ahSpawnedProcesses);
    3461             : 
    3462           8 :     if (bRet && nCurTileLocal - nCurTile != nExpectedOvrTileCount)
    3463             :     {
    3464           0 :         bRet = false;
    3465           0 :         ReportError(CE_Failure, CPLE_AppDefined,
    3466             :                     "Not all tiles at zoom level %d have been "
    3467             :                     "generated. Got %" PRIu64 ", expected %" PRIu64,
    3468           0 :                     iZ, nCurTileLocal - nCurTile, nExpectedOvrTileCount);
    3469             :     }
    3470             : 
    3471           8 :     nCurTile = nCurTileLocal;
    3472             : 
    3473           8 :     return bRet;
    3474             : }
    3475             : 
    3476             : /************************************************************************/
    3477             : /*                  GDALRasterTileAlgorithm::RunImpl()                  */
    3478             : /************************************************************************/
    3479             : 
    3480         129 : bool GDALRasterTileAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
    3481             :                                       void *pProgressData)
    3482             : {
    3483         129 :     m_poSrcDS = m_dataset.GetDatasetRef();
    3484         129 :     CPLAssert(m_poSrcDS);
    3485         129 :     const int nSrcWidth = m_poSrcDS->GetRasterXSize();
    3486         129 :     const int nSrcHeight = m_poSrcDS->GetRasterYSize();
    3487         129 :     if (m_poSrcDS->GetRasterCount() == 0 || nSrcWidth == 0 || nSrcHeight == 0)
    3488             :     {
    3489           1 :         ReportError(CE_Failure, CPLE_AppDefined, "Invalid source dataset");
    3490           1 :         return false;
    3491             :     }
    3492             : 
    3493         128 :     const bool bIsNamedSource = m_poSrcDS->GetDescription()[0] != 0;
    3494         128 :     auto poSrcDriver = m_poSrcDS->GetDriver();
    3495             :     const bool bIsMEMSource =
    3496         128 :         poSrcDriver && EQUAL(poSrcDriver->GetDescription(), "MEM");
    3497         128 :     m_bIsNamedNonMemSrcDS = bIsNamedSource && !bIsMEMSource;
    3498         128 :     const bool bSrcIsFineForFork = bIsNamedSource || bIsMEMSource;
    3499             : 
    3500         128 :     if (m_parallelMethod == "spawn")
    3501             :     {
    3502           5 :         const char *pszErrorMsg = "";
    3503           5 :         if (!IsCompatibleOfSpawn(pszErrorMsg))
    3504             :         {
    3505           3 :             if (pszErrorMsg[0])
    3506           2 :                 ReportError(CE_Failure, CPLE_AppDefined, "%s", pszErrorMsg);
    3507           3 :             return false;
    3508             :         }
    3509             :     }
    3510             : #ifdef FORK_ALLOWED
    3511         123 :     else if (m_parallelMethod == "fork")
    3512             :     {
    3513           3 :         if (!bSrcIsFineForFork)
    3514             :         {
    3515           1 :             ReportError(CE_Failure, CPLE_AppDefined,
    3516             :                         "Unnamed non-MEM source are not supported "
    3517             :                         "with fork parallelization method");
    3518           1 :             return false;
    3519             :         }
    3520           2 :         if (cpl::starts_with(m_outputDirectory, "/vsimem/"))
    3521             :         {
    3522           1 :             ReportError(CE_Failure, CPLE_AppDefined,
    3523             :                         "/vsimem/ output directory not supported with fork "
    3524             :                         "parallelization method");
    3525           1 :             return false;
    3526             :         }
    3527             :     }
    3528             : #endif
    3529             : 
    3530         123 :     if (m_resampling == "near")
    3531           1 :         m_resampling = "nearest";
    3532         123 :     if (m_overviewResampling == "near")
    3533           1 :         m_overviewResampling = "nearest";
    3534         122 :     else if (m_overviewResampling.empty())
    3535         117 :         m_overviewResampling = m_resampling;
    3536             : 
    3537         246 :     CPLStringList aosWarpOptions;
    3538         123 :     if (!m_excludedValues.empty() || m_nodataValuesPctThreshold < 100)
    3539             :     {
    3540             :         aosWarpOptions.SetNameValue(
    3541             :             "NODATA_VALUES_PCT_THRESHOLD",
    3542           3 :             CPLSPrintf("%g", m_nodataValuesPctThreshold));
    3543           3 :         if (!m_excludedValues.empty())
    3544             :         {
    3545             :             aosWarpOptions.SetNameValue("EXCLUDED_VALUES",
    3546           1 :                                         m_excludedValues.c_str());
    3547             :             aosWarpOptions.SetNameValue(
    3548             :                 "EXCLUDED_VALUES_PCT_THRESHOLD",
    3549           1 :                 CPLSPrintf("%g", m_excludedValuesPctThreshold));
    3550             :         }
    3551             :     }
    3552             : 
    3553         123 :     if (m_poSrcDS->GetRasterBand(1)->GetColorInterpretation() ==
    3554         127 :             GCI_PaletteIndex &&
    3555           4 :         ((m_resampling != "nearest" && m_resampling != "mode") ||
    3556           1 :          (m_overviewResampling != "nearest" && m_overviewResampling != "mode")))
    3557             :     {
    3558           1 :         ReportError(CE_Failure, CPLE_NotSupported,
    3559             :                     "Datasets with color table not supported with non-nearest "
    3560             :                     "or non-mode resampling. Run 'gdal raster "
    3561             :                     "color-map' before or set the 'resampling' argument to "
    3562             :                     "'nearest' or 'mode'.");
    3563           1 :         return false;
    3564             :     }
    3565             : 
    3566         122 :     const auto eSrcDT = m_poSrcDS->GetRasterBand(1)->GetRasterDataType();
    3567         122 :     m_poDstDriver =
    3568         122 :         GetGDALDriverManager()->GetDriverByName(m_outputFormat.c_str());
    3569         122 :     if (!m_poDstDriver)
    3570             :     {
    3571           1 :         ReportError(CE_Failure, CPLE_AppDefined,
    3572             :                     "Invalid value for argument 'output-format'. Driver '%s' "
    3573             :                     "does not exist",
    3574             :                     m_outputFormat.c_str());
    3575           1 :         return false;
    3576             :     }
    3577             : 
    3578         121 :     if (!ValidateOutputFormat(eSrcDT))
    3579           8 :         return false;
    3580             : 
    3581             :     const char *pszExtensions =
    3582         113 :         m_poDstDriver->GetMetadataItem(GDAL_DMD_EXTENSIONS);
    3583         113 :     CPLAssert(pszExtensions && pszExtensions[0] != 0);
    3584             :     const CPLStringList aosExtensions(
    3585         226 :         CSLTokenizeString2(pszExtensions, " ", 0));
    3586         113 :     const char *pszExtension = aosExtensions[0];
    3587         113 :     GDALGeoTransform srcGT;
    3588         113 :     const bool bHasSrcGT = m_poSrcDS->GetGeoTransform(srcGT) == CE_None;
    3589             :     const bool bHasNorthUpSrcGT =
    3590         113 :         bHasSrcGT && srcGT[2] == 0 && srcGT[4] == 0 && srcGT[5] < 0;
    3591         226 :     OGRSpatialReference oSRS_TMS;
    3592             : 
    3593         113 :     if (m_tilingScheme == "raster")
    3594             :     {
    3595           4 :         if (const auto poSRS = m_poSrcDS->GetSpatialRef())
    3596           3 :             oSRS_TMS = *poSRS;
    3597             :     }
    3598             :     else
    3599             :     {
    3600           1 :         if (!bHasSrcGT && m_poSrcDS->GetGCPCount() == 0 &&
    3601         111 :             m_poSrcDS->GetMetadata("GEOLOCATION") == nullptr &&
    3602           1 :             m_poSrcDS->GetMetadata("RPC") == nullptr)
    3603             :         {
    3604           1 :             ReportError(CE_Failure, CPLE_NotSupported,
    3605             :                         "Ungeoreferenced datasets are not supported, unless "
    3606             :                         "'tiling-scheme' is set to 'raster'");
    3607           1 :             return false;
    3608             :         }
    3609             : 
    3610         108 :         if (m_poSrcDS->GetMetadata("GEOLOCATION") == nullptr &&
    3611         108 :             m_poSrcDS->GetMetadata("RPC") == nullptr &&
    3612         217 :             m_poSrcDS->GetSpatialRef() == nullptr &&
    3613           1 :             m_poSrcDS->GetGCPSpatialRef() == nullptr)
    3614             :         {
    3615           1 :             ReportError(CE_Failure, CPLE_NotSupported,
    3616             :                         "Ungeoreferenced datasets are not supported, unless "
    3617             :                         "'tiling-scheme' is set to 'raster'");
    3618           1 :             return false;
    3619             :         }
    3620             :     }
    3621             : 
    3622         111 :     if (m_copySrcMetadata)
    3623             :     {
    3624           8 :         CPLStringList aosMD(CSLDuplicate(m_poSrcDS->GetMetadata()));
    3625           4 :         const CPLStringList aosNewMD(m_metadata);
    3626           8 :         for (const auto [key, value] : cpl::IterateNameValue(aosNewMD))
    3627             :         {
    3628           4 :             aosMD.SetNameValue(key, value);
    3629             :         }
    3630           4 :         m_metadata = aosMD;
    3631             :     }
    3632             : 
    3633         222 :     std::vector<BandMetadata> aoBandMetadata;
    3634       65914 :     for (int i = 1; i <= m_poSrcDS->GetRasterCount(); ++i)
    3635             :     {
    3636       65803 :         auto poBand = m_poSrcDS->GetRasterBand(i);
    3637      131606 :         BandMetadata bm;
    3638       65803 :         bm.osDescription = poBand->GetDescription();
    3639       65803 :         bm.eDT = poBand->GetRasterDataType();
    3640       65803 :         bm.eColorInterp = poBand->GetColorInterpretation();
    3641       65803 :         if (const char *pszCenterWavelength =
    3642       65803 :                 poBand->GetMetadataItem("CENTRAL_WAVELENGTH_UM", "IMAGERY"))
    3643           0 :             bm.osCenterWaveLength = pszCenterWavelength;
    3644       65803 :         if (const char *pszFWHM = poBand->GetMetadataItem("FWHM_UM", "IMAGERY"))
    3645           0 :             bm.osFWHM = pszFWHM;
    3646       65803 :         aoBandMetadata.emplace_back(std::move(bm));
    3647             :     }
    3648             : 
    3649         111 :     GDALGeoTransform srcGTModif{0, 1, 0, 0, 0, -1};
    3650             : 
    3651         111 :     if (m_tilingScheme == "mercator")
    3652           1 :         m_tilingScheme = "WebMercatorQuad";
    3653         110 :     else if (m_tilingScheme == "geodetic")
    3654           1 :         m_tilingScheme = "WorldCRS84Quad";
    3655         109 :     else if (m_tilingScheme == "raster")
    3656             :     {
    3657           4 :         if (m_tileSize == 0)
    3658           4 :             m_tileSize = 256;
    3659           4 :         if (m_maxZoomLevel < 0)
    3660             :         {
    3661           3 :             m_maxZoomLevel = static_cast<int>(std::ceil(std::log2(
    3662           6 :                 std::max(1, std::max(nSrcWidth, nSrcHeight) / m_tileSize))));
    3663             :         }
    3664           4 :         if (bHasNorthUpSrcGT)
    3665             :         {
    3666           3 :             srcGTModif = srcGT;
    3667             :         }
    3668             :     }
    3669             : 
    3670             :     auto poTMS =
    3671         111 :         m_tilingScheme == "raster"
    3672             :             ? gdal::TileMatrixSet::createRaster(
    3673           4 :                   nSrcWidth, nSrcHeight, m_tileSize, 1 + m_maxZoomLevel,
    3674          16 :                   srcGTModif[0], srcGTModif[3], srcGTModif[1], -srcGTModif[5],
    3675         115 :                   oSRS_TMS.IsEmpty() ? std::string() : oSRS_TMS.exportToWkt())
    3676             :             : gdal::TileMatrixSet::parse(
    3677         226 :                   m_mapTileMatrixIdentifierToScheme[m_tilingScheme].c_str());
    3678             :     // Enforced by SetChoices() on the m_tilingScheme argument
    3679         111 :     CPLAssert(poTMS && !poTMS->hasVariableMatrixWidth());
    3680             : 
    3681         222 :     CPLStringList aosTO;
    3682         111 :     if (m_tilingScheme == "raster")
    3683             :     {
    3684           4 :         aosTO.SetNameValue("SRC_METHOD", "GEOTRANSFORM");
    3685             :     }
    3686             :     else
    3687             :     {
    3688         107 :         CPL_IGNORE_RET_VAL(oSRS_TMS.SetFromUserInput(poTMS->crs().c_str()));
    3689         107 :         aosTO.SetNameValue("DST_SRS", oSRS_TMS.exportToWkt().c_str());
    3690             :     }
    3691             : 
    3692         111 :     const char *pszAuthName = oSRS_TMS.GetAuthorityName(nullptr);
    3693         111 :     const char *pszAuthCode = oSRS_TMS.GetAuthorityCode(nullptr);
    3694         111 :     const int nEPSGCode =
    3695         110 :         (pszAuthName && pszAuthCode && EQUAL(pszAuthName, "EPSG"))
    3696         221 :             ? atoi(pszAuthCode)
    3697             :             : 0;
    3698             : 
    3699             :     const bool bInvertAxisTMS =
    3700         218 :         m_tilingScheme != "raster" &&
    3701         107 :         (oSRS_TMS.EPSGTreatsAsLatLong() != FALSE ||
    3702         107 :          oSRS_TMS.EPSGTreatsAsNorthingEasting() != FALSE);
    3703             : 
    3704         111 :     oSRS_TMS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    3705             : 
    3706             :     std::unique_ptr<void, decltype(&GDALDestroyTransformer)> hTransformArg(
    3707         222 :         nullptr, GDALDestroyTransformer);
    3708             : 
    3709             :     // Hack to compensate for GDALSuggestedWarpOutput2() failure (or not
    3710             :     // ideal suggestion with PROJ 8) when reprojecting latitude = +/- 90 to
    3711             :     // EPSG:3857.
    3712         111 :     std::unique_ptr<GDALDataset> poTmpDS;
    3713         111 :     bool bEPSG3857Adjust = false;
    3714         111 :     if (nEPSGCode == 3857 && bHasNorthUpSrcGT)
    3715             :     {
    3716          85 :         const auto poSrcSRS = m_poSrcDS->GetSpatialRef();
    3717          85 :         if (poSrcSRS && poSrcSRS->IsGeographic())
    3718             :         {
    3719          54 :             double maxLat = srcGT[3];
    3720          54 :             double minLat = srcGT[3] + nSrcHeight * srcGT[5];
    3721             :             // Corresponds to the latitude of below MAX_GM
    3722          54 :             constexpr double MAX_LAT = 85.0511287798066;
    3723          54 :             bool bModified = false;
    3724          54 :             if (maxLat > MAX_LAT)
    3725             :             {
    3726          53 :                 maxLat = MAX_LAT;
    3727          53 :                 bModified = true;
    3728             :             }
    3729          54 :             if (minLat < -MAX_LAT)
    3730             :             {
    3731          53 :                 minLat = -MAX_LAT;
    3732          53 :                 bModified = true;
    3733             :             }
    3734          54 :             if (bModified)
    3735             :             {
    3736         106 :                 CPLStringList aosOptions;
    3737          53 :                 aosOptions.AddString("-of");
    3738          53 :                 aosOptions.AddString("VRT");
    3739          53 :                 aosOptions.AddString("-projwin");
    3740          53 :                 aosOptions.AddString(CPLSPrintf("%.17g", srcGT[0]));
    3741          53 :                 aosOptions.AddString(CPLSPrintf("%.17g", maxLat));
    3742             :                 aosOptions.AddString(
    3743          53 :                     CPLSPrintf("%.17g", srcGT[0] + nSrcWidth * srcGT[1]));
    3744          53 :                 aosOptions.AddString(CPLSPrintf("%.17g", minLat));
    3745             :                 auto psOptions =
    3746          53 :                     GDALTranslateOptionsNew(aosOptions.List(), nullptr);
    3747          53 :                 poTmpDS.reset(GDALDataset::FromHandle(GDALTranslate(
    3748             :                     "", GDALDataset::ToHandle(m_poSrcDS), psOptions, nullptr)));
    3749          53 :                 GDALTranslateOptionsFree(psOptions);
    3750          53 :                 if (poTmpDS)
    3751             :                 {
    3752          53 :                     bEPSG3857Adjust = true;
    3753          53 :                     hTransformArg.reset(GDALCreateGenImgProjTransformer2(
    3754          53 :                         GDALDataset::FromHandle(poTmpDS.get()), nullptr,
    3755          53 :                         aosTO.List()));
    3756             :                 }
    3757             :             }
    3758             :         }
    3759             :     }
    3760             : 
    3761         111 :     GDALGeoTransform dstGT;
    3762             :     double adfExtent[4];
    3763             :     int nXSize, nYSize;
    3764             : 
    3765             :     bool bSuggestOK;
    3766         111 :     if (m_tilingScheme == "raster")
    3767             :     {
    3768           4 :         bSuggestOK = true;
    3769           4 :         nXSize = nSrcWidth;
    3770           4 :         nYSize = nSrcHeight;
    3771           4 :         dstGT = srcGTModif;
    3772           4 :         adfExtent[0] = dstGT[0];
    3773           4 :         adfExtent[1] = dstGT[3] + nSrcHeight * dstGT[5];
    3774           4 :         adfExtent[2] = dstGT[0] + nSrcWidth * dstGT[1];
    3775           4 :         adfExtent[3] = dstGT[3];
    3776             :     }
    3777             :     else
    3778             :     {
    3779         107 :         if (!hTransformArg)
    3780             :         {
    3781          54 :             hTransformArg.reset(GDALCreateGenImgProjTransformer2(
    3782          54 :                 m_poSrcDS, nullptr, aosTO.List()));
    3783             :         }
    3784         107 :         if (!hTransformArg)
    3785             :         {
    3786           1 :             return false;
    3787             :         }
    3788         106 :         CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
    3789         106 :         bSuggestOK =
    3790         212 :             (GDALSuggestedWarpOutput2(
    3791         106 :                  m_poSrcDS,
    3792         106 :                  static_cast<GDALTransformerInfo *>(hTransformArg.get())
    3793             :                      ->pfnTransform,
    3794             :                  hTransformArg.get(), dstGT.data(), &nXSize, &nYSize, adfExtent,
    3795             :                  0) == CE_None);
    3796             :     }
    3797         110 :     if (!bSuggestOK)
    3798             :     {
    3799           1 :         ReportError(CE_Failure, CPLE_AppDefined,
    3800             :                     "Cannot determine extent of raster in target CRS");
    3801           1 :         return false;
    3802             :     }
    3803             : 
    3804         109 :     poTmpDS.reset();
    3805             : 
    3806         109 :     if (bEPSG3857Adjust)
    3807             :     {
    3808          53 :         constexpr double SPHERICAL_RADIUS = 6378137.0;
    3809          53 :         constexpr double MAX_GM =
    3810             :             SPHERICAL_RADIUS * M_PI;  // 20037508.342789244
    3811          53 :         double maxNorthing = dstGT[3];
    3812          53 :         double minNorthing = dstGT[3] + dstGT[5] * nYSize;
    3813          53 :         bool bChanged = false;
    3814          53 :         if (maxNorthing > MAX_GM)
    3815             :         {
    3816          50 :             bChanged = true;
    3817          50 :             maxNorthing = MAX_GM;
    3818             :         }
    3819          53 :         if (minNorthing < -MAX_GM)
    3820             :         {
    3821          50 :             bChanged = true;
    3822          50 :             minNorthing = -MAX_GM;
    3823             :         }
    3824          53 :         if (bChanged)
    3825             :         {
    3826          50 :             dstGT[3] = maxNorthing;
    3827          50 :             nYSize = int((maxNorthing - minNorthing) / (-dstGT[5]) + 0.5);
    3828          50 :             adfExtent[1] = maxNorthing + nYSize * dstGT[5];
    3829          50 :             adfExtent[3] = maxNorthing;
    3830             :         }
    3831             :     }
    3832             : 
    3833         109 :     const auto &tileMatrixList = poTMS->tileMatrixList();
    3834         109 :     if (m_maxZoomLevel >= 0)
    3835             :     {
    3836          60 :         if (m_maxZoomLevel >= static_cast<int>(tileMatrixList.size()))
    3837             :         {
    3838           1 :             ReportError(CE_Failure, CPLE_AppDefined,
    3839             :                         "max-zoom = %d is invalid. It must be in [0,%d] range",
    3840             :                         m_maxZoomLevel,
    3841           1 :                         static_cast<int>(tileMatrixList.size()) - 1);
    3842           1 :             return false;
    3843             :         }
    3844             :     }
    3845             :     else
    3846             :     {
    3847          49 :         const double dfComputedRes = dstGT[1];
    3848          49 :         double dfPrevRes = 0.0;
    3849          49 :         double dfRes = 0.0;
    3850          49 :         constexpr double EPSILON = 1e-8;
    3851             : 
    3852          49 :         if (m_minZoomLevel >= 0)
    3853          17 :             m_maxZoomLevel = m_minZoomLevel;
    3854             :         else
    3855          32 :             m_maxZoomLevel = 0;
    3856             : 
    3857         164 :         for (; m_maxZoomLevel < static_cast<int>(tileMatrixList.size());
    3858         115 :              m_maxZoomLevel++)
    3859             :         {
    3860         163 :             dfRes = tileMatrixList[m_maxZoomLevel].mResX;
    3861         163 :             if (dfComputedRes > dfRes ||
    3862         115 :                 fabs(dfComputedRes - dfRes) / dfRes <= EPSILON)
    3863             :                 break;
    3864         115 :             dfPrevRes = dfRes;
    3865             :         }
    3866          49 :         if (m_maxZoomLevel >= static_cast<int>(tileMatrixList.size()))
    3867             :         {
    3868           1 :             ReportError(CE_Failure, CPLE_AppDefined,
    3869             :                         "Could not find an appropriate zoom level. Perhaps "
    3870             :                         "min-zoom is too large?");
    3871           1 :             return false;
    3872             :         }
    3873             : 
    3874          48 :         if (m_maxZoomLevel > 0 && fabs(dfComputedRes - dfRes) / dfRes > EPSILON)
    3875             :         {
    3876             :             // Round to closest resolution
    3877          43 :             if (dfPrevRes / dfComputedRes < dfComputedRes / dfRes)
    3878          23 :                 m_maxZoomLevel--;
    3879             :         }
    3880             :     }
    3881         107 :     if (m_minZoomLevel < 0)
    3882          53 :         m_minZoomLevel = m_maxZoomLevel;
    3883             : 
    3884         214 :     auto tileMatrix = tileMatrixList[m_maxZoomLevel];
    3885         107 :     int nMinTileX = 0;
    3886         107 :     int nMinTileY = 0;
    3887         107 :     int nMaxTileX = 0;
    3888         107 :     int nMaxTileY = 0;
    3889         107 :     bool bIntersects = false;
    3890         107 :     if (!GetTileIndices(tileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
    3891             :                         nMinTileX, nMinTileY, nMaxTileX, nMaxTileY,
    3892         107 :                         m_noIntersectionIsOK, bIntersects,
    3893             :                         /* checkRasterOverflow = */ false))
    3894             :     {
    3895           1 :         return false;
    3896             :     }
    3897         106 :     if (!bIntersects)
    3898           1 :         return true;
    3899             : 
    3900             :     // Potentially restrict tiling to user specified coordinates
    3901         105 :     if (m_minTileX >= tileMatrix.mMatrixWidth)
    3902             :     {
    3903           1 :         ReportError(CE_Failure, CPLE_IllegalArg,
    3904             :                     "'min-x' value must be in [0,%d] range",
    3905           1 :                     tileMatrix.mMatrixWidth - 1);
    3906           1 :         return false;
    3907             :     }
    3908         104 :     if (m_maxTileX >= tileMatrix.mMatrixWidth)
    3909             :     {
    3910           1 :         ReportError(CE_Failure, CPLE_IllegalArg,
    3911             :                     "'max-x' value must be in [0,%d] range",
    3912           1 :                     tileMatrix.mMatrixWidth - 1);
    3913           1 :         return false;
    3914             :     }
    3915         103 :     if (m_minTileY >= tileMatrix.mMatrixHeight)
    3916             :     {
    3917           1 :         ReportError(CE_Failure, CPLE_IllegalArg,
    3918             :                     "'min-y' value must be in [0,%d] range",
    3919           1 :                     tileMatrix.mMatrixHeight - 1);
    3920           1 :         return false;
    3921             :     }
    3922         102 :     if (m_maxTileY >= tileMatrix.mMatrixHeight)
    3923             :     {
    3924           1 :         ReportError(CE_Failure, CPLE_IllegalArg,
    3925             :                     "'max-y' value must be in [0,%d] range",
    3926           1 :                     tileMatrix.mMatrixHeight - 1);
    3927           1 :         return false;
    3928             :     }
    3929             : 
    3930         101 :     if ((m_minTileX >= 0 && m_minTileX > nMaxTileX) ||
    3931          99 :         (m_minTileY >= 0 && m_minTileY > nMaxTileY) ||
    3932          99 :         (m_maxTileX >= 0 && m_maxTileX < nMinTileX) ||
    3933          99 :         (m_maxTileY >= 0 && m_maxTileY < nMinTileY))
    3934             :     {
    3935           2 :         ReportError(
    3936           2 :             m_noIntersectionIsOK ? CE_Warning : CE_Failure, CPLE_AppDefined,
    3937             :             "Dataset extent not intersecting specified min/max X/Y tile "
    3938             :             "coordinates");
    3939           2 :         return m_noIntersectionIsOK;
    3940             :     }
    3941          99 :     if (m_minTileX >= 0 && m_minTileX > nMinTileX)
    3942             :     {
    3943           2 :         nMinTileX = m_minTileX;
    3944           2 :         adfExtent[0] = tileMatrix.mTopLeftX +
    3945           2 :                        nMinTileX * tileMatrix.mResX * tileMatrix.mTileWidth;
    3946             :     }
    3947          99 :     if (m_minTileY >= 0 && m_minTileY > nMinTileY)
    3948             :     {
    3949           8 :         nMinTileY = m_minTileY;
    3950           8 :         adfExtent[3] = tileMatrix.mTopLeftY -
    3951           8 :                        nMinTileY * tileMatrix.mResY * tileMatrix.mTileHeight;
    3952             :     }
    3953          99 :     if (m_maxTileX >= 0 && m_maxTileX < nMaxTileX)
    3954             :     {
    3955           2 :         nMaxTileX = m_maxTileX;
    3956           2 :         adfExtent[2] = tileMatrix.mTopLeftX + (nMaxTileX + 1) *
    3957           2 :                                                   tileMatrix.mResX *
    3958           2 :                                                   tileMatrix.mTileWidth;
    3959             :     }
    3960          99 :     if (m_maxTileY >= 0 && m_maxTileY < nMaxTileY)
    3961             :     {
    3962           9 :         nMaxTileY = m_maxTileY;
    3963           9 :         adfExtent[1] = tileMatrix.mTopLeftY - (nMaxTileY + 1) *
    3964           9 :                                                   tileMatrix.mResY *
    3965           9 :                                                   tileMatrix.mTileHeight;
    3966             :     }
    3967             : 
    3968          99 :     if (nMaxTileX - nMinTileX + 1 > INT_MAX / tileMatrix.mTileWidth ||
    3969          98 :         nMaxTileY - nMinTileY + 1 > INT_MAX / tileMatrix.mTileHeight)
    3970             :     {
    3971           1 :         ReportError(CE_Failure, CPLE_AppDefined, "Too large zoom level");
    3972           1 :         return false;
    3973             :     }
    3974             : 
    3975         196 :     dstGT[0] = tileMatrix.mTopLeftX +
    3976          98 :                nMinTileX * tileMatrix.mResX * tileMatrix.mTileWidth;
    3977          98 :     dstGT[1] = tileMatrix.mResX;
    3978          98 :     dstGT[2] = 0;
    3979         196 :     dstGT[3] = tileMatrix.mTopLeftY -
    3980          98 :                nMinTileY * tileMatrix.mResY * tileMatrix.mTileHeight;
    3981          98 :     dstGT[4] = 0;
    3982          98 :     dstGT[5] = -tileMatrix.mResY;
    3983             : 
    3984             :     /* -------------------------------------------------------------------- */
    3985             :     /*      Setup warp options.                                             */
    3986             :     /* -------------------------------------------------------------------- */
    3987             :     std::unique_ptr<GDALWarpOptions, decltype(&GDALDestroyWarpOptions)> psWO(
    3988         196 :         GDALCreateWarpOptions(), GDALDestroyWarpOptions);
    3989             : 
    3990          98 :     psWO->papszWarpOptions = CSLSetNameValue(nullptr, "OPTIMIZE_SIZE", "YES");
    3991         196 :     psWO->papszWarpOptions =
    3992          98 :         CSLSetNameValue(psWO->papszWarpOptions, "SAMPLE_GRID", "YES");
    3993         196 :     psWO->papszWarpOptions =
    3994          98 :         CSLMerge(psWO->papszWarpOptions, aosWarpOptions.List());
    3995             : 
    3996          98 :     int bHasSrcNoData = false;
    3997             :     const double dfSrcNoDataValue =
    3998          98 :         m_poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHasSrcNoData);
    3999             : 
    4000             :     const bool bLastSrcBandIsAlpha =
    4001         167 :         (m_poSrcDS->GetRasterCount() > 1 &&
    4002          69 :          m_poSrcDS->GetRasterBand(m_poSrcDS->GetRasterCount())
    4003          69 :                  ->GetColorInterpretation() == GCI_AlphaBand);
    4004             : 
    4005          98 :     const bool bOutputSupportsAlpha = !EQUAL(m_outputFormat.c_str(), "JPEG");
    4006          98 :     const bool bOutputSupportsNoData = EQUAL(m_outputFormat.c_str(), "GTiff");
    4007          98 :     const bool bDstNoDataSpecified = GetArg("dst-nodata")->IsExplicitlySet();
    4008             :     auto poColorTable = std::unique_ptr<GDALColorTable>(
    4009          98 :         [this]()
    4010             :         {
    4011          98 :             auto poCT = m_poSrcDS->GetRasterBand(1)->GetColorTable();
    4012          98 :             return poCT ? poCT->Clone() : nullptr;
    4013         196 :         }());
    4014             : 
    4015          98 :     const bool bUserAskedForAlpha = m_addalpha;
    4016          98 :     if (!m_noalpha && !m_addalpha)
    4017             :     {
    4018         108 :         m_addalpha = !(bHasSrcNoData && bOutputSupportsNoData) &&
    4019         108 :                      !bDstNoDataSpecified && poColorTable == nullptr;
    4020             :     }
    4021          98 :     m_addalpha &= bOutputSupportsAlpha;
    4022             : 
    4023          98 :     psWO->nBandCount = m_poSrcDS->GetRasterCount();
    4024          98 :     if (bLastSrcBandIsAlpha)
    4025             :     {
    4026           5 :         --psWO->nBandCount;
    4027           5 :         psWO->nSrcAlphaBand = m_poSrcDS->GetRasterCount();
    4028             :     }
    4029             : 
    4030          98 :     if (bHasSrcNoData)
    4031             :     {
    4032          26 :         psWO->padfSrcNoDataReal =
    4033          13 :             static_cast<double *>(CPLCalloc(psWO->nBandCount, sizeof(double)));
    4034          32 :         for (int i = 0; i < psWO->nBandCount; ++i)
    4035             :         {
    4036          19 :             psWO->padfSrcNoDataReal[i] = dfSrcNoDataValue;
    4037             :         }
    4038             :     }
    4039             : 
    4040          98 :     if ((bHasSrcNoData && !m_addalpha && bOutputSupportsNoData) ||
    4041             :         bDstNoDataSpecified)
    4042             :     {
    4043          18 :         psWO->padfDstNoDataReal =
    4044           9 :             static_cast<double *>(CPLCalloc(psWO->nBandCount, sizeof(double)));
    4045          18 :         for (int i = 0; i < psWO->nBandCount; ++i)
    4046             :         {
    4047           9 :             psWO->padfDstNoDataReal[i] =
    4048           9 :                 bDstNoDataSpecified ? m_dstNoData : dfSrcNoDataValue;
    4049             :         }
    4050             :     }
    4051             : 
    4052          98 :     psWO->eWorkingDataType = eSrcDT;
    4053             : 
    4054          98 :     GDALGetWarpResampleAlg(m_resampling.c_str(), psWO->eResampleAlg);
    4055             : 
    4056             :     /* -------------------------------------------------------------------- */
    4057             :     /*      Setup band mapping.                                             */
    4058             :     /* -------------------------------------------------------------------- */
    4059             : 
    4060         196 :     psWO->panSrcBands =
    4061          98 :         static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
    4062         196 :     psWO->panDstBands =
    4063          98 :         static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
    4064             : 
    4065       65867 :     for (int i = 0; i < psWO->nBandCount; i++)
    4066             :     {
    4067       65769 :         psWO->panSrcBands[i] = i + 1;
    4068       65769 :         psWO->panDstBands[i] = i + 1;
    4069             :     }
    4070             : 
    4071          98 :     if (m_addalpha)
    4072          85 :         psWO->nDstAlphaBand = psWO->nBandCount + 1;
    4073             : 
    4074             :     const int nDstBands =
    4075          98 :         psWO->nDstAlphaBand ? psWO->nDstAlphaBand : psWO->nBandCount;
    4076             : 
    4077         196 :     std::vector<GByte> dstBuffer;
    4078             :     const uint64_t dstBufferSize =
    4079          98 :         static_cast<uint64_t>(tileMatrix.mTileWidth) * tileMatrix.mTileHeight *
    4080          98 :         nDstBands * GDALGetDataTypeSizeBytes(psWO->eWorkingDataType);
    4081             :     const uint64_t nUsableRAM =
    4082          98 :         std::min<uint64_t>(INT_MAX, CPLGetUsablePhysicalRAM() / 4);
    4083          98 :     if (dstBufferSize <=
    4084          98 :         (nUsableRAM ? nUsableRAM : static_cast<uint64_t>(INT_MAX)))
    4085             :     {
    4086             :         try
    4087             :         {
    4088          97 :             dstBuffer.resize(static_cast<size_t>(dstBufferSize));
    4089             :         }
    4090           0 :         catch (const std::exception &)
    4091             :         {
    4092             :         }
    4093             :     }
    4094          98 :     if (dstBuffer.size() < dstBufferSize)
    4095             :     {
    4096           1 :         ReportError(CE_Failure, CPLE_AppDefined,
    4097             :                     "Tile size and/or number of bands too large compared to "
    4098             :                     "available RAM");
    4099           1 :         return false;
    4100             :     }
    4101             : 
    4102             :     FakeMaxZoomDataset oFakeMaxZoomDS(
    4103          97 :         (nMaxTileX - nMinTileX + 1) * tileMatrix.mTileWidth,
    4104          97 :         (nMaxTileY - nMinTileY + 1) * tileMatrix.mTileHeight, nDstBands,
    4105          97 :         tileMatrix.mTileWidth, tileMatrix.mTileHeight, psWO->eWorkingDataType,
    4106         194 :         dstGT, oSRS_TMS, dstBuffer);
    4107          97 :     CPL_IGNORE_RET_VAL(oFakeMaxZoomDS.GetSpatialRef());
    4108             : 
    4109          97 :     psWO->hSrcDS = GDALDataset::ToHandle(m_poSrcDS);
    4110          97 :     psWO->hDstDS = GDALDataset::ToHandle(&oFakeMaxZoomDS);
    4111             : 
    4112          97 :     std::unique_ptr<GDALDataset> tmpSrcDS;
    4113          97 :     if (m_tilingScheme == "raster" && !bHasNorthUpSrcGT)
    4114             :     {
    4115           1 :         CPLStringList aosOptions;
    4116           1 :         aosOptions.AddString("-of");
    4117           1 :         aosOptions.AddString("VRT");
    4118           1 :         aosOptions.AddString("-a_ullr");
    4119           1 :         aosOptions.AddString(CPLSPrintf("%.17g", srcGTModif[0]));
    4120           1 :         aosOptions.AddString(CPLSPrintf("%.17g", srcGTModif[3]));
    4121             :         aosOptions.AddString(
    4122           1 :             CPLSPrintf("%.17g", srcGTModif[0] + nSrcWidth * srcGTModif[1]));
    4123             :         aosOptions.AddString(
    4124           1 :             CPLSPrintf("%.17g", srcGTModif[3] + nSrcHeight * srcGTModif[5]));
    4125           1 :         if (oSRS_TMS.IsEmpty())
    4126             :         {
    4127           1 :             aosOptions.AddString("-a_srs");
    4128           1 :             aosOptions.AddString("none");
    4129             :         }
    4130             : 
    4131             :         GDALTranslateOptions *psOptions =
    4132           1 :             GDALTranslateOptionsNew(aosOptions.List(), nullptr);
    4133             : 
    4134           1 :         tmpSrcDS.reset(GDALDataset::FromHandle(GDALTranslate(
    4135             :             "", GDALDataset::ToHandle(m_poSrcDS), psOptions, nullptr)));
    4136           1 :         GDALTranslateOptionsFree(psOptions);
    4137           1 :         if (!tmpSrcDS)
    4138           0 :             return false;
    4139             :     }
    4140          98 :     hTransformArg.reset(GDALCreateGenImgProjTransformer2(
    4141          98 :         tmpSrcDS ? tmpSrcDS.get() : m_poSrcDS, &oFakeMaxZoomDS, aosTO.List()));
    4142          97 :     CPLAssert(hTransformArg);
    4143             : 
    4144             :     /* -------------------------------------------------------------------- */
    4145             :     /*      Warp the transformer with a linear approximator                 */
    4146             :     /* -------------------------------------------------------------------- */
    4147          97 :     hTransformArg.reset(GDALCreateApproxTransformer(
    4148             :         GDALGenImgProjTransform, hTransformArg.release(), 0.125));
    4149          97 :     GDALApproxTransformerOwnsSubtransformer(hTransformArg.get(), TRUE);
    4150             : 
    4151          97 :     psWO->pfnTransformer = GDALApproxTransform;
    4152          97 :     psWO->pTransformerArg = hTransformArg.get();
    4153             : 
    4154             :     /* -------------------------------------------------------------------- */
    4155             :     /*      Determine total number of tiles                                 */
    4156             :     /* -------------------------------------------------------------------- */
    4157          97 :     const int nBaseTilesPerRow = nMaxTileX - nMinTileX + 1;
    4158          97 :     const int nBaseTilesPerCol = nMaxTileY - nMinTileY + 1;
    4159          97 :     const uint64_t nBaseTiles =
    4160          97 :         static_cast<uint64_t>(nBaseTilesPerCol) * nBaseTilesPerRow;
    4161          97 :     uint64_t nTotalTiles = nBaseTiles;
    4162          97 :     std::atomic<uint64_t> nCurTile = 0;
    4163          97 :     bool bRet = true;
    4164             : 
    4165         204 :     for (int iZ = m_maxZoomLevel - 1;
    4166         204 :          bRet && bIntersects && iZ >= m_minZoomLevel; --iZ)
    4167             :     {
    4168         214 :         auto ovrTileMatrix = tileMatrixList[iZ];
    4169         107 :         int nOvrMinTileX = 0;
    4170         107 :         int nOvrMinTileY = 0;
    4171         107 :         int nOvrMaxTileX = 0;
    4172         107 :         int nOvrMaxTileY = 0;
    4173         107 :         bRet =
    4174         214 :             GetTileIndices(ovrTileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
    4175             :                            nOvrMinTileX, nOvrMinTileY, nOvrMaxTileX,
    4176         107 :                            nOvrMaxTileY, m_noIntersectionIsOK, bIntersects);
    4177         107 :         if (bIntersects)
    4178             :         {
    4179         107 :             nTotalTiles +=
    4180         107 :                 static_cast<uint64_t>(nOvrMaxTileY - nOvrMinTileY + 1) *
    4181         107 :                 (nOvrMaxTileX - nOvrMinTileX + 1);
    4182             :         }
    4183             :     }
    4184             : 
    4185             :     /* -------------------------------------------------------------------- */
    4186             :     /*      Generate tiles at max zoom level                                */
    4187             :     /* -------------------------------------------------------------------- */
    4188         194 :     GDALWarpOperation oWO;
    4189             : 
    4190          97 :     bRet = oWO.Initialize(psWO.get()) == CE_None && bRet;
    4191             : 
    4192             :     const auto GetUpdatedCreationOptions =
    4193         386 :         [this](const gdal::TileMatrixSet::TileMatrix &oTM)
    4194             :     {
    4195         136 :         CPLStringList aosCreationOptions(m_creationOptions);
    4196         136 :         if (m_outputFormat == "GTiff")
    4197             :         {
    4198          48 :             if (aosCreationOptions.FetchNameValue("TILED") == nullptr &&
    4199          24 :                 aosCreationOptions.FetchNameValue("BLOCKYSIZE") == nullptr)
    4200             :             {
    4201          24 :                 if (oTM.mTileWidth <= 512 && oTM.mTileHeight <= 512)
    4202             :                 {
    4203          22 :                     aosCreationOptions.SetNameValue(
    4204          22 :                         "BLOCKYSIZE", CPLSPrintf("%d", oTM.mTileHeight));
    4205             :                 }
    4206             :                 else
    4207             :                 {
    4208           2 :                     aosCreationOptions.SetNameValue("TILED", "YES");
    4209             :                 }
    4210             :             }
    4211          24 :             if (aosCreationOptions.FetchNameValue("COMPRESS") == nullptr)
    4212          24 :                 aosCreationOptions.SetNameValue("COMPRESS", "LZW");
    4213             :         }
    4214         112 :         else if (m_outputFormat == "COG")
    4215             :         {
    4216           2 :             if (aosCreationOptions.FetchNameValue("OVERVIEW_RESAMPLING") ==
    4217             :                 nullptr)
    4218             :             {
    4219             :                 aosCreationOptions.SetNameValue("OVERVIEW_RESAMPLING",
    4220           2 :                                                 m_overviewResampling.c_str());
    4221             :             }
    4222           2 :             if (aosCreationOptions.FetchNameValue("BLOCKSIZE") == nullptr &&
    4223           2 :                 oTM.mTileWidth <= 512 && oTM.mTileWidth == oTM.mTileHeight)
    4224             :             {
    4225             :                 aosCreationOptions.SetNameValue(
    4226           2 :                     "BLOCKSIZE", CPLSPrintf("%d", oTM.mTileWidth));
    4227             :             }
    4228             :         }
    4229         136 :         return aosCreationOptions;
    4230          97 :     };
    4231             : 
    4232          97 :     VSIMkdir(m_outputDirectory.c_str(), 0755);
    4233             :     VSIStatBufL sStat;
    4234         193 :     if (VSIStatL(m_outputDirectory.c_str(), &sStat) != 0 ||
    4235          96 :         !VSI_ISDIR(sStat.st_mode))
    4236             :     {
    4237           1 :         ReportError(CE_Failure, CPLE_FileIO,
    4238             :                     "Cannot create output directory %s",
    4239             :                     m_outputDirectory.c_str());
    4240           1 :         return false;
    4241             :     }
    4242             : 
    4243         192 :     OGRSpatialReference oWGS84;
    4244          96 :     oWGS84.importFromEPSG(4326);
    4245          96 :     oWGS84.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    4246             : 
    4247          96 :     std::unique_ptr<OGRCoordinateTransformation> poCTToWGS84;
    4248          96 :     if (!oSRS_TMS.IsEmpty())
    4249             :     {
    4250         190 :         CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
    4251          95 :         poCTToWGS84.reset(
    4252             :             OGRCreateCoordinateTransformation(&oSRS_TMS, &oWGS84));
    4253             :     }
    4254             : 
    4255          98 :     const bool kmlCompatible = m_kml &&
    4256          17 :                                [this, &poTMS, &poCTToWGS84, bInvertAxisTMS]()
    4257             :     {
    4258           2 :         CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
    4259           2 :         double dfX = poTMS->tileMatrixList()[0].mTopLeftX;
    4260           2 :         double dfY = poTMS->tileMatrixList()[0].mTopLeftY;
    4261           2 :         if (bInvertAxisTMS)
    4262           0 :             std::swap(dfX, dfY);
    4263           3 :         return (m_minZoomLevel == m_maxZoomLevel ||
    4264           1 :                 (poTMS->haveAllLevelsSameTopLeft() &&
    4265           1 :                  poTMS->haveAllLevelsSameTileSize() &&
    4266           3 :                  poTMS->hasOnlyPowerOfTwoVaryingScales())) &&
    4267           7 :                poCTToWGS84 && poCTToWGS84->Transform(1, &dfX, &dfY);
    4268           2 :     }();
    4269             :     const int kmlTileSize =
    4270          96 :         m_tileSize > 0 ? m_tileSize : poTMS->tileMatrixList()[0].mTileWidth;
    4271          96 :     if (m_kml && !kmlCompatible)
    4272             :     {
    4273           0 :         ReportError(CE_Failure, CPLE_NotSupported,
    4274             :                     "Tiling scheme not compatible with KML output");
    4275           0 :         return false;
    4276             :     }
    4277             : 
    4278          96 :     if (m_title.empty())
    4279          94 :         m_title = CPLGetFilename(m_dataset.GetName().c_str());
    4280             : 
    4281          96 :     if (!m_url.empty())
    4282             :     {
    4283           2 :         if (m_url.back() != '/')
    4284           2 :             m_url += '/';
    4285           4 :         std::string out_path = m_outputDirectory;
    4286           2 :         if (m_outputDirectory.back() == '/')
    4287           0 :             out_path.pop_back();
    4288           2 :         m_url += CPLGetFilename(out_path.c_str());
    4289             :     }
    4290             : 
    4291         192 :     CPLWorkerThreadPool oThreadPool;
    4292             : 
    4293          96 :     bool bThreadPoolInitialized = false;
    4294             :     const auto InitThreadPool =
    4295         474 :         [this, &oThreadPool, &bRet, &bThreadPoolInitialized]()
    4296             :     {
    4297         136 :         if (!bThreadPoolInitialized)
    4298             :         {
    4299          95 :             bThreadPoolInitialized = true;
    4300             : 
    4301          95 :             if (bRet && m_numThreads > 1)
    4302             :             {
    4303           6 :                 CPLDebug("gdal_raster_tile", "Using %d threads", m_numThreads);
    4304           6 :                 bRet = oThreadPool.Setup(m_numThreads, nullptr, nullptr);
    4305             :             }
    4306             :         }
    4307             : 
    4308         136 :         return bRet;
    4309          96 :     };
    4310             : 
    4311             :     // Just for unit test purposes
    4312          96 :     const bool bEmitSpuriousCharsOnStdout = CPLTestBool(
    4313             :         CPLGetConfigOption("GDAL_RASTER_TILE_EMIT_SPURIOUS_CHARS", "NO"));
    4314             : 
    4315          32 :     const auto IsCompatibleOfSpawnSilent = [bSrcIsFineForFork, this]()
    4316             :     {
    4317           8 :         const char *pszErrorMsg = "";
    4318             :         {
    4319           8 :             CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
    4320           8 :             if (IsCompatibleOfSpawn(pszErrorMsg))
    4321             :             {
    4322           1 :                 m_parallelMethod = "spawn";
    4323           1 :                 return true;
    4324             :             }
    4325             :         }
    4326             :         (void)bSrcIsFineForFork;
    4327             : #ifdef FORK_ALLOWED
    4328          14 :         if (bSrcIsFineForFork &&
    4329           7 :             !cpl::starts_with(m_outputDirectory, "/vsimem/"))
    4330             :         {
    4331           4 :             if (CPLGetCurrentThreadCount() == 1)
    4332             :             {
    4333           1 :                 CPLDebugOnce(
    4334             :                     "gdal_raster_tile",
    4335             :                     "'gdal' binary not found. Using instead "
    4336             :                     "parallel-method=fork. If causing instability issues, set "
    4337             :                     "parallel-method to 'thread' or 'spawn'");
    4338           1 :                 m_parallelMethod = "fork";
    4339           1 :                 return true;
    4340             :             }
    4341             :         }
    4342             : #endif
    4343           6 :         return false;
    4344          96 :     };
    4345             : 
    4346          96 :     m_numThreads = std::max(
    4347         192 :         1, static_cast<int>(std::min<uint64_t>(
    4348          96 :                m_numThreads, nBaseTiles / GetThresholdMinTilesPerJob())));
    4349             : 
    4350          96 :     std::atomic<bool> bParentAskedForStop = false;
    4351         192 :     std::thread threadWaitForParentStop;
    4352          96 :     if (m_spawned)
    4353             :     {
    4354          52 :         threadWaitForParentStop = std::thread(
    4355          52 :             [&bParentAskedForStop]()
    4356             :             {
    4357          26 :                 char szBuffer[81] = {0};
    4358          26 :                 while (fgets(szBuffer, 80, stdin))
    4359             :                 {
    4360          26 :                     if (strcmp(szBuffer, STOP_MARKER) == 0)
    4361             :                     {
    4362          26 :                         bParentAskedForStop = true;
    4363          26 :                         break;
    4364             :                     }
    4365             :                     else
    4366             :                     {
    4367           0 :                         CPLError(CE_Failure, CPLE_AppDefined,
    4368             :                                  "Got unexpected input from parent '%s'",
    4369             :                                  szBuffer);
    4370             :                     }
    4371             :                 }
    4372          52 :             });
    4373             :     }
    4374             : #ifdef FORK_ALLOWED
    4375          70 :     else if (m_forked)
    4376             :     {
    4377           0 :         threadWaitForParentStop = std::thread(
    4378           0 :             [&bParentAskedForStop]()
    4379             :             {
    4380           0 :                 std::string buffer;
    4381           0 :                 buffer.resize(strlen(STOP_MARKER));
    4382           0 :                 if (CPLPipeRead(pipeIn, buffer.data(),
    4383           0 :                                 static_cast<int>(strlen(STOP_MARKER))) &&
    4384           0 :                     buffer == STOP_MARKER)
    4385             :                 {
    4386           0 :                     bParentAskedForStop = true;
    4387             :                 }
    4388             :                 else
    4389             :                 {
    4390           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    4391             :                              "Got unexpected input from parent '%s'",
    4392             :                              buffer.c_str());
    4393             :                 }
    4394           0 :             });
    4395             :     }
    4396             : #endif
    4397             : 
    4398          96 :     if (m_ovrZoomLevel >= 0)
    4399             :     {
    4400             :         // do not generate base tiles if called as a child process with
    4401             :         // --ovr-zoom-level
    4402             :     }
    4403         102 :     else if (m_numThreads > 1 && nBaseTiles > 1 &&
    4404          11 :              ((m_parallelMethod.empty() &&
    4405           7 :                m_numThreads >= GetThresholdMinThreadsForSpawn() &&
    4406          13 :                IsCompatibleOfSpawnSilent()) ||
    4407          16 :               (m_parallelMethod == "spawn" || m_parallelMethod == "fork")))
    4408             :     {
    4409           5 :         if (!GenerateBaseTilesSpawnMethod(nBaseTilesPerCol, nBaseTilesPerRow,
    4410             :                                           nMinTileX, nMinTileY, nMaxTileX,
    4411             :                                           nMaxTileY, nTotalTiles, nBaseTiles,
    4412             :                                           pfnProgress, pProgressData))
    4413             :         {
    4414           1 :             return false;
    4415             :         }
    4416           4 :         nCurTile = nBaseTiles;
    4417             :     }
    4418             :     else
    4419             :     {
    4420             :         // Branch for multi-threaded or single-threaded max zoom level tile
    4421             :         // generation
    4422             : 
    4423             :         PerThreadMaxZoomResourceManager oResourceManager(
    4424          75 :             m_poSrcDS, psWO.get(), hTransformArg.get(), oFakeMaxZoomDS,
    4425         225 :             dstBuffer.size());
    4426             : 
    4427             :         const CPLStringList aosCreationOptions(
    4428         150 :             GetUpdatedCreationOptions(tileMatrix));
    4429             : 
    4430          75 :         CPLDebug("gdal_raster_tile",
    4431             :                  "Generating tiles z=%d, y=%d...%d, x=%d...%d", m_maxZoomLevel,
    4432             :                  nMinTileY, nMaxTileY, nMinTileX, nMaxTileX);
    4433             : 
    4434          75 :         bRet &= InitThreadPool();
    4435             : 
    4436          75 :         if (bRet && m_numThreads > 1)
    4437             :         {
    4438           6 :             std::atomic<bool> bFailure = false;
    4439           6 :             std::atomic<int> nQueuedJobs = 0;
    4440             : 
    4441             :             double dfTilesYPerJob;
    4442             :             int nYOuterIterations;
    4443             :             double dfTilesXPerJob;
    4444             :             int nXOuterIterations;
    4445           6 :             ComputeJobChunkSize(m_numThreads, nBaseTilesPerCol,
    4446             :                                 nBaseTilesPerRow, dfTilesYPerJob,
    4447             :                                 nYOuterIterations, dfTilesXPerJob,
    4448             :                                 nXOuterIterations);
    4449             : 
    4450           6 :             CPLDebugOnly("gdal_raster_tile",
    4451             :                          "nYOuterIterations=%d, dfTilesYPerJob=%g, "
    4452             :                          "nXOuterIterations=%d, dfTilesXPerJob=%g",
    4453             :                          nYOuterIterations, dfTilesYPerJob, nXOuterIterations,
    4454             :                          dfTilesXPerJob);
    4455             : 
    4456           6 :             int nLastYEndIncluded = nMinTileY - 1;
    4457          30 :             for (int iYOuterIter = 0; bRet && iYOuterIter < nYOuterIterations &&
    4458          24 :                                       nLastYEndIncluded < nMaxTileY;
    4459             :                  ++iYOuterIter)
    4460             :             {
    4461          24 :                 const int iYStart = nLastYEndIncluded + 1;
    4462             :                 const int iYEndIncluded =
    4463          24 :                     iYOuterIter + 1 == nYOuterIterations
    4464          42 :                         ? nMaxTileY
    4465             :                         : std::max(
    4466             :                               iYStart,
    4467          42 :                               static_cast<int>(std::floor(
    4468          18 :                                   nMinTileY +
    4469          18 :                                   (iYOuterIter + 1) * dfTilesYPerJob - 1)));
    4470             : 
    4471          24 :                 nLastYEndIncluded = iYEndIncluded;
    4472             : 
    4473          24 :                 int nLastXEndIncluded = nMinTileX - 1;
    4474          24 :                 for (int iXOuterIter = 0;
    4475          48 :                      bRet && iXOuterIter < nXOuterIterations &&
    4476          24 :                      nLastXEndIncluded < nMaxTileX;
    4477             :                      ++iXOuterIter)
    4478             :                 {
    4479          24 :                     const int iXStart = nLastXEndIncluded + 1;
    4480             :                     const int iXEndIncluded =
    4481          24 :                         iXOuterIter + 1 == nXOuterIterations
    4482          24 :                             ? nMaxTileX
    4483             :                             : std::max(
    4484             :                                   iXStart,
    4485          24 :                                   static_cast<int>(std::floor(
    4486           0 :                                       nMinTileX +
    4487           0 :                                       (iXOuterIter + 1) * dfTilesXPerJob - 1)));
    4488             : 
    4489          24 :                     nLastXEndIncluded = iXEndIncluded;
    4490             : 
    4491          24 :                     CPLDebugOnly("gdal_raster_tile",
    4492             :                                  "Job for y in [%d,%d] and x in [%d,%d]",
    4493             :                                  iYStart, iYEndIncluded, iXStart,
    4494             :                                  iXEndIncluded);
    4495             : 
    4496          24 :                     auto job = [this, &oThreadPool, &oResourceManager,
    4497             :                                 &bFailure, &bParentAskedForStop, &nCurTile,
    4498             :                                 &nQueuedJobs, pszExtension, &aosCreationOptions,
    4499             :                                 &psWO, &tileMatrix, nDstBands, iXStart,
    4500             :                                 iXEndIncluded, iYStart, iYEndIncluded,
    4501             :                                 nMinTileX, nMinTileY, &poColorTable,
    4502        2090 :                                 bUserAskedForAlpha]()
    4503             :                     {
    4504          24 :                         CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
    4505             : 
    4506          23 :                         auto resources = oResourceManager.AcquireResources();
    4507          24 :                         if (resources)
    4508             :                         {
    4509          60 :                             for (int iY = iYStart;
    4510          60 :                                  iY <= iYEndIncluded && !bParentAskedForStop;
    4511             :                                  ++iY)
    4512             :                             {
    4513         462 :                                 for (int iX = iXStart; iX <= iXEndIncluded &&
    4514         219 :                                                        !bParentAskedForStop;
    4515             :                                      ++iX)
    4516             :                                 {
    4517         621 :                                     if (!GenerateTile(
    4518         207 :                                             resources->poSrcDS.get(),
    4519             :                                             m_poDstDriver, pszExtension,
    4520             :                                             aosCreationOptions.List(),
    4521         207 :                                             *(resources->poWO.get()),
    4522         207 :                                             *(resources->poFakeMaxZoomDS
    4523         207 :                                                   ->GetSpatialRef()),
    4524         207 :                                             psWO->eWorkingDataType, tileMatrix,
    4525         207 :                                             m_outputDirectory, nDstBands,
    4526         207 :                                             psWO->padfDstNoDataReal
    4527           0 :                                                 ? &(psWO->padfDstNoDataReal[0])
    4528             :                                                 : nullptr,
    4529             :                                             m_maxZoomLevel, iX, iY,
    4530         207 :                                             m_convention, nMinTileX, nMinTileY,
    4531         207 :                                             m_skipBlank, bUserAskedForAlpha,
    4532         207 :                                             m_auxXML, m_resume, m_metadata,
    4533         207 :                                             poColorTable.get(),
    4534         207 :                                             resources->dstBuffer))
    4535             :                                     {
    4536           0 :                                         oResourceManager.SetError();
    4537           0 :                                         bFailure = true;
    4538           0 :                                         --nQueuedJobs;
    4539           0 :                                         return;
    4540             :                                     }
    4541         207 :                                     ++nCurTile;
    4542         207 :                                     oThreadPool.WakeUpWaitEvent();
    4543             :                                 }
    4544             :                             }
    4545          24 :                             oResourceManager.ReleaseResources(
    4546          24 :                                 std::move(resources));
    4547             :                         }
    4548             :                         else
    4549             :                         {
    4550           0 :                             oResourceManager.SetError();
    4551           0 :                             bFailure = true;
    4552             :                         }
    4553             : 
    4554          24 :                         --nQueuedJobs;
    4555          24 :                     };
    4556             : 
    4557          24 :                     ++nQueuedJobs;
    4558          24 :                     oThreadPool.SubmitJob(std::move(job));
    4559             :                 }
    4560             :             }
    4561             : 
    4562             :             // Wait for completion of all jobs
    4563         210 :             while (bRet && nQueuedJobs > 0)
    4564             :             {
    4565         204 :                 oThreadPool.WaitEvent();
    4566         204 :                 bRet &= !bFailure;
    4567         273 :                 if (bRet && pfnProgress &&
    4568         138 :                     !pfnProgress(static_cast<double>(nCurTile) /
    4569          69 :                                      static_cast<double>(nTotalTiles),
    4570             :                                  "", pProgressData))
    4571             :                 {
    4572           3 :                     bParentAskedForStop = true;
    4573           3 :                     bRet = false;
    4574           3 :                     CPLError(CE_Failure, CPLE_UserInterrupt,
    4575             :                              "Process interrupted by user");
    4576             :                 }
    4577             :             }
    4578           6 :             oThreadPool.WaitCompletion();
    4579           6 :             bRet &=
    4580          10 :                 !bFailure && (!pfnProgress ||
    4581           8 :                               pfnProgress(static_cast<double>(nCurTile) /
    4582           4 :                                               static_cast<double>(nTotalTiles),
    4583           6 :                                           "", pProgressData));
    4584             : 
    4585           6 :             if (!oResourceManager.GetErrorMsg().empty())
    4586             :             {
    4587             :                 // Re-emit error message from worker thread to main thread
    4588           0 :                 ReportError(CE_Failure, CPLE_AppDefined, "%s",
    4589           0 :                             oResourceManager.GetErrorMsg().c_str());
    4590           6 :             }
    4591             :         }
    4592             :         else
    4593             :         {
    4594             :             // Branch for single-thread max zoom level tile generation
    4595         174 :             for (int iY = nMinTileY;
    4596         174 :                  bRet && !bParentAskedForStop && iY <= nMaxTileY; ++iY)
    4597             :             {
    4598         420 :                 for (int iX = nMinTileX;
    4599         420 :                      bRet && !bParentAskedForStop && iX <= nMaxTileX; ++iX)
    4600             :                 {
    4601         630 :                     bRet = GenerateTile(
    4602             :                         m_poSrcDS, m_poDstDriver, pszExtension,
    4603             :                         aosCreationOptions.List(), oWO, oSRS_TMS,
    4604         315 :                         psWO->eWorkingDataType, tileMatrix, m_outputDirectory,
    4605             :                         nDstBands,
    4606         315 :                         psWO->padfDstNoDataReal ? &(psWO->padfDstNoDataReal[0])
    4607             :                                                 : nullptr,
    4608         315 :                         m_maxZoomLevel, iX, iY, m_convention, nMinTileX,
    4609         315 :                         nMinTileY, m_skipBlank, bUserAskedForAlpha, m_auxXML,
    4610         315 :                         m_resume, m_metadata, poColorTable.get(), dstBuffer);
    4611             : 
    4612         315 :                     if (m_spawned)
    4613             :                     {
    4614         130 :                         if (bEmitSpuriousCharsOnStdout)
    4615          64 :                             fwrite(&PROGRESS_MARKER[0], 1, 1, stdout);
    4616         130 :                         fwrite(PROGRESS_MARKER, sizeof(PROGRESS_MARKER), 1,
    4617             :                                stdout);
    4618         130 :                         fflush(stdout);
    4619             :                     }
    4620             : #ifdef FORK_ALLOWED
    4621         185 :                     else if (m_forked)
    4622             :                     {
    4623           0 :                         CPLPipeWrite(pipeOut, PROGRESS_MARKER,
    4624             :                                      sizeof(PROGRESS_MARKER));
    4625             :                     }
    4626             : #endif
    4627             :                     else
    4628             :                     {
    4629         185 :                         ++nCurTile;
    4630         188 :                         if (bRet && pfnProgress &&
    4631           6 :                             !pfnProgress(static_cast<double>(nCurTile) /
    4632           3 :                                              static_cast<double>(nTotalTiles),
    4633             :                                          "", pProgressData))
    4634             :                         {
    4635           1 :                             bRet = false;
    4636           1 :                             CPLError(CE_Failure, CPLE_UserInterrupt,
    4637             :                                      "Process interrupted by user");
    4638             :                         }
    4639             :                     }
    4640             :                 }
    4641             :             }
    4642             :         }
    4643             : 
    4644          75 :         if (m_kml && bRet)
    4645             :         {
    4646           4 :             for (int iY = nMinTileY; iY <= nMaxTileY; ++iY)
    4647             :             {
    4648           4 :                 for (int iX = nMinTileX; iX <= nMaxTileX; ++iX)
    4649             :                 {
    4650             :                     const int nFileY =
    4651           2 :                         GetFileY(iY, poTMS->tileMatrixList()[m_maxZoomLevel],
    4652           2 :                                  m_convention);
    4653             :                     std::string osFilename = CPLFormFilenameSafe(
    4654             :                         m_outputDirectory.c_str(),
    4655           4 :                         CPLSPrintf("%d", m_maxZoomLevel), nullptr);
    4656           4 :                     osFilename = CPLFormFilenameSafe(
    4657           2 :                         osFilename.c_str(), CPLSPrintf("%d", iX), nullptr);
    4658           4 :                     osFilename = CPLFormFilenameSafe(
    4659             :                         osFilename.c_str(),
    4660           2 :                         CPLSPrintf("%d.%s", nFileY, pszExtension), nullptr);
    4661           2 :                     if (VSIStatL(osFilename.c_str(), &sStat) == 0)
    4662             :                     {
    4663           4 :                         GenerateKML(m_outputDirectory, m_title, iX, iY,
    4664             :                                     m_maxZoomLevel, kmlTileSize, pszExtension,
    4665           2 :                                     m_url, poTMS.get(), bInvertAxisTMS,
    4666           2 :                                     m_convention, poCTToWGS84.get(), {});
    4667             :                     }
    4668             :                 }
    4669             :             }
    4670             :         }
    4671             :     }
    4672             : 
    4673             :     // Close source dataset if we have opened it (in GDALAlgorithm core code),
    4674             :     // to free file descriptors, particularly if it is a VRT file.
    4675          95 :     std::vector<GDALColorInterp> aeColorInterp;
    4676         327 :     for (int i = 1; i <= m_poSrcDS->GetRasterCount(); ++i)
    4677         232 :         aeColorInterp.push_back(
    4678         232 :             m_poSrcDS->GetRasterBand(i)->GetColorInterpretation());
    4679          95 :     if (m_dataset.HasDatasetBeenOpenedByAlgorithm())
    4680             :     {
    4681          56 :         m_dataset.Close();
    4682          56 :         m_poSrcDS = nullptr;
    4683             :     }
    4684             : 
    4685             :     /* -------------------------------------------------------------------- */
    4686             :     /*      Generate tiles at lower zoom levels                             */
    4687             :     /* -------------------------------------------------------------------- */
    4688          95 :     const int iZStart =
    4689          95 :         m_ovrZoomLevel >= 0 ? m_ovrZoomLevel : m_maxZoomLevel - 1;
    4690          95 :     const int iZEnd = m_ovrZoomLevel >= 0 ? m_ovrZoomLevel : m_minZoomLevel;
    4691         164 :     for (int iZ = iZStart; bRet && iZ >= iZEnd; --iZ)
    4692             :     {
    4693          69 :         int nOvrMinTileX = 0;
    4694          69 :         int nOvrMinTileY = 0;
    4695          69 :         int nOvrMaxTileX = 0;
    4696          69 :         int nOvrMaxTileY = 0;
    4697             : 
    4698         138 :         auto ovrTileMatrix = tileMatrixList[iZ];
    4699          69 :         CPL_IGNORE_RET_VAL(
    4700          69 :             GetTileIndices(ovrTileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
    4701             :                            nOvrMinTileX, nOvrMinTileY, nOvrMaxTileX,
    4702          69 :                            nOvrMaxTileY, m_noIntersectionIsOK, bIntersects));
    4703             : 
    4704          69 :         bRet = bIntersects;
    4705             : 
    4706          69 :         if (m_minOvrTileX >= 0)
    4707             :         {
    4708          16 :             bRet = true;
    4709          16 :             nOvrMinTileX = m_minOvrTileX;
    4710          16 :             nOvrMinTileY = m_minOvrTileY;
    4711          16 :             nOvrMaxTileX = m_maxOvrTileX;
    4712          16 :             nOvrMaxTileY = m_maxOvrTileY;
    4713             :         }
    4714             : 
    4715          69 :         if (bRet)
    4716             :         {
    4717          69 :             CPLDebug("gdal_raster_tile",
    4718             :                      "Generating overview tiles z=%d, y=%d...%d, x=%d...%d", iZ,
    4719             :                      nOvrMinTileY, nOvrMaxTileY, nOvrMinTileX, nOvrMaxTileX);
    4720             :         }
    4721             : 
    4722          69 :         const int nOvrTilesPerCol = nOvrMaxTileY - nOvrMinTileY + 1;
    4723          69 :         const int nOvrTilesPerRow = nOvrMaxTileX - nOvrMinTileX + 1;
    4724          69 :         const uint64_t nOvrTileCount =
    4725          69 :             static_cast<uint64_t>(nOvrTilesPerCol) * nOvrTilesPerRow;
    4726             : 
    4727          69 :         m_numThreads = std::max(
    4728         138 :             1,
    4729         138 :             static_cast<int>(std::min<uint64_t>(
    4730          69 :                 m_numThreads, nOvrTileCount / GetThresholdMinTilesPerJob())));
    4731             : 
    4732          97 :         if (m_numThreads > 1 && nOvrTileCount > 1 &&
    4733          14 :             ((m_parallelMethod.empty() &&
    4734           6 :               m_numThreads >= GetThresholdMinThreadsForSpawn() &&
    4735          18 :               IsCompatibleOfSpawnSilent()) ||
    4736          24 :              (m_parallelMethod == "spawn" || m_parallelMethod == "fork")))
    4737             :         {
    4738           8 :             bRet &= GenerateOverviewTilesSpawnMethod(
    4739             :                 iZ, nOvrMinTileX, nOvrMinTileY, nOvrMaxTileX, nOvrMaxTileY,
    4740           8 :                 nCurTile, nTotalTiles, pfnProgress, pProgressData);
    4741             :         }
    4742             :         else
    4743             :         {
    4744          61 :             bRet &= InitThreadPool();
    4745             : 
    4746         122 :             auto srcTileMatrix = tileMatrixList[iZ + 1];
    4747          61 :             int nSrcMinTileX = 0;
    4748          61 :             int nSrcMinTileY = 0;
    4749          61 :             int nSrcMaxTileX = 0;
    4750          61 :             int nSrcMaxTileY = 0;
    4751             : 
    4752          61 :             CPL_IGNORE_RET_VAL(GetTileIndices(
    4753             :                 srcTileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
    4754             :                 nSrcMinTileX, nSrcMinTileY, nSrcMaxTileX, nSrcMaxTileY,
    4755          61 :                 m_noIntersectionIsOK, bIntersects));
    4756             : 
    4757          61 :             constexpr double EPSILON = 1e-3;
    4758          61 :             int maxCacheTileSizePerThread = static_cast<int>(
    4759          61 :                 (1 + std::ceil(
    4760          61 :                          (ovrTileMatrix.mResY * ovrTileMatrix.mTileHeight) /
    4761          61 :                              (srcTileMatrix.mResY * srcTileMatrix.mTileHeight) -
    4762          61 :                          EPSILON)) *
    4763          61 :                 (1 + std::ceil(
    4764          61 :                          (ovrTileMatrix.mResX * ovrTileMatrix.mTileWidth) /
    4765          61 :                              (srcTileMatrix.mResX * srcTileMatrix.mTileWidth) -
    4766             :                          EPSILON)));
    4767             : 
    4768          61 :             CPLDebugOnly("gdal_raster_tile",
    4769             :                          "Ideal maxCacheTileSizePerThread = %d",
    4770             :                          maxCacheTileSizePerThread);
    4771             : 
    4772             : #ifndef _WIN32
    4773             :             const int remainingFileDescriptorCount =
    4774          61 :                 CPLGetRemainingFileDescriptorCount();
    4775          61 :             CPLDebugOnly("gdal_raster_tile",
    4776             :                          "remainingFileDescriptorCount = %d",
    4777             :                          remainingFileDescriptorCount);
    4778          61 :             if (remainingFileDescriptorCount >= 0 &&
    4779             :                 remainingFileDescriptorCount <
    4780          61 :                     (1 + maxCacheTileSizePerThread) * m_numThreads)
    4781             :             {
    4782             :                 const int newNumThreads =
    4783           0 :                     std::max(1, remainingFileDescriptorCount /
    4784           0 :                                     (1 + maxCacheTileSizePerThread));
    4785           0 :                 if (newNumThreads < m_numThreads)
    4786             :                 {
    4787           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    4788             :                              "Not enough file descriptors available given the "
    4789             :                              "number of "
    4790             :                              "threads. Reducing the number of threads %d to %d",
    4791             :                              m_numThreads, newNumThreads);
    4792           0 :                     m_numThreads = newNumThreads;
    4793             :                 }
    4794             :             }
    4795             : #endif
    4796             : 
    4797             :             MosaicDataset oSrcDS(
    4798         122 :                 CPLFormFilenameSafe(m_outputDirectory.c_str(),
    4799             :                                     CPLSPrintf("%d", iZ + 1), nullptr),
    4800          61 :                 pszExtension, m_outputFormat, aeColorInterp, srcTileMatrix,
    4801             :                 oSRS_TMS, nSrcMinTileX, nSrcMinTileY, nSrcMaxTileX,
    4802          61 :                 nSrcMaxTileY, m_convention, nDstBands, psWO->eWorkingDataType,
    4803           9 :                 psWO->padfDstNoDataReal ? &(psWO->padfDstNoDataReal[0])
    4804             :                                         : nullptr,
    4805         314 :                 m_metadata, poColorTable.get(), maxCacheTileSizePerThread);
    4806             : 
    4807             :             const CPLStringList aosCreationOptions(
    4808         122 :                 GetUpdatedCreationOptions(ovrTileMatrix));
    4809             : 
    4810         122 :             PerThreadLowerZoomResourceManager oResourceManager(oSrcDS);
    4811          61 :             std::atomic<bool> bFailure = false;
    4812          61 :             std::atomic<int> nQueuedJobs = 0;
    4813             : 
    4814          61 :             const bool bUseThreads = m_numThreads > 1 && nOvrTileCount > 1;
    4815             : 
    4816          61 :             if (bUseThreads)
    4817             :             {
    4818             :                 double dfTilesYPerJob;
    4819             :                 int nYOuterIterations;
    4820             :                 double dfTilesXPerJob;
    4821             :                 int nXOuterIterations;
    4822           6 :                 ComputeJobChunkSize(m_numThreads, nOvrTilesPerCol,
    4823             :                                     nOvrTilesPerRow, dfTilesYPerJob,
    4824             :                                     nYOuterIterations, dfTilesXPerJob,
    4825             :                                     nXOuterIterations);
    4826             : 
    4827           6 :                 CPLDebugOnly("gdal_raster_tile",
    4828             :                              "z=%d, nYOuterIterations=%d, dfTilesYPerJob=%g, "
    4829             :                              "nXOuterIterations=%d, dfTilesXPerJob=%g",
    4830             :                              iZ, nYOuterIterations, dfTilesYPerJob,
    4831             :                              nXOuterIterations, dfTilesXPerJob);
    4832             : 
    4833           6 :                 int nLastYEndIncluded = nOvrMinTileY - 1;
    4834          24 :                 for (int iYOuterIter = 0;
    4835          24 :                      bRet && iYOuterIter < nYOuterIterations &&
    4836          18 :                      nLastYEndIncluded < nOvrMaxTileY;
    4837             :                      ++iYOuterIter)
    4838             :                 {
    4839          18 :                     const int iYStart = nLastYEndIncluded + 1;
    4840             :                     const int iYEndIncluded =
    4841          18 :                         iYOuterIter + 1 == nYOuterIterations
    4842          30 :                             ? nOvrMaxTileY
    4843             :                             : std::max(
    4844             :                                   iYStart,
    4845          30 :                                   static_cast<int>(std::floor(
    4846          12 :                                       nOvrMinTileY +
    4847          12 :                                       (iYOuterIter + 1) * dfTilesYPerJob - 1)));
    4848             : 
    4849          18 :                     nLastYEndIncluded = iYEndIncluded;
    4850             : 
    4851          18 :                     int nLastXEndIncluded = nOvrMinTileX - 1;
    4852          18 :                     for (int iXOuterIter = 0;
    4853          42 :                          bRet && iXOuterIter < nXOuterIterations &&
    4854          24 :                          nLastXEndIncluded < nOvrMaxTileX;
    4855             :                          ++iXOuterIter)
    4856             :                     {
    4857          24 :                         const int iXStart = nLastXEndIncluded + 1;
    4858             :                         const int iXEndIncluded =
    4859          24 :                             iXOuterIter + 1 == nXOuterIterations
    4860          30 :                                 ? nOvrMaxTileX
    4861          30 :                                 : std::max(iXStart, static_cast<int>(std::floor(
    4862           6 :                                                         nOvrMinTileX +
    4863           6 :                                                         (iXOuterIter + 1) *
    4864             :                                                             dfTilesXPerJob -
    4865           6 :                                                         1)));
    4866             : 
    4867          24 :                         nLastXEndIncluded = iXEndIncluded;
    4868             : 
    4869          24 :                         CPLDebugOnly(
    4870             :                             "gdal_raster_tile",
    4871             :                             "Job for z=%d, y in [%d,%d] and x in [%d,%d]", iZ,
    4872             :                             iYStart, iYEndIncluded, iXStart, iXEndIncluded);
    4873             :                         auto job =
    4874          24 :                             [this, &oThreadPool, &oResourceManager, &bFailure,
    4875             :                              &bParentAskedForStop, &nCurTile, &nQueuedJobs,
    4876             :                              pszExtension, &aosCreationOptions, &aosWarpOptions,
    4877             :                              &ovrTileMatrix, iZ, iXStart, iXEndIncluded,
    4878         588 :                              iYStart, iYEndIncluded, bUserAskedForAlpha]()
    4879             :                         {
    4880             :                             CPLErrorStateBackuper oBackuper(
    4881          24 :                                 CPLQuietErrorHandler);
    4882             : 
    4883             :                             auto resources =
    4884          24 :                                 oResourceManager.AcquireResources();
    4885          24 :                             if (resources)
    4886             :                             {
    4887          72 :                                 for (int iY = iYStart; iY <= iYEndIncluded &&
    4888          24 :                                                        !bParentAskedForStop;
    4889             :                                      ++iY)
    4890             :                                 {
    4891          84 :                                     for (int iX = iXStart;
    4892         144 :                                          iX <= iXEndIncluded &&
    4893          60 :                                          !bParentAskedForStop;
    4894             :                                          ++iX)
    4895             :                                     {
    4896         120 :                                         if (!GenerateOverviewTile(
    4897          60 :                                                 *(resources->poSrcDS.get()),
    4898          60 :                                                 m_poDstDriver, m_outputFormat,
    4899             :                                                 pszExtension,
    4900             :                                                 aosCreationOptions.List(),
    4901          60 :                                                 aosWarpOptions.List(),
    4902          60 :                                                 m_overviewResampling,
    4903             :                                                 ovrTileMatrix,
    4904          60 :                                                 m_outputDirectory, iZ, iX, iY,
    4905          60 :                                                 m_convention, m_skipBlank,
    4906          60 :                                                 bUserAskedForAlpha, m_auxXML,
    4907          60 :                                                 m_resume))
    4908             :                                         {
    4909           0 :                                             oResourceManager.SetError();
    4910           0 :                                             bFailure = true;
    4911           0 :                                             --nQueuedJobs;
    4912           0 :                                             return;
    4913             :                                         }
    4914             : 
    4915          60 :                                         ++nCurTile;
    4916          60 :                                         oThreadPool.WakeUpWaitEvent();
    4917             :                                     }
    4918             :                                 }
    4919          24 :                                 oResourceManager.ReleaseResources(
    4920          24 :                                     std::move(resources));
    4921             :                             }
    4922             :                             else
    4923             :                             {
    4924           0 :                                 oResourceManager.SetError();
    4925           0 :                                 bFailure = true;
    4926             :                             }
    4927          24 :                             --nQueuedJobs;
    4928          24 :                         };
    4929             : 
    4930          24 :                         ++nQueuedJobs;
    4931          24 :                         oThreadPool.SubmitJob(std::move(job));
    4932             :                     }
    4933             :                 }
    4934             : 
    4935             :                 // Wait for completion of all jobs
    4936          84 :                 while (bRet && nQueuedJobs > 0)
    4937             :                 {
    4938          78 :                     oThreadPool.WaitEvent();
    4939          78 :                     bRet &= !bFailure;
    4940         103 :                     if (bRet && pfnProgress &&
    4941          50 :                         !pfnProgress(static_cast<double>(nCurTile) /
    4942          25 :                                          static_cast<double>(nTotalTiles),
    4943             :                                      "", pProgressData))
    4944             :                     {
    4945           0 :                         bParentAskedForStop = true;
    4946           0 :                         bRet = false;
    4947           0 :                         CPLError(CE_Failure, CPLE_UserInterrupt,
    4948             :                                  "Process interrupted by user");
    4949             :                     }
    4950             :                 }
    4951           6 :                 oThreadPool.WaitCompletion();
    4952           8 :                 bRet &= !bFailure &&
    4953           2 :                         (!pfnProgress ||
    4954           4 :                          pfnProgress(static_cast<double>(nCurTile) /
    4955           2 :                                          static_cast<double>(nTotalTiles),
    4956           6 :                                      "", pProgressData));
    4957             : 
    4958           6 :                 if (!oResourceManager.GetErrorMsg().empty())
    4959             :                 {
    4960             :                     // Re-emit error message from worker thread to main thread
    4961           0 :                     ReportError(CE_Failure, CPLE_AppDefined, "%s",
    4962           0 :                                 oResourceManager.GetErrorMsg().c_str());
    4963             :                 }
    4964             :             }
    4965             :             else
    4966             :             {
    4967             :                 // Branch for single-thread overview generation
    4968             : 
    4969         114 :                 for (int iY = nOvrMinTileY;
    4970         114 :                      bRet && !bParentAskedForStop && iY <= nOvrMaxTileY; ++iY)
    4971             :                 {
    4972         156 :                     for (int iX = nOvrMinTileX;
    4973         156 :                          bRet && !bParentAskedForStop && iX <= nOvrMaxTileX;
    4974             :                          ++iX)
    4975             :                     {
    4976          97 :                         bRet = GenerateOverviewTile(
    4977          97 :                             oSrcDS, m_poDstDriver, m_outputFormat, pszExtension,
    4978          97 :                             aosCreationOptions.List(), aosWarpOptions.List(),
    4979          97 :                             m_overviewResampling, ovrTileMatrix,
    4980          97 :                             m_outputDirectory, iZ, iX, iY, m_convention,
    4981          97 :                             m_skipBlank, bUserAskedForAlpha, m_auxXML,
    4982          97 :                             m_resume);
    4983             : 
    4984          97 :                         if (m_spawned)
    4985             :                         {
    4986          40 :                             if (bEmitSpuriousCharsOnStdout)
    4987          20 :                                 fwrite(&PROGRESS_MARKER[0], 1, 1, stdout);
    4988          40 :                             fwrite(PROGRESS_MARKER, sizeof(PROGRESS_MARKER), 1,
    4989             :                                    stdout);
    4990          40 :                             fflush(stdout);
    4991             :                         }
    4992             : #ifdef FORK_ALLOWED
    4993          57 :                         else if (m_forked)
    4994             :                         {
    4995           0 :                             CPLPipeWrite(pipeOut, PROGRESS_MARKER,
    4996             :                                          sizeof(PROGRESS_MARKER));
    4997             :                         }
    4998             : #endif
    4999             :                         else
    5000             :                         {
    5001          57 :                             ++nCurTile;
    5002          59 :                             if (bRet && pfnProgress &&
    5003           2 :                                 !pfnProgress(
    5004           2 :                                     static_cast<double>(nCurTile) /
    5005           2 :                                         static_cast<double>(nTotalTiles),
    5006             :                                     "", pProgressData))
    5007             :                             {
    5008           0 :                                 bRet = false;
    5009           0 :                                 CPLError(CE_Failure, CPLE_UserInterrupt,
    5010             :                                          "Process interrupted by user");
    5011             :                             }
    5012             :                         }
    5013             :                     }
    5014             :                 }
    5015             :             }
    5016             :         }
    5017             : 
    5018          69 :         if (m_kml && bRet)
    5019             :         {
    5020           2 :             for (int iY = nOvrMinTileY; bRet && iY <= nOvrMaxTileY; ++iY)
    5021             :             {
    5022           2 :                 for (int iX = nOvrMinTileX; bRet && iX <= nOvrMaxTileX; ++iX)
    5023             :                 {
    5024             :                     int nFileY =
    5025           1 :                         GetFileY(iY, poTMS->tileMatrixList()[iZ], m_convention);
    5026             :                     std::string osFilename =
    5027             :                         CPLFormFilenameSafe(m_outputDirectory.c_str(),
    5028           2 :                                             CPLSPrintf("%d", iZ), nullptr);
    5029           2 :                     osFilename = CPLFormFilenameSafe(
    5030           1 :                         osFilename.c_str(), CPLSPrintf("%d", iX), nullptr);
    5031           2 :                     osFilename = CPLFormFilenameSafe(
    5032             :                         osFilename.c_str(),
    5033           1 :                         CPLSPrintf("%d.%s", nFileY, pszExtension), nullptr);
    5034           1 :                     if (VSIStatL(osFilename.c_str(), &sStat) == 0)
    5035             :                     {
    5036           1 :                         std::vector<TileCoordinates> children;
    5037             : 
    5038           3 :                         for (int iChildY = 0; iChildY <= 1; ++iChildY)
    5039             :                         {
    5040           6 :                             for (int iChildX = 0; iChildX <= 1; ++iChildX)
    5041             :                             {
    5042             :                                 nFileY =
    5043           4 :                                     GetFileY(iY * 2 + iChildY,
    5044           4 :                                              poTMS->tileMatrixList()[iZ + 1],
    5045           4 :                                              m_convention);
    5046           8 :                                 osFilename = CPLFormFilenameSafe(
    5047             :                                     m_outputDirectory.c_str(),
    5048           4 :                                     CPLSPrintf("%d", iZ + 1), nullptr);
    5049           8 :                                 osFilename = CPLFormFilenameSafe(
    5050             :                                     osFilename.c_str(),
    5051           4 :                                     CPLSPrintf("%d", iX * 2 + iChildX),
    5052           4 :                                     nullptr);
    5053           8 :                                 osFilename = CPLFormFilenameSafe(
    5054             :                                     osFilename.c_str(),
    5055             :                                     CPLSPrintf("%d.%s", nFileY, pszExtension),
    5056           4 :                                     nullptr);
    5057           4 :                                 if (VSIStatL(osFilename.c_str(), &sStat) == 0)
    5058             :                                 {
    5059           1 :                                     TileCoordinates tc;
    5060           1 :                                     tc.nTileX = iX * 2 + iChildX;
    5061           1 :                                     tc.nTileY = iY * 2 + iChildY;
    5062           1 :                                     tc.nTileZ = iZ + 1;
    5063           1 :                                     children.push_back(std::move(tc));
    5064             :                                 }
    5065             :                             }
    5066             :                         }
    5067             : 
    5068           2 :                         GenerateKML(m_outputDirectory, m_title, iX, iY, iZ,
    5069           1 :                                     kmlTileSize, pszExtension, m_url,
    5070           1 :                                     poTMS.get(), bInvertAxisTMS, m_convention,
    5071             :                                     poCTToWGS84.get(), children);
    5072             :                     }
    5073             :                 }
    5074             :             }
    5075             :         }
    5076             :     }
    5077             : 
    5078         786 :     const auto IsWebViewerEnabled = [this](const char *name)
    5079             :     {
    5080         262 :         return std::find_if(m_webviewers.begin(), m_webviewers.end(),
    5081         438 :                             [name](const std::string &s) {
    5082         266 :                                 return s == "all" || s == name;
    5083         262 :                             }) != m_webviewers.end();
    5084          95 :     };
    5085             : 
    5086         149 :     if (m_ovrZoomLevel < 0 && bRet &&
    5087         244 :         poTMS->identifier() == "GoogleMapsCompatible" &&
    5088          52 :         IsWebViewerEnabled("leaflet"))
    5089             :     {
    5090          19 :         double dfSouthLat = -90;
    5091          19 :         double dfWestLon = -180;
    5092          19 :         double dfNorthLat = 90;
    5093          19 :         double dfEastLon = 180;
    5094             : 
    5095          19 :         if (poCTToWGS84)
    5096             :         {
    5097          19 :             poCTToWGS84->TransformBounds(
    5098             :                 adfExtent[0], adfExtent[1], adfExtent[2], adfExtent[3],
    5099          19 :                 &dfWestLon, &dfSouthLat, &dfEastLon, &dfNorthLat, 21);
    5100             :         }
    5101             : 
    5102          19 :         GenerateLeaflet(m_outputDirectory, m_title, dfSouthLat, dfWestLon,
    5103             :                         dfNorthLat, dfEastLon, m_minZoomLevel, m_maxZoomLevel,
    5104          19 :                         tileMatrix.mTileWidth, pszExtension, m_url, m_copyright,
    5105          19 :                         m_convention == "xyz");
    5106             :     }
    5107             : 
    5108          95 :     if (m_ovrZoomLevel < 0 && bRet && IsWebViewerEnabled("openlayers"))
    5109             :     {
    5110          54 :         GenerateOpenLayers(
    5111          27 :             m_outputDirectory, m_title, adfExtent[0], adfExtent[1],
    5112             :             adfExtent[2], adfExtent[3], m_minZoomLevel, m_maxZoomLevel,
    5113          27 :             tileMatrix.mTileWidth, pszExtension, m_url, m_copyright,
    5114          27 :             *(poTMS.get()), bInvertAxisTMS, oSRS_TMS, m_convention == "xyz");
    5115             :     }
    5116             : 
    5117         104 :     if (m_ovrZoomLevel < 0 && bRet && IsWebViewerEnabled("mapml") &&
    5118         199 :         poTMS->identifier() != "raster" && m_convention == "xyz")
    5119             :     {
    5120          20 :         GenerateMapML(m_outputDirectory, m_mapmlTemplate, m_title, nMinTileX,
    5121             :                       nMinTileY, nMaxTileX, nMaxTileY, m_minZoomLevel,
    5122          20 :                       m_maxZoomLevel, pszExtension, m_url, m_copyright,
    5123          20 :                       *(poTMS.get()));
    5124             :     }
    5125             : 
    5126         120 :     if (m_ovrZoomLevel < 0 && bRet && IsWebViewerEnabled("stac") &&
    5127          25 :         m_convention == "xyz")
    5128             :     {
    5129          24 :         OGRCoordinateTransformation *poCT = poCTToWGS84.get();
    5130           0 :         std::unique_ptr<OGRCoordinateTransformation> poCTToLongLat;
    5131          24 :         if (!poCTToWGS84)
    5132             :         {
    5133           2 :             CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
    5134           2 :             OGRSpatialReference oLongLat;
    5135           1 :             oLongLat.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    5136           1 :             oLongLat.CopyGeogCSFrom(&oSRS_TMS);
    5137           1 :             poCTToLongLat.reset(
    5138             :                 OGRCreateCoordinateTransformation(&oSRS_TMS, &oLongLat));
    5139           1 :             poCT = poCTToLongLat.get();
    5140             :         }
    5141             : 
    5142          24 :         double dfSouthLat = -90;
    5143          24 :         double dfWestLon = -180;
    5144          24 :         double dfNorthLat = 90;
    5145          24 :         double dfEastLon = 180;
    5146          24 :         if (poCT)
    5147             :         {
    5148          23 :             poCT->TransformBounds(adfExtent[0], adfExtent[1], adfExtent[2],
    5149             :                                   adfExtent[3], &dfWestLon, &dfSouthLat,
    5150          23 :                                   &dfEastLon, &dfNorthLat, 21);
    5151             :         }
    5152             : 
    5153          48 :         GenerateSTAC(m_outputDirectory, m_title, dfWestLon, dfSouthLat,
    5154          24 :                      dfEastLon, dfNorthLat, m_metadata, aoBandMetadata,
    5155             :                      m_minZoomLevel, m_maxZoomLevel, pszExtension,
    5156          24 :                      m_outputFormat, m_url, m_copyright, oSRS_TMS,
    5157          24 :                      *(poTMS.get()), bInvertAxisTMS, m_tileSize, adfExtent,
    5158          24 :                      m_dataset);
    5159             :     }
    5160             : 
    5161          95 :     if (m_ovrZoomLevel < 0 && bRet && m_kml)
    5162             :     {
    5163           4 :         std::vector<TileCoordinates> children;
    5164             : 
    5165           2 :         auto ovrTileMatrix = tileMatrixList[m_minZoomLevel];
    5166           2 :         int nOvrMinTileX = 0;
    5167           2 :         int nOvrMinTileY = 0;
    5168           2 :         int nOvrMaxTileX = 0;
    5169           2 :         int nOvrMaxTileY = 0;
    5170           2 :         CPL_IGNORE_RET_VAL(
    5171           2 :             GetTileIndices(ovrTileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
    5172             :                            nOvrMinTileX, nOvrMinTileY, nOvrMaxTileX,
    5173           2 :                            nOvrMaxTileY, m_noIntersectionIsOK, bIntersects));
    5174             : 
    5175           4 :         for (int iY = nOvrMinTileY; bRet && iY <= nOvrMaxTileY; ++iY)
    5176             :         {
    5177           4 :             for (int iX = nOvrMinTileX; bRet && iX <= nOvrMaxTileX; ++iX)
    5178             :             {
    5179           2 :                 int nFileY = GetFileY(
    5180           2 :                     iY, poTMS->tileMatrixList()[m_minZoomLevel], m_convention);
    5181             :                 std::string osFilename = CPLFormFilenameSafe(
    5182             :                     m_outputDirectory.c_str(), CPLSPrintf("%d", m_minZoomLevel),
    5183           4 :                     nullptr);
    5184           4 :                 osFilename = CPLFormFilenameSafe(osFilename.c_str(),
    5185           2 :                                                  CPLSPrintf("%d", iX), nullptr);
    5186           4 :                 osFilename = CPLFormFilenameSafe(
    5187             :                     osFilename.c_str(),
    5188           2 :                     CPLSPrintf("%d.%s", nFileY, pszExtension), nullptr);
    5189           2 :                 if (VSIStatL(osFilename.c_str(), &sStat) == 0)
    5190             :                 {
    5191           2 :                     TileCoordinates tc;
    5192           2 :                     tc.nTileX = iX;
    5193           2 :                     tc.nTileY = iY;
    5194           2 :                     tc.nTileZ = m_minZoomLevel;
    5195           2 :                     children.push_back(std::move(tc));
    5196             :                 }
    5197             :             }
    5198             :         }
    5199           4 :         GenerateKML(m_outputDirectory, m_title, -1, -1, -1, kmlTileSize,
    5200           2 :                     pszExtension, m_url, poTMS.get(), bInvertAxisTMS,
    5201           2 :                     m_convention, poCTToWGS84.get(), children);
    5202             :     }
    5203             : 
    5204          95 :     if (!bRet && CPLGetLastErrorType() == CE_None)
    5205             :     {
    5206             :         // If that happens, this is a programming error
    5207           0 :         ReportError(CE_Failure, CPLE_AppDefined,
    5208             :                     "Bug: process failed without returning an error message");
    5209             :     }
    5210             : 
    5211          95 :     if (m_spawned)
    5212             :     {
    5213          26 :         fwrite(END_MARKER, sizeof(END_MARKER), 1, stdout);
    5214          26 :         fflush(stdout);
    5215          26 :         fclose(stdout);
    5216          26 :         threadWaitForParentStop.join();
    5217             :     }
    5218             : #ifdef FORK_ALLOWED
    5219          69 :     else if (m_forked)
    5220             :     {
    5221           0 :         CPLPipeWrite(pipeOut, END_MARKER, sizeof(END_MARKER));
    5222           0 :         threadWaitForParentStop.join();
    5223             :     }
    5224             : #endif
    5225             : 
    5226          95 :     return bRet;
    5227             : }
    5228             : 
    5229             : //! @endcond

Generated by: LCOV version 1.14