LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_tile.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2917 3130 93.2 %
Date: 2026-06-03 12:46:18 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          15 : static int GetThresholdMinThreadsForSpawn()
      97             : {
      98             :     // Minimum number of threads for automatic switch to spawning
      99          15 :     constexpr int THRESHOLD_MIN_THREADS_FOR_SPAWN = 8;
     100             : 
     101             :     // Config option for test only
     102          15 :     return std::max(1, atoi(CPLGetConfigOption(
     103             :                            "GDAL_THRESHOLD_MIN_THREADS_FOR_SPAWN",
     104          15 :                            CPLSPrintf("%d", THRESHOLD_MIN_THREADS_FOR_SPAWN))));
     105             : }
     106             : 
     107             : /************************************************************************/
     108             : /*                     GetThresholdMinTilesPerJob()                     */
     109             : /************************************************************************/
     110             : 
     111         319 : static int GetThresholdMinTilesPerJob()
     112             : {
     113             :     // Minimum number of tiles per job to decide for automatic switch to spawning
     114         319 :     constexpr int THRESHOLD_TILES_PER_JOB = 100;
     115             : 
     116             :     // Config option for test only
     117             :     return std::max(
     118         319 :         1, atoi(CPLGetConfigOption("GDAL_THRESHOLD_MIN_TILES_PER_JOB",
     119         319 :                                    CPLSPrintf("%d", THRESHOLD_TILES_PER_JOB))));
     120             : }
     121             : 
     122             : /************************************************************************/
     123             : /*          GDALRasterTileAlgorithm::GDALRasterTileAlgorithm()          */
     124             : /************************************************************************/
     125             : 
     126         324 : GDALRasterTileAlgorithm::GDALRasterTileAlgorithm(bool standaloneStep)
     127             :     : GDALRasterPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
     128           0 :                                       ConstructorOptions()
     129         324 :                                           .SetStandaloneStep(standaloneStep)
     130         324 :                                           .SetInputDatasetMaxCount(1)
     131         324 :                                           .SetAddDefaultArguments(false)
     132         648 :                                           .SetInputDatasetAlias("dataset"))
     133             : {
     134         324 :     if (standaloneStep)
     135         256 :         AddProgressArg();
     136             :     AddArg("spawned", 0, _("Whether this is a spawned worker"),
     137         648 :            &m_spawned)
     138         324 :         .SetHidden();  // Used in spawn mode
     139             : #ifdef FORK_ALLOWED
     140             :     AddArg("forked", 0, _("Whether this is a forked worker"),
     141         648 :            &m_forked)
     142         324 :         .SetHidden();  // Used in forked mode
     143             : #else
     144             :     CPL_IGNORE_RET_VAL(m_forked);
     145             : #endif
     146         648 :     AddArg("config-options-in-stdin", 0, _(""), &m_dummy)
     147         324 :         .SetHidden();  // Used in spawn mode
     148             :     AddArg("ovr-zoom-level", 0, _("Overview zoom level to compute"),
     149         648 :            &m_ovrZoomLevel)
     150         324 :         .SetMinValueIncluded(0)
     151         324 :         .SetHidden();  // Used in spawn mode
     152         648 :     AddArg("ovr-min-x", 0, _("Minimum tile X coordinate"), &m_minOvrTileX)
     153         324 :         .SetMinValueIncluded(0)
     154         324 :         .SetHidden();  // Used in spawn mode
     155         648 :     AddArg("ovr-max-x", 0, _("Maximum tile X coordinate"), &m_maxOvrTileX)
     156         324 :         .SetMinValueIncluded(0)
     157         324 :         .SetHidden();  // Used in spawn mode
     158         648 :     AddArg("ovr-min-y", 0, _("Minimum tile Y coordinate"), &m_minOvrTileY)
     159         324 :         .SetMinValueIncluded(0)
     160         324 :         .SetHidden();  // Used in spawn mode
     161         648 :     AddArg("ovr-max-y", 0, _("Maximum tile Y coordinate"), &m_maxOvrTileY)
     162         324 :         .SetMinValueIncluded(0)
     163         324 :         .SetHidden();  // Used in spawn mode
     164             : 
     165         324 :     if (standaloneStep)
     166             :     {
     167         256 :         AddRasterInputArgs(/* openForMixedRasterVector = */ false,
     168             :                            /* hiddenForCLI = */ false);
     169             :     }
     170             :     else
     171             :     {
     172          68 :         AddRasterHiddenInputDatasetArg();
     173             :     }
     174             : 
     175         324 :     m_format = "PNG";
     176         324 :     AddOutputFormatArg(&m_format)
     177         324 :         .SetDefault(m_format)
     178             :         .AddMetadataItem(
     179             :             GAAMDI_REQUIRED_CAPABILITIES,
     180        1620 :             {GDAL_DCAP_RASTER, GDAL_DCAP_CREATECOPY, GDAL_DMD_EXTENSIONS})
     181         648 :         .AddMetadataItem(GAAMDI_VRT_COMPATIBLE, {"false"});
     182         324 :     AddCreationOptionsArg(&m_creationOptions);
     183             : 
     184         648 :     AddArg(GDAL_ARG_NAME_OUTPUT, 'o', _("Output directory"), &m_outputDir)
     185         324 :         .SetRequired()
     186         324 :         .SetIsInput()
     187         324 :         .SetMinCharCount(1)
     188         324 :         .SetPositional();
     189             : 
     190        1296 :     std::vector<std::string> tilingSchemes{"raster"};
     191        3240 :     for (const std::string &scheme :
     192        6804 :          gdal::TileMatrixSet::listPredefinedTileMatrixSets(/* hidden = */ true))
     193             :     {
     194        6480 :         auto poTMS = gdal::TileMatrixSet::parse(scheme.c_str());
     195        6480 :         OGRSpatialReference oSRS_TMS;
     196        6480 :         if (poTMS && !poTMS->hasVariableMatrixWidth() &&
     197        3240 :             oSRS_TMS.SetFromUserInput(poTMS->crs().c_str()) == OGRERR_NONE)
     198             :         {
     199        3240 :             std::string identifier = scheme == "GoogleMapsCompatible"
     200             :                                          ? "WebMercatorQuad"
     201        6480 :                                          : poTMS->identifier();
     202        3240 :             m_mapTileMatrixIdentifierToScheme[identifier] = scheme;
     203        3240 :             tilingSchemes.push_back(std::move(identifier));
     204             :         }
     205             :     }
     206         648 :     AddArg("tiling-scheme", 0, _("Tiling scheme"), &m_tilingScheme)
     207         324 :         .SetDefault("WebMercatorQuad")
     208         324 :         .SetChoices(tilingSchemes)
     209             :         .SetHiddenChoices(
     210             :             "GoogleMapsCompatible",  // equivalent of WebMercatorQuad
     211             :             "mercator",              // gdal2tiles equivalent of WebMercatorQuad
     212             :             "GlobalGeodeticOriginLat270"  // gdal2tiles geodetic without --tmscompatible
     213         324 :         );
     214             : 
     215         648 :     AddArg("min-zoom", 0, _("Minimum zoom level"), &m_minZoomLevel)
     216         324 :         .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         648 :            &m_minZoomLevelSingleTile)
     222         324 :         .SetHidden();
     223             : 
     224         648 :     AddArg("max-zoom", 0, _("Maximum zoom level"), &m_maxZoomLevel)
     225         324 :         .SetMinValueIncluded(0);
     226             : 
     227         648 :     AddArg("min-x", 0, _("Minimum tile X coordinate"), &m_minTileX)
     228         324 :         .SetMinValueIncluded(0);
     229         648 :     AddArg("max-x", 0, _("Maximum tile X coordinate"), &m_maxTileX)
     230         324 :         .SetMinValueIncluded(0);
     231         648 :     AddArg("min-y", 0, _("Minimum tile Y coordinate"), &m_minTileY)
     232         324 :         .SetMinValueIncluded(0);
     233         648 :     AddArg("max-y", 0, _("Maximum tile Y coordinate"), &m_maxTileY)
     234         324 :         .SetMinValueIncluded(0);
     235             :     AddArg("no-intersection-ok", 0,
     236             :            _("Whether dataset extent not intersecting tile matrix is only a "
     237             :              "warning"),
     238         324 :            &m_noIntersectionIsOK);
     239             : 
     240             :     AddArg("resampling", 'r', _("Resampling method for max zoom"),
     241         648 :            &m_resampling)
     242             :         .SetChoices("nearest", "bilinear", "cubic", "cubicspline", "lanczos",
     243             :                     "average", "rms", "mode", "min", "max", "med", "q1", "q3",
     244         324 :                     "sum")
     245         324 :         .SetDefault("cubic")
     246         324 :         .SetHiddenChoices("near");
     247             :     AddArg("overview-resampling", 0, _("Resampling method for overviews"),
     248         648 :            &m_overviewResampling)
     249             :         .SetChoices("nearest", "bilinear", "cubic", "cubicspline", "lanczos",
     250             :                     "average", "rms", "mode", "min", "max", "med", "q1", "q3",
     251         324 :                     "sum")
     252         324 :         .SetHiddenChoices("near");
     253             : 
     254             :     AddArg("convention", 0,
     255             :            _("Tile numbering convention: xyz (from top) or tms (from bottom)"),
     256         648 :            &m_convention)
     257         324 :         .SetDefault(m_convention)
     258         324 :         .SetChoices("xyz", "tms");
     259         648 :     AddArg("tile-size", 0, _("Override default tile size"), &m_tileSize)
     260         324 :         .SetMinValueIncluded(64)
     261         324 :         .SetMaxValueIncluded(32768);
     262             :     AddArg("add-alpha", 0, _("Whether to force adding an alpha channel"),
     263         648 :            &m_addalpha)
     264         324 :         .SetMutualExclusionGroup("alpha");
     265             :     AddArg("no-alpha", 0, _("Whether to disable adding an alpha channel"),
     266         648 :            &m_noalpha)
     267         324 :         .SetMutualExclusionGroup("alpha");
     268             :     auto &dstNoDataArg =
     269         648 :         AddArg("output-nodata", 0, _("Output nodata value"), &m_dstNoData)
     270         324 :             .AddHiddenAlias("dst-nodata");
     271         324 :     AddArg("skip-blank", 0, _("Do not generate blank tiles"), &m_skipBlank);
     272             : 
     273             :     {
     274             :         auto &arg = AddArg("metadata", 0,
     275         648 :                            _("Add metadata item to output tiles"), &m_metadata)
     276         648 :                         .SetMetaVar("<KEY>=<VALUE>")
     277         324 :                         .SetPackedValuesAllowed(false);
     278          46 :         arg.AddValidationAction([this, &arg]()
     279         370 :                                 { return ParseAndValidateKeyValue(arg); });
     280         324 :         arg.AddHiddenAlias("mo");
     281             :     }
     282             :     AddArg("copy-src-metadata", 0,
     283             :            _("Whether to copy metadata from source dataset"),
     284         324 :            &m_copySrcMetadata);
     285             : 
     286             :     AddArg("aux-xml", 0, _("Generate .aux.xml sidecar files when needed"),
     287         324 :            &m_auxXML);
     288         324 :     AddArg("kml", 0, _("Generate KML files"), &m_kml);
     289         324 :     AddArg("resume", 0, _("Generate only missing files"), &m_resume);
     290             : 
     291         324 :     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         648 :            &m_parallelMethod)
     300             :         .SetChoices("thread", "spawn"
     301             : #ifdef FORK_ALLOWED
     302             :                     ,
     303             :                     "fork"
     304             : #endif
     305         324 :         );
     306             : 
     307         324 :     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         648 :                &m_excludedValues)
     314         324 :             .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         648 :             &m_excludedValuesPctThreshold)
     322         324 :             .SetDefault(m_excludedValuesPctThreshold)
     323         324 :             .SetMinValueIncluded(0)
     324         324 :             .SetMaxValueIncluded(100)
     325         324 :             .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         648 :             &m_nodataValuesPctThreshold)
     333         324 :             .SetDefault(m_nodataValuesPctThreshold)
     334         324 :             .SetMinValueIncluded(0)
     335         324 :             .SetMaxValueIncluded(100)
     336         324 :             .SetCategory(ADVANCED_RESAMPLING_CATEGORY);
     337             : 
     338         324 :     constexpr const char *PUBLICATION_CATEGORY = "Publication";
     339         648 :     AddArg("webviewer", 0, _("Web viewer to generate"), &m_webviewers)
     340         324 :         .SetDefault("all")
     341         324 :         .SetChoices("none", "all", "leaflet", "openlayers", "mapml", "stac")
     342         324 :         .SetCategory(PUBLICATION_CATEGORY);
     343             :     AddArg("url", 0,
     344             :            _("URL address where the generated tiles are going to be published"),
     345         648 :            &m_url)
     346         324 :         .SetCategory(PUBLICATION_CATEGORY);
     347         648 :     AddArg("title", 0, _("Title of the map"), &m_title)
     348         324 :         .SetCategory(PUBLICATION_CATEGORY);
     349         648 :     AddArg("copyright", 0, _("Copyright for the map"), &m_copyright)
     350         324 :         .SetCategory(PUBLICATION_CATEGORY);
     351             :     AddArg("mapml-template", 0,
     352             :            _("Filename of a template mapml file where variables will be "
     353             :              "substituted"),
     354         648 :            &m_mapmlTemplate)
     355         324 :         .SetMinCharCount(1)
     356         324 :         .SetCategory(PUBLICATION_CATEGORY);
     357             : 
     358         324 :     AddValidationAction(
     359         293 :         [this, &dstNoDataArg, &excludedValuesArg,
     360        1912 :          &excludedValuesPctThresholdArg, &nodataValuesPctThresholdArg]()
     361             :         {
     362         293 :             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         292 :             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         291 :             if (m_minZoomLevel >= 0 && m_maxZoomLevel >= 0 &&
     377         113 :                 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         290 :             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         861 :             for (const auto *arg :
     393             :                  {&excludedValuesArg, &excludedValuesPctThresholdArg,
     394        1150 :                   &nodataValuesPctThresholdArg})
     395             :             {
     396         863 :                 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         863 :                 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         287 :             return true;
     416             :         });
     417         324 : }
     418             : 
     419             : /************************************************************************/
     420             : /*                      ~GDALRasterTileAlgorithm()                      */
     421             : /************************************************************************/
     422             : 
     423         368 : GDALRasterTileAlgorithm::~GDALRasterTileAlgorithm()
     424             : {
     425         324 :     if (m_poSrcOvrDS)
     426             :     {
     427           0 :         m_poSrcOvrDS->ReleaseRef();
     428             :     }
     429         368 : }
     430             : 
     431             : /************************************************************************/
     432             : /*                           GetTileIndices()                           */
     433             : /************************************************************************/
     434             : 
     435         771 : 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         771 :     if (tileSize > 0)
     443             :     {
     444         192 :         tileMatrix.mResX *=
     445         192 :             static_cast<double>(tileMatrix.mTileWidth) / tileSize;
     446         192 :         tileMatrix.mResY *=
     447         192 :             static_cast<double>(tileMatrix.mTileHeight) / tileSize;
     448         192 :         tileMatrix.mTileWidth = tileSize;
     449         192 :         tileMatrix.mTileHeight = tileSize;
     450             :     }
     451             : 
     452         771 :     if (bInvertAxisTMS)
     453           2 :         std::swap(tileMatrix.mTopLeftX, tileMatrix.mTopLeftY);
     454             : 
     455         771 :     const double dfTileWidth = tileMatrix.mResX * tileMatrix.mTileWidth;
     456         771 :     const double dfTileHeight = tileMatrix.mResY * tileMatrix.mTileHeight;
     457             : 
     458         771 :     constexpr double EPSILON = 1e-3;
     459         771 :     const double dfMinTileX =
     460         771 :         (adfExtent[0] - tileMatrix.mTopLeftX) / dfTileWidth;
     461         771 :     nMinTileX = static_cast<int>(
     462        1542 :         std::clamp(std::floor(dfMinTileX + EPSILON), 0.0,
     463         771 :                    static_cast<double>(tileMatrix.mMatrixWidth - 1)));
     464         771 :     const double dfMinTileY =
     465         771 :         (tileMatrix.mTopLeftY - adfExtent[3]) / dfTileHeight;
     466         771 :     nMinTileY = static_cast<int>(
     467        1542 :         std::clamp(std::floor(dfMinTileY + EPSILON), 0.0,
     468         771 :                    static_cast<double>(tileMatrix.mMatrixHeight - 1)));
     469         771 :     const double dfMaxTileX =
     470         771 :         (adfExtent[2] - tileMatrix.mTopLeftX) / dfTileWidth;
     471         771 :     nMaxTileX = static_cast<int>(
     472        1542 :         std::clamp(std::floor(dfMaxTileX + EPSILON), 0.0,
     473         771 :                    static_cast<double>(tileMatrix.mMatrixWidth - 1)));
     474         771 :     const double dfMaxTileY =
     475         771 :         (tileMatrix.mTopLeftY - adfExtent[1]) / dfTileHeight;
     476         771 :     nMaxTileY = static_cast<int>(
     477        1542 :         std::clamp(std::floor(dfMaxTileY + EPSILON), 0.0,
     478         771 :                    static_cast<double>(tileMatrix.mMatrixHeight - 1)));
     479             : 
     480         771 :     bIntersects = (dfMinTileX <= tileMatrix.mMatrixWidth && dfMaxTileX >= 0 &&
     481        1542 :                    dfMinTileY <= tileMatrix.mMatrixHeight && dfMaxTileY >= 0);
     482         771 :     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         769 :     if (checkRasterOverflow)
     494             :     {
     495         573 :         if (nMaxTileX - nMinTileX + 1 > INT_MAX / tileMatrix.mTileWidth ||
     496         573 :             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         769 :     return true;
     503             : }
     504             : 
     505             : /************************************************************************/
     506             : /*                              GetFileY()                              */
     507             : /************************************************************************/
     508             : 
     509        6611 : static int GetFileY(int iY, const gdal::TileMatrixSet::TileMatrix &tileMatrix,
     510             :                     const std::string &convention)
     511             : {
     512        6611 :     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      560237 : inline GByte PNG_SUB(int nVal, int nValPrev)
     522             : {
     523      560237 :     return static_cast<GByte>((nVal - nValPrev) & 0xff);
     524             : }
     525             : 
     526   143683000 : inline GByte PNG_AVG(int nVal, int nValPrev, int nValUp)
     527             : {
     528   143683000 :     return static_cast<GByte>((nVal - (nValPrev + nValUp) / 2) & 0xff);
     529             : }
     530             : 
     531     6100580 : inline GByte PNG_PAETH(int nVal, int nValPrev, int nValUp, int nValUpPrev)
     532             : {
     533     6100580 :     const int p = nValPrev + nValUp - nValUpPrev;
     534     6100580 :     const int pa = std::abs(p - nValPrev);
     535     6100580 :     const int pb = std::abs(p - nValUp);
     536     6100580 :     const int pc = std::abs(p - nValUpPrev);
     537     6100580 :     if (pa <= pb && pa <= pc)
     538     3903530 :         return static_cast<GByte>((nVal - nValPrev) & 0xff);
     539     2197050 :     else if (pb <= pc)
     540     1932180 :         return static_cast<GByte>((nVal - nValUp) & 0xff);
     541             :     else
     542      264864 :         return static_cast<GByte>((nVal - nValUpPrev) & 0xff);
     543             : }
     544             : 
     545             : #ifdef USE_PAETH_SSE2
     546             : 
     547    50244000 : 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    50244000 :     __m128i mask = _mm_srai_epi16(x, 15);
     553   100488000 :     return _mm_sub_epi16(_mm_xor_si128(x, mask), mask);
     554             : #endif
     555             : }
     556             : 
     557    50244000 : 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   150732000 :     return _mm_or_si128(_mm_andnot_si128(mask, a), _mm_and_si128(mask, b));
     563             : #endif
     564             : }
     565             : 
     566     8374010 : static inline __m128i PNG_PAETH_SSE2(__m128i up_prev, __m128i up, __m128i prev,
     567             :                                      __m128i cur, __m128i &cost)
     568             : {
     569    16748000 :     auto cur_lo = _mm_unpacklo_epi8(cur, _mm_setzero_si128());
     570    16748000 :     auto prev_lo = _mm_unpacklo_epi8(prev, _mm_setzero_si128());
     571    16748000 :     auto up_lo = _mm_unpacklo_epi8(up, _mm_setzero_si128());
     572    16748000 :     auto up_prev_lo = _mm_unpacklo_epi8(up_prev, _mm_setzero_si128());
     573    16748000 :     auto cur_hi = _mm_unpackhi_epi8(cur, _mm_setzero_si128());
     574    16748000 :     auto prev_hi = _mm_unpackhi_epi8(prev, _mm_setzero_si128());
     575    16748000 :     auto up_hi = _mm_unpackhi_epi8(up, _mm_setzero_si128());
     576    16748000 :     auto up_prev_hi = _mm_unpackhi_epi8(up_prev, _mm_setzero_si128());
     577             : 
     578     8374010 :     auto pa_lo = _mm_sub_epi16(up_lo, up_prev_lo);
     579     8374010 :     auto pb_lo = _mm_sub_epi16(prev_lo, up_prev_lo);
     580     8374010 :     auto pc_lo = _mm_add_epi16(pa_lo, pb_lo);
     581     8374010 :     pa_lo = abs_epi16(pa_lo);
     582     8374010 :     pb_lo = abs_epi16(pb_lo);
     583     8374010 :     pc_lo = abs_epi16(pc_lo);
     584    16748000 :     auto min_lo = _mm_min_epi16(_mm_min_epi16(pa_lo, pb_lo), pc_lo);
     585             : 
     586     8374010 :     auto res_lo = blendv(up_prev_lo, up_lo, _mm_cmpeq_epi16(min_lo, pb_lo));
     587     8374010 :     res_lo = blendv(res_lo, prev_lo, _mm_cmpeq_epi16(min_lo, pa_lo));
     588    25122000 :     res_lo = _mm_and_si128(_mm_sub_epi16(cur_lo, res_lo), _mm_set1_epi16(0xFF));
     589             : 
     590    33496000 :     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     8374010 :     auto pa_hi = _mm_sub_epi16(up_hi, up_prev_hi);
     594     8374010 :     auto pb_hi = _mm_sub_epi16(prev_hi, up_prev_hi);
     595     8374010 :     auto pc_hi = _mm_add_epi16(pa_hi, pb_hi);
     596     8374010 :     pa_hi = abs_epi16(pa_hi);
     597     8374010 :     pb_hi = abs_epi16(pb_hi);
     598     8374010 :     pc_hi = abs_epi16(pc_hi);
     599    16748000 :     auto min_hi = _mm_min_epi16(_mm_min_epi16(pa_hi, pb_hi), pc_hi);
     600             : 
     601     8374010 :     auto res_hi = blendv(up_prev_hi, up_hi, _mm_cmpeq_epi16(min_hi, pb_hi));
     602     8374010 :     res_hi = blendv(res_hi, prev_hi, _mm_cmpeq_epi16(min_hi, pa_hi));
     603    25122000 :     res_hi = _mm_and_si128(_mm_sub_epi16(cur_hi, res_hi), _mm_set1_epi16(0xFF));
     604             : 
     605    33496000 :     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     8374010 :     cost_lo = _mm_add_epi16(cost_lo, cost_hi);
     609             : 
     610     8374010 :     cost =
     611    25122000 :         _mm_add_epi32(cost, _mm_unpacklo_epi16(cost_lo, _mm_setzero_si128()));
     612     8374010 :     cost =
     613    25122000 :         _mm_add_epi32(cost, _mm_unpackhi_epi16(cost_lo, _mm_setzero_si128()));
     614             : 
     615     8374010 :     return _mm_packus_epi16(res_lo, res_hi);
     616             : }
     617             : 
     618      182071 : static int RunPaeth(const GByte *srcBuffer, int nBands,
     619             :                     int nSrcBufferBandStride, GByte *outBuffer, int W,
     620             :                     int &costPaeth)
     621             : {
     622      182071 :     __m128i xmm_cost = _mm_setzero_si128();
     623      182071 :     int i = 1;
     624      739248 :     for (int k = 0; k < nBands; ++k)
     625             :     {
     626     8931180 :         for (i = 1; i + 15 < W; i += 16)
     627             :         {
     628     8374010 :             auto up_prev = _mm_loadu_si128(
     629     8374010 :                 reinterpret_cast<const __m128i *>(srcBuffer - W + (i - 1)));
     630     8374010 :             auto up = _mm_loadu_si128(
     631     8374010 :                 reinterpret_cast<const __m128i *>(srcBuffer - W + i));
     632     8374010 :             auto prev = _mm_loadu_si128(
     633     8374010 :                 reinterpret_cast<const __m128i *>(srcBuffer + (i - 1)));
     634     8374010 :             auto cur = _mm_loadu_si128(
     635     8374010 :                 reinterpret_cast<const __m128i *>(srcBuffer + i));
     636             : 
     637     8374010 :             auto res = PNG_PAETH_SSE2(up_prev, up, prev, cur, xmm_cost);
     638             : 
     639     8374010 :             _mm_storeu_si128(reinterpret_cast<__m128i *>(outBuffer + k * W + i),
     640             :                              res);
     641             :         }
     642      557177 :         srcBuffer += nSrcBufferBandStride;
     643             :     }
     644             : 
     645             :     int32_t ar_cost[4];
     646      182071 :     _mm_storeu_si128(reinterpret_cast<__m128i *>(ar_cost), xmm_cost);
     647      910355 :     for (int k = 0; k < 4; ++k)
     648      728284 :         costPaeth += ar_cost[k];
     649             : 
     650      182071 :     return i;
     651             : }
     652             : 
     653             : #endif  // USE_PAETH_SSE2
     654             : 
     655         818 : 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        1636 :         outputDirectory.c_str(), CPLSPrintf("%d", nZoomLevel), nullptr);
     669             :     const std::string osDirX =
     670        1636 :         CPLFormFilenameSafe(osDirZ.c_str(), CPLSPrintf("%d", iX), nullptr);
     671         818 :     const int iFileY = GetFileY(iY, tileMatrix, convention);
     672             :     const std::string osFilename = CPLFormFilenameSafe(
     673        1636 :         osDirX.c_str(), CPLSPrintf("%d", iFileY), pszExtension);
     674             : 
     675         818 :     if (bResume)
     676             :     {
     677             :         VSIStatBufL sStat;
     678           5 :         if (VSIStatL(osFilename.c_str(), &sStat) == 0)
     679           5 :             return true;
     680             :     }
     681             : 
     682         813 :     const int nDstXOff = (iX - nMinTileX) * tileMatrix.mTileWidth;
     683         813 :     const int nDstYOff = (iY - nMinTileY) * tileMatrix.mTileHeight;
     684         813 :     memset(dstBuffer.data(), 0, dstBuffer.size());
     685        1626 :     const CPLErr eErr = oWO.WarpRegionToBuffer(
     686         813 :         nDstXOff, nDstYOff, tileMatrix.mTileWidth, tileMatrix.mTileHeight,
     687         813 :         dstBuffer.data(), eWorkingDataType);
     688         813 :     if (eErr != CE_None)
     689           2 :         return false;
     690             : 
     691             :     bool bDstHasAlpha =
     692         885 :         nBands > poSrcDS->GetRasterCount() ||
     693          74 :         (nBands == poSrcDS->GetRasterCount() &&
     694          73 :          poSrcDS->GetRasterBand(nBands)->GetColorInterpretation() ==
     695         811 :              GCI_AlphaBand);
     696         811 :     const size_t nBytesPerBand = static_cast<size_t>(tileMatrix.mTileWidth) *
     697         811 :                                  tileMatrix.mTileHeight *
     698         811 :                                  GDALGetDataTypeSizeBytes(eWorkingDataType);
     699         811 :     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         768 :     if (bDstHasAlpha && !bUserAskedForAlpha)
     710             :     {
     711         746 :         bool bAllOpaque = true;
     712    40464100 :         for (size_t i = 0; i < nBytesPerBand && bAllOpaque; ++i)
     713             :         {
     714    40463400 :             bAllOpaque = (dstBuffer[(nBands - 1) * nBytesPerBand + i] == 255);
     715             :         }
     716         746 :         if (bAllOpaque)
     717             :         {
     718         602 :             bDstHasAlpha = false;
     719         602 :             nBands--;
     720             :         }
     721             :     }
     722             : 
     723         768 :     VSIMkdir(osDirZ.c_str(), 0755);
     724         768 :     VSIMkdir(osDirX.c_str(), 0755);
     725             : 
     726             :     const bool bSupportsCreateOnlyVisibleAtCloseTime =
     727        1536 :         m_poDstDriver->GetMetadataItem(
     728         768 :             GDAL_DCAP_CREATE_ONLY_VISIBLE_AT_CLOSE_TIME) != nullptr;
     729             : 
     730             :     const std::string osTmpFilename = bSupportsCreateOnlyVisibleAtCloseTime
     731             :                                           ? osFilename
     732        1536 :                                           : osFilename + ".tmp." + pszExtension;
     733             : 
     734         768 :     const int W = tileMatrix.mTileWidth;
     735         768 :     const int H = tileMatrix.mTileHeight;
     736         768 :     constexpr int EXTRA_BYTE_PER_ROW = 1;  // for filter type
     737         768 :     constexpr int EXTRA_ROWS = 2;          // for paethBuffer and paethBufferTmp
     738         764 :     if (!bAuxXML && EQUAL(pszExtension, "png") &&
     739         737 :         eWorkingDataType == GDT_UInt8 && poColorTable == nullptr &&
     740         736 :         pdfDstNoData == nullptr && W <= INT_MAX / nBands &&
     741         736 :         nBands * W <= INT_MAX - EXTRA_BYTE_PER_ROW &&
     742         736 :         H <= INT_MAX - EXTRA_ROWS &&
     743         736 :         EXTRA_BYTE_PER_ROW + nBands * W <= INT_MAX / (H + EXTRA_ROWS) &&
     744        2268 :         CSLCount(creationOptions) == 0 &&
     745         736 :         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         736 :         const int nDstBytesPerRow = EXTRA_BYTE_PER_ROW + nBands * W;
     753         736 :         const int nBPB = static_cast<int>(nBytesPerBand);
     754             : 
     755         736 :         bool bBlank = false;
     756         736 :         if (bDstHasAlpha)
     757             :         {
     758         138 :             bBlank = true;
     759     3361820 :             for (int i = 0; i < nBPB && bBlank; ++i)
     760             :             {
     761     3361680 :                 bBlank = (dstBuffer[(nBands - 1) * nBPB + i] == 0);
     762             :             }
     763             :         }
     764             : 
     765         736 :         constexpr GByte PNG_FILTER_SUB = 1;  // horizontal diff
     766         736 :         constexpr GByte PNG_FILTER_AVG = 3;  // average with pixel before and up
     767         736 :         constexpr GByte PNG_FILTER_PAETH = 4;
     768             : 
     769         736 :         if (bBlank)
     770          18 :             tmpBuffer.clear();
     771         736 :         const int tmpBufferSize = cpl::fits_on<int>(nDstBytesPerRow * H);
     772             :         try
     773             :         {
     774             :             // cppcheck-suppress integerOverflowCond
     775         736 :             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         736 :         GByte *const paethBuffer = tmpBuffer.data() + tmpBufferSize;
     784             : #ifdef USE_PAETH_SSE2
     785             :         GByte *const paethBufferTmp =
     786         736 :             tmpBuffer.data() + tmpBufferSize + nDstBytesPerRow;
     787             : #endif
     788             : 
     789             :         const char *pszGDAL_RASTER_TILE_PNG_FILTER =
     790         736 :             CPLGetConfigOption("GDAL_RASTER_TILE_PNG_FILTER", "");
     791         736 :         const bool bForcePaeth = EQUAL(pszGDAL_RASTER_TILE_PNG_FILTER, "PAETH");
     792         736 :         const bool bForceAvg = EQUAL(pszGDAL_RASTER_TILE_PNG_FILTER, "AVERAGE");
     793             : 
     794      184800 :         for (int j = 0; !bBlank && j < H; ++j)
     795             :         {
     796      184064 :             if (j > 0)
     797             :             {
     798      183346 :                 tmpBuffer[cpl::fits_on<int>(j * nDstBytesPerRow)] =
     799             :                     PNG_FILTER_AVG;
     800      743838 :                 for (int i = 0; i < nBands; ++i)
     801             :                 {
     802      560492 :                     tmpBuffer[1 + j * nDstBytesPerRow + i] =
     803      560492 :                         PNG_AVG(dstBuffer[i * nBPB + j * W], 0,
     804      560492 :                                 dstBuffer[i * nBPB + (j - 1) * W]);
     805             :                 }
     806             :             }
     807             :             else
     808             :             {
     809         718 :                 tmpBuffer[cpl::fits_on<int>(j * nDstBytesPerRow)] =
     810             :                     PNG_FILTER_SUB;
     811        2914 :                 for (int i = 0; i < nBands; ++i)
     812             :                 {
     813        2196 :                     tmpBuffer[1 + j * nDstBytesPerRow + i] =
     814        2196 :                         dstBuffer[i * nBPB + j * W];
     815             :                 }
     816             :             }
     817             : 
     818      184064 :             if (nBands == 1)
     819             :             {
     820        2048 :                 if (j > 0)
     821             :                 {
     822        2040 :                     int costAvg = 0;
     823      522240 :                     for (int i = 1; i < W; ++i)
     824             :                     {
     825             :                         const GByte v =
     826      520200 :                             PNG_AVG(dstBuffer[0 * nBPB + j * W + i],
     827      520200 :                                     dstBuffer[0 * nBPB + j * W + i - 1],
     828      520200 :                                     dstBuffer[0 * nBPB + (j - 1) * W + i]);
     829      520200 :                         tmpBuffer[1 + j * nDstBytesPerRow + i * nBands + 0] = v;
     830             : 
     831      520200 :                         costAvg += (v < 128) ? v : 256 - v;
     832             :                     }
     833             : 
     834        2040 :                     if (!bForceAvg)
     835             :                     {
     836        1785 :                         int costPaeth = 0;
     837             :                         {
     838        1785 :                             const int i = 0;
     839        1785 :                             const GByte v = PNG_PAETH(
     840        1785 :                                 dstBuffer[0 * nBPB + j * W + i], 0,
     841        1785 :                                 dstBuffer[0 * nBPB + (j - 1) * W + i], 0);
     842        1785 :                             paethBuffer[i] = v;
     843             : 
     844        1785 :                             costPaeth += (v < 128) ? v : 256 - v;
     845             :                         }
     846             : 
     847             : #ifdef USE_PAETH_SSE2
     848             :                         const int iLimitSSE2 =
     849        1785 :                             RunPaeth(dstBuffer.data() + j * W, nBands, nBPB,
     850             :                                      paethBuffer, W, costPaeth);
     851        1785 :                         int i = iLimitSSE2;
     852             : #else
     853             :                         int i = 1;
     854             : #endif
     855        7815 :                         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        1785 :                         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        2048 :                     for (int i = 1; i < W; ++i)
     880             :                     {
     881        2040 :                         tmpBuffer[1 + j * nDstBytesPerRow + i * nBands + 0] =
     882        2040 :                             PNG_SUB(dstBuffer[0 * nBPB + j * W + i],
     883        2040 :                                     dstBuffer[0 * nBPB + j * W + i - 1]);
     884             :                     }
     885             :                 }
     886             :             }
     887      182016 :             else if (nBands == 2)
     888             :             {
     889        8192 :                 if (j > 0)
     890             :                 {
     891        8161 :                     int costAvg = 0;
     892     2220030 :                     for (int i = 1; i < W; ++i)
     893             :                     {
     894             :                         {
     895             :                             const GByte v =
     896     2211870 :                                 PNG_AVG(dstBuffer[0 * nBPB + j * W + i],
     897     2211870 :                                         dstBuffer[0 * nBPB + j * W + i - 1],
     898     2211870 :                                         dstBuffer[0 * nBPB + (j - 1) * W + i]);
     899     2211870 :                             tmpBuffer[1 + j * nDstBytesPerRow + i * nBands +
     900     2211870 :                                       0] = v;
     901             : 
     902     2211870 :                             costAvg += (v < 128) ? v : 256 - v;
     903             :                         }
     904             :                         {
     905             :                             const GByte v =
     906     2211870 :                                 PNG_AVG(dstBuffer[1 * nBPB + j * W + i],
     907     2211870 :                                         dstBuffer[1 * nBPB + j * W + i - 1],
     908     2211870 :                                         dstBuffer[1 * nBPB + (j - 1) * W + i]);
     909     2211870 :                             tmpBuffer[1 + j * nDstBytesPerRow + i * nBands +
     910     2211870 :                                       1] = v;
     911             : 
     912     2211870 :                             costAvg += (v < 128) ? v : 256 - v;
     913             :                         }
     914             :                     }
     915             : 
     916        8161 :                     if (!bForceAvg)
     917             :                     {
     918        7906 :                         int costPaeth = 0;
     919       23718 :                         for (int k = 0; k < nBands; ++k)
     920             :                         {
     921       15812 :                             const int i = 0;
     922       15812 :                             const GByte v = PNG_PAETH(
     923       15812 :                                 dstBuffer[k * nBPB + j * W + i], 0,
     924       15812 :                                 dstBuffer[k * nBPB + (j - 1) * W + i], 0);
     925       15812 :                             paethBuffer[i * nBands + k] = v;
     926             : 
     927       15812 :                             costPaeth += (v < 128) ? v : 256 - v;
     928             :                         }
     929             : 
     930             : #ifdef USE_PAETH_SSE2
     931             :                         const int iLimitSSE2 =
     932        7906 :                             RunPaeth(dstBuffer.data() + j * W, nBands, nBPB,
     933             :                                      paethBufferTmp, W, costPaeth);
     934        7906 :                         int i = iLimitSSE2;
     935             : #else
     936             :                         int i = 1;
     937             : #endif
     938       68221 :                         for (; i < W && (costPaeth < costAvg || bForcePaeth);
     939             :                              ++i)
     940             :                         {
     941             :                             {
     942       60315 :                                 const GByte v = PNG_PAETH(
     943       60315 :                                     dstBuffer[0 * nBPB + j * W + i],
     944       60315 :                                     dstBuffer[0 * nBPB + j * W + i - 1],
     945       60315 :                                     dstBuffer[0 * nBPB + (j - 1) * W + i],
     946       60315 :                                     dstBuffer[0 * nBPB + (j - 1) * W + i - 1]);
     947       60315 :                                 paethBuffer[i * nBands + 0] = v;
     948             : 
     949       60315 :                                 costPaeth += (v < 128) ? v : 256 - v;
     950             :                             }
     951             :                             {
     952       60315 :                                 const GByte v = PNG_PAETH(
     953       60315 :                                     dstBuffer[1 * nBPB + j * W + i],
     954       60315 :                                     dstBuffer[1 * nBPB + j * W + i - 1],
     955       60315 :                                     dstBuffer[1 * nBPB + (j - 1) * W + i],
     956       60315 :                                     dstBuffer[1 * nBPB + (j - 1) * W + i - 1]);
     957       60315 :                                 paethBuffer[i * nBands + 1] = v;
     958             : 
     959       60315 :                                 costPaeth += (v < 128) ? v : 256 - v;
     960             :                             }
     961             :                         }
     962        7906 :                         if (costPaeth < costAvg || bForcePaeth)
     963             :                         {
     964        4021 :                             GByte *out = tmpBuffer.data() +
     965        4021 :                                          cpl::fits_on<int>(j * nDstBytesPerRow);
     966        4021 :                             *out = PNG_FILTER_PAETH;
     967        4021 :                             ++out;
     968             : #ifdef USE_PAETH_SSE2
     969        4021 :                             memcpy(out, paethBuffer, nBands);
     970      979045 :                             for (int iTmp = 1; iTmp < iLimitSSE2; ++iTmp)
     971             :                             {
     972      975024 :                                 out[nBands * iTmp + 0] =
     973      975024 :                                     paethBufferTmp[0 * W + iTmp];
     974      975024 :                                 out[nBands * iTmp + 1] =
     975      975024 :                                     paethBufferTmp[1 * W + iTmp];
     976             :                             }
     977        4021 :                             memcpy(
     978        4021 :                                 out + iLimitSSE2 * nBands,
     979        4021 :                                 paethBuffer + iLimitSSE2 * nBands,
     980        4021 :                                 cpl::fits_on<int>((W - iLimitSSE2) * nBands));
     981             : #else
     982             :                             memcpy(out, paethBuffer, nDstBytesPerRow - 1);
     983             : #endif
     984             :                         }
     985             :                     }
     986             :                 }
     987             :                 else
     988             :                 {
     989        8192 :                     for (int i = 1; i < W; ++i)
     990             :                     {
     991        8161 :                         tmpBuffer[1 + j * nDstBytesPerRow + i * nBands + 0] =
     992        8161 :                             PNG_SUB(dstBuffer[0 * nBPB + j * W + i],
     993        8161 :                                     dstBuffer[0 * nBPB + j * W + i - 1]);
     994        8161 :                         tmpBuffer[1 + j * nDstBytesPerRow + i * nBands + 1] =
     995        8161 :                             PNG_SUB(dstBuffer[1 * nBPB + j * W + i],
     996        8161 :                                     dstBuffer[1 * nBPB + j * W + i - 1]);
     997             :                     }
     998             :                 }
     999             :             }
    1000      173824 :             else if (nBands == 3)
    1001             :             {
    1002      151296 :                 if (j > 0)
    1003             :                 {
    1004      150705 :                     int costAvg = 0;
    1005    38580500 :                     for (int i = 1; i < W; ++i)
    1006             :                     {
    1007             :                         {
    1008             :                             const GByte v =
    1009    38429800 :                                 PNG_AVG(dstBuffer[0 * nBPB + j * W + i],
    1010    38429800 :                                         dstBuffer[0 * nBPB + j * W + i - 1],
    1011    38429800 :                                         dstBuffer[0 * nBPB + (j - 1) * W + i]);
    1012    38429800 :                             tmpBuffer[1 + j * nDstBytesPerRow + i * nBands +
    1013    38429800 :                                       0] = v;
    1014             : 
    1015    38429800 :                             costAvg += (v < 128) ? v : 256 - v;
    1016             :                         }
    1017             :                         {
    1018             :                             const GByte v =
    1019    38429800 :                                 PNG_AVG(dstBuffer[1 * nBPB + j * W + i],
    1020    38429800 :                                         dstBuffer[1 * nBPB + j * W + i - 1],
    1021    38429800 :                                         dstBuffer[1 * nBPB + (j - 1) * W + i]);
    1022    38429800 :                             tmpBuffer[1 + j * nDstBytesPerRow + i * nBands +
    1023    38429800 :                                       1] = v;
    1024             : 
    1025    38429800 :                             costAvg += (v < 128) ? v : 256 - v;
    1026             :                         }
    1027             :                         {
    1028             :                             const GByte v =
    1029    38429800 :                                 PNG_AVG(dstBuffer[2 * nBPB + j * W + i],
    1030    38429800 :                                         dstBuffer[2 * nBPB + j * W + i - 1],
    1031    38429800 :                                         dstBuffer[2 * nBPB + (j - 1) * W + i]);
    1032    38429800 :                             tmpBuffer[1 + j * nDstBytesPerRow + i * nBands +
    1033    38429800 :                                       2] = v;
    1034             : 
    1035    38429800 :                             costAvg += (v < 128) ? v : 256 - v;
    1036             :                         }
    1037             :                     }
    1038             : 
    1039      150705 :                     if (!bForceAvg)
    1040             :                     {
    1041      150195 :                         int costPaeth = 0;
    1042      600780 :                         for (int k = 0; k < nBands; ++k)
    1043             :                         {
    1044      450585 :                             const int i = 0;
    1045      450585 :                             const GByte v = PNG_PAETH(
    1046      450585 :                                 dstBuffer[k * nBPB + j * W + i], 0,
    1047      450585 :                                 dstBuffer[k * nBPB + (j - 1) * W + i], 0);
    1048      450585 :                             paethBuffer[i * nBands + k] = v;
    1049             : 
    1050      450585 :                             costPaeth += (v < 128) ? v : 256 - v;
    1051             :                         }
    1052             : 
    1053             : #ifdef USE_PAETH_SSE2
    1054             :                         const int iLimitSSE2 =
    1055      150195 :                             RunPaeth(dstBuffer.data() + j * W, nBands, nBPB,
    1056             :                                      paethBufferTmp, W, costPaeth);
    1057      150195 :                         int i = iLimitSSE2;
    1058             : #else
    1059             :                         int i = 1;
    1060             : #endif
    1061     1801390 :                         for (; i < W && (costPaeth < costAvg || bForcePaeth);
    1062             :                              ++i)
    1063             :                         {
    1064             :                             {
    1065     1651200 :                                 const GByte v = PNG_PAETH(
    1066     1651200 :                                     dstBuffer[0 * nBPB + j * W + i],
    1067     1651200 :                                     dstBuffer[0 * nBPB + j * W + i - 1],
    1068     1651200 :                                     dstBuffer[0 * nBPB + (j - 1) * W + i],
    1069     1651200 :                                     dstBuffer[0 * nBPB + (j - 1) * W + i - 1]);
    1070     1651200 :                                 paethBuffer[i * nBands + 0] = v;
    1071             : 
    1072     1651200 :                                 costPaeth += (v < 128) ? v : 256 - v;
    1073             :                             }
    1074             :                             {
    1075     1651200 :                                 const GByte v = PNG_PAETH(
    1076     1651200 :                                     dstBuffer[1 * nBPB + j * W + i],
    1077     1651200 :                                     dstBuffer[1 * nBPB + j * W + i - 1],
    1078     1651200 :                                     dstBuffer[1 * nBPB + (j - 1) * W + i],
    1079     1651200 :                                     dstBuffer[1 * nBPB + (j - 1) * W + i - 1]);
    1080     1651200 :                                 paethBuffer[i * nBands + 1] = v;
    1081             : 
    1082     1651200 :                                 costPaeth += (v < 128) ? v : 256 - v;
    1083             :                             }
    1084             :                             {
    1085     1651200 :                                 const GByte v = PNG_PAETH(
    1086     1651200 :                                     dstBuffer[2 * nBPB + j * W + i],
    1087     1651200 :                                     dstBuffer[2 * nBPB + j * W + i - 1],
    1088     1651200 :                                     dstBuffer[2 * nBPB + (j - 1) * W + i],
    1089     1651200 :                                     dstBuffer[2 * nBPB + (j - 1) * W + i - 1]);
    1090     1651200 :                                 paethBuffer[i * nBands + 2] = v;
    1091             : 
    1092     1651200 :                                 costPaeth += (v < 128) ? v : 256 - v;
    1093             :                             }
    1094             :                         }
    1095             : 
    1096      150195 :                         if (costPaeth < costAvg || bForcePaeth)
    1097             :                         {
    1098      108960 :                             GByte *out = tmpBuffer.data() +
    1099      108960 :                                          cpl::fits_on<int>(j * nDstBytesPerRow);
    1100      108960 :                             *out = PNG_FILTER_PAETH;
    1101      108960 :                             ++out;
    1102             : #ifdef USE_PAETH_SSE2
    1103      108960 :                             memcpy(out, paethBuffer, nBands);
    1104    26259400 :                             for (int iTmp = 1; iTmp < iLimitSSE2; ++iTmp)
    1105             :                             {
    1106    26150400 :                                 out[nBands * iTmp + 0] =
    1107    26150400 :                                     paethBufferTmp[0 * W + iTmp];
    1108    26150400 :                                 out[nBands * iTmp + 1] =
    1109    26150400 :                                     paethBufferTmp[1 * W + iTmp];
    1110    26150400 :                                 out[nBands * iTmp + 2] =
    1111    26150400 :                                     paethBufferTmp[2 * W + iTmp];
    1112             :                             }
    1113      108960 :                             memcpy(
    1114      108960 :                                 out + iLimitSSE2 * nBands,
    1115      108960 :                                 paethBuffer + iLimitSSE2 * nBands,
    1116      108960 :                                 cpl::fits_on<int>((W - iLimitSSE2) * nBands));
    1117             : #else
    1118             :                             memcpy(out, paethBuffer, nDstBytesPerRow - 1);
    1119             : #endif
    1120             :                         }
    1121             :                     }
    1122             :                 }
    1123             :                 else
    1124             :                 {
    1125      151296 :                     for (int i = 1; i < W; ++i)
    1126             :                     {
    1127      150705 :                         tmpBuffer[1 + j * nDstBytesPerRow + i * nBands + 0] =
    1128      150705 :                             PNG_SUB(dstBuffer[0 * nBPB + j * W + i],
    1129      150705 :                                     dstBuffer[0 * nBPB + j * W + i - 1]);
    1130      150705 :                         tmpBuffer[1 + j * nDstBytesPerRow + i * nBands + 1] =
    1131      150705 :                             PNG_SUB(dstBuffer[1 * nBPB + j * W + i],
    1132      150705 :                                     dstBuffer[1 * nBPB + j * W + i - 1]);
    1133      150705 :                         tmpBuffer[1 + j * nDstBytesPerRow + i * nBands + 2] =
    1134      150705 :                             PNG_SUB(dstBuffer[2 * nBPB + j * W + i],
    1135      150705 :                                     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         736 :         size_t nOutSize = 0;
    1304             :         // Shouldn't happen given the care we have done to dimension dstBuffer
    1305        1472 :         if (CPLZLibDeflate(tmpBuffer.data(), tmpBufferSize, -1,
    1306         736 :                            dstBuffer.data(), dstBuffer.size(),
    1307        1472 :                            &nOutSize) == nullptr ||
    1308         736 :             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         736 :         VSILFILE *fp = VSIFOpenL(osTmpFilename.c_str(), "wb");
    1316         736 :         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         736 :         fp->Write("\x89PNG\x0D\x0A\x1A\x0A", 8, 1);
    1328             : 
    1329             :         uLong crc;
    1330        7360 :         const auto WriteAndUpdateCRC_Byte = [fp, &crc](uint8_t nVal)
    1331             :         {
    1332        3680 :             fp->Write(&nVal, 1, sizeof(nVal));
    1333        3680 :             crc = crc32(crc, &nVal, sizeof(nVal));
    1334        4416 :         };
    1335        4416 :         const auto WriteAndUpdateCRC_Int = [fp, &crc](int32_t nVal)
    1336             :         {
    1337        1472 :             CPL_MSBPTR32(&nVal);
    1338        1472 :             fp->Write(&nVal, 1, sizeof(nVal));
    1339        1472 :             crc = crc32(crc, reinterpret_cast<const Bytef *>(&nVal),
    1340             :                         sizeof(nVal));
    1341        2208 :         };
    1342             : 
    1343             :         // IHDR chunk
    1344         736 :         uint32_t nIHDRSize = 13;
    1345         736 :         CPL_MSBPTR32(&nIHDRSize);
    1346         736 :         fp->Write(&nIHDRSize, 1, sizeof(nIHDRSize));
    1347         736 :         crc = crc32(0, reinterpret_cast<const Bytef *>("IHDR"), 4);
    1348         736 :         fp->Write("IHDR", 1, 4);
    1349         736 :         WriteAndUpdateCRC_Int(W);
    1350         736 :         WriteAndUpdateCRC_Int(H);
    1351         736 :         WriteAndUpdateCRC_Byte(8);  // Number of bits per pixel
    1352         736 :         const uint8_t nColorType = nBands == 1   ? 0
    1353             :                                    : nBands == 2 ? 4
    1354             :                                    : nBands == 3 ? 2
    1355             :                                                  : 6;
    1356         736 :         WriteAndUpdateCRC_Byte(nColorType);
    1357         736 :         WriteAndUpdateCRC_Byte(0);  // Compression method
    1358         736 :         WriteAndUpdateCRC_Byte(0);  // Filter method
    1359         736 :         WriteAndUpdateCRC_Byte(0);  // Interlacing=off
    1360             :         {
    1361         736 :             uint32_t nCrc32 = static_cast<uint32_t>(crc);
    1362         736 :             CPL_MSBPTR32(&nCrc32);
    1363         736 :             fp->Write(&nCrc32, 1, sizeof(nCrc32));
    1364             :         }
    1365             : 
    1366             :         // IDAT chunk
    1367         736 :         uint32_t nIDATSize = static_cast<uint32_t>(nOutSize);
    1368         736 :         CPL_MSBPTR32(&nIDATSize);
    1369         736 :         fp->Write(&nIDATSize, 1, sizeof(nIDATSize));
    1370         736 :         crc = crc32(0, reinterpret_cast<const Bytef *>("IDAT"), 4);
    1371         736 :         fp->Write("IDAT", 1, 4);
    1372         736 :         crc = crc32(crc, dstBuffer.data(), static_cast<uint32_t>(nOutSize));
    1373         736 :         fp->Write(dstBuffer.data(), 1, nOutSize);
    1374             :         {
    1375         736 :             uint32_t nCrc32 = static_cast<uint32_t>(crc);
    1376         736 :             CPL_MSBPTR32(&nCrc32);
    1377         736 :             fp->Write(&nCrc32, 1, sizeof(nCrc32));
    1378             :         }
    1379             : 
    1380             :         // IEND chunk
    1381         736 :         fp->Write("\x00\x00\x00\x00IEND\xAE\x42\x60\x82", 12, 1);
    1382             : 
    1383             :         bool bRet =
    1384         736 :             fp->Tell() == 8 + 4 + 4 + 13 + 4 + 4 + 4 + nOutSize + 4 + 12;
    1385        1472 :         bRet = VSIFCloseL(fp) == 0 && bRet &&
    1386         736 :                VSIRename(osTmpFilename.c_str(), osFilename.c_str()) == 0;
    1387         736 :         if (!bRet)
    1388           0 :             VSIUnlink(osTmpFilename.c_str());
    1389             : 
    1390         736 :         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(CPLSPrintf("%.17g", dfMinX));
    1703          48 :             aosOptions.AddString(CPLSPrintf("%.17g", dfMaxY));
    1704          48 :             aosOptions.AddString(CPLSPrintf("%.17g", dfMaxX));
    1705          48 :             aosOptions.AddString(CPLSPrintf("%.17g", 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(CPLSPrintf("%.17g", dfMinX));
    1723           9 :         aosOptions.AddString(CPLSPrintf("%.17g", dfMinY));
    1724           9 :         aosOptions.AddString(CPLSPrintf("%.17g", dfMaxX));
    1725           9 :         aosOptions.AddString(CPLSPrintf("%.17g", 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         746 :     FakeMaxZoomRasterBand(int nBandIn, int nWidth, int nHeight,
    1809             :                           int nBlockXSizeIn, int nBlockYSizeIn,
    1810             :                           GDALDataType eDT, void *pDstBuffer)
    1811         746 :         : m_pDstBuffer(pDstBuffer)
    1812             :     {
    1813         746 :         nBand = nBandIn;
    1814         746 :         nRasterXSize = nWidth;
    1815         746 :         nRasterYSize = nHeight;
    1816         746 :         nBlockXSize = nBlockXSizeIn;
    1817         746 :         nBlockYSize = nBlockYSizeIn;
    1818         746 :         eDataType = eDT;
    1819         746 :     }
    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        1582 :     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        1582 :         CPLAssert((nXOff % nBlockXSize) == 0);
    1846        1582 :         CPLAssert((nYOff % nBlockYSize) == 0);
    1847        1582 :         CPLAssert(nXSize == nBufXSize);
    1848        1582 :         CPLAssert(nXSize == nBlockXSize);
    1849        1582 :         CPLAssert(nYSize == nBufYSize);
    1850        1582 :         CPLAssert(nYSize == nBlockYSize);
    1851        1582 :         CPLAssert(nLineSpace == nBlockXSize * nPixelSpace);
    1852        1582 :         CPLAssert(
    1853             :             nBand ==
    1854             :             poDS->GetRasterCount());  // only alpha band is accessed this way
    1855        1582 :         if (eRWFlag == GF_Read)
    1856             :         {
    1857         791 :             double dfZero = 0;
    1858         791 :             GDALCopyWords64(&dfZero, GDT_Float64, 0, pData, eBufType,
    1859             :                             static_cast<int>(nPixelSpace),
    1860         791 :                             static_cast<size_t>(nBlockXSize) * nBlockYSize);
    1861             :         }
    1862             :         else
    1863             :         {
    1864         791 :             GDALCopyWords64(pData, eBufType, static_cast<int>(nPixelSpace),
    1865             :                             m_pDstBuffer, eDataType,
    1866             :                             GDALGetDataTypeSizeBytes(eDataType),
    1867         791 :                             static_cast<size_t>(nBlockXSize) * nBlockYSize);
    1868             :         }
    1869        1582 :         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         212 :     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         212 :         : m_nBlockXSize(nBlockXSize), m_nBlockYSize(nBlockYSize), m_oSRS(oSRS),
    1896         212 :           m_gt(gt)
    1897             :     {
    1898         212 :         eAccess = GA_Update;
    1899         212 :         nRasterXSize = nWidth;
    1900         212 :         nRasterYSize = nHeight;
    1901         958 :         for (int i = 1; i <= nBandsIn; ++i)
    1902             :         {
    1903         746 :             SetBand(i,
    1904             :                     new FakeMaxZoomRasterBand(
    1905             :                         i, nWidth, nHeight, nBlockXSize, nBlockYSize, eDT,
    1906         746 :                         dstBuffer.data() + static_cast<size_t>(i - 1) *
    1907        1492 :                                                nBlockXSize * nBlockYSize *
    1908         746 :                                                GDALGetDataTypeSizeBytes(eDT)));
    1909             :         }
    1910         212 :     }
    1911             : 
    1912         400 :     const OGRSpatialReference *GetSpatialRef() const override
    1913             :     {
    1914         400 :         return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
    1915             :     }
    1916             : 
    1917         188 :     CPLErr GetGeoTransform(GDALGeoTransform &gt) const override
    1918             :     {
    1919         188 :         gt = m_gt;
    1920         188 :         return CE_None;
    1921             :     }
    1922             : 
    1923             :     using GDALDataset::Clone;
    1924             : 
    1925             :     std::unique_ptr<FakeMaxZoomDataset>
    1926          24 :     Clone(std::vector<GByte> &dstBuffer) const
    1927             :     {
    1928             :         return std::make_unique<FakeMaxZoomDataset>(
    1929          24 :             nRasterXSize, nRasterYSize, nBands, m_nBlockXSize, m_nBlockYSize,
    1930          48 :             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, new MosaicRasterBand(
    2063         446 :                            this, i, nRasterXSize, nRasterYSize, oTM.mTileWidth,
    2064         446 :                            oTM.mTileHeight, eDT, eColorInterp, nTileMinX,
    2065             :                            nTileMinY, oTM, convention, directory, extension,
    2066         446 :                            pdfDstNoData, poCT));
    2067             :         }
    2068         126 :         SetMetadataItem("INTERLEAVE", "PIXEL", "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         306 : static void ApplySubstitutions(CPLString &s,
    2161             :                                const std::map<std::string, std::string> &substs)
    2162             : {
    2163        4675 :     for (const auto &[key, value] : substs)
    2164             :     {
    2165        4369 :         s.replaceAll("%(" + key + ")s", value);
    2166        4369 :         s.replaceAll("%(" + key + ")d", value);
    2167        4369 :         s.replaceAll("%(" + key + ")f", value);
    2168        4369 :         s.replaceAll("${" + key + "}", value);
    2169             :     }
    2170         306 : }
    2171             : 
    2172             : /************************************************************************/
    2173             : /*                          GenerateLeaflet()                           */
    2174             : /************************************************************************/
    2175             : 
    2176          81 : 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          81 :     if (const char *pszTemplate = CPLFindFile("gdal", "leaflet_template.html"))
    2185             :     {
    2186         162 :         const std::string osFilename(pszTemplate);
    2187         162 :         std::map<std::string, std::string> substs;
    2188             : 
    2189             :         // For tests
    2190             :         const char *pszFmt =
    2191          81 :             atoi(CPLGetConfigOption("GDAL_RASTER_TILE_HTML_PREC", "17")) == 10
    2192             :                 ? "%.10g"
    2193          81 :                 : "%.17g";
    2194             : 
    2195         162 :         substs["double_quote_escaped_title"] =
    2196         243 :             CPLString(osTitle).replaceAll('"', "\\\"");
    2197          81 :         char *pszStr = CPLEscapeString(osTitle.c_str(), -1, CPLES_XML);
    2198          81 :         substs["xml_escaped_title"] = pszStr;
    2199          81 :         CPLFree(pszStr);
    2200          81 :         substs["south"] = CPLSPrintf(pszFmt, dfSouthLat);
    2201          81 :         substs["west"] = CPLSPrintf(pszFmt, dfWestLon);
    2202          81 :         substs["north"] = CPLSPrintf(pszFmt, dfNorthLat);
    2203          81 :         substs["east"] = CPLSPrintf(pszFmt, dfEastLon);
    2204          81 :         substs["centerlon"] = CPLSPrintf(pszFmt, (dfNorthLat + dfSouthLat) / 2);
    2205          81 :         substs["centerlat"] = CPLSPrintf(pszFmt, (dfWestLon + dfEastLon) / 2);
    2206          81 :         substs["minzoom"] = CPLSPrintf("%d", nMinZoom);
    2207          81 :         substs["maxzoom"] = CPLSPrintf("%d", nMaxZoom);
    2208          81 :         substs["beginzoom"] = CPLSPrintf("%d", nMaxZoom);
    2209          81 :         substs["tile_size"] = CPLSPrintf("%d", nTileSize);  // not used
    2210          81 :         substs["tileformat"] = osExtension;
    2211          81 :         substs["publishurl"] = osURL;  // not used
    2212          81 :         substs["copyright"] = CPLString(osCopyright).replaceAll('"', "\\\"");
    2213          81 :         substs["tms"] = bXYZ ? "0" : "1";
    2214             : 
    2215          81 :         GByte *pabyRet = nullptr;
    2216          81 :         CPL_IGNORE_RET_VAL(VSIIngestFile(nullptr, osFilename.c_str(), &pabyRet,
    2217             :                                          nullptr, 10 * 1024 * 1024));
    2218          81 :         if (pabyRet)
    2219             :         {
    2220         162 :             CPLString osHTML(reinterpret_cast<char *>(pabyRet));
    2221          81 :             CPLFree(pabyRet);
    2222             : 
    2223          81 :             ApplySubstitutions(osHTML, substs);
    2224             : 
    2225          81 :             VSILFILE *f = VSIFOpenL(CPLFormFilenameSafe(osDirectory.c_str(),
    2226             :                                                         "leaflet.html", nullptr)
    2227             :                                         .c_str(),
    2228             :                                     "wb");
    2229          81 :             if (f)
    2230             :             {
    2231          81 :                 VSIFWriteL(osHTML.data(), 1, osHTML.size(), f);
    2232          81 :                 VSIFCloseL(f);
    2233             :             }
    2234             :         }
    2235             :     }
    2236          81 : }
    2237             : 
    2238             : /************************************************************************/
    2239             : /*                           GenerateMapML()                            */
    2240             : /************************************************************************/
    2241             : 
    2242             : static void
    2243          55 : 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          55 :     if (const char *pszTemplate =
    2250          55 :             (mapmlTemplate.empty() ? CPLFindFile("gdal", "template_tiles.mapml")
    2251          55 :                                    : mapmlTemplate.c_str()))
    2252             :     {
    2253         110 :         const std::string osFilename(pszTemplate);
    2254         110 :         std::map<std::string, std::string> substs;
    2255             : 
    2256          55 :         if (tms.identifier() == "GoogleMapsCompatible")
    2257          52 :             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          55 :         substs["URL"] = osURL.empty() ? "./" : osURL + "/";
    2264          55 :         substs["MINTILEX"] = CPLSPrintf("%d", nMinTileX);
    2265          55 :         substs["MINTILEY"] = CPLSPrintf("%d", nMinTileY);
    2266          55 :         substs["MAXTILEX"] = CPLSPrintf("%d", nMaxTileX);
    2267          55 :         substs["MAXTILEY"] = CPLSPrintf("%d", nMaxTileY);
    2268          55 :         substs["CURZOOM"] = CPLSPrintf("%d", nMaxZoom);
    2269          55 :         substs["MINZOOM"] = CPLSPrintf("%d", nMinZoom);
    2270          55 :         substs["MAXZOOM"] = CPLSPrintf("%d", nMaxZoom);
    2271          55 :         substs["TILEEXT"] = osExtension;
    2272          55 :         char *pszStr = CPLEscapeString(osTitle.c_str(), -1, CPLES_XML);
    2273          55 :         substs["TITLE"] = pszStr;
    2274          55 :         CPLFree(pszStr);
    2275          55 :         substs["COPYRIGHT"] = osCopyright;
    2276             : 
    2277          55 :         GByte *pabyRet = nullptr;
    2278          55 :         CPL_IGNORE_RET_VAL(VSIIngestFile(nullptr, osFilename.c_str(), &pabyRet,
    2279             :                                          nullptr, 10 * 1024 * 1024));
    2280          55 :         if (pabyRet)
    2281             :         {
    2282         110 :             CPLString osMAPML(reinterpret_cast<char *>(pabyRet));
    2283          55 :             CPLFree(pabyRet);
    2284             : 
    2285          55 :             ApplySubstitutions(osMAPML, substs);
    2286             : 
    2287          55 :             VSILFILE *f = VSIFOpenL(
    2288         110 :                 CPLFormFilenameSafe(osDirectory.c_str(), "mapml.mapml", nullptr)
    2289             :                     .c_str(),
    2290             :                 "wb");
    2291          55 :             if (f)
    2292             :             {
    2293          55 :                 VSIFWriteL(osMAPML.data(), 1, osMAPML.size(), f);
    2294          55 :                 VSIFCloseL(f);
    2295             :             }
    2296             :         }
    2297             :     }
    2298          55 : }
    2299             : 
    2300             : /************************************************************************/
    2301             : /*                            GenerateSTAC()                            */
    2302             : /************************************************************************/
    2303             : 
    2304             : static void
    2305          59 : 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         118 :     CPLJSONObject oRoot;
    2316          59 :     oRoot["stac_version"] = "1.1.0";
    2317         118 :     CPLJSONArray oExtensions;
    2318          59 :     oRoot["stac_extensions"] = oExtensions;
    2319          59 :     oRoot["id"] = osTitle;
    2320          59 :     oRoot["type"] = "Feature";
    2321          59 :     oRoot["bbox"] = {dfWestLon, dfSouthLat, dfEastLon, dfNorthLat};
    2322         118 :     CPLJSONObject oGeometry;
    2323             : 
    2324          59 :     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         413 :              CPLJSONArray::Build({x1, y1})})});
    2330             :     };
    2331             : 
    2332          59 :     if (dfWestLon <= dfEastLon)
    2333             :     {
    2334          59 :         oGeometry["type"] = "Polygon";
    2335             :         oGeometry["coordinates"] =
    2336          59 :             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          59 :     oRoot["geometry"] = std::move(oGeometry);
    2346             : 
    2347         118 :     CPLJSONObject oProperties;
    2348          59 :     oRoot["properties"] = oProperties;
    2349         118 :     const CPLStringList aosMD(metadata);
    2350         118 :     std::string osDateTime = "1970-01-01T00:00:00.000Z";
    2351          59 :     if (!dataset.GetName().empty())
    2352             :     {
    2353             :         VSIStatBufL sStat;
    2354          41 :         if (VSIStatL(dataset.GetName().c_str(), &sStat) == 0 &&
    2355          20 :             sStat.st_mtime != 0)
    2356             :         {
    2357             :             struct tm tm;
    2358          20 :             CPLUnixTimeToYMDHMS(sStat.st_mtime, &tm);
    2359             :             osDateTime = CPLSPrintf(
    2360          20 :                 "%04d-%02d-%02dT%02d:%02d:%02dZ", tm.tm_year + 1900,
    2361          20 :                 tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
    2362             :         }
    2363             :     }
    2364         118 :     std::string osStartDateTime = "0001-01-01T00:00:00.000Z";
    2365         118 :     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          60 :     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          59 :     oProperties["datetime"] = osDateTime;
    2425          59 :     oProperties["start_datetime"] = osStartDateTime;
    2426          59 :     oProperties["end_datetime"] = osEndDateTime;
    2427          59 :     if (!osCopyright.empty())
    2428           0 :         oProperties["copyright"] = osCopyright;
    2429             : 
    2430             :     // Just keep the tile matrix zoom levels we use
    2431         118 :     gdal::TileMatrixSet tmsLimitedToZoomLevelUsed(tms);
    2432          59 :     auto &tileMatrixList = tmsLimitedToZoomLevelUsed.tileMatrixList();
    2433          59 :     tileMatrixList.erase(tileMatrixList.begin() + nMaxZoom + 1,
    2434         118 :                          tileMatrixList.end());
    2435          59 :     tileMatrixList.erase(tileMatrixList.begin(),
    2436         118 :                          tileMatrixList.begin() + nMinZoom);
    2437             : 
    2438         118 :     CPLJSONObject oLimits;
    2439             :     // Patch their definition with the potentially overridden tileSize.
    2440         164 :     for (auto &tm : tileMatrixList)
    2441             :     {
    2442         105 :         int nOvrMinTileX = 0;
    2443         105 :         int nOvrMinTileY = 0;
    2444         105 :         int nOvrMaxTileX = 0;
    2445         105 :         int nOvrMaxTileY = 0;
    2446         105 :         bool bIntersects = false;
    2447         105 :         CPL_IGNORE_RET_VAL(GetTileIndices(
    2448             :             tm, bInvertAxisTMS, tileSize, adfExtent, nOvrMinTileX, nOvrMinTileY,
    2449             :             nOvrMaxTileX, nOvrMaxTileY, /* noIntersectionIsOK = */ true,
    2450             :             bIntersects));
    2451             : 
    2452         105 :         CPLJSONObject oLimit;
    2453         105 :         oLimit["min_tile_col"] = nOvrMinTileX;
    2454         105 :         oLimit["max_tile_col"] = nOvrMaxTileX;
    2455         105 :         oLimit["min_tile_row"] = nOvrMinTileY;
    2456         105 :         oLimit["max_tile_row"] = nOvrMaxTileY;
    2457         105 :         oLimits[tm.mId] = std::move(oLimit);
    2458             :     }
    2459             : 
    2460         118 :     CPLJSONObject oTilesTileMatrixSets;
    2461             :     {
    2462          59 :         CPLJSONDocument oDoc;
    2463          59 :         CPL_IGNORE_RET_VAL(
    2464          59 :             oDoc.LoadMemory(tmsLimitedToZoomLevelUsed.exportToTMSJsonV1()));
    2465             :         oTilesTileMatrixSets[tmsLimitedToZoomLevelUsed.identifier()] =
    2466          59 :             oDoc.GetRoot();
    2467             :     }
    2468          59 :     oProperties["tiles:tile_matrix_sets"] = std::move(oTilesTileMatrixSets);
    2469             : 
    2470         118 :     CPLJSONObject oTilesTileMatrixLinks;
    2471         118 :     CPLJSONObject oTilesTileMatrixLink;
    2472             :     oTilesTileMatrixLink["url"] =
    2473          59 :         std::string("#").append(tmsLimitedToZoomLevelUsed.identifier());
    2474          59 :     oTilesTileMatrixLink["limits"] = std::move(oLimits);
    2475             :     oTilesTileMatrixLinks[tmsLimitedToZoomLevelUsed.identifier()] =
    2476          59 :         std::move(oTilesTileMatrixLink);
    2477          59 :     oProperties["tiles:tile_matrix_links"] = std::move(oTilesTileMatrixLinks);
    2478             : 
    2479          59 :     const char *pszAuthName = oSRS.GetAuthorityName();
    2480          59 :     const char *pszAuthCode = oSRS.GetAuthorityCode();
    2481          59 :     if (pszAuthName && pszAuthCode)
    2482             :     {
    2483          58 :         oProperties["proj:code"] =
    2484          58 :             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          59 :         auto ovrTileMatrix = tms.tileMatrixList()[nMaxZoom];
    2500          59 :         int nOvrMinTileX = 0;
    2501          59 :         int nOvrMinTileY = 0;
    2502          59 :         int nOvrMaxTileX = 0;
    2503          59 :         int nOvrMaxTileY = 0;
    2504          59 :         bool bIntersects = false;
    2505          59 :         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          59 :             (nOvrMaxTileY - nOvrMinTileY + 1) * ovrTileMatrix.mTileHeight,
    2511          59 :             (nOvrMaxTileX - nOvrMinTileX + 1) * ovrTileMatrix.mTileWidth};
    2512             : 
    2513           0 :         oProperties["proj:transform"] = {
    2514          59 :             ovrTileMatrix.mResX,
    2515             :             0.0,
    2516          59 :             ovrTileMatrix.mTopLeftX +
    2517          59 :                 nOvrMinTileX * ovrTileMatrix.mTileWidth * ovrTileMatrix.mResX,
    2518             :             0.0,
    2519          59 :             -ovrTileMatrix.mResY,
    2520          59 :             ovrTileMatrix.mTopLeftY +
    2521          59 :                 nOvrMinTileY * ovrTileMatrix.mTileHeight * ovrTileMatrix.mResY,
    2522             :             0.0,
    2523             :             0.0,
    2524          59 :             0.0};
    2525             :     }
    2526             : 
    2527          59 :     constexpr const char *ASSET_NAME = "bands";
    2528             : 
    2529         118 :     CPLJSONObject oAssetTemplates;
    2530          59 :     oRoot["asset_templates"] = oAssetTemplates;
    2531             : 
    2532         118 :     CPLJSONObject oAssetTemplate;
    2533          59 :     oAssetTemplates[ASSET_NAME] = oAssetTemplate;
    2534             : 
    2535         118 :     std::string osHref = (osURL.empty() ? std::string(".") : std::string(osURL))
    2536          59 :                              .append("/{TileMatrix}/{TileCol}/{TileRow}.")
    2537         118 :                              .append(osExtension);
    2538             : 
    2539             :     const std::map<std::string, std::string> oMapVSIToURIPrefix = {
    2540             :         {"vsis3", "s3://"},
    2541             :         {"vsigs", "gs://"},
    2542             :         {"vsiaz", "az://"},  // Not universally recognized
    2543         354 :     };
    2544             : 
    2545             :     const CPLStringList aosSplitHref(
    2546         118 :         CSLTokenizeString2(osHref.c_str(), "/", 0));
    2547          59 :     if (!aosSplitHref.empty())
    2548             :     {
    2549          59 :         const auto oIter = oMapVSIToURIPrefix.find(aosSplitHref[0]);
    2550          59 :         if (oIter != oMapVSIToURIPrefix.end())
    2551             :         {
    2552             :             // +2 because of 2 slash characters
    2553           0 :             osHref = std::string(oIter->second)
    2554           0 :                          .append(osHref.c_str() + strlen(aosSplitHref[0]) + 2);
    2555             :         }
    2556             :     }
    2557          59 :     oAssetTemplate["href"] = osHref;
    2558             : 
    2559          59 :     if (EQUAL(osFormat.c_str(), "COG"))
    2560             :         oAssetTemplate["type"] =
    2561           0 :             "image/tiff; application=geotiff; profile=cloud-optimized";
    2562          59 :     else if (osExtension == "tif")
    2563           2 :         oAssetTemplate["type"] = "image/tiff; application=geotiff";
    2564          57 :     else if (osExtension == "png")
    2565          55 :         oAssetTemplate["type"] = "image/png";
    2566           2 :     else if (osExtension == "jpg")
    2567           1 :         oAssetTemplate["type"] = "image/jpeg";
    2568           1 :     else if (osExtension == "webp")
    2569           1 :         oAssetTemplate["type"] = "image/webp";
    2570             : 
    2571             :     const std::map<GDALDataType, const char *> oMapDTToStac = {
    2572             :         {GDT_Int8, "int8"},
    2573             :         {GDT_Int16, "int16"},
    2574             :         {GDT_Int32, "int32"},
    2575             :         {GDT_Int64, "int64"},
    2576             :         {GDT_UInt8, "uint8"},
    2577             :         {GDT_UInt16, "uint16"},
    2578             :         {GDT_UInt32, "uint32"},
    2579             :         {GDT_UInt64, "uint64"},
    2580             :         // float16: 16-bit float; unhandled
    2581             :         {GDT_Float32, "float32"},
    2582             :         {GDT_Float64, "float64"},
    2583             :         {GDT_CInt16, "cint16"},
    2584             :         {GDT_CInt32, "cint32"},
    2585             :         // cfloat16: complex 16-bit float; unhandled
    2586             :         {GDT_CFloat32, "cfloat32"},
    2587             :         {GDT_CFloat64, "cfloat64"},
    2588         118 :     };
    2589             : 
    2590         118 :     CPLJSONArray oBands;
    2591          59 :     int iBand = 1;
    2592          59 :     bool bEOExtensionUsed = false;
    2593         207 :     for (const auto &bandMD : aoBandMetadata)
    2594             :     {
    2595         296 :         CPLJSONObject oBand;
    2596         148 :         oBand["name"] = bandMD.osDescription.empty()
    2597         296 :                             ? std::string(CPLSPrintf("Band%d", iBand))
    2598         148 :                             : bandMD.osDescription;
    2599             : 
    2600         148 :         const auto oIter = oMapDTToStac.find(bandMD.eDT);
    2601         148 :         if (oIter != oMapDTToStac.end())
    2602         148 :             oBand["data_type"] = oIter->second;
    2603             : 
    2604         148 :         if (const char *pszCommonName =
    2605         148 :                 GDALGetSTACCommonNameFromColorInterp(bandMD.eColorInterp))
    2606             :         {
    2607          60 :             bEOExtensionUsed = true;
    2608          60 :             oBand["eo:common_name"] = pszCommonName;
    2609             :         }
    2610         148 :         if (!bandMD.osCenterWaveLength.empty() && !bandMD.osFWHM.empty())
    2611             :         {
    2612           0 :             bEOExtensionUsed = true;
    2613             :             oBand["eo:center_wavelength"] =
    2614           0 :                 CPLAtof(bandMD.osCenterWaveLength.c_str());
    2615           0 :             oBand["eo:full_width_half_max"] = CPLAtof(bandMD.osFWHM.c_str());
    2616             :         }
    2617         148 :         ++iBand;
    2618         148 :         oBands.Add(oBand);
    2619             :     }
    2620          59 :     oAssetTemplate["bands"] = oBands;
    2621             : 
    2622          59 :     oRoot.Add("assets", CPLJSONObject());
    2623          59 :     oRoot.Add("links", CPLJSONArray());
    2624             : 
    2625          59 :     oExtensions.Add(
    2626             :         "https://stac-extensions.github.io/tiled-assets/v1.0.0/schema.json");
    2627          59 :     oExtensions.Add(
    2628             :         "https://stac-extensions.github.io/projection/v2.0.0/schema.json");
    2629          59 :     if (bEOExtensionUsed)
    2630          20 :         oExtensions.Add(
    2631             :             "https://stac-extensions.github.io/eo/v2.0.0/schema.json");
    2632             : 
    2633             :     // Serialize JSON document to file
    2634             :     const std::string osJSON =
    2635         118 :         CPLString(oRoot.Format(CPLJSONObject::PrettyFormat::Pretty))
    2636         177 :             .replaceAll("\\/", '/');
    2637          59 :     VSILFILE *f = VSIFOpenL(
    2638         118 :         CPLFormFilenameSafe(osDirectory.c_str(), "stacta.json", nullptr)
    2639             :             .c_str(),
    2640             :         "wb");
    2641          59 :     if (f)
    2642             :     {
    2643          59 :         VSIFWriteL(osJSON.data(), 1, osJSON.size(), f);
    2644          59 :         VSIFCloseL(f);
    2645             :     }
    2646          59 : }
    2647             : 
    2648             : /************************************************************************/
    2649             : /*                         GenerateOpenLayers()                         */
    2650             : /************************************************************************/
    2651             : 
    2652          92 : static void GenerateOpenLayers(
    2653             :     const std::string &osDirectory, const std::string &osTitle, double dfMinX,
    2654             :     double dfMinY, double dfMaxX, double dfMaxY, int nMinZoom, int nMaxZoom,
    2655             :     int nTileSize, const std::string &osExtension, const std::string &osURL,
    2656             :     const std::string &osCopyright, const gdal::TileMatrixSet &tms,
    2657             :     bool bInvertAxisTMS, const OGRSpatialReference &oSRS_TMS, bool bXYZ)
    2658             : {
    2659         184 :     std::map<std::string, std::string> substs;
    2660             : 
    2661             :     // For tests
    2662             :     const char *pszFmt =
    2663          92 :         atoi(CPLGetConfigOption("GDAL_RASTER_TILE_HTML_PREC", "17")) == 10
    2664             :             ? "%.10g"
    2665          92 :             : "%.17g";
    2666             : 
    2667          92 :     char *pszStr = CPLEscapeString(osTitle.c_str(), -1, CPLES_XML);
    2668          92 :     substs["xml_escaped_title"] = pszStr;
    2669          92 :     CPLFree(pszStr);
    2670          92 :     substs["ominx"] = CPLSPrintf(pszFmt, dfMinX);
    2671          92 :     substs["ominy"] = CPLSPrintf(pszFmt, dfMinY);
    2672          92 :     substs["omaxx"] = CPLSPrintf(pszFmt, dfMaxX);
    2673          92 :     substs["omaxy"] = CPLSPrintf(pszFmt, dfMaxY);
    2674          92 :     substs["center_x"] = CPLSPrintf(pszFmt, (dfMinX + dfMaxX) / 2);
    2675          92 :     substs["center_y"] = CPLSPrintf(pszFmt, (dfMinY + dfMaxY) / 2);
    2676          92 :     substs["minzoom"] = CPLSPrintf("%d", nMinZoom);
    2677          92 :     substs["maxzoom"] = CPLSPrintf("%d", nMaxZoom);
    2678          92 :     substs["tile_size"] = CPLSPrintf("%d", nTileSize);
    2679          92 :     substs["tileformat"] = osExtension;
    2680          92 :     substs["publishurl"] = osURL;
    2681          92 :     substs["copyright"] = osCopyright;
    2682          92 :     substs["sign_y"] = bXYZ ? "" : "-";
    2683             : 
    2684             :     CPLString s(R"raw(<!DOCTYPE html>
    2685             : <html>
    2686             : <head>
    2687             :     <title>%(xml_escaped_title)s</title>
    2688             :     <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
    2689             :     <meta http-equiv='imagetoolbar' content='no'/>
    2690             :     <style type="text/css"> v\:* {behavior:url(#default#VML);}
    2691             :         html, body { overflow: hidden; padding: 0; height: 100%; width: 100%; font-family: 'Lucida Grande',Geneva,Arial,Verdana,sans-serif; }
    2692             :         body { margin: 10px; background: #fff; }
    2693             :         h1 { margin: 0; padding: 6px; border:0; font-size: 20pt; }
    2694             :         #header { height: 43px; padding: 0; background-color: #eee; border: 1px solid #888; }
    2695             :         #subheader { height: 12px; text-align: right; font-size: 10px; color: #555;}
    2696             :         #map { height: 90%; border: 1px solid #888; }
    2697             :     </style>
    2698             :     <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">
    2699             :     <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@main/dist/en/v7.0.0/legacy/ol.js"></script>
    2700             :     <script src="https://unpkg.com/ol-layerswitcher@4.1.1"></script>
    2701             :     <link rel="stylesheet" href="https://unpkg.com/ol-layerswitcher@4.1.1/src/ol-layerswitcher.css" />
    2702             : </head>
    2703             : <body>
    2704             :     <div id="header"><h1>%(xml_escaped_title)s</h1></div>
    2705             :     <div id="subheader">Generated by <a href="https://gdal.org/programs/gdal_raster_tile.html">gdal raster tile</a>&nbsp;&nbsp;&nbsp;&nbsp;</div>
    2706             :     <div id="map" class="map"></div>
    2707             :     <div id="mouse-position"></div>
    2708             :     <script type="text/javascript">
    2709             :         var mousePositionControl = new ol.control.MousePosition({
    2710             :             className: 'custom-mouse-position',
    2711             :             target: document.getElementById('mouse-position'),
    2712             :             undefinedHTML: '&nbsp;'
    2713             :         });
    2714             :         var map = new ol.Map({
    2715             :             controls: ol.control.defaults.defaults().extend([mousePositionControl]),
    2716         184 :             target: 'map',)raw");
    2717             : 
    2718         103 :     if (tms.identifier() == "GoogleMapsCompatible" ||
    2719          11 :         tms.identifier() == "WorldCRS84Quad")
    2720             :     {
    2721          84 :         s += R"raw(
    2722             :             layers: [
    2723             :                 new ol.layer.Group({
    2724             :                         title: 'Base maps',
    2725             :                         layers: [
    2726             :                             new ol.layer.Tile({
    2727             :                                 title: 'OpenStreetMap',
    2728             :                                 type: 'base',
    2729             :                                 visible: true,
    2730             :                                 source: new ol.source.OSM()
    2731             :                             }),
    2732             :                         ]
    2733             :                 }),)raw";
    2734             :     }
    2735             : 
    2736          92 :     if (tms.identifier() == "GoogleMapsCompatible")
    2737             :     {
    2738          81 :         s += R"raw(new ol.layer.Group({
    2739             :                     title: 'Overlay',
    2740             :                     layers: [
    2741             :                         new ol.layer.Tile({
    2742             :                             title: 'Overlay',
    2743             :                             // opacity: 0.7,
    2744             :                             extent: [%(ominx)f, %(ominy)f,%(omaxx)f, %(omaxy)f],
    2745             :                             source: new ol.source.XYZ({
    2746             :                                 attributions: '%(copyright)s',
    2747             :                                 minZoom: %(minzoom)d,
    2748             :                                 maxZoom: %(maxzoom)d,
    2749             :                                 url: './{z}/{x}/{%(sign_y)sy}.%(tileformat)s',
    2750             :                                 tileSize: [%(tile_size)d, %(tile_size)d]
    2751             :                             })
    2752             :                         }),
    2753             :                     ]
    2754             :                 }),)raw";
    2755             :     }
    2756          11 :     else if (tms.identifier() == "WorldCRS84Quad")
    2757             :     {
    2758           3 :         const double base_res = 180.0 / nTileSize;
    2759           6 :         std::string resolutions = "[";
    2760           7 :         for (int i = 0; i <= nMaxZoom; ++i)
    2761             :         {
    2762           4 :             if (i > 0)
    2763           1 :                 resolutions += ",";
    2764           4 :             resolutions += CPLSPrintf(pszFmt, base_res / (1 << i));
    2765             :         }
    2766           3 :         resolutions += "]";
    2767           3 :         substs["resolutions"] = std::move(resolutions);
    2768             : 
    2769           3 :         if (bXYZ)
    2770             :         {
    2771           2 :             substs["origin"] = "[-180,90]";
    2772           2 :             substs["y_formula"] = "tileCoord[2]";
    2773             :         }
    2774             :         else
    2775             :         {
    2776           1 :             substs["origin"] = "[-180,-90]";
    2777           1 :             substs["y_formula"] = "- 1 - tileCoord[2]";
    2778             :         }
    2779             : 
    2780           3 :         s += R"raw(
    2781             :                 new ol.layer.Group({
    2782             :                     title: 'Overlay',
    2783             :                     layers: [
    2784             :                         new ol.layer.Tile({
    2785             :                             title: 'Overlay',
    2786             :                             // opacity: 0.7,
    2787             :                             extent: [%(ominx)f, %(ominy)f,%(omaxx)f, %(omaxy)f],
    2788             :                             source: new ol.source.TileImage({
    2789             :                                 attributions: '%(copyright)s',
    2790             :                                 projection: 'EPSG:4326',
    2791             :                                 minZoom: %(minzoom)d,
    2792             :                                 maxZoom: %(maxzoom)d,
    2793             :                                 tileGrid: new ol.tilegrid.TileGrid({
    2794             :                                     extent: [-180,-90,180,90],
    2795             :                                     origin: %(origin)s,
    2796             :                                     resolutions: %(resolutions)s,
    2797             :                                     tileSize: [%(tile_size)d, %(tile_size)d]
    2798             :                                 }),
    2799             :                                 tileUrlFunction: function(tileCoord) {
    2800             :                                     return ('./{z}/{x}/{y}.%(tileformat)s'
    2801             :                                         .replace('{z}', String(tileCoord[0]))
    2802             :                                         .replace('{x}', String(tileCoord[1]))
    2803             :                                         .replace('{y}', String(%(y_formula)s)));
    2804             :                                 },
    2805             :                             })
    2806             :                         }),
    2807             :                     ]
    2808             :                 }),)raw";
    2809             :     }
    2810             :     else
    2811             :     {
    2812          16 :         substs["maxres"] =
    2813          16 :             CPLSPrintf(pszFmt, tms.tileMatrixList()[nMinZoom].mResX);
    2814          16 :         std::string resolutions = "[";
    2815          22 :         for (int i = 0; i <= nMaxZoom; ++i)
    2816             :         {
    2817          14 :             if (i > 0)
    2818           6 :                 resolutions += ",";
    2819          14 :             resolutions += CPLSPrintf(pszFmt, tms.tileMatrixList()[i].mResX);
    2820             :         }
    2821           8 :         resolutions += "]";
    2822           8 :         substs["resolutions"] = std::move(resolutions);
    2823             : 
    2824          16 :         std::string matrixsizes = "[";
    2825          22 :         for (int i = 0; i <= nMaxZoom; ++i)
    2826             :         {
    2827          14 :             if (i > 0)
    2828           6 :                 matrixsizes += ",";
    2829             :             matrixsizes +=
    2830          14 :                 CPLSPrintf("[%d,%d]", tms.tileMatrixList()[i].mMatrixWidth,
    2831          28 :                            tms.tileMatrixList()[i].mMatrixHeight);
    2832             :         }
    2833           8 :         matrixsizes += "]";
    2834           8 :         substs["matrixsizes"] = std::move(matrixsizes);
    2835             : 
    2836           8 :         double dfTopLeftX = tms.tileMatrixList()[0].mTopLeftX;
    2837           8 :         double dfTopLeftY = tms.tileMatrixList()[0].mTopLeftY;
    2838           8 :         if (bInvertAxisTMS)
    2839           0 :             std::swap(dfTopLeftX, dfTopLeftY);
    2840             : 
    2841           8 :         if (bXYZ)
    2842             :         {
    2843          12 :             substs["origin"] =
    2844          12 :                 CPLSPrintf("[%.17g,%.17g]", dfTopLeftX, dfTopLeftY);
    2845           6 :             substs["y_formula"] = "tileCoord[2]";
    2846             :         }
    2847             :         else
    2848             :         {
    2849           4 :             substs["origin"] = CPLSPrintf(
    2850             :                 "[%.17g,%.17g]", dfTopLeftX,
    2851           2 :                 dfTopLeftY - tms.tileMatrixList()[0].mResY *
    2852           6 :                                  tms.tileMatrixList()[0].mTileHeight);
    2853           2 :             substs["y_formula"] = "- 1 - tileCoord[2]";
    2854             :         }
    2855             : 
    2856          16 :         substs["tilegrid_extent"] =
    2857             :             CPLSPrintf("[%.17g,%.17g,%.17g,%.17g]", dfTopLeftX,
    2858           8 :                        dfTopLeftY - tms.tileMatrixList()[0].mMatrixHeight *
    2859           8 :                                         tms.tileMatrixList()[0].mResY *
    2860           8 :                                         tms.tileMatrixList()[0].mTileHeight,
    2861           8 :                        dfTopLeftX + tms.tileMatrixList()[0].mMatrixWidth *
    2862           8 :                                         tms.tileMatrixList()[0].mResX *
    2863           8 :                                         tms.tileMatrixList()[0].mTileWidth,
    2864          32 :                        dfTopLeftY);
    2865             : 
    2866           8 :         s += R"raw(
    2867             :             layers: [
    2868             :                 new ol.layer.Group({
    2869             :                     title: 'Overlay',
    2870             :                     layers: [
    2871             :                         new ol.layer.Tile({
    2872             :                             title: 'Overlay',
    2873             :                             // opacity: 0.7,
    2874             :                             extent: [%(ominx)f, %(ominy)f,%(omaxx)f, %(omaxy)f],
    2875             :                             source: new ol.source.TileImage({
    2876             :                                 attributions: '%(copyright)s',
    2877             :                                 minZoom: %(minzoom)d,
    2878             :                                 maxZoom: %(maxzoom)d,
    2879             :                                 tileGrid: new ol.tilegrid.TileGrid({
    2880             :                                     extent: %(tilegrid_extent)s,
    2881             :                                     origin: %(origin)s,
    2882             :                                     resolutions: %(resolutions)s,
    2883             :                                     sizes: %(matrixsizes)s,
    2884             :                                     tileSize: [%(tile_size)d, %(tile_size)d]
    2885             :                                 }),
    2886             :                                 tileUrlFunction: function(tileCoord) {
    2887             :                                     return ('./{z}/{x}/{y}.%(tileformat)s'
    2888             :                                         .replace('{z}', String(tileCoord[0]))
    2889             :                                         .replace('{x}', String(tileCoord[1]))
    2890             :                                         .replace('{y}', String(%(y_formula)s)));
    2891             :                                 },
    2892             :                             })
    2893             :                         }),
    2894             :                     ]
    2895             :                 }),)raw";
    2896             :     }
    2897             : 
    2898          92 :     s += R"raw(
    2899             :             ],
    2900             :             view: new ol.View({
    2901             :                 center: [%(center_x)f, %(center_y)f],)raw";
    2902             : 
    2903         103 :     if (tms.identifier() == "GoogleMapsCompatible" ||
    2904          11 :         tms.identifier() == "WorldCRS84Quad")
    2905             :     {
    2906          84 :         substs["view_zoom"] = substs["minzoom"];
    2907          84 :         if (tms.identifier() == "WorldCRS84Quad")
    2908             :         {
    2909           3 :             substs["view_zoom"] = CPLSPrintf("%d", nMinZoom + 1);
    2910             :         }
    2911             : 
    2912          84 :         s += R"raw(
    2913             :                 zoom: %(view_zoom)d,)raw";
    2914             :     }
    2915             :     else
    2916             :     {
    2917           8 :         s += R"raw(
    2918             :                 resolution: %(maxres)f,)raw";
    2919             :     }
    2920             : 
    2921          92 :     if (tms.identifier() == "WorldCRS84Quad")
    2922             :     {
    2923           3 :         s += R"raw(
    2924             :                 projection: 'EPSG:4326',)raw";
    2925             :     }
    2926          89 :     else if (!oSRS_TMS.IsEmpty() && tms.identifier() != "GoogleMapsCompatible")
    2927             :     {
    2928           7 :         const char *pszAuthName = oSRS_TMS.GetAuthorityName();
    2929           7 :         const char *pszAuthCode = oSRS_TMS.GetAuthorityCode();
    2930           7 :         if (pszAuthName && pszAuthCode && EQUAL(pszAuthName, "EPSG"))
    2931             :         {
    2932           5 :             substs["epsg_code"] = pszAuthCode;
    2933           5 :             if (oSRS_TMS.IsGeographic())
    2934             :             {
    2935           3 :                 substs["units"] = "deg";
    2936             :             }
    2937             :             else
    2938             :             {
    2939           2 :                 const char *pszUnits = "";
    2940           2 :                 if (oSRS_TMS.GetLinearUnits(&pszUnits) == 1.0)
    2941           2 :                     substs["units"] = "m";
    2942             :                 else
    2943           0 :                     substs["units"] = pszUnits;
    2944             :             }
    2945           5 :             s += R"raw(
    2946             :                 projection: new ol.proj.Projection({code: 'EPSG:%(epsg_code)s', units:'%(units)s'}),)raw";
    2947             :         }
    2948             :     }
    2949             : 
    2950          92 :     s += R"raw(
    2951             :             })
    2952             :         });)raw";
    2953             : 
    2954         103 :     if (tms.identifier() == "GoogleMapsCompatible" ||
    2955          11 :         tms.identifier() == "WorldCRS84Quad")
    2956             :     {
    2957          84 :         s += R"raw(
    2958             :         map.addControl(new ol.control.LayerSwitcher());)raw";
    2959             :     }
    2960             : 
    2961          92 :     s += R"raw(
    2962             :     </script>
    2963             : </body>
    2964             : </html>)raw";
    2965             : 
    2966          92 :     ApplySubstitutions(s, substs);
    2967             : 
    2968          92 :     VSILFILE *f = VSIFOpenL(
    2969         184 :         CPLFormFilenameSafe(osDirectory.c_str(), "openlayers.html", nullptr)
    2970             :             .c_str(),
    2971             :         "wb");
    2972          92 :     if (f)
    2973             :     {
    2974          92 :         VSIFWriteL(s.data(), 1, s.size(), f);
    2975          92 :         VSIFCloseL(f);
    2976             :     }
    2977          92 : }
    2978             : 
    2979             : /************************************************************************/
    2980             : /*                         GetTileBoundingBox()                         */
    2981             : /************************************************************************/
    2982             : 
    2983          48 : static void GetTileBoundingBox(int nTileX, int nTileY, int nTileZ,
    2984             :                                const gdal::TileMatrixSet *poTMS,
    2985             :                                bool bInvertAxisTMS,
    2986             :                                OGRCoordinateTransformation *poCTToWGS84,
    2987             :                                double &dfTLX, double &dfTLY, double &dfTRX,
    2988             :                                double &dfTRY, double &dfLLX, double &dfLLY,
    2989             :                                double &dfLRX, double &dfLRY)
    2990             : {
    2991             :     gdal::TileMatrixSet::TileMatrix tileMatrix =
    2992          96 :         poTMS->tileMatrixList()[nTileZ];
    2993          48 :     if (bInvertAxisTMS)
    2994           0 :         std::swap(tileMatrix.mTopLeftX, tileMatrix.mTopLeftY);
    2995             : 
    2996          48 :     dfTLX = tileMatrix.mTopLeftX +
    2997          48 :             nTileX * tileMatrix.mResX * tileMatrix.mTileWidth;
    2998          48 :     dfTLY = tileMatrix.mTopLeftY -
    2999          48 :             nTileY * tileMatrix.mResY * tileMatrix.mTileHeight;
    3000          48 :     poCTToWGS84->Transform(1, &dfTLX, &dfTLY);
    3001             : 
    3002          48 :     dfTRX = tileMatrix.mTopLeftX +
    3003          48 :             (nTileX + 1) * tileMatrix.mResX * tileMatrix.mTileWidth;
    3004          48 :     dfTRY = tileMatrix.mTopLeftY -
    3005          48 :             nTileY * tileMatrix.mResY * tileMatrix.mTileHeight;
    3006          48 :     poCTToWGS84->Transform(1, &dfTRX, &dfTRY);
    3007             : 
    3008          48 :     dfLLX = tileMatrix.mTopLeftX +
    3009          48 :             nTileX * tileMatrix.mResX * tileMatrix.mTileWidth;
    3010          48 :     dfLLY = tileMatrix.mTopLeftY -
    3011          48 :             (nTileY + 1) * tileMatrix.mResY * tileMatrix.mTileHeight;
    3012          48 :     poCTToWGS84->Transform(1, &dfLLX, &dfLLY);
    3013             : 
    3014          48 :     dfLRX = tileMatrix.mTopLeftX +
    3015          48 :             (nTileX + 1) * tileMatrix.mResX * tileMatrix.mTileWidth;
    3016          48 :     dfLRY = tileMatrix.mTopLeftY -
    3017          48 :             (nTileY + 1) * tileMatrix.mResY * tileMatrix.mTileHeight;
    3018          48 :     poCTToWGS84->Transform(1, &dfLRX, &dfLRY);
    3019          48 : }
    3020             : 
    3021             : /************************************************************************/
    3022             : /*                            GenerateKML()                             */
    3023             : /************************************************************************/
    3024             : 
    3025             : namespace
    3026             : {
    3027             : struct TileCoordinates
    3028             : {
    3029             :     int nTileX = 0;
    3030             :     int nTileY = 0;
    3031             :     int nTileZ = 0;
    3032             : };
    3033             : }  // namespace
    3034             : 
    3035          30 : static void GenerateKML(const std::string &osDirectory,
    3036             :                         const std::string &osTitle, int nTileX, int nTileY,
    3037             :                         int nTileZ, int nTileSize,
    3038             :                         const std::string &osExtension,
    3039             :                         const std::string &osURL,
    3040             :                         const gdal::TileMatrixSet *poTMS, bool bInvertAxisTMS,
    3041             :                         const std::string &convention,
    3042             :                         OGRCoordinateTransformation *poCTToWGS84,
    3043             :                         const std::vector<TileCoordinates> &children)
    3044             : {
    3045          60 :     std::map<std::string, std::string> substs;
    3046             : 
    3047          30 :     const bool bIsTileKML = nTileX >= 0;
    3048             : 
    3049             :     // For tests
    3050             :     const char *pszFmt =
    3051          30 :         atoi(CPLGetConfigOption("GDAL_RASTER_TILE_KML_PREC", "14")) == 10
    3052             :             ? "%.10f"
    3053          30 :             : "%.14f";
    3054             : 
    3055          30 :     substs["tx"] = CPLSPrintf("%d", nTileX);
    3056          30 :     substs["tz"] = CPLSPrintf("%d", nTileZ);
    3057          30 :     substs["tileformat"] = osExtension;
    3058          30 :     substs["minlodpixels"] = CPLSPrintf("%d", nTileSize / 2);
    3059          60 :     substs["maxlodpixels"] =
    3060          60 :         children.empty() ? "-1" : CPLSPrintf("%d", nTileSize * 8);
    3061             : 
    3062          30 :     double dfTLX = 0;
    3063          30 :     double dfTLY = 0;
    3064          30 :     double dfTRX = 0;
    3065          30 :     double dfTRY = 0;
    3066          30 :     double dfLLX = 0;
    3067          30 :     double dfLLY = 0;
    3068          30 :     double dfLRX = 0;
    3069          30 :     double dfLRY = 0;
    3070             : 
    3071          30 :     int nFileY = -1;
    3072          30 :     if (!bIsTileKML)
    3073             :     {
    3074           6 :         char *pszStr = CPLEscapeString(osTitle.c_str(), -1, CPLES_XML);
    3075           6 :         substs["xml_escaped_title"] = pszStr;
    3076           6 :         CPLFree(pszStr);
    3077             :     }
    3078             :     else
    3079             :     {
    3080          24 :         nFileY = GetFileY(nTileY, poTMS->tileMatrixList()[nTileZ], convention);
    3081          24 :         substs["realtiley"] = CPLSPrintf("%d", nFileY);
    3082          48 :         substs["xml_escaped_title"] =
    3083          48 :             CPLSPrintf("%d/%d/%d.kml", nTileZ, nTileX, nFileY);
    3084             : 
    3085          24 :         GetTileBoundingBox(nTileX, nTileY, nTileZ, poTMS, bInvertAxisTMS,
    3086             :                            poCTToWGS84, dfTLX, dfTLY, dfTRX, dfTRY, dfLLX,
    3087             :                            dfLLY, dfLRX, dfLRY);
    3088             :     }
    3089             : 
    3090          60 :     substs["drawOrder"] = CPLSPrintf("%d", nTileX == 0  ? 2 * nTileZ + 1
    3091          19 :                                            : nTileX > 0 ? 2 * nTileZ
    3092          79 :                                                         : 0);
    3093             : 
    3094          30 :     substs["url"] = osURL.empty() && bIsTileKML ? "../../" : "";
    3095             : 
    3096          30 :     const bool bIsRectangle =
    3097          30 :         (dfTLX == dfLLX && dfTRX == dfLRX && dfTLY == dfTRY && dfLLY == dfLRY);
    3098          30 :     const bool bUseGXNamespace = bIsTileKML && !bIsRectangle;
    3099             : 
    3100          60 :     substs["xmlns_gx"] = bUseGXNamespace
    3101             :                              ? " xmlns:gx=\"http://www.google.com/kml/ext/2.2\""
    3102          60 :                              : "";
    3103             : 
    3104             :     CPLString s(R"raw(<?xml version="1.0" encoding="utf-8"?>
    3105             : <kml xmlns="http://www.opengis.net/kml/2.2"%(xmlns_gx)s>
    3106             :   <Document>
    3107             :     <name>%(xml_escaped_title)s</name>
    3108             :     <description></description>
    3109             :     <Style>
    3110             :       <ListStyle id="hideChildren">
    3111             :         <listItemType>checkHideChildren</listItemType>
    3112             :       </ListStyle>
    3113             :     </Style>
    3114          60 : )raw");
    3115          30 :     ApplySubstitutions(s, substs);
    3116             : 
    3117          30 :     if (bIsTileKML)
    3118             :     {
    3119             :         CPLString s2(R"raw(    <Region>
    3120             :       <LatLonAltBox>
    3121             :         <north>%(north)f</north>
    3122             :         <south>%(south)f</south>
    3123             :         <east>%(east)f</east>
    3124             :         <west>%(west)f</west>
    3125             :       </LatLonAltBox>
    3126             :       <Lod>
    3127             :         <minLodPixels>%(minlodpixels)d</minLodPixels>
    3128             :         <maxLodPixels>%(maxlodpixels)d</maxLodPixels>
    3129             :       </Lod>
    3130             :     </Region>
    3131             :     <GroundOverlay>
    3132             :       <drawOrder>%(drawOrder)d</drawOrder>
    3133             :       <Icon>
    3134             :         <href>%(realtiley)d.%(tileformat)s</href>
    3135             :       </Icon>
    3136             :       <LatLonBox>
    3137             :         <north>%(north)f</north>
    3138             :         <south>%(south)f</south>
    3139             :         <east>%(east)f</east>
    3140             :         <west>%(west)f</west>
    3141             :       </LatLonBox>
    3142          48 : )raw");
    3143             : 
    3144          24 :         if (!bIsRectangle)
    3145             :         {
    3146             :             s2 +=
    3147           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>
    3148             : )raw";
    3149             :         }
    3150             : 
    3151          24 :         s2 += R"raw(    </GroundOverlay>
    3152             : )raw";
    3153          24 :         substs["north"] = CPLSPrintf(pszFmt, std::max(dfTLY, dfTRY));
    3154          24 :         substs["south"] = CPLSPrintf(pszFmt, std::min(dfLLY, dfLRY));
    3155          24 :         substs["east"] = CPLSPrintf(pszFmt, std::max(dfTRX, dfLRX));
    3156          24 :         substs["west"] = CPLSPrintf(pszFmt, std::min(dfLLX, dfTLX));
    3157             : 
    3158          24 :         if (!bIsRectangle)
    3159             :         {
    3160           1 :             substs["TLX"] = CPLSPrintf(pszFmt, dfTLX);
    3161           1 :             substs["TLY"] = CPLSPrintf(pszFmt, dfTLY);
    3162           1 :             substs["TRX"] = CPLSPrintf(pszFmt, dfTRX);
    3163           1 :             substs["TRY"] = CPLSPrintf(pszFmt, dfTRY);
    3164           1 :             substs["LRX"] = CPLSPrintf(pszFmt, dfLRX);
    3165           1 :             substs["LRY"] = CPLSPrintf(pszFmt, dfLRY);
    3166           1 :             substs["LLX"] = CPLSPrintf(pszFmt, dfLLX);
    3167           1 :             substs["LLY"] = CPLSPrintf(pszFmt, dfLLY);
    3168             :         }
    3169             : 
    3170          24 :         ApplySubstitutions(s2, substs);
    3171          24 :         s += s2;
    3172             :     }
    3173             : 
    3174          54 :     for (const auto &child : children)
    3175             :     {
    3176          24 :         substs["tx"] = CPLSPrintf("%d", child.nTileX);
    3177          24 :         substs["tz"] = CPLSPrintf("%d", child.nTileZ);
    3178          48 :         substs["realtiley"] = CPLSPrintf(
    3179          24 :             "%d", GetFileY(child.nTileY, poTMS->tileMatrixList()[child.nTileZ],
    3180          48 :                            convention));
    3181             : 
    3182          24 :         GetTileBoundingBox(child.nTileX, child.nTileY, child.nTileZ, poTMS,
    3183             :                            bInvertAxisTMS, poCTToWGS84, dfTLX, dfTLY, dfTRX,
    3184             :                            dfTRY, dfLLX, dfLLY, dfLRX, dfLRY);
    3185             : 
    3186             :         CPLString s2(R"raw(    <NetworkLink>
    3187             :       <name>%(tz)d/%(tx)d/%(realtiley)d.%(tileformat)s</name>
    3188             :       <Region>
    3189             :         <LatLonAltBox>
    3190             :           <north>%(north)f</north>
    3191             :           <south>%(south)f</south>
    3192             :           <east>%(east)f</east>
    3193             :           <west>%(west)f</west>
    3194             :         </LatLonAltBox>
    3195             :         <Lod>
    3196             :           <minLodPixels>%(minlodpixels)d</minLodPixels>
    3197             :           <maxLodPixels>-1</maxLodPixels>
    3198             :         </Lod>
    3199             :       </Region>
    3200             :       <Link>
    3201             :         <href>%(url)s%(tz)d/%(tx)d/%(realtiley)d.kml</href>
    3202             :         <viewRefreshMode>onRegion</viewRefreshMode>
    3203             :         <viewFormat/>
    3204             :       </Link>
    3205             :     </NetworkLink>
    3206          48 : )raw");
    3207          24 :         substs["north"] = CPLSPrintf(pszFmt, std::max(dfTLY, dfTRY));
    3208          24 :         substs["south"] = CPLSPrintf(pszFmt, std::min(dfLLY, dfLRY));
    3209          24 :         substs["east"] = CPLSPrintf(pszFmt, std::max(dfTRX, dfLRX));
    3210          24 :         substs["west"] = CPLSPrintf(pszFmt, std::min(dfLLX, dfTLX));
    3211          24 :         ApplySubstitutions(s2, substs);
    3212          24 :         s += s2;
    3213             :     }
    3214             : 
    3215          30 :     s += R"raw(</Document>
    3216             : </kml>)raw";
    3217             : 
    3218          60 :     std::string osFilename(osDirectory);
    3219          30 :     if (!bIsTileKML)
    3220             :     {
    3221             :         osFilename =
    3222           6 :             CPLFormFilenameSafe(osFilename.c_str(), "doc.kml", nullptr);
    3223             :     }
    3224             :     else
    3225             :     {
    3226          48 :         osFilename = CPLFormFilenameSafe(osFilename.c_str(),
    3227          24 :                                          CPLSPrintf("%d", nTileZ), nullptr);
    3228          48 :         osFilename = CPLFormFilenameSafe(osFilename.c_str(),
    3229          24 :                                          CPLSPrintf("%d", nTileX), nullptr);
    3230          48 :         osFilename = CPLFormFilenameSafe(osFilename.c_str(),
    3231          24 :                                          CPLSPrintf("%d.kml", nFileY), nullptr);
    3232             :     }
    3233             : 
    3234          30 :     VSILFILE *f = VSIFOpenL(osFilename.c_str(), "wb");
    3235          30 :     if (f)
    3236             :     {
    3237          30 :         VSIFWriteL(s.data(), 1, s.size(), f);
    3238          30 :         VSIFCloseL(f);
    3239             :     }
    3240          30 : }
    3241             : 
    3242             : namespace
    3243             : {
    3244             : 
    3245             : /************************************************************************/
    3246             : /*                           ResourceManager                            */
    3247             : /************************************************************************/
    3248             : 
    3249             : // Generic cache managing resources
    3250             : template <class Resource> class ResourceManager /* non final */
    3251             : {
    3252             :   public:
    3253         249 :     virtual ~ResourceManager() = default;
    3254             : 
    3255          48 :     std::unique_ptr<Resource> AcquireResources()
    3256             :     {
    3257          96 :         std::lock_guard oLock(m_oMutex);
    3258          48 :         if (!m_oResources.empty())
    3259             :         {
    3260           0 :             auto ret = std::move(m_oResources.back());
    3261           0 :             m_oResources.pop_back();
    3262           0 :             return ret;
    3263             :         }
    3264             : 
    3265          48 :         return CreateResources();
    3266             :     }
    3267             : 
    3268          48 :     void ReleaseResources(std::unique_ptr<Resource> resources)
    3269             :     {
    3270          96 :         std::lock_guard oLock(m_oMutex);
    3271          48 :         m_oResources.push_back(std::move(resources));
    3272          48 :     }
    3273             : 
    3274           0 :     void SetError()
    3275             :     {
    3276           0 :         std::lock_guard oLock(m_oMutex);
    3277           0 :         if (m_errorMsg.empty())
    3278           0 :             m_errorMsg = CPLGetLastErrorMsg();
    3279           0 :     }
    3280             : 
    3281          12 :     const std::string &GetErrorMsg() const
    3282             :     {
    3283          12 :         std::lock_guard oLock(m_oMutex);
    3284          24 :         return m_errorMsg;
    3285             :     }
    3286             : 
    3287             :   protected:
    3288             :     virtual std::unique_ptr<Resource> CreateResources() = 0;
    3289             : 
    3290             :   private:
    3291             :     mutable std::mutex m_oMutex{};
    3292             :     std::vector<std::unique_ptr<Resource>> m_oResources{};
    3293             :     std::string m_errorMsg{};
    3294             : };
    3295             : 
    3296             : /************************************************************************/
    3297             : /*                      PerThreadMaxZoomResources                       */
    3298             : /************************************************************************/
    3299             : 
    3300             : // Per-thread resources for generation of tiles at full resolution
    3301             : struct PerThreadMaxZoomResources
    3302             : {
    3303             :     struct GDALDatasetReleaser
    3304             :     {
    3305          24 :         void operator()(GDALDataset *poDS)
    3306             :         {
    3307          24 :             if (poDS)
    3308          24 :                 poDS->ReleaseRef();
    3309          24 :         }
    3310             :     };
    3311             : 
    3312             :     std::unique_ptr<GDALDataset, GDALDatasetReleaser> poSrcDS{};
    3313             :     std::vector<GByte> dstBuffer{};
    3314             :     std::unique_ptr<FakeMaxZoomDataset> poFakeMaxZoomDS{};
    3315             :     std::unique_ptr<void, decltype(&GDALDestroyTransformer)> poTransformer{
    3316             :         nullptr, GDALDestroyTransformer};
    3317             :     std::unique_ptr<GDALWarpOperation> poWO{};
    3318             : };
    3319             : 
    3320             : /************************************************************************/
    3321             : /*                   PerThreadMaxZoomResourceManager                    */
    3322             : /************************************************************************/
    3323             : 
    3324             : // Manage a cache of PerThreadMaxZoomResources instances
    3325             : class PerThreadMaxZoomResourceManager final
    3326             :     : public ResourceManager<PerThreadMaxZoomResources>
    3327             : {
    3328             :   public:
    3329         147 :     PerThreadMaxZoomResourceManager(GDALDataset *poSrcDS,
    3330             :                                     const GDALWarpOptions *psWO,
    3331             :                                     void *pTransformerArg,
    3332             :                                     const FakeMaxZoomDataset &oFakeMaxZoomDS,
    3333             :                                     size_t nBufferSize)
    3334         147 :         : m_poSrcDS(poSrcDS), m_psWOSource(psWO),
    3335             :           m_pTransformerArg(pTransformerArg), m_oFakeMaxZoomDS(oFakeMaxZoomDS),
    3336         147 :           m_nBufferSize(nBufferSize)
    3337             :     {
    3338         147 :     }
    3339             : 
    3340             :   protected:
    3341          24 :     std::unique_ptr<PerThreadMaxZoomResources> CreateResources() override
    3342             :     {
    3343          48 :         auto ret = std::make_unique<PerThreadMaxZoomResources>();
    3344             : 
    3345          24 :         ret->poSrcDS.reset(GDALGetThreadSafeDataset(m_poSrcDS, GDAL_OF_RASTER));
    3346          24 :         if (!ret->poSrcDS)
    3347           0 :             return nullptr;
    3348             : 
    3349             :         try
    3350             :         {
    3351          24 :             ret->dstBuffer.resize(m_nBufferSize);
    3352             :         }
    3353           0 :         catch (const std::exception &)
    3354             :         {
    3355           0 :             CPLError(CE_Failure, CPLE_OutOfMemory,
    3356             :                      "Out of memory allocating temporary buffer");
    3357           0 :             return nullptr;
    3358             :         }
    3359             : 
    3360          24 :         ret->poFakeMaxZoomDS = m_oFakeMaxZoomDS.Clone(ret->dstBuffer);
    3361             : 
    3362          24 :         ret->poTransformer.reset(GDALCloneTransformer(m_pTransformerArg));
    3363          24 :         if (!ret->poTransformer)
    3364           0 :             return nullptr;
    3365             : 
    3366             :         auto psWO =
    3367             :             std::unique_ptr<GDALWarpOptions, decltype(&GDALDestroyWarpOptions)>(
    3368          48 :                 GDALCloneWarpOptions(m_psWOSource), GDALDestroyWarpOptions);
    3369          24 :         if (!psWO)
    3370           0 :             return nullptr;
    3371             : 
    3372          24 :         psWO->hSrcDS = GDALDataset::ToHandle(ret->poSrcDS.get());
    3373          24 :         psWO->hDstDS = GDALDataset::ToHandle(ret->poFakeMaxZoomDS.get());
    3374          24 :         psWO->pTransformerArg = ret->poTransformer.get();
    3375          24 :         psWO->pfnTransformer = m_psWOSource->pfnTransformer;
    3376             : 
    3377          24 :         ret->poWO = std::make_unique<GDALWarpOperation>();
    3378          24 :         if (ret->poWO->Initialize(psWO.get()) != CE_None)
    3379           0 :             return nullptr;
    3380             : 
    3381          24 :         return ret;
    3382             :     }
    3383             : 
    3384             :   private:
    3385             :     GDALDataset *const m_poSrcDS;
    3386             :     const GDALWarpOptions *const m_psWOSource;
    3387             :     void *const m_pTransformerArg;
    3388             :     const FakeMaxZoomDataset &m_oFakeMaxZoomDS;
    3389             :     const size_t m_nBufferSize;
    3390             : 
    3391             :     CPL_DISALLOW_COPY_ASSIGN(PerThreadMaxZoomResourceManager)
    3392             : };
    3393             : 
    3394             : /************************************************************************/
    3395             : /*                     PerThreadLowerZoomResources                      */
    3396             : /************************************************************************/
    3397             : 
    3398             : // Per-thread resources for generation of tiles at zoom level < max
    3399             : struct PerThreadLowerZoomResources
    3400             : {
    3401             :     std::unique_ptr<GDALDataset> poSrcDS{};
    3402             : };
    3403             : 
    3404             : /************************************************************************/
    3405             : /*                  PerThreadLowerZoomResourceManager                   */
    3406             : /************************************************************************/
    3407             : 
    3408             : // Manage a cache of PerThreadLowerZoomResources instances
    3409             : class PerThreadLowerZoomResourceManager final
    3410             :     : public ResourceManager<PerThreadLowerZoomResources>
    3411             : {
    3412             :   public:
    3413         102 :     explicit PerThreadLowerZoomResourceManager(const MosaicDataset &oSrcDS)
    3414         102 :         : m_oSrcDS(oSrcDS)
    3415             :     {
    3416         102 :     }
    3417             : 
    3418             :   protected:
    3419          24 :     std::unique_ptr<PerThreadLowerZoomResources> CreateResources() override
    3420             :     {
    3421          24 :         auto ret = std::make_unique<PerThreadLowerZoomResources>();
    3422          24 :         ret->poSrcDS = m_oSrcDS.Clone();
    3423          24 :         return ret;
    3424             :     }
    3425             : 
    3426             :   private:
    3427             :     const MosaicDataset &m_oSrcDS;
    3428             : };
    3429             : 
    3430             : }  // namespace
    3431             : 
    3432             : /************************************************************************/
    3433             : /*           GDALRasterTileAlgorithm::ValidateOutputFormat()            */
    3434             : /************************************************************************/
    3435             : 
    3436         223 : bool GDALRasterTileAlgorithm::ValidateOutputFormat(GDALDataType eSrcDT) const
    3437             : {
    3438         223 :     if (m_format == "PNG")
    3439             :     {
    3440         194 :         if (m_poSrcDS->GetRasterCount() > 4)
    3441             :         {
    3442           2 :             ReportError(CE_Failure, CPLE_NotSupported,
    3443             :                         "Only up to 4 bands supported for PNG.");
    3444           2 :             return false;
    3445             :         }
    3446         192 :         if (eSrcDT != GDT_UInt8 && eSrcDT != GDT_UInt16)
    3447             :         {
    3448          10 :             ReportError(CE_Failure, CPLE_NotSupported,
    3449             :                         "Only Byte and UInt16 data types supported for PNG.");
    3450          10 :             return false;
    3451             :         }
    3452             :     }
    3453          29 :     else if (m_format == "JPEG")
    3454             :     {
    3455           8 :         if (m_poSrcDS->GetRasterCount() > 4)
    3456             :         {
    3457           1 :             ReportError(
    3458             :                 CE_Failure, CPLE_NotSupported,
    3459             :                 "Only up to 4 bands supported for JPEG (with alpha ignored).");
    3460           1 :             return false;
    3461             :         }
    3462             :         const bool bUInt16Supported =
    3463           7 :             strstr(m_poDstDriver->GetMetadataItem(GDAL_DMD_CREATIONDATATYPES),
    3464           7 :                    "UInt16") != nullptr;
    3465           7 :         if (eSrcDT != GDT_UInt8 && !(eSrcDT == GDT_UInt16 && bUInt16Supported))
    3466             :         {
    3467           1 :             ReportError(
    3468             :                 CE_Failure, CPLE_NotSupported,
    3469             :                 bUInt16Supported
    3470             :                     ? "Only Byte and UInt16 data types supported for JPEG."
    3471             :                     : "Only Byte data type supported for JPEG.");
    3472           1 :             return false;
    3473             :         }
    3474           6 :         if (eSrcDT == GDT_UInt16)
    3475             :         {
    3476           3 :             if (const char *pszNBITS =
    3477           6 :                     m_poSrcDS->GetRasterBand(1)->GetMetadataItem(
    3478           3 :                         "NBITS", "IMAGE_STRUCTURE"))
    3479             :             {
    3480           1 :                 if (atoi(pszNBITS) > 12)
    3481             :                 {
    3482           1 :                     ReportError(CE_Failure, CPLE_NotSupported,
    3483             :                                 "JPEG output only supported up to 12 bits");
    3484           1 :                     return false;
    3485             :                 }
    3486             :             }
    3487             :             else
    3488             :             {
    3489           2 :                 double adfMinMax[2] = {0, 0};
    3490           2 :                 m_poSrcDS->GetRasterBand(1)->ComputeRasterMinMax(
    3491           2 :                     /* bApproxOK = */ true, adfMinMax);
    3492           2 :                 if (adfMinMax[1] >= (1 << 12))
    3493             :                 {
    3494           1 :                     ReportError(CE_Failure, CPLE_NotSupported,
    3495             :                                 "JPEG output only supported up to 12 bits");
    3496           1 :                     return false;
    3497             :                 }
    3498             :             }
    3499             :         }
    3500             :     }
    3501          21 :     else if (m_format == "WEBP")
    3502             :     {
    3503           5 :         if (m_poSrcDS->GetRasterCount() != 3 &&
    3504           1 :             m_poSrcDS->GetRasterCount() != 4)
    3505             :         {
    3506           1 :             ReportError(CE_Failure, CPLE_NotSupported,
    3507             :                         "Only 3 or 4 bands supported for WEBP.");
    3508           1 :             return false;
    3509             :         }
    3510           3 :         if (eSrcDT != GDT_UInt8)
    3511             :         {
    3512           1 :             ReportError(CE_Failure, CPLE_NotSupported,
    3513             :                         "Only Byte data type supported for WEBP.");
    3514           1 :             return false;
    3515             :         }
    3516             :     }
    3517         205 :     return true;
    3518             : }
    3519             : 
    3520             : /************************************************************************/
    3521             : /*            GDALRasterTileAlgorithm::ComputeJobChunkSize()            */
    3522             : /************************************************************************/
    3523             : 
    3524             : // Given a number of tiles in the Y dimension being nTilesPerCol and
    3525             : // in the X dimension being nTilesPerRow, compute the (upper bound of)
    3526             : // number of jobs needed to be nYOuterIterations x nXOuterIterations,
    3527             : // with each job processing in average dfTilesYPerJob x dfTilesXPerJob
    3528             : // tiles.
    3529             : /* static */
    3530          31 : void GDALRasterTileAlgorithm::ComputeJobChunkSize(
    3531             :     int nMaxJobCount, int nTilesPerCol, int nTilesPerRow,
    3532             :     double &dfTilesYPerJob, int &nYOuterIterations, double &dfTilesXPerJob,
    3533             :     int &nXOuterIterations)
    3534             : {
    3535          31 :     CPLAssert(nMaxJobCount >= 1);
    3536          31 :     dfTilesYPerJob = static_cast<double>(nTilesPerCol) / nMaxJobCount;
    3537          31 :     nYOuterIterations = dfTilesYPerJob >= 1 ? nMaxJobCount : 1;
    3538             : 
    3539          62 :     dfTilesXPerJob = dfTilesYPerJob >= 1
    3540          31 :                          ? nTilesPerRow
    3541           9 :                          : static_cast<double>(nTilesPerRow) / nMaxJobCount;
    3542          31 :     nXOuterIterations = dfTilesYPerJob >= 1 ? 1 : nMaxJobCount;
    3543             : 
    3544          31 :     if (dfTilesYPerJob < 1 && dfTilesXPerJob < 1 &&
    3545           9 :         nTilesPerCol <= nMaxJobCount / nTilesPerRow)
    3546             :     {
    3547           9 :         dfTilesYPerJob = 1;
    3548           9 :         dfTilesXPerJob = 1;
    3549           9 :         nYOuterIterations = nTilesPerCol;
    3550           9 :         nXOuterIterations = nTilesPerRow;
    3551             :     }
    3552          31 : }
    3553             : 
    3554             : /************************************************************************/
    3555             : /*               GDALRasterTileAlgorithm::AddArgToArgv()                */
    3556             : /************************************************************************/
    3557             : 
    3558         208 : bool GDALRasterTileAlgorithm::AddArgToArgv(const GDALAlgorithmArg *arg,
    3559             :                                            CPLStringList &aosArgv) const
    3560             : {
    3561         208 :     aosArgv.push_back(CPLSPrintf("--%s", arg->GetName().c_str()));
    3562         208 :     if (arg->GetType() == GAAT_STRING)
    3563             :     {
    3564          74 :         aosArgv.push_back(arg->Get<std::string>().c_str());
    3565             :     }
    3566         134 :     else if (arg->GetType() == GAAT_STRING_LIST)
    3567             :     {
    3568          12 :         bool bFirst = true;
    3569          24 :         for (const std::string &s : arg->Get<std::vector<std::string>>())
    3570             :         {
    3571          12 :             if (!bFirst)
    3572             :             {
    3573           0 :                 aosArgv.push_back(CPLSPrintf("--%s", arg->GetName().c_str()));
    3574             :             }
    3575          12 :             bFirst = false;
    3576          12 :             aosArgv.push_back(s.c_str());
    3577             :         }
    3578             :     }
    3579         122 :     else if (arg->GetType() == GAAT_REAL)
    3580             :     {
    3581           0 :         aosArgv.push_back(CPLSPrintf("%.17g", arg->Get<double>()));
    3582             :     }
    3583         122 :     else if (arg->GetType() == GAAT_INTEGER)
    3584             :     {
    3585         122 :         aosArgv.push_back(CPLSPrintf("%d", arg->Get<int>()));
    3586             :     }
    3587           0 :     else if (arg->GetType() != GAAT_BOOLEAN)
    3588             :     {
    3589           0 :         ReportError(CE_Failure, CPLE_AppDefined,
    3590             :                     "Bug: argument of type %d not handled "
    3591             :                     "by gdal raster tile!",
    3592           0 :                     static_cast<int>(arg->GetType()));
    3593           0 :         return false;
    3594             :     }
    3595         208 :     return true;
    3596             : }
    3597             : 
    3598             : /************************************************************************/
    3599             : /*            GDALRasterTileAlgorithm::IsCompatibleOfSpawn()            */
    3600             : /************************************************************************/
    3601             : 
    3602          15 : bool GDALRasterTileAlgorithm::IsCompatibleOfSpawn(const char *&pszErrorMsg)
    3603             : {
    3604          15 :     pszErrorMsg = "";
    3605          15 :     if (!m_bIsNamedNonMemSrcDS)
    3606             :     {
    3607           1 :         pszErrorMsg = "Unnamed or memory dataset sources are not supported "
    3608             :                       "with spawn parallelization method";
    3609           1 :         return false;
    3610             :     }
    3611          14 :     if (cpl::starts_with(m_outputDir, "/vsimem/"))
    3612             :     {
    3613           4 :         pszErrorMsg = "/vsimem/ output directory not supported with spawn "
    3614             :                       "parallelization method";
    3615           4 :         return false;
    3616             :     }
    3617             : 
    3618          10 :     if (m_osGDALPath.empty())
    3619          10 :         m_osGDALPath = GDALGetGDALPath();
    3620          10 :     return !(m_osGDALPath.empty());
    3621             : }
    3622             : 
    3623             : /************************************************************************/
    3624             : /*                    GetProgressForChildProcesses()                    */
    3625             : /************************************************************************/
    3626             : 
    3627          19 : static void GetProgressForChildProcesses(
    3628             :     bool &bRet, std::vector<CPLSpawnedProcess *> &ahSpawnedProcesses,
    3629             :     std::vector<uint64_t> &anRemainingTilesForProcess, uint64_t &nCurTile,
    3630             :     uint64_t nTotalTiles, GDALProgressFunc pfnProgress, void *pProgressData)
    3631             : {
    3632          19 :     std::vector<unsigned int> anProgressState(ahSpawnedProcesses.size(), 0);
    3633          19 :     std::vector<unsigned int> anEndState(ahSpawnedProcesses.size(), 0);
    3634          19 :     std::vector<bool> abFinished(ahSpawnedProcesses.size(), false);
    3635          19 :     std::vector<unsigned int> anStartErrorState(ahSpawnedProcesses.size(), 0);
    3636             : 
    3637        1957 :     while (bRet)
    3638             :     {
    3639        1957 :         size_t iProcess = 0;
    3640        1957 :         size_t nFinished = 0;
    3641        9657 :         for (CPLSpawnedProcess *hSpawnedProcess : ahSpawnedProcesses)
    3642             :         {
    3643        7700 :             char ch = 0;
    3644       23100 :             if (abFinished[iProcess] ||
    3645        7700 :                 !CPLPipeRead(CPLSpawnAsyncGetInputFileHandle(hSpawnedProcess),
    3646        7700 :                              &ch, 1))
    3647             :             {
    3648           0 :                 ++nFinished;
    3649             :             }
    3650        7700 :             else if (ch == PROGRESS_MARKER[anProgressState[iProcess]])
    3651             :             {
    3652        1518 :                 ++anProgressState[iProcess];
    3653        1518 :                 if (anProgressState[iProcess] == sizeof(PROGRESS_MARKER))
    3654             :                 {
    3655         506 :                     anProgressState[iProcess] = 0;
    3656         506 :                     --anRemainingTilesForProcess[iProcess];
    3657         506 :                     ++nCurTile;
    3658         506 :                     if (bRet && pfnProgress)
    3659             :                     {
    3660          84 :                         if (!pfnProgress(static_cast<double>(nCurTile) /
    3661          84 :                                              static_cast<double>(nTotalTiles),
    3662             :                                          "", pProgressData))
    3663             :                         {
    3664           0 :                             CPLError(CE_Failure, CPLE_UserInterrupt,
    3665             :                                      "Process interrupted by user");
    3666           0 :                             bRet = false;
    3667           0 :                             return;
    3668             :                         }
    3669             :                     }
    3670             :                 }
    3671             :             }
    3672        6182 :             else if (ch == END_MARKER[anEndState[iProcess]])
    3673             :             {
    3674         518 :                 ++anEndState[iProcess];
    3675         518 :                 if (anEndState[iProcess] == sizeof(END_MARKER))
    3676             :                 {
    3677          74 :                     anEndState[iProcess] = 0;
    3678          74 :                     abFinished[iProcess] = true;
    3679          74 :                     ++nFinished;
    3680             :                 }
    3681             :             }
    3682        5664 :             else if (ch == ERROR_START_MARKER[anStartErrorState[iProcess]])
    3683             :             {
    3684        5580 :                 ++anStartErrorState[iProcess];
    3685        5580 :                 if (anStartErrorState[iProcess] == sizeof(ERROR_START_MARKER))
    3686             :                 {
    3687         310 :                     anStartErrorState[iProcess] = 0;
    3688         310 :                     uint32_t nErr = 0;
    3689         310 :                     CPLPipeRead(
    3690             :                         CPLSpawnAsyncGetInputFileHandle(hSpawnedProcess), &nErr,
    3691             :                         sizeof(nErr));
    3692         310 :                     uint32_t nNum = 0;
    3693         310 :                     CPLPipeRead(
    3694             :                         CPLSpawnAsyncGetInputFileHandle(hSpawnedProcess), &nNum,
    3695             :                         sizeof(nNum));
    3696         310 :                     uint16_t nMsgLen = 0;
    3697         310 :                     CPLPipeRead(
    3698             :                         CPLSpawnAsyncGetInputFileHandle(hSpawnedProcess),
    3699             :                         &nMsgLen, sizeof(nMsgLen));
    3700         620 :                     std::string osMsg;
    3701         310 :                     osMsg.resize(nMsgLen);
    3702         310 :                     CPLPipeRead(
    3703             :                         CPLSpawnAsyncGetInputFileHandle(hSpawnedProcess),
    3704         310 :                         &osMsg[0], nMsgLen);
    3705         310 :                     if (nErr <= CE_Fatal &&
    3706         310 :                         nNum <= CPLE_ObjectStorageGenericError)
    3707             :                     {
    3708         310 :                         bool bDone = false;
    3709         310 :                         if (nErr == CE_Debug)
    3710             :                         {
    3711         304 :                             auto nPos = osMsg.find(": ");
    3712         304 :                             if (nPos != std::string::npos)
    3713             :                             {
    3714         304 :                                 bDone = true;
    3715         608 :                                 CPLDebug(
    3716         608 :                                     osMsg.substr(0, nPos).c_str(),
    3717             :                                     "subprocess %d: %s",
    3718             :                                     static_cast<int>(iProcess),
    3719         608 :                                     osMsg.substr(nPos + strlen(": ")).c_str());
    3720             :                             }
    3721             :                         }
    3722             :                         // cppcheck-suppress knownConditionTrueFalse
    3723         310 :                         if (!bDone)
    3724             :                         {
    3725           6 :                             CPLError(nErr == CE_Fatal
    3726             :                                          ? CE_Failure
    3727             :                                          : static_cast<CPLErr>(nErr),
    3728             :                                      static_cast<CPLErrorNum>(nNum),
    3729             :                                      "Sub-process %d: %s",
    3730             :                                      static_cast<int>(iProcess), osMsg.c_str());
    3731             :                         }
    3732             :                     }
    3733             :                 }
    3734             :             }
    3735             :             else
    3736             :             {
    3737          84 :                 CPLErrorOnce(
    3738             :                     CE_Warning, CPLE_AppDefined,
    3739             :                     "Spurious character detected on stdout of child process");
    3740          84 :                 anProgressState[iProcess] = 0;
    3741          84 :                 if (ch == PROGRESS_MARKER[anProgressState[iProcess]])
    3742             :                 {
    3743          84 :                     ++anProgressState[iProcess];
    3744             :                 }
    3745             :             }
    3746        7700 :             ++iProcess;
    3747             :         }
    3748        1957 :         if (!bRet || nFinished == ahSpawnedProcesses.size())
    3749          19 :             break;
    3750             :     }
    3751             : }
    3752             : 
    3753             : /************************************************************************/
    3754             : /*                      WaitForSpawnedProcesses()                       */
    3755             : /************************************************************************/
    3756             : 
    3757          19 : void GDALRasterTileAlgorithm::WaitForSpawnedProcesses(
    3758             :     bool &bRet, const std::vector<std::string> &asCommandLines,
    3759             :     std::vector<CPLSpawnedProcess *> &ahSpawnedProcesses) const
    3760             : {
    3761          19 :     size_t iProcess = 0;
    3762          93 :     for (CPLSpawnedProcess *hSpawnedProcess : ahSpawnedProcesses)
    3763             :     {
    3764          74 :         CPL_IGNORE_RET_VAL(
    3765          74 :             CPLPipeWrite(CPLSpawnAsyncGetOutputFileHandle(hSpawnedProcess),
    3766             :                          STOP_MARKER, static_cast<int>(strlen(STOP_MARKER))));
    3767             : 
    3768          74 :         char ch = 0;
    3769          74 :         std::string errorMsg;
    3770          74 :         while (CPLPipeRead(CPLSpawnAsyncGetErrorFileHandle(hSpawnedProcess),
    3771          74 :                            &ch, 1))
    3772             :         {
    3773           0 :             if (ch == '\n')
    3774             :             {
    3775           0 :                 if (!errorMsg.empty())
    3776             :                 {
    3777           0 :                     if (cpl::starts_with(errorMsg, "ERROR "))
    3778             :                     {
    3779           0 :                         const auto nPos = errorMsg.find(": ");
    3780           0 :                         if (nPos != std::string::npos)
    3781           0 :                             errorMsg = errorMsg.substr(nPos + 1);
    3782           0 :                         ReportError(CE_Failure, CPLE_AppDefined, "%s",
    3783             :                                     errorMsg.c_str());
    3784             :                     }
    3785             :                     else
    3786             :                     {
    3787           0 :                         std::string osComp = "GDAL";
    3788           0 :                         const auto nPos = errorMsg.find(": ");
    3789           0 :                         if (nPos != std::string::npos)
    3790             :                         {
    3791           0 :                             osComp = errorMsg.substr(0, nPos);
    3792           0 :                             errorMsg = errorMsg.substr(nPos + 1);
    3793             :                         }
    3794           0 :                         CPLDebug(osComp.c_str(), "%s", errorMsg.c_str());
    3795             :                     }
    3796           0 :                     errorMsg.clear();
    3797             :                 }
    3798             :             }
    3799             :             else
    3800             :             {
    3801           0 :                 errorMsg += ch;
    3802             :             }
    3803             :         }
    3804             : 
    3805          74 :         if (CPLSpawnAsyncFinish(hSpawnedProcess, /* bWait = */ true,
    3806          74 :                                 /* bKill = */ false) != 0)
    3807             :         {
    3808           2 :             bRet = false;
    3809           2 :             ReportError(CE_Failure, CPLE_AppDefined,
    3810             :                         "Child process '%s' failed",
    3811           2 :                         asCommandLines[iProcess].c_str());
    3812             :         }
    3813          74 :         ++iProcess;
    3814             :     }
    3815          19 : }
    3816             : 
    3817             : /************************************************************************/
    3818             : /*               GDALRasterTileAlgorithm::GetMaxChildCount()            */
    3819             : /**********************************f**************************************/
    3820             : 
    3821          19 : int GDALRasterTileAlgorithm::GetMaxChildCount(int nMaxJobCount) const
    3822             : {
    3823             : #ifndef _WIN32
    3824             :     // Limit the number of jobs compared to how many file descriptors we have
    3825             :     // left
    3826             :     const int remainingFileDescriptorCount =
    3827          19 :         CPLGetRemainingFileDescriptorCount();
    3828          19 :     constexpr int SOME_MARGIN = 3;
    3829          19 :     constexpr int FD_PER_CHILD = 3; /* stdin, stdout and stderr */
    3830          19 :     if (FD_PER_CHILD * nMaxJobCount + SOME_MARGIN >
    3831             :         remainingFileDescriptorCount)
    3832             :     {
    3833           0 :         nMaxJobCount = std::max(
    3834           0 :             1, (remainingFileDescriptorCount - SOME_MARGIN) / FD_PER_CHILD);
    3835           0 :         ReportError(
    3836             :             CE_Warning, CPLE_AppDefined,
    3837             :             "Limiting the number of child workers to %d (instead of %d), "
    3838             :             "because there are not enough file descriptors left (%d)",
    3839           0 :             nMaxJobCount, m_numThreads, remainingFileDescriptorCount);
    3840             :     }
    3841             : #endif
    3842          19 :     return nMaxJobCount;
    3843             : }
    3844             : 
    3845             : /************************************************************************/
    3846             : /*                         SendConfigOptions()                          */
    3847             : /************************************************************************/
    3848             : 
    3849          50 : static void SendConfigOptions(CPLSpawnedProcess *hSpawnedProcess, bool &bRet)
    3850             : {
    3851             :     // Send most config options through pipe, to avoid leaking
    3852             :     // secrets when listing processes
    3853          50 :     auto handle = CPLSpawnAsyncGetOutputFileHandle(hSpawnedProcess);
    3854         150 :     for (auto pfnFunc : {&CPLGetConfigOptions, &CPLGetThreadLocalConfigOptions})
    3855             :     {
    3856         200 :         CPLStringList aosConfigOptions((*pfnFunc)());
    3857         222 :         for (const char *pszNameValue : aosConfigOptions)
    3858             :         {
    3859         122 :             if (!STARTS_WITH(pszNameValue, "GDAL_CACHEMAX") &&
    3860         122 :                 !STARTS_WITH(pszNameValue, "GDAL_NUM_THREADS"))
    3861             :             {
    3862         122 :                 constexpr const char *CONFIG_MARKER = "--config\n";
    3863         122 :                 bRet &= CPL_TO_BOOL(
    3864             :                     CPLPipeWrite(handle, CONFIG_MARKER,
    3865         122 :                                  static_cast<int>(strlen(CONFIG_MARKER))));
    3866         122 :                 char *pszEscaped = CPLEscapeString(pszNameValue, -1, CPLES_URL);
    3867         122 :                 bRet &= CPL_TO_BOOL(CPLPipeWrite(
    3868         122 :                     handle, pszEscaped, static_cast<int>(strlen(pszEscaped))));
    3869         122 :                 CPLFree(pszEscaped);
    3870         122 :                 bRet &= CPL_TO_BOOL(CPLPipeWrite(handle, "\n", 1));
    3871             :             }
    3872             :         }
    3873             :     }
    3874          50 :     constexpr const char *END_CONFIG_MARKER = "END_CONFIG\n";
    3875          50 :     bRet &=
    3876          50 :         CPL_TO_BOOL(CPLPipeWrite(handle, END_CONFIG_MARKER,
    3877          50 :                                  static_cast<int>(strlen(END_CONFIG_MARKER))));
    3878          50 : }
    3879             : 
    3880             : /************************************************************************/
    3881             : /*                      GenerateTilesForkMethod()                       */
    3882             : /************************************************************************/
    3883             : 
    3884             : #ifdef FORK_ALLOWED
    3885             : 
    3886             : namespace
    3887             : {
    3888             : struct ForkWorkStructure
    3889             : {
    3890             :     uint64_t nCacheMaxPerProcess = 0;
    3891             :     CPLStringList aosArgv{};
    3892             :     GDALDataset *poMemSrcDS{};
    3893             : };
    3894             : }  // namespace
    3895             : 
    3896             : static CPL_FILE_HANDLE pipeIn = CPL_FILE_INVALID_HANDLE;
    3897             : static CPL_FILE_HANDLE pipeOut = CPL_FILE_INVALID_HANDLE;
    3898             : 
    3899           0 : static int GenerateTilesForkMethod(CPL_FILE_HANDLE in, CPL_FILE_HANDLE out)
    3900             : {
    3901           0 :     pipeIn = in;
    3902           0 :     pipeOut = out;
    3903             : 
    3904           0 :     const ForkWorkStructure *pWorkStructure = nullptr;
    3905           0 :     CPLPipeRead(in, &pWorkStructure, sizeof(pWorkStructure));
    3906             : 
    3907           0 :     CPLSetConfigOption("GDAL_NUM_THREADS", "1");
    3908           0 :     GDALSetCacheMax64(pWorkStructure->nCacheMaxPerProcess);
    3909             : 
    3910           0 :     GDALRasterTileAlgorithmStandalone alg;
    3911           0 :     if (pWorkStructure->poMemSrcDS)
    3912             :     {
    3913           0 :         auto *inputArg = alg.GetArg(GDAL_ARG_NAME_INPUT);
    3914           0 :         std::vector<GDALArgDatasetValue> val;
    3915           0 :         val.resize(1);
    3916           0 :         val[0].Set(pWorkStructure->poMemSrcDS);
    3917           0 :         inputArg->Set(std::move(val));
    3918             :     }
    3919           0 :     return alg.ParseCommandLineArguments(pWorkStructure->aosArgv) && alg.Run()
    3920           0 :                ? 0
    3921           0 :                : 1;
    3922             : }
    3923             : 
    3924             : #endif  // FORK_ALLOWED
    3925             : 
    3926             : /************************************************************************/
    3927             : /*       GDALRasterTileAlgorithm::GenerateBaseTilesSpawnMethod()        */
    3928             : /************************************************************************/
    3929             : 
    3930           7 : bool GDALRasterTileAlgorithm::GenerateBaseTilesSpawnMethod(
    3931             :     int nBaseTilesPerCol, int nBaseTilesPerRow, int nMinTileX, int nMinTileY,
    3932             :     int nMaxTileX, int nMaxTileY, uint64_t nTotalTiles, uint64_t nBaseTiles,
    3933             :     GDALProgressFunc pfnProgress, void *pProgressData)
    3934             : {
    3935           7 :     if (m_parallelMethod == "spawn")
    3936             :     {
    3937           5 :         CPLAssert(!m_osGDALPath.empty());
    3938             :     }
    3939             : 
    3940           7 :     const int nMaxJobCount = GetMaxChildCount(std::max(
    3941           0 :         1, static_cast<int>(std::min<uint64_t>(
    3942           7 :                m_numThreads, nBaseTiles / GetThresholdMinTilesPerJob()))));
    3943             : 
    3944             :     double dfTilesYPerJob;
    3945             :     int nYOuterIterations;
    3946             :     double dfTilesXPerJob;
    3947             :     int nXOuterIterations;
    3948           7 :     ComputeJobChunkSize(nMaxJobCount, nBaseTilesPerCol, nBaseTilesPerRow,
    3949             :                         dfTilesYPerJob, nYOuterIterations, dfTilesXPerJob,
    3950             :                         nXOuterIterations);
    3951             : 
    3952           7 :     CPLDebugOnly("gdal_raster_tile",
    3953             :                  "nYOuterIterations=%d, dfTilesYPerJob=%g, "
    3954             :                  "nXOuterIterations=%d, dfTilesXPerJob=%g",
    3955             :                  nYOuterIterations, dfTilesYPerJob, nXOuterIterations,
    3956             :                  dfTilesXPerJob);
    3957             : 
    3958          14 :     std::vector<std::string> asCommandLines;
    3959          14 :     std::vector<CPLSpawnedProcess *> ahSpawnedProcesses;
    3960          14 :     std::vector<uint64_t> anRemainingTilesForProcess;
    3961             : 
    3962           7 :     const uint64_t nCacheMaxPerProcess = GDALGetCacheMax64() / nMaxJobCount;
    3963             : 
    3964           7 :     const auto poSrcDriver = m_poSrcDS->GetDriver();
    3965             :     const bool bIsMEMSource =
    3966           7 :         poSrcDriver && EQUAL(poSrcDriver->GetDescription(), "MEM");
    3967             : 
    3968           7 :     int nLastYEndIncluded = nMinTileY - 1;
    3969             : 
    3970             : #ifdef FORK_ALLOWED
    3971          14 :     std::vector<std::unique_ptr<ForkWorkStructure>> forkWorkStructures;
    3972             : #endif
    3973             : 
    3974           7 :     bool bRet = true;
    3975          33 :     for (int iYOuterIter = 0; bRet && iYOuterIter < nYOuterIterations &&
    3976          26 :                               nLastYEndIncluded < nMaxTileY;
    3977             :          ++iYOuterIter)
    3978             :     {
    3979          26 :         const int iYStart = nLastYEndIncluded + 1;
    3980             :         const int iYEndIncluded =
    3981          26 :             iYOuterIter + 1 == nYOuterIterations
    3982          45 :                 ? nMaxTileY
    3983             :                 : std::max(
    3984             :                       iYStart,
    3985          45 :                       static_cast<int>(std::floor(
    3986          19 :                           nMinTileY + (iYOuterIter + 1) * dfTilesYPerJob - 1)));
    3987             : 
    3988          26 :         nLastYEndIncluded = iYEndIncluded;
    3989             : 
    3990          26 :         int nLastXEndIncluded = nMinTileX - 1;
    3991          52 :         for (int iXOuterIter = 0; bRet && iXOuterIter < nXOuterIterations &&
    3992          26 :                                   nLastXEndIncluded < nMaxTileX;
    3993             :              ++iXOuterIter)
    3994             :         {
    3995          26 :             const int iXStart = nLastXEndIncluded + 1;
    3996             :             const int iXEndIncluded =
    3997          26 :                 iXOuterIter + 1 == nXOuterIterations
    3998          26 :                     ? nMaxTileX
    3999             :                     : std::max(iXStart,
    4000          26 :                                static_cast<int>(std::floor(
    4001           0 :                                    nMinTileX +
    4002           0 :                                    (iXOuterIter + 1) * dfTilesXPerJob - 1)));
    4003             : 
    4004          26 :             nLastXEndIncluded = iXEndIncluded;
    4005             : 
    4006          26 :             anRemainingTilesForProcess.push_back(
    4007           0 :                 static_cast<uint64_t>(iYEndIncluded - iYStart + 1) *
    4008          26 :                 (iXEndIncluded - iXStart + 1));
    4009             : 
    4010          26 :             CPLStringList aosArgv;
    4011          26 :             if (m_parallelMethod == "spawn")
    4012             :             {
    4013          18 :                 aosArgv.push_back(m_osGDALPath.c_str());
    4014          18 :                 aosArgv.push_back("raster");
    4015          18 :                 aosArgv.push_back("tile");
    4016          18 :                 aosArgv.push_back("--config-options-in-stdin");
    4017          18 :                 aosArgv.push_back("--config");
    4018          18 :                 aosArgv.push_back("GDAL_NUM_THREADS=1");
    4019          18 :                 aosArgv.push_back("--config");
    4020          18 :                 aosArgv.push_back(
    4021             :                     CPLSPrintf("GDAL_CACHEMAX=%" PRIu64, nCacheMaxPerProcess));
    4022             :             }
    4023          26 :             aosArgv.push_back(
    4024          52 :                 std::string("--").append(GDAL_ARG_NAME_NUM_THREADS).c_str());
    4025          26 :             aosArgv.push_back("1");
    4026          26 :             aosArgv.push_back("--min-x");
    4027          26 :             aosArgv.push_back(CPLSPrintf("%d", iXStart));
    4028          26 :             aosArgv.push_back("--max-x");
    4029          26 :             aosArgv.push_back(CPLSPrintf("%d", iXEndIncluded));
    4030          26 :             aosArgv.push_back("--min-y");
    4031          26 :             aosArgv.push_back(CPLSPrintf("%d", iYStart));
    4032          26 :             aosArgv.push_back("--max-y");
    4033          26 :             aosArgv.push_back(CPLSPrintf("%d", iYEndIncluded));
    4034          26 :             aosArgv.push_back("--webviewer");
    4035          26 :             aosArgv.push_back("none");
    4036          26 :             aosArgv.push_back(m_parallelMethod == "spawn" ? "--spawned"
    4037             :                                                           : "--forked");
    4038          26 :             if (!bIsMEMSource)
    4039             :             {
    4040          22 :                 aosArgv.push_back("--input");
    4041          22 :                 aosArgv.push_back(m_poSrcDS->GetDescription());
    4042             :             }
    4043        1372 :             for (const auto &arg : GetArgs())
    4044             :             {
    4045        1574 :                 if (arg->IsExplicitlySet() && arg->GetName() != "min-x" &&
    4046         342 :                     arg->GetName() != "min-y" && arg->GetName() != "max-x" &&
    4047         318 :                     arg->GetName() != "max-y" && arg->GetName() != "min-zoom" &&
    4048         180 :                     arg->GetName() != "progress" &&
    4049         180 :                     arg->GetName() != "progress-forked" &&
    4050         158 :                     arg->GetName() != GDAL_ARG_NAME_INPUT &&
    4051         134 :                     arg->GetName() != GDAL_ARG_NAME_NUM_THREADS &&
    4052        1526 :                     arg->GetName() != "webviewer" &&
    4053          66 :                     arg->GetName() != "parallel-method")
    4054             :                 {
    4055          56 :                     if (!AddArgToArgv(arg.get(), aosArgv))
    4056           0 :                         return false;
    4057             :                 }
    4058             :             }
    4059             : 
    4060          26 :             std::string cmdLine;
    4061         664 :             for (const char *arg : aosArgv)
    4062             :             {
    4063         638 :                 if (!cmdLine.empty())
    4064         612 :                     cmdLine += ' ';
    4065        1276 :                 CPLString sArg(arg);
    4066         638 :                 if (sArg.find_first_of(" \"") != std::string::npos)
    4067             :                 {
    4068           4 :                     cmdLine += '"';
    4069           4 :                     cmdLine += sArg.replaceAll('"', "\\\"");
    4070           4 :                     cmdLine += '"';
    4071             :                 }
    4072             :                 else
    4073         634 :                     cmdLine += sArg;
    4074             :             }
    4075          26 :             CPLDebugOnly("gdal_raster_tile", "%s %s",
    4076             :                          m_parallelMethod == "spawn" ? "Spawning" : "Forking",
    4077             :                          cmdLine.c_str());
    4078          26 :             asCommandLines.push_back(std::move(cmdLine));
    4079             : 
    4080             : #ifdef FORK_ALLOWED
    4081          26 :             if (m_parallelMethod == "fork")
    4082             :             {
    4083           8 :                 forkWorkStructures.push_back(
    4084          16 :                     std::make_unique<ForkWorkStructure>());
    4085           8 :                 ForkWorkStructure *pData = forkWorkStructures.back().get();
    4086           8 :                 pData->nCacheMaxPerProcess = nCacheMaxPerProcess;
    4087           8 :                 pData->aosArgv = aosArgv;
    4088           8 :                 if (bIsMEMSource)
    4089           4 :                     pData->poMemSrcDS = m_poSrcDS;
    4090             :             }
    4091          26 :             CPL_IGNORE_RET_VAL(aosArgv);
    4092             : #endif
    4093             : 
    4094          52 :             CPLSpawnedProcess *hSpawnedProcess = CPLSpawnAsync(
    4095             : #ifdef FORK_ALLOWED
    4096          26 :                 m_parallelMethod == "fork" ? GenerateTilesForkMethod :
    4097             : #endif
    4098             :                                            nullptr,
    4099          44 :                 m_parallelMethod == "fork" ? nullptr : aosArgv.List(),
    4100             :                 /* bCreateInputPipe = */ true,
    4101             :                 /* bCreateOutputPipe = */ true,
    4102          26 :                 /* bCreateErrorPipe = */ false, nullptr);
    4103          26 :             if (!hSpawnedProcess)
    4104             :             {
    4105           0 :                 ReportError(CE_Failure, CPLE_AppDefined,
    4106             :                             "Spawning child gdal process '%s' failed",
    4107           0 :                             asCommandLines.back().c_str());
    4108           0 :                 bRet = false;
    4109           0 :                 break;
    4110             :             }
    4111             : 
    4112          26 :             CPLDebugOnly("gdal_raster_tile",
    4113             :                          "Job for y in [%d,%d] and x in [%d,%d], "
    4114             :                          "run by process %" PRIu64,
    4115             :                          iYStart, iYEndIncluded, iXStart, iXEndIncluded,
    4116             :                          static_cast<uint64_t>(
    4117             :                              CPLSpawnAsyncGetChildProcessId(hSpawnedProcess)));
    4118             : 
    4119          26 :             ahSpawnedProcesses.push_back(hSpawnedProcess);
    4120             : 
    4121          26 :             if (m_parallelMethod == "spawn")
    4122             :             {
    4123          18 :                 SendConfigOptions(hSpawnedProcess, bRet);
    4124             :             }
    4125             : 
    4126             : #ifdef FORK_ALLOWED
    4127             :             else
    4128             :             {
    4129           8 :                 ForkWorkStructure *pData = forkWorkStructures.back().get();
    4130           8 :                 auto handle = CPLSpawnAsyncGetOutputFileHandle(hSpawnedProcess);
    4131           8 :                 bRet &= CPL_TO_BOOL(CPLPipeWrite(
    4132           8 :                     handle, &pData, static_cast<int>(sizeof(pData))));
    4133             :             }
    4134             : #endif
    4135             : 
    4136          26 :             if (!bRet)
    4137             :             {
    4138           0 :                 ReportError(CE_Failure, CPLE_AppDefined,
    4139             :                             "Could not transmit config options to child gdal "
    4140             :                             "process '%s'",
    4141           0 :                             asCommandLines.back().c_str());
    4142           0 :                 break;
    4143             :             }
    4144             :         }
    4145             :     }
    4146             : 
    4147           7 :     uint64_t nCurTile = 0;
    4148           7 :     GetProgressForChildProcesses(bRet, ahSpawnedProcesses,
    4149             :                                  anRemainingTilesForProcess, nCurTile,
    4150             :                                  nTotalTiles, pfnProgress, pProgressData);
    4151             : 
    4152           7 :     WaitForSpawnedProcesses(bRet, asCommandLines, ahSpawnedProcesses);
    4153             : 
    4154           7 :     if (bRet && nCurTile != nBaseTiles)
    4155             :     {
    4156           0 :         bRet = false;
    4157           0 :         ReportError(CE_Failure, CPLE_AppDefined,
    4158             :                     "Not all tiles at max zoom level have been "
    4159             :                     "generated. Got %" PRIu64 ", expected %" PRIu64,
    4160             :                     nCurTile, nBaseTiles);
    4161             :     }
    4162             : 
    4163           7 :     return bRet;
    4164             : }
    4165             : 
    4166             : /************************************************************************/
    4167             : /*     GDALRasterTileAlgorithm::GenerateOverviewTilesSpawnMethod()      */
    4168             : /************************************************************************/
    4169             : 
    4170          12 : bool GDALRasterTileAlgorithm::GenerateOverviewTilesSpawnMethod(
    4171             :     int iZ, int nOvrMinTileX, int nOvrMinTileY, int nOvrMaxTileX,
    4172             :     int nOvrMaxTileY, std::atomic<uint64_t> &nCurTile, uint64_t nTotalTiles,
    4173             :     GDALProgressFunc pfnProgress, void *pProgressData)
    4174             : {
    4175          12 :     if (m_parallelMethod == "spawn")
    4176             :     {
    4177           8 :         CPLAssert(!m_osGDALPath.empty());
    4178             :     }
    4179             : 
    4180          12 :     const int nOvrTilesPerCol = nOvrMaxTileY - nOvrMinTileY + 1;
    4181          12 :     const int nOvrTilesPerRow = nOvrMaxTileX - nOvrMinTileX + 1;
    4182          12 :     const uint64_t nExpectedOvrTileCount =
    4183          12 :         static_cast<uint64_t>(nOvrTilesPerCol) * nOvrTilesPerRow;
    4184             : 
    4185          12 :     const int nMaxJobCount = GetMaxChildCount(
    4186           0 :         std::max(1, static_cast<int>(std::min<uint64_t>(
    4187           0 :                         m_numThreads, nExpectedOvrTileCount /
    4188          12 :                                           GetThresholdMinTilesPerJob()))));
    4189             : 
    4190             :     double dfTilesYPerJob;
    4191             :     int nYOuterIterations;
    4192             :     double dfTilesXPerJob;
    4193             :     int nXOuterIterations;
    4194          12 :     ComputeJobChunkSize(nMaxJobCount, nOvrTilesPerCol, nOvrTilesPerRow,
    4195             :                         dfTilesYPerJob, nYOuterIterations, dfTilesXPerJob,
    4196             :                         nXOuterIterations);
    4197             : 
    4198          12 :     CPLDebugOnly("gdal_raster_tile",
    4199             :                  "z=%d, nYOuterIterations=%d, dfTilesYPerJob=%g, "
    4200             :                  "nXOuterIterations=%d, dfTilesXPerJob=%g",
    4201             :                  iZ, nYOuterIterations, dfTilesYPerJob, nXOuterIterations,
    4202             :                  dfTilesXPerJob);
    4203             : 
    4204          24 :     std::vector<std::string> asCommandLines;
    4205          24 :     std::vector<CPLSpawnedProcess *> ahSpawnedProcesses;
    4206          24 :     std::vector<uint64_t> anRemainingTilesForProcess;
    4207             : 
    4208             : #ifdef FORK_ALLOWED
    4209          24 :     std::vector<std::unique_ptr<ForkWorkStructure>> forkWorkStructures;
    4210             : #endif
    4211             : 
    4212          12 :     const uint64_t nCacheMaxPerProcess = GDALGetCacheMax64() / nMaxJobCount;
    4213             : 
    4214          12 :     const auto poSrcDriver = m_poSrcDS ? m_poSrcDS->GetDriver() : nullptr;
    4215             :     const bool bIsMEMSource =
    4216          12 :         poSrcDriver && EQUAL(poSrcDriver->GetDescription(), "MEM");
    4217             : 
    4218          12 :     int nLastYEndIncluded = nOvrMinTileY - 1;
    4219          12 :     bool bRet = true;
    4220          48 :     for (int iYOuterIter = 0; bRet && iYOuterIter < nYOuterIterations &&
    4221          36 :                               nLastYEndIncluded < nOvrMaxTileY;
    4222             :          ++iYOuterIter)
    4223             :     {
    4224          36 :         const int iYStart = nLastYEndIncluded + 1;
    4225             :         const int iYEndIncluded =
    4226          36 :             iYOuterIter + 1 == nYOuterIterations
    4227          60 :                 ? nOvrMaxTileY
    4228             :                 : std::max(iYStart,
    4229          60 :                            static_cast<int>(std::floor(
    4230          24 :                                nOvrMinTileY +
    4231          24 :                                (iYOuterIter + 1) * dfTilesYPerJob - 1)));
    4232             : 
    4233          36 :         nLastYEndIncluded = iYEndIncluded;
    4234             : 
    4235          36 :         int nLastXEndIncluded = nOvrMinTileX - 1;
    4236          84 :         for (int iXOuterIter = 0; bRet && iXOuterIter < nXOuterIterations &&
    4237          48 :                                   nLastXEndIncluded < nOvrMaxTileX;
    4238             :              ++iXOuterIter)
    4239             :         {
    4240          48 :             const int iXStart = nLastXEndIncluded + 1;
    4241             :             const int iXEndIncluded =
    4242          48 :                 iXOuterIter + 1 == nXOuterIterations
    4243          60 :                     ? nOvrMaxTileX
    4244             :                     : std::max(iXStart,
    4245          60 :                                static_cast<int>(std::floor(
    4246          12 :                                    nOvrMinTileX +
    4247          12 :                                    (iXOuterIter + 1) * dfTilesXPerJob - 1)));
    4248             : 
    4249          48 :             nLastXEndIncluded = iXEndIncluded;
    4250             : 
    4251          48 :             anRemainingTilesForProcess.push_back(
    4252           0 :                 static_cast<uint64_t>(iYEndIncluded - iYStart + 1) *
    4253          48 :                 (iXEndIncluded - iXStart + 1));
    4254             : 
    4255          48 :             CPLStringList aosArgv;
    4256          48 :             if (m_parallelMethod == "spawn")
    4257             :             {
    4258          32 :                 aosArgv.push_back(m_osGDALPath.c_str());
    4259          32 :                 aosArgv.push_back("raster");
    4260          32 :                 aosArgv.push_back("tile");
    4261          32 :                 aosArgv.push_back("--config-options-in-stdin");
    4262          32 :                 aosArgv.push_back("--config");
    4263          32 :                 aosArgv.push_back("GDAL_NUM_THREADS=1");
    4264          32 :                 aosArgv.push_back("--config");
    4265          32 :                 aosArgv.push_back(
    4266             :                     CPLSPrintf("GDAL_CACHEMAX=%" PRIu64, nCacheMaxPerProcess));
    4267             :             }
    4268          48 :             aosArgv.push_back(
    4269          96 :                 std::string("--").append(GDAL_ARG_NAME_NUM_THREADS).c_str());
    4270          48 :             aosArgv.push_back("1");
    4271          48 :             aosArgv.push_back("--ovr-zoom-level");
    4272          48 :             aosArgv.push_back(CPLSPrintf("%d", iZ));
    4273          48 :             aosArgv.push_back("--ovr-min-x");
    4274          48 :             aosArgv.push_back(CPLSPrintf("%d", iXStart));
    4275          48 :             aosArgv.push_back("--ovr-max-x");
    4276          48 :             aosArgv.push_back(CPLSPrintf("%d", iXEndIncluded));
    4277          48 :             aosArgv.push_back("--ovr-min-y");
    4278          48 :             aosArgv.push_back(CPLSPrintf("%d", iYStart));
    4279          48 :             aosArgv.push_back("--ovr-max-y");
    4280          48 :             aosArgv.push_back(CPLSPrintf("%d", iYEndIncluded));
    4281          48 :             aosArgv.push_back("--webviewer");
    4282          48 :             aosArgv.push_back("none");
    4283          48 :             aosArgv.push_back(m_parallelMethod == "spawn" ? "--spawned"
    4284             :                                                           : "--forked");
    4285          48 :             if (!bIsMEMSource)
    4286             :             {
    4287          40 :                 aosArgv.push_back("--input");
    4288          40 :                 aosArgv.push_back(m_inputDataset[0].GetName().c_str());
    4289             :             }
    4290        2528 :             for (const auto &arg : GetArgs())
    4291             :             {
    4292        2896 :                 if (arg->IsExplicitlySet() && arg->GetName() != "progress" &&
    4293         416 :                     arg->GetName() != "progress-forked" &&
    4294         376 :                     arg->GetName() != GDAL_ARG_NAME_INPUT &&
    4295         336 :                     arg->GetName() != GDAL_ARG_NAME_NUM_THREADS &&
    4296        2856 :                     arg->GetName() != "webviewer" &&
    4297         168 :                     arg->GetName() != "parallel-method")
    4298             :                 {
    4299         152 :                     if (!AddArgToArgv(arg.get(), aosArgv))
    4300           0 :                         return false;
    4301             :                 }
    4302             :             }
    4303             : 
    4304          48 :             std::string cmdLine;
    4305        1408 :             for (const char *arg : aosArgv)
    4306             :             {
    4307        1360 :                 if (!cmdLine.empty())
    4308        1312 :                     cmdLine += ' ';
    4309        2720 :                 CPLString sArg(arg);
    4310        1360 :                 if (sArg.find_first_of(" \"") != std::string::npos)
    4311             :                 {
    4312           8 :                     cmdLine += '"';
    4313           8 :                     cmdLine += sArg.replaceAll('"', "\\\"");
    4314           8 :                     cmdLine += '"';
    4315             :                 }
    4316             :                 else
    4317        1352 :                     cmdLine += sArg;
    4318             :             }
    4319          48 :             CPLDebugOnly("gdal_raster_tile", "%s %s",
    4320             :                          m_parallelMethod == "spawn" ? "Spawning" : "Forking",
    4321             :                          cmdLine.c_str());
    4322          48 :             asCommandLines.push_back(std::move(cmdLine));
    4323             : 
    4324             : #ifdef FORK_ALLOWED
    4325          48 :             if (m_parallelMethod == "fork")
    4326             :             {
    4327          16 :                 forkWorkStructures.push_back(
    4328          32 :                     std::make_unique<ForkWorkStructure>());
    4329          16 :                 ForkWorkStructure *pData = forkWorkStructures.back().get();
    4330          16 :                 pData->nCacheMaxPerProcess = nCacheMaxPerProcess;
    4331          16 :                 pData->aosArgv = aosArgv;
    4332          16 :                 if (bIsMEMSource)
    4333           8 :                     pData->poMemSrcDS = m_poSrcDS;
    4334             :             }
    4335          48 :             CPL_IGNORE_RET_VAL(aosArgv);
    4336             : #endif
    4337             : 
    4338          96 :             CPLSpawnedProcess *hSpawnedProcess = CPLSpawnAsync(
    4339             : #ifdef FORK_ALLOWED
    4340          48 :                 m_parallelMethod == "fork" ? GenerateTilesForkMethod :
    4341             : #endif
    4342             :                                            nullptr,
    4343          80 :                 m_parallelMethod == "fork" ? nullptr : aosArgv.List(),
    4344             :                 /* bCreateInputPipe = */ true,
    4345             :                 /* bCreateOutputPipe = */ true,
    4346          48 :                 /* bCreateErrorPipe = */ true, nullptr);
    4347          48 :             if (!hSpawnedProcess)
    4348             :             {
    4349           0 :                 ReportError(CE_Failure, CPLE_AppDefined,
    4350             :                             "Spawning child gdal process '%s' failed",
    4351           0 :                             asCommandLines.back().c_str());
    4352           0 :                 bRet = false;
    4353           0 :                 break;
    4354             :             }
    4355             : 
    4356          48 :             CPLDebugOnly("gdal_raster_tile",
    4357             :                          "Job for z = %d, y in [%d,%d] and x in [%d,%d], "
    4358             :                          "run by process %" PRIu64,
    4359             :                          iZ, iYStart, iYEndIncluded, iXStart, iXEndIncluded,
    4360             :                          static_cast<uint64_t>(
    4361             :                              CPLSpawnAsyncGetChildProcessId(hSpawnedProcess)));
    4362             : 
    4363          48 :             ahSpawnedProcesses.push_back(hSpawnedProcess);
    4364             : 
    4365          48 :             if (m_parallelMethod == "spawn")
    4366             :             {
    4367          32 :                 SendConfigOptions(hSpawnedProcess, bRet);
    4368             :             }
    4369             : 
    4370             : #ifdef FORK_ALLOWED
    4371             :             else
    4372             :             {
    4373          16 :                 ForkWorkStructure *pData = forkWorkStructures.back().get();
    4374          16 :                 auto handle = CPLSpawnAsyncGetOutputFileHandle(hSpawnedProcess);
    4375          16 :                 bRet &= CPL_TO_BOOL(CPLPipeWrite(
    4376          16 :                     handle, &pData, static_cast<int>(sizeof(pData))));
    4377             :             }
    4378             : #endif
    4379          48 :             if (!bRet)
    4380             :             {
    4381           0 :                 ReportError(CE_Failure, CPLE_AppDefined,
    4382             :                             "Could not transmit config options to child gdal "
    4383             :                             "process '%s'",
    4384           0 :                             asCommandLines.back().c_str());
    4385           0 :                 break;
    4386             :             }
    4387             :         }
    4388             :     }
    4389             : 
    4390          12 :     uint64_t nCurTileLocal = nCurTile;
    4391          12 :     GetProgressForChildProcesses(bRet, ahSpawnedProcesses,
    4392             :                                  anRemainingTilesForProcess, nCurTileLocal,
    4393             :                                  nTotalTiles, pfnProgress, pProgressData);
    4394             : 
    4395          12 :     WaitForSpawnedProcesses(bRet, asCommandLines, ahSpawnedProcesses);
    4396             : 
    4397          12 :     if (bRet && nCurTileLocal - nCurTile != nExpectedOvrTileCount)
    4398             :     {
    4399           0 :         bRet = false;
    4400           0 :         ReportError(CE_Failure, CPLE_AppDefined,
    4401             :                     "Not all tiles at zoom level %d have been "
    4402             :                     "generated. Got %" PRIu64 ", expected %" PRIu64,
    4403           0 :                     iZ, nCurTileLocal - nCurTile, nExpectedOvrTileCount);
    4404             :     }
    4405             : 
    4406          12 :     nCurTile = nCurTileLocal;
    4407             : 
    4408          12 :     return bRet;
    4409             : }
    4410             : 
    4411             : /************************************************************************/
    4412             : /*                  GDALRasterTileAlgorithm::RunImpl()                  */
    4413             : /************************************************************************/
    4414             : 
    4415         228 : bool GDALRasterTileAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
    4416             :                                       void *pProgressData)
    4417             : {
    4418         228 :     GDALPipelineStepRunContext stepCtxt;
    4419         228 :     stepCtxt.m_pfnProgress = pfnProgress;
    4420         228 :     stepCtxt.m_pProgressData = pProgressData;
    4421         456 :     return RunStep(stepCtxt);
    4422             : }
    4423             : 
    4424             : /************************************************************************/
    4425             : /*                        SpawnedErrorHandler()                         */
    4426             : /************************************************************************/
    4427             : 
    4428         310 : static void CPL_STDCALL SpawnedErrorHandler(CPLErr eErr, CPLErrorNum eNum,
    4429             :                                             const char *pszMsg)
    4430             : {
    4431         310 :     fwrite(ERROR_START_MARKER, sizeof(ERROR_START_MARKER), 1, stdout);
    4432         310 :     uint32_t nErr = eErr;
    4433         310 :     fwrite(&nErr, sizeof(nErr), 1, stdout);
    4434         310 :     uint32_t nNum = eNum;
    4435         310 :     fwrite(&nNum, sizeof(nNum), 1, stdout);
    4436         310 :     uint16_t nLen = static_cast<uint16_t>(strlen(pszMsg));
    4437         310 :     fwrite(&nLen, sizeof(nLen), 1, stdout);
    4438         310 :     fwrite(pszMsg, nLen, 1, stdout);
    4439         310 :     fflush(stdout);
    4440         310 : }
    4441             : 
    4442             : /************************************************************************/
    4443             : /*                  GDALRasterTileAlgorithm::RunStep()                  */
    4444             : /************************************************************************/
    4445             : 
    4446         232 : bool GDALRasterTileAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
    4447             : {
    4448         232 :     auto pfnProgress = ctxt.m_pfnProgress;
    4449         232 :     auto pProgressData = ctxt.m_pProgressData;
    4450         232 :     CPLAssert(m_inputDataset.size() == 1);
    4451         232 :     m_poSrcDS = m_inputDataset[0].GetDatasetRef();
    4452         232 :     CPLAssert(m_poSrcDS);
    4453             : 
    4454         232 :     const int nSrcWidth = m_poSrcDS->GetRasterXSize();
    4455         232 :     const int nSrcHeight = m_poSrcDS->GetRasterYSize();
    4456         232 :     if (m_poSrcDS->GetRasterCount() == 0 || nSrcWidth == 0 || nSrcHeight == 0)
    4457             :     {
    4458           2 :         ReportError(CE_Failure, CPLE_AppDefined, "Invalid source dataset");
    4459           2 :         return false;
    4460             :     }
    4461             : 
    4462         230 :     const bool bIsNamedSource = m_poSrcDS->GetDescription()[0] != 0;
    4463         230 :     auto poSrcDriver = m_poSrcDS->GetDriver();
    4464             :     const bool bIsMEMSource =
    4465         230 :         poSrcDriver && EQUAL(poSrcDriver->GetDescription(), "MEM");
    4466         230 :     m_bIsNamedNonMemSrcDS = bIsNamedSource && !bIsMEMSource;
    4467         230 :     const bool bSrcIsFineForFork = bIsNamedSource || bIsMEMSource;
    4468             : 
    4469         230 :     if (m_parallelMethod == "spawn")
    4470             :     {
    4471           5 :         const char *pszErrorMsg = "";
    4472           5 :         if (!IsCompatibleOfSpawn(pszErrorMsg))
    4473             :         {
    4474           3 :             if (pszErrorMsg[0])
    4475           2 :                 ReportError(CE_Failure, CPLE_AppDefined, "%s", pszErrorMsg);
    4476           3 :             return false;
    4477             :         }
    4478             :     }
    4479             : #ifdef FORK_ALLOWED
    4480         225 :     else if (m_parallelMethod == "fork")
    4481             :     {
    4482           3 :         if (!bSrcIsFineForFork)
    4483             :         {
    4484           1 :             ReportError(CE_Failure, CPLE_AppDefined,
    4485             :                         "Unnamed non-MEM source are not supported "
    4486             :                         "with fork parallelization method");
    4487           1 :             return false;
    4488             :         }
    4489           2 :         if (cpl::starts_with(m_outputDir, "/vsimem/"))
    4490             :         {
    4491           1 :             ReportError(CE_Failure, CPLE_AppDefined,
    4492             :                         "/vsimem/ output directory not supported with fork "
    4493             :                         "parallelization method");
    4494           1 :             return false;
    4495             :         }
    4496             :     }
    4497             : #endif
    4498             : 
    4499         225 :     if (m_resampling == "near")
    4500           2 :         m_resampling = "nearest";
    4501         225 :     if (m_overviewResampling == "near")
    4502           1 :         m_overviewResampling = "nearest";
    4503         224 :     else if (m_overviewResampling.empty())
    4504         196 :         m_overviewResampling = m_resampling;
    4505             : 
    4506         450 :     CPLStringList aosWarpOptions;
    4507         225 :     if (!m_excludedValues.empty() || m_nodataValuesPctThreshold < 100)
    4508             :     {
    4509             :         aosWarpOptions.SetNameValue(
    4510             :             "NODATA_VALUES_PCT_THRESHOLD",
    4511           6 :             CPLSPrintf("%g", m_nodataValuesPctThreshold));
    4512           6 :         if (!m_excludedValues.empty())
    4513             :         {
    4514             :             aosWarpOptions.SetNameValue("EXCLUDED_VALUES",
    4515           2 :                                         m_excludedValues.c_str());
    4516             :             aosWarpOptions.SetNameValue(
    4517             :                 "EXCLUDED_VALUES_PCT_THRESHOLD",
    4518           2 :                 CPLSPrintf("%g", m_excludedValuesPctThreshold));
    4519             :         }
    4520             :     }
    4521             : 
    4522         225 :     if (m_poSrcDS->GetRasterBand(1)->GetColorInterpretation() ==
    4523         229 :             GCI_PaletteIndex &&
    4524           4 :         ((m_resampling != "nearest" && m_resampling != "mode") ||
    4525           1 :          (m_overviewResampling != "nearest" && m_overviewResampling != "mode")))
    4526             :     {
    4527           1 :         ReportError(CE_Failure, CPLE_NotSupported,
    4528             :                     "Datasets with color table not supported with non-nearest "
    4529             :                     "or non-mode resampling. Run 'gdal raster "
    4530             :                     "color-map' before or set the 'resampling' argument to "
    4531             :                     "'nearest' or 'mode'.");
    4532           1 :         return false;
    4533             :     }
    4534             : 
    4535         224 :     const auto eSrcDT = m_poSrcDS->GetRasterBand(1)->GetRasterDataType();
    4536         224 :     m_poDstDriver = GetGDALDriverManager()->GetDriverByName(m_format.c_str());
    4537         224 :     if (!m_poDstDriver)
    4538             :     {
    4539           1 :         ReportError(CE_Failure, CPLE_AppDefined,
    4540             :                     "Invalid value for argument 'output-format'. Driver '%s' "
    4541             :                     "does not exist",
    4542             :                     m_format.c_str());
    4543           1 :         return false;
    4544             :     }
    4545             : 
    4546         223 :     if (!ValidateOutputFormat(eSrcDT))
    4547          18 :         return false;
    4548             : 
    4549             :     const char *pszExtensions =
    4550         205 :         m_poDstDriver->GetMetadataItem(GDAL_DMD_EXTENSIONS);
    4551         205 :     CPLAssert(pszExtensions && pszExtensions[0] != 0);
    4552             :     const CPLStringList aosExtensions(
    4553         410 :         CSLTokenizeString2(pszExtensions, " ", 0));
    4554         205 :     const char *pszExtension = aosExtensions[0];
    4555         205 :     GDALGeoTransform srcGT;
    4556         205 :     const bool bHasSrcGT = m_poSrcDS->GetGeoTransform(srcGT) == CE_None;
    4557             :     const bool bHasNorthUpSrcGT =
    4558         205 :         bHasSrcGT && srcGT[2] == 0 && srcGT[4] == 0 && srcGT[5] < 0;
    4559         410 :     OGRSpatialReference oSRS_TMS;
    4560             : 
    4561         205 :     if (m_tilingScheme == "raster")
    4562             :     {
    4563           6 :         if (const auto poSRS = m_poSrcDS->GetSpatialRef())
    4564           5 :             oSRS_TMS = *poSRS;
    4565             :     }
    4566             :     else
    4567             :     {
    4568           3 :         if (!bHasSrcGT && m_poSrcDS->GetGCPCount() == 0 &&
    4569         203 :             m_poSrcDS->GetMetadata("GEOLOCATION") == nullptr &&
    4570           1 :             m_poSrcDS->GetMetadata("RPC") == nullptr)
    4571             :         {
    4572           1 :             ReportError(CE_Failure, CPLE_NotSupported,
    4573             :                         "Ungeoreferenced datasets are not supported, unless "
    4574             :                         "'tiling-scheme' is set to 'raster'");
    4575           1 :             return false;
    4576             :         }
    4577             : 
    4578         198 :         if (m_poSrcDS->GetMetadata("GEOLOCATION") == nullptr &&
    4579         198 :             m_poSrcDS->GetMetadata("RPC") == nullptr &&
    4580         398 :             m_poSrcDS->GetSpatialRef() == nullptr &&
    4581           2 :             m_poSrcDS->GetGCPSpatialRef() == nullptr)
    4582             :         {
    4583           2 :             ReportError(CE_Failure, CPLE_NotSupported,
    4584             :                         "Ungeoreferenced datasets are not supported, unless "
    4585             :                         "'tiling-scheme' is set to 'raster'");
    4586           2 :             return false;
    4587             :         }
    4588             :     }
    4589             : 
    4590         202 :     if (m_copySrcMetadata)
    4591             :     {
    4592           8 :         CPLStringList aosMD(CSLDuplicate(m_poSrcDS->GetMetadata()));
    4593           4 :         const CPLStringList aosNewMD(m_metadata);
    4594           8 :         for (const auto [key, value] : cpl::IterateNameValue(aosNewMD))
    4595             :         {
    4596           4 :             aosMD.SetNameValue(key, value);
    4597             :         }
    4598           4 :         m_metadata = aosMD;
    4599             :     }
    4600             : 
    4601         404 :     std::vector<BandMetadata> aoBandMetadata;
    4602       66262 :     for (int i = 1; i <= m_poSrcDS->GetRasterCount(); ++i)
    4603             :     {
    4604       66060 :         auto poBand = m_poSrcDS->GetRasterBand(i);
    4605      132120 :         BandMetadata bm;
    4606       66060 :         bm.osDescription = poBand->GetDescription();
    4607       66060 :         bm.eDT = poBand->GetRasterDataType();
    4608       66060 :         bm.eColorInterp = poBand->GetColorInterpretation();
    4609       66060 :         if (const char *pszCenterWavelength =
    4610       66060 :                 poBand->GetMetadataItem("CENTRAL_WAVELENGTH_UM", "IMAGERY"))
    4611           0 :             bm.osCenterWaveLength = pszCenterWavelength;
    4612       66060 :         if (const char *pszFWHM = poBand->GetMetadataItem("FWHM_UM", "IMAGERY"))
    4613           0 :             bm.osFWHM = pszFWHM;
    4614       66060 :         aoBandMetadata.emplace_back(std::move(bm));
    4615             :     }
    4616             : 
    4617         202 :     GDALGeoTransform srcGTModif{0, 1, 0, 0, 0, -1};
    4618             : 
    4619         202 :     if (m_tilingScheme == "mercator")
    4620          31 :         m_tilingScheme = "WebMercatorQuad";
    4621         171 :     else if (m_tilingScheme == "raster")
    4622             :     {
    4623           6 :         if (m_tileSize == 0)
    4624           4 :             m_tileSize = 256;
    4625           6 :         if (m_maxZoomLevel < 0)
    4626             :         {
    4627           3 :             m_maxZoomLevel = static_cast<int>(std::ceil(std::log2(
    4628           6 :                 std::max(1, std::max(nSrcWidth, nSrcHeight) / m_tileSize))));
    4629             :         }
    4630           6 :         if (bHasNorthUpSrcGT)
    4631             :         {
    4632           5 :             srcGTModif = srcGT;
    4633             :         }
    4634             :     }
    4635             : 
    4636             :     auto poTMS =
    4637         202 :         m_tilingScheme == "raster"
    4638             :             ? gdal::TileMatrixSet::createRaster(
    4639           6 :                   nSrcWidth, nSrcHeight, m_tileSize, 1 + m_maxZoomLevel,
    4640          24 :                   srcGTModif[0], srcGTModif[3], srcGTModif[1], -srcGTModif[5],
    4641         208 :                   oSRS_TMS.IsEmpty() ? std::string() : oSRS_TMS.exportToWkt())
    4642             :             : gdal::TileMatrixSet::parse(
    4643         410 :                   m_mapTileMatrixIdentifierToScheme[m_tilingScheme].c_str());
    4644             :     // Enforced by SetChoices() on the m_tilingScheme argument
    4645         202 :     CPLAssert(poTMS && !poTMS->hasVariableMatrixWidth());
    4646             : 
    4647         404 :     CPLStringList aosTO;
    4648         202 :     if (m_tilingScheme == "raster")
    4649             :     {
    4650           6 :         aosTO.SetNameValue("SRC_METHOD", "GEOTRANSFORM");
    4651             :     }
    4652             :     else
    4653             :     {
    4654         196 :         CPL_IGNORE_RET_VAL(oSRS_TMS.SetFromUserInput(poTMS->crs().c_str()));
    4655         196 :         aosTO.SetNameValue("DST_SRS", oSRS_TMS.exportToWkt().c_str());
    4656             :     }
    4657             : 
    4658         202 :     const char *pszAuthName = oSRS_TMS.GetAuthorityName();
    4659         202 :     const char *pszAuthCode = oSRS_TMS.GetAuthorityCode();
    4660         202 :     const int nEPSGCode =
    4661         201 :         (pszAuthName && pszAuthCode && EQUAL(pszAuthName, "EPSG"))
    4662         403 :             ? atoi(pszAuthCode)
    4663             :             : 0;
    4664             : 
    4665             :     const bool bInvertAxisTMS =
    4666         398 :         m_tilingScheme != "raster" &&
    4667         196 :         (oSRS_TMS.EPSGTreatsAsLatLong() != FALSE ||
    4668         196 :          oSRS_TMS.EPSGTreatsAsNorthingEasting() != FALSE);
    4669             : 
    4670         202 :     oSRS_TMS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    4671             : 
    4672             :     std::unique_ptr<void, decltype(&GDALDestroyTransformer)> hTransformArg(
    4673         404 :         nullptr, GDALDestroyTransformer);
    4674             : 
    4675             :     // Hack to compensate for GDALSuggestedWarpOutput2() failure (or not
    4676             :     // ideal suggestion with PROJ 8) when reprojecting latitude = +/- 90 to
    4677             :     // EPSG:3857.
    4678         202 :     std::unique_ptr<GDALDataset> poTmpDS;
    4679         202 :     bool bEPSG3857Adjust = false;
    4680         202 :     if (nEPSGCode == 3857 && bHasNorthUpSrcGT)
    4681             :     {
    4682         171 :         const auto poSrcSRS = m_poSrcDS->GetSpatialRef();
    4683         171 :         if (poSrcSRS && poSrcSRS->IsGeographic())
    4684             :         {
    4685         106 :             double maxLat = srcGT[3];
    4686         106 :             double minLat = srcGT[3] + nSrcHeight * srcGT[5];
    4687             :             // Corresponds to the latitude of below MAX_GM
    4688         106 :             constexpr double MAX_LAT = 85.0511287798066;
    4689         106 :             bool bModified = false;
    4690         106 :             if (maxLat > MAX_LAT)
    4691             :             {
    4692          99 :                 maxLat = MAX_LAT;
    4693          99 :                 bModified = true;
    4694             :             }
    4695         106 :             if (minLat < -MAX_LAT)
    4696             :             {
    4697          99 :                 minLat = -MAX_LAT;
    4698          99 :                 bModified = true;
    4699             :             }
    4700         106 :             if (bModified)
    4701             :             {
    4702         198 :                 CPLStringList aosOptions;
    4703          99 :                 aosOptions.AddString("-of");
    4704          99 :                 aosOptions.AddString("VRT");
    4705          99 :                 aosOptions.AddString("-projwin");
    4706          99 :                 aosOptions.AddString(CPLSPrintf("%.17g", srcGT[0]));
    4707          99 :                 aosOptions.AddString(CPLSPrintf("%.17g", maxLat));
    4708             :                 aosOptions.AddString(
    4709          99 :                     CPLSPrintf("%.17g", srcGT[0] + nSrcWidth * srcGT[1]));
    4710          99 :                 aosOptions.AddString(CPLSPrintf("%.17g", minLat));
    4711             :                 auto psOptions =
    4712          99 :                     GDALTranslateOptionsNew(aosOptions.List(), nullptr);
    4713          99 :                 poTmpDS.reset(GDALDataset::FromHandle(GDALTranslate(
    4714             :                     "", GDALDataset::ToHandle(m_poSrcDS), psOptions, nullptr)));
    4715          99 :                 GDALTranslateOptionsFree(psOptions);
    4716          99 :                 if (poTmpDS)
    4717             :                 {
    4718          99 :                     bEPSG3857Adjust = true;
    4719          99 :                     hTransformArg.reset(GDALCreateGenImgProjTransformer2(
    4720          99 :                         GDALDataset::FromHandle(poTmpDS.get()), nullptr,
    4721          99 :                         aosTO.List()));
    4722             :                 }
    4723             :             }
    4724             :         }
    4725             :     }
    4726             : 
    4727         202 :     GDALGeoTransform dstGT;
    4728             :     double adfExtent[4];
    4729             :     int nXSize, nYSize;
    4730             : 
    4731             :     bool bSuggestOK;
    4732         202 :     if (m_tilingScheme == "raster")
    4733             :     {
    4734           6 :         bSuggestOK = true;
    4735           6 :         nXSize = nSrcWidth;
    4736           6 :         nYSize = nSrcHeight;
    4737           6 :         dstGT = srcGTModif;
    4738           6 :         adfExtent[0] = dstGT[0];
    4739           6 :         adfExtent[1] = dstGT[3] + nSrcHeight * dstGT[5];
    4740           6 :         adfExtent[2] = dstGT[0] + nSrcWidth * dstGT[1];
    4741           6 :         adfExtent[3] = dstGT[3];
    4742             :     }
    4743             :     else
    4744             :     {
    4745         196 :         if (!hTransformArg)
    4746             :         {
    4747          97 :             hTransformArg.reset(GDALCreateGenImgProjTransformer2(
    4748          97 :                 m_poSrcDS, nullptr, aosTO.List()));
    4749             :         }
    4750         196 :         if (!hTransformArg)
    4751             :         {
    4752           1 :             return false;
    4753             :         }
    4754         195 :         CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
    4755         195 :         bSuggestOK =
    4756         390 :             (GDALSuggestedWarpOutput2(
    4757         195 :                  m_poSrcDS,
    4758         195 :                  static_cast<GDALTransformerInfo *>(hTransformArg.get())
    4759             :                      ->pfnTransform,
    4760             :                  hTransformArg.get(), dstGT.data(), &nXSize, &nYSize, adfExtent,
    4761             :                  0) == CE_None);
    4762             :     }
    4763         201 :     if (!bSuggestOK)
    4764             :     {
    4765           1 :         ReportError(CE_Failure, CPLE_AppDefined,
    4766             :                     "Cannot determine extent of raster in target CRS");
    4767           1 :         return false;
    4768             :     }
    4769             : 
    4770         200 :     poTmpDS.reset();
    4771             : 
    4772         200 :     if (bEPSG3857Adjust)
    4773             :     {
    4774          99 :         constexpr double SPHERICAL_RADIUS = 6378137.0;
    4775          99 :         constexpr double MAX_GM =
    4776             :             SPHERICAL_RADIUS * M_PI;  // 20037508.342789244
    4777          99 :         double maxNorthing = dstGT[3];
    4778          99 :         double minNorthing = dstGT[3] + dstGT[5] * nYSize;
    4779          99 :         bool bChanged = false;
    4780          99 :         if (maxNorthing > MAX_GM)
    4781             :         {
    4782          96 :             bChanged = true;
    4783          96 :             maxNorthing = MAX_GM;
    4784             :         }
    4785          99 :         if (minNorthing < -MAX_GM)
    4786             :         {
    4787          96 :             bChanged = true;
    4788          96 :             minNorthing = -MAX_GM;
    4789             :         }
    4790          99 :         if (bChanged)
    4791             :         {
    4792          96 :             dstGT[3] = maxNorthing;
    4793          96 :             nYSize = int((maxNorthing - minNorthing) / (-dstGT[5]) + 0.5);
    4794          96 :             adfExtent[1] = maxNorthing + nYSize * dstGT[5];
    4795          96 :             adfExtent[3] = maxNorthing;
    4796             :         }
    4797             :     }
    4798             : 
    4799         200 :     const auto &tileMatrixList = poTMS->tileMatrixList();
    4800         200 :     if (m_maxZoomLevel >= 0)
    4801             :     {
    4802         121 :         if (m_maxZoomLevel >= static_cast<int>(tileMatrixList.size()))
    4803             :         {
    4804           1 :             ReportError(CE_Failure, CPLE_AppDefined,
    4805             :                         "max-zoom = %d is invalid. It must be in [0,%d] range",
    4806             :                         m_maxZoomLevel,
    4807           1 :                         static_cast<int>(tileMatrixList.size()) - 1);
    4808           1 :             return false;
    4809             :         }
    4810             :     }
    4811             :     else
    4812             :     {
    4813          79 :         const double dfComputedRes = dstGT[1];
    4814          79 :         double dfPrevRes = 0.0;
    4815          79 :         double dfRes = 0.0;
    4816          79 :         constexpr double EPSILON = 1e-8;
    4817             : 
    4818          79 :         if (m_minZoomLevel >= 0)
    4819          17 :             m_maxZoomLevel = m_minZoomLevel;
    4820             :         else
    4821          62 :             m_maxZoomLevel = 0;
    4822             : 
    4823         397 :         for (; m_maxZoomLevel < static_cast<int>(tileMatrixList.size());
    4824         318 :              m_maxZoomLevel++)
    4825             :         {
    4826         396 :             dfRes = tileMatrixList[m_maxZoomLevel].mResX;
    4827         396 :             if (dfComputedRes > dfRes ||
    4828         327 :                 fabs(dfComputedRes - dfRes) / dfRes <= EPSILON)
    4829             :                 break;
    4830         318 :             dfPrevRes = dfRes;
    4831             :         }
    4832          79 :         if (m_maxZoomLevel >= static_cast<int>(tileMatrixList.size()))
    4833             :         {
    4834           1 :             ReportError(CE_Failure, CPLE_AppDefined,
    4835             :                         "Could not find an appropriate zoom level. Perhaps "
    4836             :                         "min-zoom is too large?");
    4837           1 :             return false;
    4838             :         }
    4839             : 
    4840          78 :         if (m_maxZoomLevel > 0 && fabs(dfComputedRes - dfRes) / dfRes > EPSILON)
    4841             :         {
    4842             :             // Round to closest resolution
    4843          64 :             if (dfPrevRes / dfComputedRes < dfComputedRes / dfRes)
    4844          44 :                 m_maxZoomLevel--;
    4845             :         }
    4846             :     }
    4847             : 
    4848         396 :     auto tileMatrix = tileMatrixList[m_maxZoomLevel];
    4849         198 :     int nMinTileX = 0;
    4850         198 :     int nMinTileY = 0;
    4851         198 :     int nMaxTileX = 0;
    4852         198 :     int nMaxTileY = 0;
    4853         198 :     bool bIntersects = false;
    4854         198 :     if (!GetTileIndices(tileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
    4855             :                         nMinTileX, nMinTileY, nMaxTileX, nMaxTileY,
    4856         198 :                         m_noIntersectionIsOK, bIntersects,
    4857             :                         /* checkRasterOverflow = */ false))
    4858             :     {
    4859           1 :         return false;
    4860             :     }
    4861         197 :     if (!bIntersects)
    4862           1 :         return true;
    4863             : 
    4864             :     // Potentially restrict tiling to user specified coordinates
    4865         196 :     if (m_minTileX >= tileMatrix.mMatrixWidth)
    4866             :     {
    4867           1 :         ReportError(CE_Failure, CPLE_IllegalArg,
    4868             :                     "'min-x' value must be in [0,%d] range",
    4869           1 :                     tileMatrix.mMatrixWidth - 1);
    4870           1 :         return false;
    4871             :     }
    4872         195 :     if (m_maxTileX >= tileMatrix.mMatrixWidth)
    4873             :     {
    4874           1 :         ReportError(CE_Failure, CPLE_IllegalArg,
    4875             :                     "'max-x' value must be in [0,%d] range",
    4876           1 :                     tileMatrix.mMatrixWidth - 1);
    4877           1 :         return false;
    4878             :     }
    4879         194 :     if (m_minTileY >= tileMatrix.mMatrixHeight)
    4880             :     {
    4881           1 :         ReportError(CE_Failure, CPLE_IllegalArg,
    4882             :                     "'min-y' value must be in [0,%d] range",
    4883           1 :                     tileMatrix.mMatrixHeight - 1);
    4884           1 :         return false;
    4885             :     }
    4886         193 :     if (m_maxTileY >= tileMatrix.mMatrixHeight)
    4887             :     {
    4888           1 :         ReportError(CE_Failure, CPLE_IllegalArg,
    4889             :                     "'max-y' value must be in [0,%d] range",
    4890           1 :                     tileMatrix.mMatrixHeight - 1);
    4891           1 :         return false;
    4892             :     }
    4893             : 
    4894         192 :     if ((m_minTileX >= 0 && m_minTileX > nMaxTileX) ||
    4895         190 :         (m_minTileY >= 0 && m_minTileY > nMaxTileY) ||
    4896         190 :         (m_maxTileX >= 0 && m_maxTileX < nMinTileX) ||
    4897         190 :         (m_maxTileY >= 0 && m_maxTileY < nMinTileY))
    4898             :     {
    4899           2 :         ReportError(
    4900           2 :             m_noIntersectionIsOK ? CE_Warning : CE_Failure, CPLE_AppDefined,
    4901             :             "Dataset extent not intersecting specified min/max X/Y tile "
    4902             :             "coordinates");
    4903           2 :         return m_noIntersectionIsOK;
    4904             :     }
    4905         190 :     if (m_minTileX >= 0 && m_minTileX > nMinTileX)
    4906             :     {
    4907           2 :         nMinTileX = m_minTileX;
    4908           2 :         adfExtent[0] = tileMatrix.mTopLeftX +
    4909           2 :                        nMinTileX * tileMatrix.mResX * tileMatrix.mTileWidth;
    4910             :     }
    4911         190 :     if (m_minTileY >= 0 && m_minTileY > nMinTileY)
    4912             :     {
    4913          14 :         nMinTileY = m_minTileY;
    4914          14 :         adfExtent[3] = tileMatrix.mTopLeftY -
    4915          14 :                        nMinTileY * tileMatrix.mResY * tileMatrix.mTileHeight;
    4916             :     }
    4917         190 :     if (m_maxTileX >= 0 && m_maxTileX < nMaxTileX)
    4918             :     {
    4919           2 :         nMaxTileX = m_maxTileX;
    4920           2 :         adfExtent[2] = tileMatrix.mTopLeftX + (nMaxTileX + 1) *
    4921           2 :                                                   tileMatrix.mResX *
    4922           2 :                                                   tileMatrix.mTileWidth;
    4923             :     }
    4924         190 :     if (m_maxTileY >= 0 && m_maxTileY < nMaxTileY)
    4925             :     {
    4926          15 :         nMaxTileY = m_maxTileY;
    4927          15 :         adfExtent[1] = tileMatrix.mTopLeftY - (nMaxTileY + 1) *
    4928          15 :                                                   tileMatrix.mResY *
    4929          15 :                                                   tileMatrix.mTileHeight;
    4930             :     }
    4931             : 
    4932         190 :     if (nMaxTileX - nMinTileX + 1 > INT_MAX / tileMatrix.mTileWidth ||
    4933         189 :         nMaxTileY - nMinTileY + 1 > INT_MAX / tileMatrix.mTileHeight)
    4934             :     {
    4935           1 :         ReportError(CE_Failure, CPLE_AppDefined, "Too large zoom level");
    4936           1 :         return false;
    4937             :     }
    4938             : 
    4939         378 :     dstGT[0] = tileMatrix.mTopLeftX +
    4940         189 :                nMinTileX * tileMatrix.mResX * tileMatrix.mTileWidth;
    4941         189 :     dstGT[1] = tileMatrix.mResX;
    4942         189 :     dstGT[2] = 0;
    4943         378 :     dstGT[3] = tileMatrix.mTopLeftY -
    4944         189 :                nMinTileY * tileMatrix.mResY * tileMatrix.mTileHeight;
    4945         189 :     dstGT[4] = 0;
    4946         189 :     dstGT[5] = -tileMatrix.mResY;
    4947             : 
    4948         189 :     if (m_minZoomLevelSingleTile)
    4949             :     {
    4950          13 :         const int nMaxDim = std::max(nXSize, nYSize);
    4951             :         const int nOvrCount = static_cast<int>(
    4952          26 :             std::ceil(std::max(0.0, std::log2(static_cast<double>(nMaxDim) /
    4953          13 :                                               tileMatrix.mTileWidth))));
    4954          13 :         m_minZoomLevel = std::max(0, m_maxZoomLevel - nOvrCount);
    4955             :     }
    4956         176 :     else if (m_minZoomLevel < 0)
    4957          83 :         m_minZoomLevel = m_maxZoomLevel;
    4958             : 
    4959             :     /* -------------------------------------------------------------------- */
    4960             :     /*      Setup warp options.                                             */
    4961             :     /* -------------------------------------------------------------------- */
    4962             :     std::unique_ptr<GDALWarpOptions, decltype(&GDALDestroyWarpOptions)> psWO(
    4963         378 :         GDALCreateWarpOptions(), GDALDestroyWarpOptions);
    4964             : 
    4965         189 :     psWO->papszWarpOptions = CSLSetNameValue(nullptr, "OPTIMIZE_SIZE", "YES");
    4966         378 :     psWO->papszWarpOptions =
    4967         189 :         CSLSetNameValue(psWO->papszWarpOptions, "SAMPLE_GRID", "YES");
    4968         378 :     psWO->papszWarpOptions =
    4969         189 :         CSLMerge(psWO->papszWarpOptions, aosWarpOptions.List());
    4970             : 
    4971         189 :     int bHasSrcNoData = false;
    4972             :     const double dfSrcNoDataValue =
    4973         189 :         m_poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHasSrcNoData);
    4974             : 
    4975             :     const bool bLastSrcBandIsAlpha =
    4976         338 :         (m_poSrcDS->GetRasterCount() > 1 &&
    4977         149 :          m_poSrcDS->GetRasterBand(m_poSrcDS->GetRasterCount())
    4978         149 :                  ->GetColorInterpretation() == GCI_AlphaBand);
    4979             : 
    4980         189 :     const bool bOutputSupportsAlpha = !EQUAL(m_format.c_str(), "JPEG");
    4981         189 :     const bool bOutputSupportsNoData = EQUAL(m_format.c_str(), "GTiff");
    4982         189 :     const bool bDstNoDataSpecified = GetArg("dst-nodata")->IsExplicitlySet();
    4983             :     auto poColorTable = std::unique_ptr<GDALColorTable>(
    4984         189 :         [this]()
    4985             :         {
    4986         189 :             auto poCT = m_poSrcDS->GetRasterBand(1)->GetColorTable();
    4987         189 :             return poCT ? poCT->Clone() : nullptr;
    4988         378 :         }());
    4989             : 
    4990         189 :     const bool bUserAskedForAlpha = m_addalpha;
    4991         189 :     if (!m_noalpha && !m_addalpha)
    4992             :     {
    4993         206 :         m_addalpha = !(bHasSrcNoData && bOutputSupportsNoData) &&
    4994         206 :                      !bDstNoDataSpecified && poColorTable == nullptr;
    4995             :     }
    4996         189 :     m_addalpha &= bOutputSupportsAlpha;
    4997             : 
    4998         189 :     psWO->nBandCount = m_poSrcDS->GetRasterCount();
    4999         189 :     if (bLastSrcBandIsAlpha)
    5000             :     {
    5001          17 :         --psWO->nBandCount;
    5002          17 :         psWO->nSrcAlphaBand = m_poSrcDS->GetRasterCount();
    5003             :     }
    5004             : 
    5005         189 :     if (bHasSrcNoData)
    5006             :     {
    5007          40 :         psWO->padfSrcNoDataReal =
    5008          20 :             static_cast<double *>(CPLCalloc(psWO->nBandCount, sizeof(double)));
    5009          60 :         for (int i = 0; i < psWO->nBandCount; ++i)
    5010             :         {
    5011          40 :             psWO->padfSrcNoDataReal[i] = dfSrcNoDataValue;
    5012             :         }
    5013             :     }
    5014             : 
    5015         189 :     if ((bHasSrcNoData && !m_addalpha && bOutputSupportsNoData) ||
    5016             :         bDstNoDataSpecified)
    5017             :     {
    5018          18 :         psWO->padfDstNoDataReal =
    5019           9 :             static_cast<double *>(CPLCalloc(psWO->nBandCount, sizeof(double)));
    5020          18 :         for (int i = 0; i < psWO->nBandCount; ++i)
    5021             :         {
    5022           9 :             psWO->padfDstNoDataReal[i] =
    5023           9 :                 bDstNoDataSpecified ? m_dstNoData : dfSrcNoDataValue;
    5024             :         }
    5025             :     }
    5026             : 
    5027         189 :     psWO->eWorkingDataType = eSrcDT;
    5028             : 
    5029         189 :     GDALGetWarpResampleAlg(m_resampling.c_str(), psWO->eResampleAlg);
    5030             : 
    5031             :     /* -------------------------------------------------------------------- */
    5032             :     /*      Setup band mapping.                                             */
    5033             :     /* -------------------------------------------------------------------- */
    5034             : 
    5035         378 :     psWO->panSrcBands =
    5036         189 :         static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
    5037         378 :     psWO->panDstBands =
    5038         189 :         static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
    5039             : 
    5040       66203 :     for (int i = 0; i < psWO->nBandCount; i++)
    5041             :     {
    5042       66014 :         psWO->panSrcBands[i] = i + 1;
    5043       66014 :         psWO->panDstBands[i] = i + 1;
    5044             :     }
    5045             : 
    5046         189 :     if (m_addalpha)
    5047         173 :         psWO->nDstAlphaBand = psWO->nBandCount + 1;
    5048             : 
    5049             :     const int nDstBands =
    5050         189 :         psWO->nDstAlphaBand ? psWO->nDstAlphaBand : psWO->nBandCount;
    5051             : 
    5052         378 :     std::vector<GByte> dstBuffer;
    5053         189 :     const bool bIsPNGOutput = EQUAL(pszExtension, "png");
    5054             :     uint64_t dstBufferSize =
    5055         189 :         (static_cast<uint64_t>(tileMatrix.mTileWidth) *
    5056             :              // + 1 for PNG filter type / row byte
    5057         189 :              nDstBands * GDALGetDataTypeSizeBytes(psWO->eWorkingDataType) +
    5058         189 :          (bIsPNGOutput ? 1 : 0)) *
    5059         189 :         tileMatrix.mTileHeight;
    5060         189 :     if (bIsPNGOutput)
    5061             :     {
    5062             :         // Security margin for deflate compression
    5063         166 :         dstBufferSize += dstBufferSize / 10;
    5064             :     }
    5065             :     const uint64_t nUsableRAM =
    5066         189 :         std::min<uint64_t>(INT_MAX, CPLGetUsablePhysicalRAM() / 4);
    5067         189 :     if (dstBufferSize <=
    5068         189 :         (nUsableRAM ? nUsableRAM : static_cast<uint64_t>(INT_MAX)))
    5069             :     {
    5070             :         try
    5071             :         {
    5072         188 :             dstBuffer.resize(static_cast<size_t>(dstBufferSize));
    5073             :         }
    5074           0 :         catch (const std::exception &)
    5075             :         {
    5076             :         }
    5077             :     }
    5078         189 :     if (dstBuffer.size() < dstBufferSize)
    5079             :     {
    5080           1 :         ReportError(CE_Failure, CPLE_AppDefined,
    5081             :                     "Tile size and/or number of bands too large compared to "
    5082             :                     "available RAM");
    5083           1 :         return false;
    5084             :     }
    5085             : 
    5086             :     /* -------------------------------------------------------------------- */
    5087             :     /*      Select source overview                                          */
    5088             :     /* -------------------------------------------------------------------- */
    5089             : 
    5090         188 :     const int nDstXSize = (nMaxTileX - nMinTileX + 1) * tileMatrix.mTileWidth;
    5091         188 :     const int nDstYSize = (nMaxTileY - nMinTileY + 1) * tileMatrix.mTileHeight;
    5092             : 
    5093         188 :     const int nSrcOvrCount = m_poSrcDS->GetRasterBand(1)->GetOverviewCount();
    5094         191 :     if (nSrcOvrCount > 0 &&
    5095         189 :         m_poSrcDS->GetRasterXSize() > tileMatrix.mTileWidth &&
    5096           1 :         m_poSrcDS->GetRasterYSize() > tileMatrix.mTileHeight)
    5097             :     {
    5098             :         const double dfTargetRatioX =
    5099           1 :             static_cast<double>(m_poSrcDS->GetRasterXSize()) / nDstXSize;
    5100             :         const double dfTargetRatioY =
    5101           1 :             static_cast<double>(m_poSrcDS->GetRasterYSize()) / nDstYSize;
    5102             :         // take the minimum of these ratios #7019
    5103           1 :         const double dfTargetRatio = std::min(dfTargetRatioX, dfTargetRatioY);
    5104           1 :         if (dfTargetRatio > 1.0)
    5105             :         {
    5106           1 :             const int iBestOvr = GDALBandGetBestOverviewLevel(
    5107           1 :                 m_poSrcDS->GetRasterBand(1), dfTargetRatio,
    5108             :                 /* dfOversamplingThreshold = */ 1.0);
    5109           1 :             if (iBestOvr >= 0)
    5110             :             {
    5111           1 :                 CPLDebug("WARP", "Selecting overview level %d", iBestOvr);
    5112           1 :                 m_poSrcOvrDS =
    5113           1 :                     GDALCreateOverviewDataset(m_poSrcDS, iBestOvr,
    5114             :                                               /* bThisLevelOnly = */ false);
    5115           1 :                 m_poSrcDS = m_poSrcOvrDS;
    5116             :             }
    5117             :         }
    5118             :     }
    5119             : 
    5120             :     FakeMaxZoomDataset oFakeMaxZoomDS(
    5121             :         nDstXSize, nDstYSize, nDstBands, tileMatrix.mTileWidth,
    5122         188 :         tileMatrix.mTileHeight, psWO->eWorkingDataType, dstGT, oSRS_TMS,
    5123         376 :         dstBuffer);
    5124         188 :     CPL_IGNORE_RET_VAL(oFakeMaxZoomDS.GetSpatialRef());
    5125             : 
    5126         188 :     psWO->hSrcDS = GDALDataset::ToHandle(m_poSrcDS);
    5127         188 :     psWO->hDstDS = GDALDataset::ToHandle(&oFakeMaxZoomDS);
    5128             : 
    5129         188 :     std::unique_ptr<GDALDataset> tmpSrcDS;
    5130         188 :     if (m_tilingScheme == "raster" && !bHasNorthUpSrcGT)
    5131             :     {
    5132           1 :         CPLStringList aosOptions;
    5133           1 :         aosOptions.AddString("-of");
    5134           1 :         aosOptions.AddString("VRT");
    5135           1 :         aosOptions.AddString("-a_ullr");
    5136           1 :         aosOptions.AddString(CPLSPrintf("%.17g", srcGTModif[0]));
    5137           1 :         aosOptions.AddString(CPLSPrintf("%.17g", srcGTModif[3]));
    5138             :         aosOptions.AddString(
    5139           1 :             CPLSPrintf("%.17g", srcGTModif[0] + nSrcWidth * srcGTModif[1]));
    5140             :         aosOptions.AddString(
    5141           1 :             CPLSPrintf("%.17g", 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         189 :     hTransformArg.reset(GDALCreateGenImgProjTransformer2(
    5158         189 :         tmpSrcDS ? tmpSrcDS.get() : m_poSrcDS, &oFakeMaxZoomDS, aosTO.List()));
    5159         188 :     CPLAssert(hTransformArg);
    5160             : 
    5161             :     /* -------------------------------------------------------------------- */
    5162             :     /*      Warp the transformer with a linear approximator                 */
    5163             :     /* -------------------------------------------------------------------- */
    5164         188 :     hTransformArg.reset(GDALCreateApproxTransformer(
    5165             :         GDALGenImgProjTransform, hTransformArg.release(), 0.125));
    5166         188 :     GDALApproxTransformerOwnsSubtransformer(hTransformArg.get(), TRUE);
    5167             : 
    5168         188 :     psWO->pfnTransformer = GDALApproxTransform;
    5169         188 :     psWO->pTransformerArg = hTransformArg.get();
    5170             : 
    5171             :     /* -------------------------------------------------------------------- */
    5172             :     /*      Determine total number of tiles                                 */
    5173             :     /* -------------------------------------------------------------------- */
    5174         188 :     const int nBaseTilesPerRow = nMaxTileX - nMinTileX + 1;
    5175         188 :     const int nBaseTilesPerCol = nMaxTileY - nMinTileY + 1;
    5176         188 :     const uint64_t nBaseTiles =
    5177         188 :         static_cast<uint64_t>(nBaseTilesPerCol) * nBaseTilesPerRow;
    5178         188 :     uint64_t nTotalTiles = nBaseTiles;
    5179         188 :     std::atomic<uint64_t> nCurTile = 0;
    5180         188 :     bool bRet = true;
    5181             : 
    5182         375 :     for (int iZ = m_maxZoomLevel - 1;
    5183         375 :          bRet && bIntersects && iZ >= m_minZoomLevel; --iZ)
    5184             :     {
    5185         374 :         auto ovrTileMatrix = tileMatrixList[iZ];
    5186         187 :         int nOvrMinTileX = 0;
    5187         187 :         int nOvrMinTileY = 0;
    5188         187 :         int nOvrMaxTileX = 0;
    5189         187 :         int nOvrMaxTileY = 0;
    5190         187 :         bRet =
    5191         374 :             GetTileIndices(ovrTileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
    5192             :                            nOvrMinTileX, nOvrMinTileY, nOvrMaxTileX,
    5193         187 :                            nOvrMaxTileY, m_noIntersectionIsOK, bIntersects);
    5194         187 :         if (bIntersects)
    5195             :         {
    5196         187 :             nTotalTiles +=
    5197         187 :                 static_cast<uint64_t>(nOvrMaxTileY - nOvrMinTileY + 1) *
    5198         187 :                 (nOvrMaxTileX - nOvrMinTileX + 1);
    5199             :         }
    5200             :     }
    5201             : 
    5202             :     /* -------------------------------------------------------------------- */
    5203             :     /*      Generate tiles at max zoom level                                */
    5204             :     /* -------------------------------------------------------------------- */
    5205         376 :     GDALWarpOperation oWO;
    5206             : 
    5207         188 :     bRet = oWO.Initialize(psWO.get()) == CE_None && bRet;
    5208             : 
    5209             :     const auto GetUpdatedCreationOptions =
    5210         725 :         [this](const gdal::TileMatrixSet::TileMatrix &oTM)
    5211             :     {
    5212         249 :         CPLStringList aosCreationOptions(m_creationOptions);
    5213         249 :         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         225 :         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         249 :         return aosCreationOptions;
    5247         188 :     };
    5248             : 
    5249         188 :     VSIMkdir(m_outputDir.c_str(), 0755);
    5250             :     VSIStatBufL sStat;
    5251         188 :     if (VSIStatL(m_outputDir.c_str(), &sStat) != 0 || !VSI_ISDIR(sStat.st_mode))
    5252             :     {
    5253           2 :         ReportError(CE_Failure, CPLE_FileIO,
    5254             :                     "Cannot create output directory %s", m_outputDir.c_str());
    5255           2 :         return false;
    5256             :     }
    5257             : 
    5258         372 :     OGRSpatialReference oWGS84;
    5259         186 :     oWGS84.importFromEPSG(4326);
    5260         186 :     oWGS84.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    5261             : 
    5262         186 :     std::unique_ptr<OGRCoordinateTransformation> poCTToWGS84;
    5263         186 :     if (!oSRS_TMS.IsEmpty())
    5264             :     {
    5265         370 :         CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
    5266         185 :         poCTToWGS84.reset(
    5267             :             OGRCreateCoordinateTransformation(&oSRS_TMS, &oWGS84));
    5268             :     }
    5269             : 
    5270         192 :     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         186 :         m_tileSize > 0 ? m_tileSize : poTMS->tileMatrixList()[0].mTileWidth;
    5286         186 :     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         186 :     if (m_title.empty())
    5294         150 :         m_title = CPLGetFilename(m_inputDataset[0].GetName().c_str());
    5295             : 
    5296         186 :     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         372 :     CPLWorkerThreadPool oThreadPool;
    5307             : 
    5308         186 :     bool bThreadPoolInitialized = false;
    5309             :     const auto InitThreadPool =
    5310         880 :         [this, &oThreadPool, &bRet, &bThreadPoolInitialized]()
    5311             :     {
    5312         249 :         if (!bThreadPoolInitialized)
    5313             :         {
    5314         185 :             bThreadPoolInitialized = true;
    5315             : 
    5316         185 :             if (bRet && m_numThreads > 1)
    5317             :             {
    5318           6 :                 CPLDebug("gdal_raster_tile", "Using %d threads", m_numThreads);
    5319           6 :                 bRet = oThreadPool.Setup(m_numThreads, nullptr, nullptr);
    5320             :             }
    5321             :         }
    5322             : 
    5323         249 :         return bRet;
    5324         186 :     };
    5325             : 
    5326             :     // Just for unit test purposes
    5327         186 :     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         186 :     };
    5359             : 
    5360         186 :     m_numThreads = std::max(
    5361         372 :         1, static_cast<int>(std::min<uint64_t>(
    5362         186 :                m_numThreads, nBaseTiles / GetThresholdMinTilesPerJob())));
    5363             : 
    5364         186 :     std::atomic<bool> bParentAskedForStop = false;
    5365         372 :     std::thread threadWaitForParentStop;
    5366         186 :     std::unique_ptr<CPLErrorHandlerPusher> poErrorHandlerPusher;
    5367         186 :     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         136 :     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         186 :     if (m_ovrZoomLevel >= 0)
    5419             :     {
    5420             :         // do not generate base tiles if called as a child process with
    5421             :         // --ovr-zoom-level
    5422             :     }
    5423         180 :     else if (m_numThreads > 1 && nBaseTiles > 1 &&
    5424          13 :              ((m_parallelMethod.empty() &&
    5425           9 :                m_numThreads >= GetThresholdMinThreadsForSpawn() &&
    5426          15 :                IsCompatibleOfSpawnSilent()) ||
    5427          16 :               (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         147 :             m_poSrcDS, psWO.get(), hTransformArg.get(), oFakeMaxZoomDS,
    5445         441 :             dstBuffer.size());
    5446             : 
    5447             :         const CPLStringList aosCreationOptions(
    5448         294 :             GetUpdatedCreationOptions(tileMatrix));
    5449             : 
    5450         147 :         CPLDebug("gdal_raster_tile",
    5451             :                  "Generating tiles z=%d, y=%d...%d, x=%d...%d", m_maxZoomLevel,
    5452             :                  nMinTileY, nMaxTileY, nMinTileX, nMaxTileX);
    5453             : 
    5454         147 :         bRet &= InitThreadPool();
    5455             : 
    5456         147 :         if (bRet && m_numThreads > 1)
    5457             :         {
    5458           6 :             std::atomic<bool> bFailure = false;
    5459           6 :             std::atomic<int> nQueuedJobs = 0;
    5460             : 
    5461             :             double dfTilesYPerJob;
    5462             :             int nYOuterIterations;
    5463             :             double dfTilesXPerJob;
    5464             :             int nXOuterIterations;
    5465           6 :             ComputeJobChunkSize(m_numThreads, nBaseTilesPerCol,
    5466             :                                 nBaseTilesPerRow, dfTilesYPerJob,
    5467             :                                 nYOuterIterations, dfTilesXPerJob,
    5468             :                                 nXOuterIterations);
    5469             : 
    5470           6 :             CPLDebugOnly("gdal_raster_tile",
    5471             :                          "nYOuterIterations=%d, dfTilesYPerJob=%g, "
    5472             :                          "nXOuterIterations=%d, dfTilesXPerJob=%g",
    5473             :                          nYOuterIterations, dfTilesYPerJob, nXOuterIterations,
    5474             :                          dfTilesXPerJob);
    5475             : 
    5476           6 :             int nLastYEndIncluded = nMinTileY - 1;
    5477          30 :             for (int iYOuterIter = 0; bRet && iYOuterIter < nYOuterIterations &&
    5478          24 :                                       nLastYEndIncluded < nMaxTileY;
    5479             :                  ++iYOuterIter)
    5480             :             {
    5481          24 :                 const int iYStart = nLastYEndIncluded + 1;
    5482             :                 const int iYEndIncluded =
    5483          24 :                     iYOuterIter + 1 == nYOuterIterations
    5484          42 :                         ? nMaxTileY
    5485             :                         : std::max(
    5486             :                               iYStart,
    5487          42 :                               static_cast<int>(std::floor(
    5488          18 :                                   nMinTileY +
    5489          18 :                                   (iYOuterIter + 1) * dfTilesYPerJob - 1)));
    5490             : 
    5491          24 :                 nLastYEndIncluded = iYEndIncluded;
    5492             : 
    5493          24 :                 int nLastXEndIncluded = nMinTileX - 1;
    5494          24 :                 for (int iXOuterIter = 0;
    5495          48 :                      bRet && iXOuterIter < nXOuterIterations &&
    5496          24 :                      nLastXEndIncluded < nMaxTileX;
    5497             :                      ++iXOuterIter)
    5498             :                 {
    5499          24 :                     const int iXStart = nLastXEndIncluded + 1;
    5500             :                     const int iXEndIncluded =
    5501          24 :                         iXOuterIter + 1 == nXOuterIterations
    5502          24 :                             ? nMaxTileX
    5503             :                             : std::max(
    5504             :                                   iXStart,
    5505          24 :                                   static_cast<int>(std::floor(
    5506           0 :                                       nMinTileX +
    5507           0 :                                       (iXOuterIter + 1) * dfTilesXPerJob - 1)));
    5508             : 
    5509          24 :                     nLastXEndIncluded = iXEndIncluded;
    5510             : 
    5511          24 :                     CPLDebugOnly("gdal_raster_tile",
    5512             :                                  "Job for y in [%d,%d] and x in [%d,%d]",
    5513             :                                  iYStart, iYEndIncluded, iXStart,
    5514             :                                  iXEndIncluded);
    5515             : 
    5516          24 :                     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        2078 :                                 bUserAskedForAlpha]()
    5523             :                     {
    5524          24 :                         CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
    5525             : 
    5526          24 :                         auto resources = oResourceManager.AcquireResources();
    5527          24 :                         if (resources)
    5528             :                         {
    5529          24 :                             std::vector<GByte> tmpBuffer;
    5530          59 :                             for (int iY = iYStart;
    5531          59 :                                  iY <= iYEndIncluded && !bParentAskedForStop;
    5532             :                                  ++iY)
    5533             :                             {
    5534         458 :                                 for (int iX = iXStart; iX <= iXEndIncluded &&
    5535         217 :                                                        !bParentAskedForStop;
    5536             :                                      ++iX)
    5537             :                                 {
    5538         618 :                                     if (!GenerateTile(
    5539         206 :                                             resources->poSrcDS.get(),
    5540             :                                             m_poDstDriver, pszExtension,
    5541             :                                             aosCreationOptions.List(),
    5542         206 :                                             *(resources->poWO.get()),
    5543         206 :                                             *(resources->poFakeMaxZoomDS
    5544         206 :                                                   ->GetSpatialRef()),
    5545         206 :                                             psWO->eWorkingDataType, tileMatrix,
    5546         206 :                                             m_outputDir, nDstBands,
    5547         206 :                                             psWO->padfDstNoDataReal
    5548           0 :                                                 ? &(psWO->padfDstNoDataReal[0])
    5549             :                                                 : nullptr,
    5550             :                                             m_maxZoomLevel, iX, iY,
    5551         206 :                                             m_convention, nMinTileX, nMinTileY,
    5552         206 :                                             m_skipBlank, bUserAskedForAlpha,
    5553         206 :                                             m_auxXML, m_resume, m_metadata,
    5554         206 :                                             poColorTable.get(),
    5555         206 :                                             resources->dstBuffer, tmpBuffer))
    5556             :                                     {
    5557           0 :                                         oResourceManager.SetError();
    5558           0 :                                         bFailure = true;
    5559           0 :                                         --nQueuedJobs;
    5560           0 :                                         return;
    5561             :                                     }
    5562         206 :                                     ++nCurTile;
    5563         206 :                                     oThreadPool.WakeUpWaitEvent();
    5564             :                                 }
    5565             :                             }
    5566          24 :                             oResourceManager.ReleaseResources(
    5567          24 :                                 std::move(resources));
    5568             :                         }
    5569             :                         else
    5570             :                         {
    5571           0 :                             oResourceManager.SetError();
    5572           0 :                             bFailure = true;
    5573             :                         }
    5574             : 
    5575          24 :                         --nQueuedJobs;
    5576          24 :                     };
    5577             : 
    5578          24 :                     ++nQueuedJobs;
    5579          24 :                     oThreadPool.SubmitJob(std::move(job));
    5580             :                 }
    5581             :             }
    5582             : 
    5583             :             // Wait for completion of all jobs
    5584         207 :             while (bRet && nQueuedJobs > 0)
    5585             :             {
    5586         201 :                 oThreadPool.WaitEvent();
    5587         201 :                 bRet &= !bFailure;
    5588         269 :                 if (bRet && pfnProgress &&
    5589         136 :                     !pfnProgress(static_cast<double>(nCurTile) /
    5590          68 :                                      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           6 :             oThreadPool.WaitCompletion();
    5600           6 :             bRet &=
    5601          10 :                 !bFailure && (!pfnProgress ||
    5602           8 :                               pfnProgress(static_cast<double>(nCurTile) /
    5603           4 :                                               static_cast<double>(nTotalTiles),
    5604           6 :                                           "", pProgressData));
    5605             : 
    5606           6 :             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           6 :             }
    5612             :         }
    5613             :         else
    5614             :         {
    5615             :             // Branch for single-thread max zoom level tile generation
    5616         282 :             std::vector<GByte> tmpBuffer;
    5617         354 :             for (int iY = nMinTileY;
    5618         354 :                  bRet && !bParentAskedForStop && iY <= nMaxTileY; ++iY)
    5619             :             {
    5620         825 :                 for (int iX = nMinTileX;
    5621         825 :                      bRet && !bParentAskedForStop && iX <= nMaxTileX; ++iX)
    5622             :                 {
    5623        1224 :                     bRet = GenerateTile(
    5624             :                         m_poSrcDS, m_poDstDriver, pszExtension,
    5625             :                         aosCreationOptions.List(), oWO, oSRS_TMS,
    5626         612 :                         psWO->eWorkingDataType, tileMatrix, m_outputDir,
    5627             :                         nDstBands,
    5628         612 :                         psWO->padfDstNoDataReal ? &(psWO->padfDstNoDataReal[0])
    5629             :                                                 : nullptr,
    5630         612 :                         m_maxZoomLevel, iX, iY, m_convention, nMinTileX,
    5631         612 :                         nMinTileY, m_skipBlank, bUserAskedForAlpha, m_auxXML,
    5632         612 :                         m_resume, m_metadata, poColorTable.get(), dstBuffer,
    5633             :                         tmpBuffer);
    5634             : 
    5635         612 :                     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         354 :                     else if (m_forked)
    5645             :                     {
    5646           0 :                         CPLPipeWrite(pipeOut, PROGRESS_MARKER,
    5647             :                                      sizeof(PROGRESS_MARKER));
    5648             :                     }
    5649             : #endif
    5650             :                     else
    5651             :                     {
    5652         354 :                         ++nCurTile;
    5653         373 :                         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         147 :         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         185 :     std::vector<GDALColorInterp> aeColorInterp;
    5699         673 :     for (int i = 1; i <= m_poSrcDS->GetRasterCount(); ++i)
    5700         488 :         aeColorInterp.push_back(
    5701         488 :             m_poSrcDS->GetRasterBand(i)->GetColorInterpretation());
    5702         185 :     if (m_poSrcOvrDS)
    5703             :     {
    5704           1 :         m_poSrcOvrDS->ReleaseRef();
    5705           1 :         m_poSrcOvrDS = nullptr;
    5706             :     }
    5707         185 :     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         185 :     const int iZStart =
    5717         185 :         m_ovrZoomLevel >= 0 ? m_ovrZoomLevel : m_maxZoomLevel - 1;
    5718         185 :     const int iZEnd = m_ovrZoomLevel >= 0 ? m_ovrZoomLevel : m_minZoomLevel;
    5719         299 :     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          73 :                 while (bRet && nQueuedJobs > 0)
    5964             :                 {
    5965          67 :                     oThreadPool.WaitEvent();
    5966          67 :                     bRet &= !bFailure;
    5967          86 :                     if (bRet && pfnProgress &&
    5968          38 :                         !pfnProgress(static_cast<double>(nCurTile) /
    5969          19 :                                          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        1662 :     const auto IsWebViewerEnabled = [this](const char *name)
    6104             :     {
    6105         554 :         return std::find_if(m_webviewers.begin(), m_webviewers.end(),
    6106         765 :                             [name](const std::string &s)
    6107        1112 :                             { return s == "all" || s == name; }) !=
    6108        1108 :                m_webviewers.end();
    6109         185 :     };
    6110             : 
    6111         297 :     if (m_ovrZoomLevel < 0 && bRet &&
    6112         482 :         poTMS->identifier() == "GoogleMapsCompatible" &&
    6113         122 :         IsWebViewerEnabled("leaflet"))
    6114             :     {
    6115          81 :         double dfSouthLat = -90;
    6116          81 :         double dfWestLon = -180;
    6117          81 :         double dfNorthLat = 90;
    6118          81 :         double dfEastLon = 180;
    6119             : 
    6120          81 :         if (poCTToWGS84)
    6121             :         {
    6122          81 :             poCTToWGS84->TransformBounds(
    6123             :                 adfExtent[0], adfExtent[1], adfExtent[2], adfExtent[3],
    6124          81 :                 &dfWestLon, &dfSouthLat, &dfEastLon, &dfNorthLat, 21);
    6125             :         }
    6126             : 
    6127          81 :         GenerateLeaflet(m_outputDir, m_title, dfSouthLat, dfWestLon, dfNorthLat,
    6128             :                         dfEastLon, m_minZoomLevel, m_maxZoomLevel,
    6129          81 :                         tileMatrix.mTileWidth, pszExtension, m_url, m_copyright,
    6130          81 :                         m_convention == "xyz");
    6131             :     }
    6132             : 
    6133         185 :     if (m_ovrZoomLevel < 0 && bRet && IsWebViewerEnabled("openlayers"))
    6134             :     {
    6135          92 :         GenerateOpenLayers(m_outputDir, m_title, adfExtent[0], adfExtent[1],
    6136             :                            adfExtent[2], adfExtent[3], m_minZoomLevel,
    6137             :                            m_maxZoomLevel, tileMatrix.mTileWidth, pszExtension,
    6138          92 :                            m_url, m_copyright, *(poTMS.get()), bInvertAxisTMS,
    6139          92 :                            oSRS_TMS, m_convention == "xyz");
    6140             :     }
    6141             : 
    6142         244 :     if (m_ovrZoomLevel < 0 && bRet && IsWebViewerEnabled("mapml") &&
    6143         429 :         poTMS->identifier() != "raster" && m_convention == "xyz")
    6144             :     {
    6145          55 :         GenerateMapML(m_outputDir, m_mapmlTemplate, m_title, nMinTileX,
    6146             :                       nMinTileY, nMaxTileX, nMaxTileY, m_minZoomLevel,
    6147          55 :                       m_maxZoomLevel, pszExtension, m_url, m_copyright,
    6148          55 :                       *(poTMS.get()));
    6149             :     }
    6150             : 
    6151         275 :     if (m_ovrZoomLevel < 0 && bRet && IsWebViewerEnabled("stac") &&
    6152          90 :         m_convention == "xyz")
    6153             :     {
    6154          59 :         OGRCoordinateTransformation *poCT = poCTToWGS84.get();
    6155           0 :         std::unique_ptr<OGRCoordinateTransformation> poCTToLongLat;
    6156          59 :         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          59 :         double dfSouthLat = -90;
    6168          59 :         double dfWestLon = -180;
    6169          59 :         double dfNorthLat = 90;
    6170          59 :         double dfEastLon = 180;
    6171          59 :         if (poCT)
    6172             :         {
    6173          58 :             poCT->TransformBounds(adfExtent[0], adfExtent[1], adfExtent[2],
    6174             :                                   adfExtent[3], &dfWestLon, &dfSouthLat,
    6175          58 :                                   &dfEastLon, &dfNorthLat, 21);
    6176             :         }
    6177             : 
    6178         118 :         GenerateSTAC(m_outputDir, m_title, dfWestLon, dfSouthLat, dfEastLon,
    6179          59 :                      dfNorthLat, m_metadata, aoBandMetadata, m_minZoomLevel,
    6180          59 :                      m_maxZoomLevel, pszExtension, m_format, m_url, m_copyright,
    6181          59 :                      oSRS_TMS, *(poTMS.get()), bInvertAxisTMS, m_tileSize,
    6182          59 :                      adfExtent, m_inputDataset[0]);
    6183             :     }
    6184             : 
    6185         185 :     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         185 :     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         185 :     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         135 :     else if (m_forked)
    6247             :     {
    6248           0 :         CPLPipeWrite(pipeOut, END_MARKER, sizeof(END_MARKER));
    6249           0 :         threadWaitForParentStop.join();
    6250             :     }
    6251             : #endif
    6252             : 
    6253         185 :     return bRet;
    6254             : }
    6255             : 
    6256             : GDALRasterTileAlgorithmStandalone::~GDALRasterTileAlgorithmStandalone() =
    6257             :     default;
    6258             : 
    6259             : //! @endcond

Generated by: LCOV version 1.14