LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_tile.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2845 3056 93.1 %
Date: 2025-09-10 17:48:50 Functions: 76 83 91.6 %

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

Generated by: LCOV version 1.14