LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_tile.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2920 3133 93.2 %
Date: 2026-09-11 05:09:32 Functions: 79 86 91.9 %

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

Generated by: LCOV version 1.14