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

Generated by: LCOV version 1.14