LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_tile.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2894 3106 93.2 %
Date: 2026-05-13 23:47:50 Functions: 77 84 91.7 %

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

Generated by: LCOV version 1.14