Coverage Report

Created: 2026-07-25 06:50

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