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

Generated by: LCOV version 1.14