Coverage Report

Created: 2026-09-14 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(dfMinX);
1703
0
            aosOptions.AddString(dfMaxY);
1704
0
            aosOptions.AddString(dfMaxX);
1705
0
            aosOptions.AddString(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(dfMinX);
1723
0
        aosOptions.AddString(dfMinY);
1724
0
        aosOptions.AddString(dfMaxX);
1725
0
        aosOptions.AddString(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, (dfWestLon + dfEastLon) / 2);
2205
0
        substs["centerlat"] = CPLSPrintf(pszFmt, (dfNorthLat + dfSouthLat) / 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 + static_cast<double>(nOvrMinTileX) *
2517
0
                                          ovrTileMatrix.mTileWidth *
2518
0
                                          ovrTileMatrix.mResX,
2519
0
            0.0,
2520
0
            -ovrTileMatrix.mResY,
2521
0
            ovrTileMatrix.mTopLeftY - static_cast<double>(nOvrMinTileY) *
2522
0
                                          ovrTileMatrix.mTileHeight *
2523
0
                                          ovrTileMatrix.mResY,
2524
0
            0.0,
2525
0
            0.0,
2526
0
            0.0};
2527
0
    }
2528
2529
0
    constexpr const char *ASSET_NAME = "bands";
2530
2531
0
    CPLJSONObject oAssetTemplates;
2532
0
    oRoot["asset_templates"] = oAssetTemplates;
2533
2534
0
    CPLJSONObject oAssetTemplate;
2535
0
    oAssetTemplates[ASSET_NAME] = oAssetTemplate;
2536
2537
0
    std::string osHref = (osURL.empty() ? std::string(".") : std::string(osURL))
2538
0
                             .append("/{TileMatrix}/{TileCol}/{TileRow}.")
2539
0
                             .append(osExtension);
2540
2541
0
    const std::map<std::string, std::string> oMapVSIToURIPrefix = {
2542
0
        {"vsis3", "s3://"},
2543
0
        {"vsigs", "gs://"},
2544
0
        {"vsiaz", "az://"},  // Not universally recognized
2545
0
    };
2546
2547
0
    const CPLStringList aosSplitHref(
2548
0
        CSLTokenizeString2(osHref.c_str(), "/", 0));
2549
0
    if (!aosSplitHref.empty())
2550
0
    {
2551
0
        const auto oIter = oMapVSIToURIPrefix.find(aosSplitHref[0]);
2552
0
        if (oIter != oMapVSIToURIPrefix.end())
2553
0
        {
2554
            // +2 because of 2 slash characters
2555
0
            osHref = std::string(oIter->second)
2556
0
                         .append(osHref.c_str() + strlen(aosSplitHref[0]) + 2);
2557
0
        }
2558
0
    }
2559
0
    oAssetTemplate["href"] = osHref;
2560
2561
0
    if (EQUAL(osFormat.c_str(), "COG"))
2562
0
        oAssetTemplate["type"] =
2563
0
            "image/tiff; application=geotiff; profile=cloud-optimized";
2564
0
    else if (osExtension == "tif")
2565
0
        oAssetTemplate["type"] = "image/tiff; application=geotiff";
2566
0
    else if (osExtension == "png")
2567
0
        oAssetTemplate["type"] = "image/png";
2568
0
    else if (osExtension == "jpg")
2569
0
        oAssetTemplate["type"] = "image/jpeg";
2570
0
    else if (osExtension == "webp")
2571
0
        oAssetTemplate["type"] = "image/webp";
2572
2573
0
    const std::map<GDALDataType, const char *> oMapDTToStac = {
2574
0
        {GDT_Int8, "int8"},
2575
0
        {GDT_Int16, "int16"},
2576
0
        {GDT_Int32, "int32"},
2577
0
        {GDT_Int64, "int64"},
2578
0
        {GDT_UInt8, "uint8"},
2579
0
        {GDT_UInt16, "uint16"},
2580
0
        {GDT_UInt32, "uint32"},
2581
0
        {GDT_UInt64, "uint64"},
2582
        // float16: 16-bit float; unhandled
2583
0
        {GDT_Float32, "float32"},
2584
0
        {GDT_Float64, "float64"},
2585
0
        {GDT_CInt16, "cint16"},
2586
0
        {GDT_CInt32, "cint32"},
2587
        // cfloat16: complex 16-bit float; unhandled
2588
0
        {GDT_CFloat32, "cfloat32"},
2589
0
        {GDT_CFloat64, "cfloat64"},
2590
0
    };
2591
2592
0
    CPLJSONArray oBands;
2593
0
    int iBand = 1;
2594
0
    bool bEOExtensionUsed = false;
2595
0
    for (const auto &bandMD : aoBandMetadata)
2596
0
    {
2597
0
        CPLJSONObject oBand;
2598
0
        oBand["name"] = bandMD.osDescription.empty()
2599
0
                            ? std::string(CPLSPrintf("Band%d", iBand))
2600
0
                            : bandMD.osDescription;
2601
2602
0
        const auto oIter = oMapDTToStac.find(bandMD.eDT);
2603
0
        if (oIter != oMapDTToStac.end())
2604
0
            oBand["data_type"] = oIter->second;
2605
2606
0
        if (const char *pszCommonName =
2607
0
                GDALGetSTACCommonNameFromColorInterp(bandMD.eColorInterp))
2608
0
        {
2609
0
            bEOExtensionUsed = true;
2610
0
            oBand["eo:common_name"] = pszCommonName;
2611
0
        }
2612
0
        if (!bandMD.osCenterWaveLength.empty() && !bandMD.osFWHM.empty())
2613
0
        {
2614
0
            bEOExtensionUsed = true;
2615
0
            oBand["eo:center_wavelength"] =
2616
0
                CPLAtof(bandMD.osCenterWaveLength.c_str());
2617
0
            oBand["eo:full_width_half_max"] = CPLAtof(bandMD.osFWHM.c_str());
2618
0
        }
2619
0
        ++iBand;
2620
0
        oBands.Add(oBand);
2621
0
    }
2622
0
    oAssetTemplate["bands"] = oBands;
2623
2624
0
    oRoot.Add("assets", CPLJSONObject());
2625
0
    oRoot.Add("links", CPLJSONArray());
2626
2627
0
    oExtensions.Add(
2628
0
        "https://stac-extensions.github.io/tiled-assets/v1.0.0/schema.json");
2629
0
    oExtensions.Add(
2630
0
        "https://stac-extensions.github.io/projection/v2.0.0/schema.json");
2631
0
    if (bEOExtensionUsed)
2632
0
        oExtensions.Add(
2633
0
            "https://stac-extensions.github.io/eo/v2.0.0/schema.json");
2634
2635
    // Serialize JSON document to file
2636
0
    const std::string osJSON =
2637
0
        CPLString(oRoot.Format(CPLJSONObject::PrettyFormat::Pretty))
2638
0
            .replaceAll("\\/", '/');
2639
0
    VSILFILE *f = VSIFOpenL(
2640
0
        CPLFormFilenameSafe(osDirectory.c_str(), "stacta.json", nullptr)
2641
0
            .c_str(),
2642
0
        "wb");
2643
0
    if (f)
2644
0
    {
2645
0
        VSIFWriteL(osJSON.data(), 1, osJSON.size(), f);
2646
0
        VSIFCloseL(f);
2647
0
    }
2648
0
}
2649
2650
/************************************************************************/
2651
/*                         GenerateOpenLayers()                         */
2652
/************************************************************************/
2653
2654
static void GenerateOpenLayers(
2655
    const std::string &osDirectory, const std::string &osTitle, double dfMinX,
2656
    double dfMinY, double dfMaxX, double dfMaxY, int nMinZoom, int nMaxZoom,
2657
    int nTileSize, const std::string &osExtension, const std::string &osURL,
2658
    const std::string &osCopyright, const gdal::TileMatrixSet &tms,
2659
    bool bInvertAxisTMS, const OGRSpatialReference &oSRS_TMS, bool bXYZ)
2660
0
{
2661
0
    std::map<std::string, std::string> substs;
2662
2663
    // For tests
2664
0
    const char *pszFmt =
2665
0
        atoi(CPLGetConfigOption("GDAL_RASTER_TILE_HTML_PREC", "17")) == 10
2666
0
            ? "%.10g"
2667
0
            : "%.17g";
2668
2669
0
    char *pszStr = CPLEscapeString(osTitle.c_str(), -1, CPLES_XML);
2670
0
    substs["xml_escaped_title"] = pszStr;
2671
0
    CPLFree(pszStr);
2672
0
    substs["ominx"] = CPLSPrintf(pszFmt, dfMinX);
2673
0
    substs["ominy"] = CPLSPrintf(pszFmt, dfMinY);
2674
0
    substs["omaxx"] = CPLSPrintf(pszFmt, dfMaxX);
2675
0
    substs["omaxy"] = CPLSPrintf(pszFmt, dfMaxY);
2676
0
    substs["center_x"] = CPLSPrintf(pszFmt, (dfMinX + dfMaxX) / 2);
2677
0
    substs["center_y"] = CPLSPrintf(pszFmt, (dfMinY + dfMaxY) / 2);
2678
0
    substs["minzoom"] = CPLSPrintf("%d", nMinZoom);
2679
0
    substs["maxzoom"] = CPLSPrintf("%d", nMaxZoom);
2680
0
    substs["tile_size"] = CPLSPrintf("%d", nTileSize);
2681
0
    substs["tileformat"] = osExtension;
2682
0
    substs["publishurl"] = osURL;
2683
0
    substs["copyright"] = osCopyright;
2684
0
    substs["sign_y"] = bXYZ ? "" : "-";
2685
2686
0
    CPLString s(R"raw(<!DOCTYPE html>
2687
0
<html>
2688
0
<head>
2689
0
    <title>%(xml_escaped_title)s</title>
2690
0
    <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
2691
0
    <meta http-equiv='imagetoolbar' content='no'/>
2692
0
    <style type="text/css"> v\:* {behavior:url(#default#VML);}
2693
0
        html, body { overflow: hidden; padding: 0; height: 100%; width: 100%; font-family: 'Lucida Grande',Geneva,Arial,Verdana,sans-serif; }
2694
0
        body { margin: 10px; background: #fff; }
2695
0
        h1 { margin: 0; padding: 6px; border:0; font-size: 20pt; }
2696
0
        #header { height: 43px; padding: 0; background-color: #eee; border: 1px solid #888; }
2697
0
        #subheader { height: 12px; text-align: right; font-size: 10px; color: #555;}
2698
0
        #map { height: 90%; border: 1px solid #888; }
2699
0
    </style>
2700
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">
2701
0
    <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@main/dist/en/v7.0.0/legacy/ol.js"></script>
2702
0
    <script src="https://unpkg.com/ol-layerswitcher@4.1.1"></script>
2703
0
    <link rel="stylesheet" href="https://unpkg.com/ol-layerswitcher@4.1.1/src/ol-layerswitcher.css" />
2704
0
</head>
2705
0
<body>
2706
0
    <div id="header"><h1>%(xml_escaped_title)s</h1></div>
2707
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>
2708
0
    <div id="map" class="map"></div>
2709
0
    <div id="mouse-position"></div>
2710
0
    <script type="text/javascript">
2711
0
        var mousePositionControl = new ol.control.MousePosition({
2712
0
            className: 'custom-mouse-position',
2713
0
            target: document.getElementById('mouse-position'),
2714
0
            undefinedHTML: '&nbsp;'
2715
0
        });
2716
0
        var map = new ol.Map({
2717
0
            controls: ol.control.defaults.defaults().extend([mousePositionControl]),
2718
0
            target: 'map',)raw");
2719
2720
0
    if (tms.identifier() == "GoogleMapsCompatible" ||
2721
0
        tms.identifier() == "WorldCRS84Quad")
2722
0
    {
2723
0
        s += R"raw(
2724
0
            layers: [
2725
0
                new ol.layer.Group({
2726
0
                        title: 'Base maps',
2727
0
                        layers: [
2728
0
                            new ol.layer.Tile({
2729
0
                                title: 'OpenStreetMap',
2730
0
                                type: 'base',
2731
0
                                visible: true,
2732
0
                                source: new ol.source.OSM()
2733
0
                            }),
2734
0
                        ]
2735
0
                }),)raw";
2736
0
    }
2737
2738
0
    if (tms.identifier() == "GoogleMapsCompatible")
2739
0
    {
2740
0
        s += R"raw(new ol.layer.Group({
2741
0
                    title: 'Overlay',
2742
0
                    layers: [
2743
0
                        new ol.layer.Tile({
2744
0
                            title: 'Overlay',
2745
0
                            // opacity: 0.7,
2746
0
                            extent: [%(ominx)f, %(ominy)f,%(omaxx)f, %(omaxy)f],
2747
0
                            source: new ol.source.XYZ({
2748
0
                                attributions: '%(copyright)s',
2749
0
                                minZoom: %(minzoom)d,
2750
0
                                maxZoom: %(maxzoom)d,
2751
0
                                url: './{z}/{x}/{%(sign_y)sy}.%(tileformat)s',
2752
0
                                tileSize: [%(tile_size)d, %(tile_size)d]
2753
0
                            })
2754
0
                        }),
2755
0
                    ]
2756
0
                }),)raw";
2757
0
    }
2758
0
    else if (tms.identifier() == "WorldCRS84Quad")
2759
0
    {
2760
0
        const double base_res = 180.0 / nTileSize;
2761
0
        std::string resolutions = "[";
2762
0
        for (int i = 0; i <= nMaxZoom; ++i)
2763
0
        {
2764
0
            if (i > 0)
2765
0
                resolutions += ",";
2766
0
            resolutions += CPLSPrintf(pszFmt, base_res / (1 << i));
2767
0
        }
2768
0
        resolutions += "]";
2769
0
        substs["resolutions"] = std::move(resolutions);
2770
2771
0
        if (bXYZ)
2772
0
        {
2773
0
            substs["origin"] = "[-180,90]";
2774
0
            substs["y_formula"] = "tileCoord[2]";
2775
0
        }
2776
0
        else
2777
0
        {
2778
0
            substs["origin"] = "[-180,-90]";
2779
0
            substs["y_formula"] = "- 1 - tileCoord[2]";
2780
0
        }
2781
2782
0
        s += R"raw(
2783
0
                new ol.layer.Group({
2784
0
                    title: 'Overlay',
2785
0
                    layers: [
2786
0
                        new ol.layer.Tile({
2787
0
                            title: 'Overlay',
2788
0
                            // opacity: 0.7,
2789
0
                            extent: [%(ominx)f, %(ominy)f,%(omaxx)f, %(omaxy)f],
2790
0
                            source: new ol.source.TileImage({
2791
0
                                attributions: '%(copyright)s',
2792
0
                                projection: 'EPSG:4326',
2793
0
                                minZoom: %(minzoom)d,
2794
0
                                maxZoom: %(maxzoom)d,
2795
0
                                tileGrid: new ol.tilegrid.TileGrid({
2796
0
                                    extent: [-180,-90,180,90],
2797
0
                                    origin: %(origin)s,
2798
0
                                    resolutions: %(resolutions)s,
2799
0
                                    tileSize: [%(tile_size)d, %(tile_size)d]
2800
0
                                }),
2801
0
                                tileUrlFunction: function(tileCoord) {
2802
0
                                    return ('./{z}/{x}/{y}.%(tileformat)s'
2803
0
                                        .replace('{z}', String(tileCoord[0]))
2804
0
                                        .replace('{x}', String(tileCoord[1]))
2805
0
                                        .replace('{y}', String(%(y_formula)s)));
2806
0
                                },
2807
0
                            })
2808
0
                        }),
2809
0
                    ]
2810
0
                }),)raw";
2811
0
    }
2812
0
    else
2813
0
    {
2814
0
        substs["maxres"] =
2815
0
            CPLSPrintf(pszFmt, tms.tileMatrixList()[nMinZoom].mResX);
2816
0
        std::string resolutions = "[";
2817
0
        for (int i = 0; i <= nMaxZoom; ++i)
2818
0
        {
2819
0
            if (i > 0)
2820
0
                resolutions += ",";
2821
0
            resolutions += CPLSPrintf(pszFmt, tms.tileMatrixList()[i].mResX);
2822
0
        }
2823
0
        resolutions += "]";
2824
0
        substs["resolutions"] = std::move(resolutions);
2825
2826
0
        std::string matrixsizes = "[";
2827
0
        for (int i = 0; i <= nMaxZoom; ++i)
2828
0
        {
2829
0
            if (i > 0)
2830
0
                matrixsizes += ",";
2831
0
            matrixsizes +=
2832
0
                CPLSPrintf("[%d,%d]", tms.tileMatrixList()[i].mMatrixWidth,
2833
0
                           tms.tileMatrixList()[i].mMatrixHeight);
2834
0
        }
2835
0
        matrixsizes += "]";
2836
0
        substs["matrixsizes"] = std::move(matrixsizes);
2837
2838
0
        double dfTopLeftX = tms.tileMatrixList()[0].mTopLeftX;
2839
0
        double dfTopLeftY = tms.tileMatrixList()[0].mTopLeftY;
2840
0
        if (bInvertAxisTMS)
2841
0
            std::swap(dfTopLeftX, dfTopLeftY);
2842
2843
0
        if (bXYZ)
2844
0
        {
2845
0
            substs["origin"] =
2846
0
                CPLSPrintf("[%.17g,%.17g]", dfTopLeftX, dfTopLeftY);
2847
0
            substs["y_formula"] = "tileCoord[2]";
2848
0
        }
2849
0
        else
2850
0
        {
2851
0
            substs["origin"] = CPLSPrintf(
2852
0
                "[%.17g,%.17g]", dfTopLeftX,
2853
0
                dfTopLeftY - tms.tileMatrixList()[0].mResY *
2854
0
                                 tms.tileMatrixList()[0].mTileHeight);
2855
0
            substs["y_formula"] = "- 1 - tileCoord[2]";
2856
0
        }
2857
2858
0
        substs["tilegrid_extent"] =
2859
0
            CPLSPrintf("[%.17g,%.17g,%.17g,%.17g]", dfTopLeftX,
2860
0
                       dfTopLeftY - tms.tileMatrixList()[0].mMatrixHeight *
2861
0
                                        tms.tileMatrixList()[0].mResY *
2862
0
                                        tms.tileMatrixList()[0].mTileHeight,
2863
0
                       dfTopLeftX + tms.tileMatrixList()[0].mMatrixWidth *
2864
0
                                        tms.tileMatrixList()[0].mResX *
2865
0
                                        tms.tileMatrixList()[0].mTileWidth,
2866
0
                       dfTopLeftY);
2867
2868
0
        s += R"raw(
2869
0
            layers: [
2870
0
                new ol.layer.Group({
2871
0
                    title: 'Overlay',
2872
0
                    layers: [
2873
0
                        new ol.layer.Tile({
2874
0
                            title: 'Overlay',
2875
0
                            // opacity: 0.7,
2876
0
                            extent: [%(ominx)f, %(ominy)f,%(omaxx)f, %(omaxy)f],
2877
0
                            source: new ol.source.TileImage({
2878
0
                                attributions: '%(copyright)s',
2879
0
                                minZoom: %(minzoom)d,
2880
0
                                maxZoom: %(maxzoom)d,
2881
0
                                tileGrid: new ol.tilegrid.TileGrid({
2882
0
                                    extent: %(tilegrid_extent)s,
2883
0
                                    origin: %(origin)s,
2884
0
                                    resolutions: %(resolutions)s,
2885
0
                                    sizes: %(matrixsizes)s,
2886
0
                                    tileSize: [%(tile_size)d, %(tile_size)d]
2887
0
                                }),
2888
0
                                tileUrlFunction: function(tileCoord) {
2889
0
                                    return ('./{z}/{x}/{y}.%(tileformat)s'
2890
0
                                        .replace('{z}', String(tileCoord[0]))
2891
0
                                        .replace('{x}', String(tileCoord[1]))
2892
0
                                        .replace('{y}', String(%(y_formula)s)));
2893
0
                                },
2894
0
                            })
2895
0
                        }),
2896
0
                    ]
2897
0
                }),)raw";
2898
0
    }
2899
2900
0
    s += R"raw(
2901
0
            ],
2902
0
            view: new ol.View({
2903
0
                center: [%(center_x)f, %(center_y)f],)raw";
2904
2905
0
    if (tms.identifier() == "GoogleMapsCompatible" ||
2906
0
        tms.identifier() == "WorldCRS84Quad")
2907
0
    {
2908
0
        substs["view_zoom"] = substs["minzoom"];
2909
0
        if (tms.identifier() == "WorldCRS84Quad")
2910
0
        {
2911
0
            substs["view_zoom"] = CPLSPrintf("%d", nMinZoom + 1);
2912
0
        }
2913
2914
0
        s += R"raw(
2915
0
                zoom: %(view_zoom)d,)raw";
2916
0
    }
2917
0
    else
2918
0
    {
2919
0
        s += R"raw(
2920
0
                resolution: %(maxres)f,)raw";
2921
0
    }
2922
2923
0
    if (tms.identifier() == "WorldCRS84Quad")
2924
0
    {
2925
0
        s += R"raw(
2926
0
                projection: 'EPSG:4326',)raw";
2927
0
    }
2928
0
    else if (!oSRS_TMS.IsEmpty() && tms.identifier() != "GoogleMapsCompatible")
2929
0
    {
2930
0
        const char *pszAuthName = oSRS_TMS.GetAuthorityName();
2931
0
        const char *pszAuthCode = oSRS_TMS.GetAuthorityCode();
2932
0
        if (pszAuthName && pszAuthCode && EQUAL(pszAuthName, "EPSG"))
2933
0
        {
2934
0
            substs["epsg_code"] = pszAuthCode;
2935
0
            if (oSRS_TMS.IsGeographic())
2936
0
            {
2937
0
                substs["units"] = "deg";
2938
0
            }
2939
0
            else
2940
0
            {
2941
0
                const char *pszUnits = "";
2942
0
                if (oSRS_TMS.GetLinearUnits(&pszUnits) == 1.0)
2943
0
                    substs["units"] = "m";
2944
0
                else
2945
0
                    substs["units"] = pszUnits;
2946
0
            }
2947
0
            s += R"raw(
2948
0
                projection: new ol.proj.Projection({code: 'EPSG:%(epsg_code)s', units:'%(units)s'}),)raw";
2949
0
        }
2950
0
    }
2951
2952
0
    s += R"raw(
2953
0
            })
2954
0
        });)raw";
2955
2956
0
    if (tms.identifier() == "GoogleMapsCompatible" ||
2957
0
        tms.identifier() == "WorldCRS84Quad")
2958
0
    {
2959
0
        s += R"raw(
2960
0
        map.addControl(new ol.control.LayerSwitcher());)raw";
2961
0
    }
2962
2963
0
    s += R"raw(
2964
0
    </script>
2965
0
</body>
2966
0
</html>)raw";
2967
2968
0
    ApplySubstitutions(s, substs);
2969
2970
0
    VSILFILE *f = VSIFOpenL(
2971
0
        CPLFormFilenameSafe(osDirectory.c_str(), "openlayers.html", nullptr)
2972
0
            .c_str(),
2973
0
        "wb");
2974
0
    if (f)
2975
0
    {
2976
0
        VSIFWriteL(s.data(), 1, s.size(), f);
2977
0
        VSIFCloseL(f);
2978
0
    }
2979
0
}
2980
2981
/************************************************************************/
2982
/*                         GetTileBoundingBox()                         */
2983
/************************************************************************/
2984
2985
static void GetTileBoundingBox(int nTileX, int nTileY, int nTileZ,
2986
                               const gdal::TileMatrixSet *poTMS,
2987
                               bool bInvertAxisTMS,
2988
                               OGRCoordinateTransformation *poCTToWGS84,
2989
                               double &dfTLX, double &dfTLY, double &dfTRX,
2990
                               double &dfTRY, double &dfLLX, double &dfLLY,
2991
                               double &dfLRX, double &dfLRY)
2992
0
{
2993
0
    gdal::TileMatrixSet::TileMatrix tileMatrix =
2994
0
        poTMS->tileMatrixList()[nTileZ];
2995
0
    if (bInvertAxisTMS)
2996
0
        std::swap(tileMatrix.mTopLeftX, tileMatrix.mTopLeftY);
2997
2998
0
    dfTLX = tileMatrix.mTopLeftX +
2999
0
            nTileX * tileMatrix.mResX * tileMatrix.mTileWidth;
3000
0
    dfTLY = tileMatrix.mTopLeftY -
3001
0
            nTileY * tileMatrix.mResY * tileMatrix.mTileHeight;
3002
0
    poCTToWGS84->Transform(1, &dfTLX, &dfTLY);
3003
3004
0
    dfTRX = tileMatrix.mTopLeftX +
3005
0
            (nTileX + 1) * tileMatrix.mResX * tileMatrix.mTileWidth;
3006
0
    dfTRY = tileMatrix.mTopLeftY -
3007
0
            nTileY * tileMatrix.mResY * tileMatrix.mTileHeight;
3008
0
    poCTToWGS84->Transform(1, &dfTRX, &dfTRY);
3009
3010
0
    dfLLX = tileMatrix.mTopLeftX +
3011
0
            nTileX * tileMatrix.mResX * tileMatrix.mTileWidth;
3012
0
    dfLLY = tileMatrix.mTopLeftY -
3013
0
            (nTileY + 1) * tileMatrix.mResY * tileMatrix.mTileHeight;
3014
0
    poCTToWGS84->Transform(1, &dfLLX, &dfLLY);
3015
3016
0
    dfLRX = tileMatrix.mTopLeftX +
3017
0
            (nTileX + 1) * tileMatrix.mResX * tileMatrix.mTileWidth;
3018
0
    dfLRY = tileMatrix.mTopLeftY -
3019
0
            (nTileY + 1) * tileMatrix.mResY * tileMatrix.mTileHeight;
3020
0
    poCTToWGS84->Transform(1, &dfLRX, &dfLRY);
3021
0
}
3022
3023
/************************************************************************/
3024
/*                            GenerateKML()                             */
3025
/************************************************************************/
3026
3027
namespace
3028
{
3029
struct TileCoordinates
3030
{
3031
    int nTileX = 0;
3032
    int nTileY = 0;
3033
    int nTileZ = 0;
3034
};
3035
}  // namespace
3036
3037
static void GenerateKML(const std::string &osDirectory,
3038
                        const std::string &osTitle, int nTileX, int nTileY,
3039
                        int nTileZ, int nTileSize,
3040
                        const std::string &osExtension,
3041
                        const std::string &osURL,
3042
                        const gdal::TileMatrixSet *poTMS, bool bInvertAxisTMS,
3043
                        const std::string &convention,
3044
                        OGRCoordinateTransformation *poCTToWGS84,
3045
                        const std::vector<TileCoordinates> &children)
3046
0
{
3047
0
    std::map<std::string, std::string> substs;
3048
3049
0
    const bool bIsTileKML = nTileX >= 0;
3050
3051
    // For tests
3052
0
    const char *pszFmt =
3053
0
        atoi(CPLGetConfigOption("GDAL_RASTER_TILE_KML_PREC", "14")) == 10
3054
0
            ? "%.10f"
3055
0
            : "%.14f";
3056
3057
0
    substs["tx"] = CPLSPrintf("%d", nTileX);
3058
0
    substs["tz"] = CPLSPrintf("%d", nTileZ);
3059
0
    substs["tileformat"] = osExtension;
3060
0
    substs["minlodpixels"] = CPLSPrintf("%d", nTileSize / 2);
3061
0
    substs["maxlodpixels"] =
3062
0
        children.empty() ? "-1" : CPLSPrintf("%d", nTileSize * 8);
3063
3064
0
    double dfTLX = 0;
3065
0
    double dfTLY = 0;
3066
0
    double dfTRX = 0;
3067
0
    double dfTRY = 0;
3068
0
    double dfLLX = 0;
3069
0
    double dfLLY = 0;
3070
0
    double dfLRX = 0;
3071
0
    double dfLRY = 0;
3072
3073
0
    int nFileY = -1;
3074
0
    if (!bIsTileKML)
3075
0
    {
3076
0
        char *pszStr = CPLEscapeString(osTitle.c_str(), -1, CPLES_XML);
3077
0
        substs["xml_escaped_title"] = pszStr;
3078
0
        CPLFree(pszStr);
3079
0
    }
3080
0
    else
3081
0
    {
3082
0
        nFileY = GetFileY(nTileY, poTMS->tileMatrixList()[nTileZ], convention);
3083
0
        substs["realtiley"] = CPLSPrintf("%d", nFileY);
3084
0
        substs["xml_escaped_title"] =
3085
0
            CPLSPrintf("%d/%d/%d.kml", nTileZ, nTileX, nFileY);
3086
3087
0
        GetTileBoundingBox(nTileX, nTileY, nTileZ, poTMS, bInvertAxisTMS,
3088
0
                           poCTToWGS84, dfTLX, dfTLY, dfTRX, dfTRY, dfLLX,
3089
0
                           dfLLY, dfLRX, dfLRY);
3090
0
    }
3091
3092
0
    substs["drawOrder"] = CPLSPrintf("%d", nTileX == 0  ? 2 * nTileZ + 1
3093
0
                                           : nTileX > 0 ? 2 * nTileZ
3094
0
                                                        : 0);
3095
3096
0
    substs["url"] = osURL.empty() && bIsTileKML ? "../../" : "";
3097
3098
0
    const bool bIsRectangle =
3099
0
        (dfTLX == dfLLX && dfTRX == dfLRX && dfTLY == dfTRY && dfLLY == dfLRY);
3100
0
    const bool bUseGXNamespace = bIsTileKML && !bIsRectangle;
3101
3102
0
    substs["xmlns_gx"] = bUseGXNamespace
3103
0
                             ? " xmlns:gx=\"http://www.google.com/kml/ext/2.2\""
3104
0
                             : "";
3105
3106
0
    CPLString s(R"raw(<?xml version="1.0" encoding="utf-8"?>
3107
0
<kml xmlns="http://www.opengis.net/kml/2.2"%(xmlns_gx)s>
3108
0
  <Document>
3109
0
    <name>%(xml_escaped_title)s</name>
3110
0
    <description></description>
3111
0
    <Style>
3112
0
      <ListStyle id="hideChildren">
3113
0
        <listItemType>checkHideChildren</listItemType>
3114
0
      </ListStyle>
3115
0
    </Style>
3116
0
)raw");
3117
0
    ApplySubstitutions(s, substs);
3118
3119
0
    if (bIsTileKML)
3120
0
    {
3121
0
        CPLString s2(R"raw(    <Region>
3122
0
      <LatLonAltBox>
3123
0
        <north>%(north)f</north>
3124
0
        <south>%(south)f</south>
3125
0
        <east>%(east)f</east>
3126
0
        <west>%(west)f</west>
3127
0
      </LatLonAltBox>
3128
0
      <Lod>
3129
0
        <minLodPixels>%(minlodpixels)d</minLodPixels>
3130
0
        <maxLodPixels>%(maxlodpixels)d</maxLodPixels>
3131
0
      </Lod>
3132
0
    </Region>
3133
0
    <GroundOverlay>
3134
0
      <drawOrder>%(drawOrder)d</drawOrder>
3135
0
      <Icon>
3136
0
        <href>%(realtiley)d.%(tileformat)s</href>
3137
0
      </Icon>
3138
0
      <LatLonBox>
3139
0
        <north>%(north)f</north>
3140
0
        <south>%(south)f</south>
3141
0
        <east>%(east)f</east>
3142
0
        <west>%(west)f</west>
3143
0
      </LatLonBox>
3144
0
)raw");
3145
3146
0
        if (!bIsRectangle)
3147
0
        {
3148
0
            s2 +=
3149
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>
3150
0
)raw";
3151
0
        }
3152
3153
0
        s2 += R"raw(    </GroundOverlay>
3154
0
)raw";
3155
0
        substs["north"] = CPLSPrintf(pszFmt, std::max(dfTLY, dfTRY));
3156
0
        substs["south"] = CPLSPrintf(pszFmt, std::min(dfLLY, dfLRY));
3157
0
        substs["east"] = CPLSPrintf(pszFmt, std::max(dfTRX, dfLRX));
3158
0
        substs["west"] = CPLSPrintf(pszFmt, std::min(dfLLX, dfTLX));
3159
3160
0
        if (!bIsRectangle)
3161
0
        {
3162
0
            substs["TLX"] = CPLSPrintf(pszFmt, dfTLX);
3163
0
            substs["TLY"] = CPLSPrintf(pszFmt, dfTLY);
3164
0
            substs["TRX"] = CPLSPrintf(pszFmt, dfTRX);
3165
0
            substs["TRY"] = CPLSPrintf(pszFmt, dfTRY);
3166
0
            substs["LRX"] = CPLSPrintf(pszFmt, dfLRX);
3167
0
            substs["LRY"] = CPLSPrintf(pszFmt, dfLRY);
3168
0
            substs["LLX"] = CPLSPrintf(pszFmt, dfLLX);
3169
0
            substs["LLY"] = CPLSPrintf(pszFmt, dfLLY);
3170
0
        }
3171
3172
0
        ApplySubstitutions(s2, substs);
3173
0
        s += s2;
3174
0
    }
3175
3176
0
    for (const auto &child : children)
3177
0
    {
3178
0
        substs["tx"] = CPLSPrintf("%d", child.nTileX);
3179
0
        substs["tz"] = CPLSPrintf("%d", child.nTileZ);
3180
0
        substs["realtiley"] = CPLSPrintf(
3181
0
            "%d", GetFileY(child.nTileY, poTMS->tileMatrixList()[child.nTileZ],
3182
0
                           convention));
3183
3184
0
        GetTileBoundingBox(child.nTileX, child.nTileY, child.nTileZ, poTMS,
3185
0
                           bInvertAxisTMS, poCTToWGS84, dfTLX, dfTLY, dfTRX,
3186
0
                           dfTRY, dfLLX, dfLLY, dfLRX, dfLRY);
3187
3188
0
        CPLString s2(R"raw(    <NetworkLink>
3189
0
      <name>%(tz)d/%(tx)d/%(realtiley)d.%(tileformat)s</name>
3190
0
      <Region>
3191
0
        <LatLonAltBox>
3192
0
          <north>%(north)f</north>
3193
0
          <south>%(south)f</south>
3194
0
          <east>%(east)f</east>
3195
0
          <west>%(west)f</west>
3196
0
        </LatLonAltBox>
3197
0
        <Lod>
3198
0
          <minLodPixels>%(minlodpixels)d</minLodPixels>
3199
0
          <maxLodPixels>-1</maxLodPixels>
3200
0
        </Lod>
3201
0
      </Region>
3202
0
      <Link>
3203
0
        <href>%(url)s%(tz)d/%(tx)d/%(realtiley)d.kml</href>
3204
0
        <viewRefreshMode>onRegion</viewRefreshMode>
3205
0
        <viewFormat/>
3206
0
      </Link>
3207
0
    </NetworkLink>
3208
0
)raw");
3209
0
        substs["north"] = CPLSPrintf(pszFmt, std::max(dfTLY, dfTRY));
3210
0
        substs["south"] = CPLSPrintf(pszFmt, std::min(dfLLY, dfLRY));
3211
0
        substs["east"] = CPLSPrintf(pszFmt, std::max(dfTRX, dfLRX));
3212
0
        substs["west"] = CPLSPrintf(pszFmt, std::min(dfLLX, dfTLX));
3213
0
        ApplySubstitutions(s2, substs);
3214
0
        s += s2;
3215
0
    }
3216
3217
0
    s += R"raw(</Document>
3218
0
</kml>)raw";
3219
3220
0
    std::string osFilename(osDirectory);
3221
0
    if (!bIsTileKML)
3222
0
    {
3223
0
        osFilename =
3224
0
            CPLFormFilenameSafe(osFilename.c_str(), "doc.kml", nullptr);
3225
0
    }
3226
0
    else
3227
0
    {
3228
0
        osFilename = CPLFormFilenameSafe(osFilename.c_str(),
3229
0
                                         CPLSPrintf("%d", nTileZ), nullptr);
3230
0
        osFilename = CPLFormFilenameSafe(osFilename.c_str(),
3231
0
                                         CPLSPrintf("%d", nTileX), nullptr);
3232
0
        osFilename = CPLFormFilenameSafe(osFilename.c_str(),
3233
0
                                         CPLSPrintf("%d.kml", nFileY), nullptr);
3234
0
    }
3235
3236
0
    VSILFILE *f = VSIFOpenL(osFilename.c_str(), "wb");
3237
0
    if (f)
3238
0
    {
3239
0
        VSIFWriteL(s.data(), 1, s.size(), f);
3240
0
        VSIFCloseL(f);
3241
0
    }
3242
0
}
3243
3244
namespace
3245
{
3246
3247
/************************************************************************/
3248
/*                           ResourceManager                            */
3249
/************************************************************************/
3250
3251
// Generic cache managing resources
3252
template <class Resource> class ResourceManager /* non final */
3253
{
3254
  public:
3255
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()
3256
3257
    std::unique_ptr<Resource> AcquireResources()
3258
0
    {
3259
0
        std::lock_guard oLock(m_oMutex);
3260
0
        if (!m_oResources.empty())
3261
0
        {
3262
0
            auto ret = std::move(m_oResources.back());
3263
0
            m_oResources.pop_back();
3264
0
            return ret;
3265
0
        }
3266
3267
0
        return CreateResources();
3268
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()
3269
3270
    void ReleaseResources(std::unique_ptr<Resource> resources)
3271
0
    {
3272
0
        std::lock_guard oLock(m_oMutex);
3273
0
        m_oResources.push_back(std::move(resources));
3274
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> >)
3275
3276
    void SetError()
3277
0
    {
3278
0
        std::lock_guard oLock(m_oMutex);
3279
0
        if (m_errorMsg.empty())
3280
0
            m_errorMsg = CPLGetLastErrorMsg();
3281
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()
3282
3283
    const std::string &GetErrorMsg() const
3284
0
    {
3285
0
        std::lock_guard oLock(m_oMutex);
3286
0
        return m_errorMsg;
3287
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
3288
3289
  protected:
3290
    virtual std::unique_ptr<Resource> CreateResources() = 0;
3291
3292
  private:
3293
    mutable std::mutex m_oMutex{};
3294
    std::vector<std::unique_ptr<Resource>> m_oResources{};
3295
    std::string m_errorMsg{};
3296
};
3297
3298
/************************************************************************/
3299
/*                      PerThreadMaxZoomResources                       */
3300
/************************************************************************/
3301
3302
// Per-thread resources for generation of tiles at full resolution
3303
struct PerThreadMaxZoomResources
3304
{
3305
    struct GDALDatasetReleaser
3306
    {
3307
        void operator()(GDALDataset *poDS)
3308
0
        {
3309
0
            if (poDS)
3310
0
                poDS->ReleaseRef();
3311
0
        }
3312
    };
3313
3314
    std::unique_ptr<GDALDataset, GDALDatasetReleaser> poSrcDS{};
3315
    std::vector<GByte> dstBuffer{};
3316
    std::unique_ptr<FakeMaxZoomDataset> poFakeMaxZoomDS{};
3317
    std::unique_ptr<void, decltype(&GDALDestroyTransformer)> poTransformer{
3318
        nullptr, GDALDestroyTransformer};
3319
    std::unique_ptr<GDALWarpOperation> poWO{};
3320
};
3321
3322
/************************************************************************/
3323
/*                   PerThreadMaxZoomResourceManager                    */
3324
/************************************************************************/
3325
3326
// Manage a cache of PerThreadMaxZoomResources instances
3327
class PerThreadMaxZoomResourceManager final
3328
    : public ResourceManager<PerThreadMaxZoomResources>
3329
{
3330
  public:
3331
    PerThreadMaxZoomResourceManager(GDALDataset *poSrcDS,
3332
                                    const GDALWarpOptions *psWO,
3333
                                    void *pTransformerArg,
3334
                                    const FakeMaxZoomDataset &oFakeMaxZoomDS,
3335
                                    size_t nBufferSize)
3336
0
        : m_poSrcDS(poSrcDS), m_psWOSource(psWO),
3337
0
          m_pTransformerArg(pTransformerArg), m_oFakeMaxZoomDS(oFakeMaxZoomDS),
3338
0
          m_nBufferSize(nBufferSize)
3339
0
    {
3340
0
    }
3341
3342
  protected:
3343
    std::unique_ptr<PerThreadMaxZoomResources> CreateResources() override
3344
0
    {
3345
0
        auto ret = std::make_unique<PerThreadMaxZoomResources>();
3346
3347
0
        ret->poSrcDS.reset(GDALGetThreadSafeDataset(m_poSrcDS, GDAL_OF_RASTER));
3348
0
        if (!ret->poSrcDS)
3349
0
            return nullptr;
3350
3351
0
        try
3352
0
        {
3353
0
            ret->dstBuffer.resize(m_nBufferSize);
3354
0
        }
3355
0
        catch (const std::exception &)
3356
0
        {
3357
0
            CPLError(CE_Failure, CPLE_OutOfMemory,
3358
0
                     "Out of memory allocating temporary buffer");
3359
0
            return nullptr;
3360
0
        }
3361
3362
0
        ret->poFakeMaxZoomDS = m_oFakeMaxZoomDS.Clone(ret->dstBuffer);
3363
3364
0
        ret->poTransformer.reset(GDALCloneTransformer(m_pTransformerArg));
3365
0
        if (!ret->poTransformer)
3366
0
            return nullptr;
3367
3368
0
        auto psWO =
3369
0
            std::unique_ptr<GDALWarpOptions, decltype(&GDALDestroyWarpOptions)>(
3370
0
                GDALCloneWarpOptions(m_psWOSource), GDALDestroyWarpOptions);
3371
0
        if (!psWO)
3372
0
            return nullptr;
3373
3374
0
        psWO->hSrcDS = GDALDataset::ToHandle(ret->poSrcDS.get());
3375
0
        psWO->hDstDS = GDALDataset::ToHandle(ret->poFakeMaxZoomDS.get());
3376
0
        psWO->pTransformerArg = ret->poTransformer.get();
3377
0
        psWO->pfnTransformer = m_psWOSource->pfnTransformer;
3378
3379
0
        ret->poWO = std::make_unique<GDALWarpOperation>();
3380
0
        if (ret->poWO->Initialize(psWO.get()) != CE_None)
3381
0
            return nullptr;
3382
3383
0
        return ret;
3384
0
    }
3385
3386
  private:
3387
    GDALDataset *const m_poSrcDS;
3388
    const GDALWarpOptions *const m_psWOSource;
3389
    void *const m_pTransformerArg;
3390
    const FakeMaxZoomDataset &m_oFakeMaxZoomDS;
3391
    const size_t m_nBufferSize;
3392
3393
    CPL_DISALLOW_COPY_ASSIGN(PerThreadMaxZoomResourceManager)
3394
};
3395
3396
/************************************************************************/
3397
/*                     PerThreadLowerZoomResources                      */
3398
/************************************************************************/
3399
3400
// Per-thread resources for generation of tiles at zoom level < max
3401
struct PerThreadLowerZoomResources
3402
{
3403
    std::unique_ptr<GDALDataset> poSrcDS{};
3404
};
3405
3406
/************************************************************************/
3407
/*                  PerThreadLowerZoomResourceManager                   */
3408
/************************************************************************/
3409
3410
// Manage a cache of PerThreadLowerZoomResources instances
3411
class PerThreadLowerZoomResourceManager final
3412
    : public ResourceManager<PerThreadLowerZoomResources>
3413
{
3414
  public:
3415
    explicit PerThreadLowerZoomResourceManager(const MosaicDataset &oSrcDS)
3416
0
        : m_oSrcDS(oSrcDS)
3417
0
    {
3418
0
    }
3419
3420
  protected:
3421
    std::unique_ptr<PerThreadLowerZoomResources> CreateResources() override
3422
0
    {
3423
0
        auto ret = std::make_unique<PerThreadLowerZoomResources>();
3424
0
        ret->poSrcDS = m_oSrcDS.Clone();
3425
0
        return ret;
3426
0
    }
3427
3428
  private:
3429
    const MosaicDataset &m_oSrcDS;
3430
};
3431
3432
}  // namespace
3433
3434
/************************************************************************/
3435
/*           GDALRasterTileAlgorithm::ValidateOutputFormat()            */
3436
/************************************************************************/
3437
3438
bool GDALRasterTileAlgorithm::ValidateOutputFormat(GDALDataType eSrcDT) const
3439
0
{
3440
0
    if (m_format == "PNG")
3441
0
    {
3442
0
        if (m_poSrcDS->GetRasterCount() > 4)
3443
0
        {
3444
0
            ReportError(CE_Failure, CPLE_NotSupported,
3445
0
                        "Only up to 4 bands supported for PNG.");
3446
0
            return false;
3447
0
        }
3448
0
        if (eSrcDT != GDT_UInt8 && eSrcDT != GDT_UInt16)
3449
0
        {
3450
0
            ReportError(CE_Failure, CPLE_NotSupported,
3451
0
                        "Only Byte and UInt16 data types supported for PNG.");
3452
0
            return false;
3453
0
        }
3454
0
    }
3455
0
    else if (m_format == "JPEG")
3456
0
    {
3457
0
        if (m_poSrcDS->GetRasterCount() > 4)
3458
0
        {
3459
0
            ReportError(
3460
0
                CE_Failure, CPLE_NotSupported,
3461
0
                "Only up to 4 bands supported for JPEG (with alpha ignored).");
3462
0
            return false;
3463
0
        }
3464
0
        const bool bUInt16Supported =
3465
0
            strstr(m_poDstDriver->GetMetadataItem(GDAL_DMD_CREATIONDATATYPES),
3466
0
                   "UInt16") != nullptr;
3467
0
        if (eSrcDT != GDT_UInt8 && !(eSrcDT == GDT_UInt16 && bUInt16Supported))
3468
0
        {
3469
0
            ReportError(
3470
0
                CE_Failure, CPLE_NotSupported,
3471
0
                bUInt16Supported
3472
0
                    ? "Only Byte and UInt16 data types supported for JPEG."
3473
0
                    : "Only Byte data type supported for JPEG.");
3474
0
            return false;
3475
0
        }
3476
0
        if (eSrcDT == GDT_UInt16)
3477
0
        {
3478
0
            if (const char *pszNBITS =
3479
0
                    m_poSrcDS->GetRasterBand(1)->GetMetadataItem(
3480
0
                        GDALMD_NBITS, GDAL_MDD_IMAGE_STRUCTURE))
3481
0
            {
3482
0
                if (atoi(pszNBITS) > 12)
3483
0
                {
3484
0
                    ReportError(CE_Failure, CPLE_NotSupported,
3485
0
                                "JPEG output only supported up to 12 bits");
3486
0
                    return false;
3487
0
                }
3488
0
            }
3489
0
            else
3490
0
            {
3491
0
                double adfMinMax[2] = {0, 0};
3492
0
                m_poSrcDS->GetRasterBand(1)->ComputeRasterMinMax(
3493
0
                    /* bApproxOK = */ true, adfMinMax);
3494
0
                if (adfMinMax[1] >= (1 << 12))
3495
0
                {
3496
0
                    ReportError(CE_Failure, CPLE_NotSupported,
3497
0
                                "JPEG output only supported up to 12 bits");
3498
0
                    return false;
3499
0
                }
3500
0
            }
3501
0
        }
3502
0
    }
3503
0
    else if (m_format == "WEBP")
3504
0
    {
3505
0
        if (m_poSrcDS->GetRasterCount() != 3 &&
3506
0
            m_poSrcDS->GetRasterCount() != 4)
3507
0
        {
3508
0
            ReportError(CE_Failure, CPLE_NotSupported,
3509
0
                        "Only 3 or 4 bands supported for WEBP.");
3510
0
            return false;
3511
0
        }
3512
0
        if (eSrcDT != GDT_UInt8)
3513
0
        {
3514
0
            ReportError(CE_Failure, CPLE_NotSupported,
3515
0
                        "Only Byte data type supported for WEBP.");
3516
0
            return false;
3517
0
        }
3518
0
    }
3519
0
    return true;
3520
0
}
3521
3522
/************************************************************************/
3523
/*            GDALRasterTileAlgorithm::ComputeJobChunkSize()            */
3524
/************************************************************************/
3525
3526
// Given a number of tiles in the Y dimension being nTilesPerCol and
3527
// in the X dimension being nTilesPerRow, compute the (upper bound of)
3528
// number of jobs needed to be nYOuterIterations x nXOuterIterations,
3529
// with each job processing in average dfTilesYPerJob x dfTilesXPerJob
3530
// tiles.
3531
/* static */
3532
void GDALRasterTileAlgorithm::ComputeJobChunkSize(
3533
    int nMaxJobCount, int nTilesPerCol, int nTilesPerRow,
3534
    double &dfTilesYPerJob, int &nYOuterIterations, double &dfTilesXPerJob,
3535
    int &nXOuterIterations)
3536
0
{
3537
0
    CPLAssert(nMaxJobCount >= 1);
3538
0
    dfTilesYPerJob = static_cast<double>(nTilesPerCol) / nMaxJobCount;
3539
0
    nYOuterIterations = dfTilesYPerJob >= 1 ? nMaxJobCount : 1;
3540
3541
0
    dfTilesXPerJob = dfTilesYPerJob >= 1
3542
0
                         ? nTilesPerRow
3543
0
                         : static_cast<double>(nTilesPerRow) / nMaxJobCount;
3544
0
    nXOuterIterations = dfTilesYPerJob >= 1 ? 1 : nMaxJobCount;
3545
3546
0
    if (dfTilesYPerJob < 1 && dfTilesXPerJob < 1 &&
3547
0
        nTilesPerCol <= nMaxJobCount / nTilesPerRow)
3548
0
    {
3549
0
        dfTilesYPerJob = 1;
3550
0
        dfTilesXPerJob = 1;
3551
0
        nYOuterIterations = nTilesPerCol;
3552
0
        nXOuterIterations = nTilesPerRow;
3553
0
    }
3554
0
}
3555
3556
/************************************************************************/
3557
/*               GDALRasterTileAlgorithm::AddArgToArgv()                */
3558
/************************************************************************/
3559
3560
bool GDALRasterTileAlgorithm::AddArgToArgv(const GDALAlgorithmArg *arg,
3561
                                           CPLStringList &aosArgv) const
3562
0
{
3563
0
    aosArgv.push_back(CPLSPrintf("--%s", arg->GetName().c_str()));
3564
0
    if (arg->GetType() == GAAT_STRING)
3565
0
    {
3566
0
        aosArgv.push_back(arg->Get<std::string>().c_str());
3567
0
    }
3568
0
    else if (arg->GetType() == GAAT_STRING_LIST)
3569
0
    {
3570
0
        bool bFirst = true;
3571
0
        for (const std::string &s : arg->Get<std::vector<std::string>>())
3572
0
        {
3573
0
            if (!bFirst)
3574
0
            {
3575
0
                aosArgv.push_back(CPLSPrintf("--%s", arg->GetName().c_str()));
3576
0
            }
3577
0
            bFirst = false;
3578
0
            aosArgv.push_back(s.c_str());
3579
0
        }
3580
0
    }
3581
0
    else if (arg->GetType() == GAAT_REAL)
3582
0
    {
3583
0
        aosArgv.push_back(CPLSPrintf("%.17g", arg->Get<double>()));
3584
0
    }
3585
0
    else if (arg->GetType() == GAAT_INTEGER)
3586
0
    {
3587
0
        aosArgv.push_back(CPLSPrintf("%d", arg->Get<int>()));
3588
0
    }
3589
0
    else if (arg->GetType() != GAAT_BOOLEAN)
3590
0
    {
3591
0
        ReportError(CE_Failure, CPLE_AppDefined,
3592
0
                    "Bug: argument of type %d not handled "
3593
0
                    "by gdal raster tile!",
3594
0
                    static_cast<int>(arg->GetType()));
3595
0
        return false;
3596
0
    }
3597
0
    return true;
3598
0
}
3599
3600
/************************************************************************/
3601
/*            GDALRasterTileAlgorithm::IsCompatibleOfSpawn()            */
3602
/************************************************************************/
3603
3604
bool GDALRasterTileAlgorithm::IsCompatibleOfSpawn(const char *&pszErrorMsg)
3605
0
{
3606
0
    pszErrorMsg = "";
3607
0
    if (!m_bIsNamedNonMemSrcDS)
3608
0
    {
3609
0
        pszErrorMsg = "Unnamed or memory dataset sources are not supported "
3610
0
                      "with spawn parallelization method";
3611
0
        return false;
3612
0
    }
3613
0
    if (cpl::starts_with(m_outputDir, "/vsimem/"))
3614
0
    {
3615
0
        pszErrorMsg = "/vsimem/ output directory not supported with spawn "
3616
0
                      "parallelization method";
3617
0
        return false;
3618
0
    }
3619
3620
0
    if (m_osGDALPath.empty())
3621
0
        m_osGDALPath = GDALGetGDALPath();
3622
0
    return !(m_osGDALPath.empty());
3623
0
}
3624
3625
/************************************************************************/
3626
/*                    GetProgressForChildProcesses()                    */
3627
/************************************************************************/
3628
3629
static void GetProgressForChildProcesses(
3630
    bool &bRet, std::vector<CPLSpawnedProcess *> &ahSpawnedProcesses,
3631
    std::vector<uint64_t> &anRemainingTilesForProcess, uint64_t &nCurTile,
3632
    uint64_t nTotalTiles, GDALProgressFunc pfnProgress, void *pProgressData)
3633
0
{
3634
0
    std::vector<unsigned int> anProgressState(ahSpawnedProcesses.size(), 0);
3635
0
    std::vector<unsigned int> anEndState(ahSpawnedProcesses.size(), 0);
3636
0
    std::vector<bool> abFinished(ahSpawnedProcesses.size(), false);
3637
0
    std::vector<unsigned int> anStartErrorState(ahSpawnedProcesses.size(), 0);
3638
3639
0
    while (bRet)
3640
0
    {
3641
0
        size_t iProcess = 0;
3642
0
        size_t nFinished = 0;
3643
0
        for (CPLSpawnedProcess *hSpawnedProcess : ahSpawnedProcesses)
3644
0
        {
3645
0
            char ch = 0;
3646
0
            if (abFinished[iProcess] ||
3647
0
                !CPLPipeRead(CPLSpawnAsyncGetInputFileHandle(hSpawnedProcess),
3648
0
                             &ch, 1))
3649
0
            {
3650
0
                ++nFinished;
3651
0
            }
3652
0
            else if (ch == PROGRESS_MARKER[anProgressState[iProcess]])
3653
0
            {
3654
0
                ++anProgressState[iProcess];
3655
0
                if (anProgressState[iProcess] == sizeof(PROGRESS_MARKER))
3656
0
                {
3657
0
                    anProgressState[iProcess] = 0;
3658
0
                    --anRemainingTilesForProcess[iProcess];
3659
0
                    ++nCurTile;
3660
0
                    if (bRet && pfnProgress)
3661
0
                    {
3662
0
                        if (!pfnProgress(static_cast<double>(nCurTile) /
3663
0
                                             static_cast<double>(nTotalTiles),
3664
0
                                         "", pProgressData))
3665
0
                        {
3666
0
                            CPLError(CE_Failure, CPLE_UserInterrupt,
3667
0
                                     "Process interrupted by user");
3668
0
                            bRet = false;
3669
0
                            return;
3670
0
                        }
3671
0
                    }
3672
0
                }
3673
0
            }
3674
0
            else if (ch == END_MARKER[anEndState[iProcess]])
3675
0
            {
3676
0
                ++anEndState[iProcess];
3677
0
                if (anEndState[iProcess] == sizeof(END_MARKER))
3678
0
                {
3679
0
                    anEndState[iProcess] = 0;
3680
0
                    abFinished[iProcess] = true;
3681
0
                    ++nFinished;
3682
0
                }
3683
0
            }
3684
0
            else if (ch == ERROR_START_MARKER[anStartErrorState[iProcess]])
3685
0
            {
3686
0
                ++anStartErrorState[iProcess];
3687
0
                if (anStartErrorState[iProcess] == sizeof(ERROR_START_MARKER))
3688
0
                {
3689
0
                    anStartErrorState[iProcess] = 0;
3690
0
                    uint32_t nErr = 0;
3691
0
                    CPLPipeRead(
3692
0
                        CPLSpawnAsyncGetInputFileHandle(hSpawnedProcess), &nErr,
3693
0
                        sizeof(nErr));
3694
0
                    uint32_t nNum = 0;
3695
0
                    CPLPipeRead(
3696
0
                        CPLSpawnAsyncGetInputFileHandle(hSpawnedProcess), &nNum,
3697
0
                        sizeof(nNum));
3698
0
                    uint16_t nMsgLen = 0;
3699
0
                    CPLPipeRead(
3700
0
                        CPLSpawnAsyncGetInputFileHandle(hSpawnedProcess),
3701
0
                        &nMsgLen, sizeof(nMsgLen));
3702
0
                    std::string osMsg;
3703
0
                    osMsg.resize(nMsgLen);
3704
0
                    CPLPipeRead(
3705
0
                        CPLSpawnAsyncGetInputFileHandle(hSpawnedProcess),
3706
0
                        &osMsg[0], nMsgLen);
3707
0
                    if (nErr <= CE_Fatal &&
3708
0
                        nNum <= CPLE_ObjectStorageGenericError)
3709
0
                    {
3710
0
                        bool bDone = false;
3711
0
                        if (nErr == CE_Debug)
3712
0
                        {
3713
0
                            auto nPos = osMsg.find(": ");
3714
0
                            if (nPos != std::string::npos)
3715
0
                            {
3716
0
                                bDone = true;
3717
0
                                CPLDebug(
3718
0
                                    osMsg.substr(0, nPos).c_str(),
3719
0
                                    "subprocess %d: %s",
3720
0
                                    static_cast<int>(iProcess),
3721
0
                                    osMsg.substr(nPos + strlen(": ")).c_str());
3722
0
                            }
3723
0
                        }
3724
                        // cppcheck-suppress knownConditionTrueFalse
3725
0
                        if (!bDone)
3726
0
                        {
3727
0
                            CPLError(nErr == CE_Fatal
3728
0
                                         ? CE_Failure
3729
0
                                         : static_cast<CPLErr>(nErr),
3730
0
                                     static_cast<CPLErrorNum>(nNum),
3731
0
                                     "Sub-process %d: %s",
3732
0
                                     static_cast<int>(iProcess), osMsg.c_str());
3733
0
                        }
3734
0
                    }
3735
0
                }
3736
0
            }
3737
0
            else
3738
0
            {
3739
0
                CPLErrorOnce(
3740
0
                    CE_Warning, CPLE_AppDefined,
3741
0
                    "Spurious character detected on stdout of child process");
3742
0
                anProgressState[iProcess] = 0;
3743
0
                if (ch == PROGRESS_MARKER[anProgressState[iProcess]])
3744
0
                {
3745
0
                    ++anProgressState[iProcess];
3746
0
                }
3747
0
            }
3748
0
            ++iProcess;
3749
0
        }
3750
0
        if (!bRet || nFinished == ahSpawnedProcesses.size())
3751
0
            break;
3752
0
    }
3753
0
}
3754
3755
/************************************************************************/
3756
/*                      WaitForSpawnedProcesses()                       */
3757
/************************************************************************/
3758
3759
void GDALRasterTileAlgorithm::WaitForSpawnedProcesses(
3760
    bool &bRet, const std::vector<std::string> &asCommandLines,
3761
    std::vector<CPLSpawnedProcess *> &ahSpawnedProcesses) const
3762
0
{
3763
0
    size_t iProcess = 0;
3764
0
    for (CPLSpawnedProcess *hSpawnedProcess : ahSpawnedProcesses)
3765
0
    {
3766
0
        CPL_IGNORE_RET_VAL(
3767
0
            CPLPipeWrite(CPLSpawnAsyncGetOutputFileHandle(hSpawnedProcess),
3768
0
                         STOP_MARKER, static_cast<int>(strlen(STOP_MARKER))));
3769
3770
0
        char ch = 0;
3771
0
        std::string errorMsg;
3772
0
        while (CPLPipeRead(CPLSpawnAsyncGetErrorFileHandle(hSpawnedProcess),
3773
0
                           &ch, 1))
3774
0
        {
3775
0
            if (ch == '\n')
3776
0
            {
3777
0
                if (!errorMsg.empty())
3778
0
                {
3779
0
                    if (cpl::starts_with(errorMsg, "ERROR "))
3780
0
                    {
3781
0
                        const auto nPos = errorMsg.find(": ");
3782
0
                        if (nPos != std::string::npos)
3783
0
                            errorMsg = errorMsg.substr(nPos + 1);
3784
0
                        ReportError(CE_Failure, CPLE_AppDefined, "%s",
3785
0
                                    errorMsg.c_str());
3786
0
                    }
3787
0
                    else
3788
0
                    {
3789
0
                        std::string osComp = "GDAL";
3790
0
                        const auto nPos = errorMsg.find(": ");
3791
0
                        if (nPos != std::string::npos)
3792
0
                        {
3793
0
                            osComp = errorMsg.substr(0, nPos);
3794
0
                            errorMsg = errorMsg.substr(nPos + 1);
3795
0
                        }
3796
0
                        CPLDebug(osComp.c_str(), "%s", errorMsg.c_str());
3797
0
                    }
3798
0
                    errorMsg.clear();
3799
0
                }
3800
0
            }
3801
0
            else
3802
0
            {
3803
0
                errorMsg += ch;
3804
0
            }
3805
0
        }
3806
3807
0
        if (CPLSpawnAsyncFinish(hSpawnedProcess, /* bWait = */ true,
3808
0
                                /* bKill = */ false) != 0)
3809
0
        {
3810
0
            bRet = false;
3811
0
            ReportError(CE_Failure, CPLE_AppDefined,
3812
0
                        "Child process '%s' failed",
3813
0
                        asCommandLines[iProcess].c_str());
3814
0
        }
3815
0
        ++iProcess;
3816
0
    }
3817
0
}
3818
3819
/************************************************************************/
3820
/*               GDALRasterTileAlgorithm::GetMaxChildCount()            */
3821
/**********************************f**************************************/
3822
3823
int GDALRasterTileAlgorithm::GetMaxChildCount(int nMaxJobCount) const
3824
0
{
3825
0
#ifndef _WIN32
3826
    // Limit the number of jobs compared to how many file descriptors we have
3827
    // left
3828
0
    const int remainingFileDescriptorCount =
3829
0
        CPLGetRemainingFileDescriptorCount();
3830
0
    constexpr int SOME_MARGIN = 3;
3831
0
    constexpr int FD_PER_CHILD = 3; /* stdin, stdout and stderr */
3832
0
    if (FD_PER_CHILD * nMaxJobCount + SOME_MARGIN >
3833
0
        remainingFileDescriptorCount)
3834
0
    {
3835
0
        nMaxJobCount = std::max(
3836
0
            1, (remainingFileDescriptorCount - SOME_MARGIN) / FD_PER_CHILD);
3837
0
        ReportError(
3838
0
            CE_Warning, CPLE_AppDefined,
3839
0
            "Limiting the number of child workers to %d (instead of %d), "
3840
0
            "because there are not enough file descriptors left (%d)",
3841
0
            nMaxJobCount, m_numThreads, remainingFileDescriptorCount);
3842
0
    }
3843
0
#endif
3844
0
    return nMaxJobCount;
3845
0
}
3846
3847
/************************************************************************/
3848
/*                         SendConfigOptions()                          */
3849
/************************************************************************/
3850
3851
static void SendConfigOptions(CPLSpawnedProcess *hSpawnedProcess, bool &bRet)
3852
0
{
3853
    // Send most config options through pipe, to avoid leaking
3854
    // secrets when listing processes
3855
0
    auto handle = CPLSpawnAsyncGetOutputFileHandle(hSpawnedProcess);
3856
0
    for (auto pfnFunc : {&CPLGetConfigOptions, &CPLGetThreadLocalConfigOptions})
3857
0
    {
3858
0
        CPLStringList aosConfigOptions((*pfnFunc)());
3859
0
        for (const char *pszNameValue : aosConfigOptions)
3860
0
        {
3861
0
            if (!STARTS_WITH(pszNameValue, "GDAL_CACHEMAX") &&
3862
0
                !STARTS_WITH(pszNameValue, "GDAL_NUM_THREADS"))
3863
0
            {
3864
0
                constexpr const char *CONFIG_MARKER = "--config\n";
3865
0
                bRet &= CPL_TO_BOOL(
3866
0
                    CPLPipeWrite(handle, CONFIG_MARKER,
3867
0
                                 static_cast<int>(strlen(CONFIG_MARKER))));
3868
0
                char *pszEscaped = CPLEscapeString(pszNameValue, -1, CPLES_URL);
3869
0
                bRet &= CPL_TO_BOOL(CPLPipeWrite(
3870
0
                    handle, pszEscaped, static_cast<int>(strlen(pszEscaped))));
3871
0
                CPLFree(pszEscaped);
3872
0
                bRet &= CPL_TO_BOOL(CPLPipeWrite(handle, "\n", 1));
3873
0
            }
3874
0
        }
3875
0
    }
3876
0
    constexpr const char *END_CONFIG_MARKER = "END_CONFIG\n";
3877
0
    bRet &=
3878
0
        CPL_TO_BOOL(CPLPipeWrite(handle, END_CONFIG_MARKER,
3879
0
                                 static_cast<int>(strlen(END_CONFIG_MARKER))));
3880
0
}
3881
3882
/************************************************************************/
3883
/*                      GenerateTilesForkMethod()                       */
3884
/************************************************************************/
3885
3886
#ifdef FORK_ALLOWED
3887
3888
namespace
3889
{
3890
struct ForkWorkStructure
3891
{
3892
    uint64_t nCacheMaxPerProcess = 0;
3893
    CPLStringList aosArgv{};
3894
    GDALDataset *poMemSrcDS{};
3895
};
3896
}  // namespace
3897
3898
static CPL_FILE_HANDLE pipeIn = CPL_FILE_INVALID_HANDLE;
3899
static CPL_FILE_HANDLE pipeOut = CPL_FILE_INVALID_HANDLE;
3900
3901
static int GenerateTilesForkMethod(CPL_FILE_HANDLE in, CPL_FILE_HANDLE out)
3902
0
{
3903
0
    pipeIn = in;
3904
0
    pipeOut = out;
3905
3906
0
    const ForkWorkStructure *pWorkStructure = nullptr;
3907
0
    CPLPipeRead(in, &pWorkStructure, sizeof(pWorkStructure));
3908
3909
0
    CPLSetConfigOption("GDAL_NUM_THREADS", "1");
3910
0
    GDALSetCacheMax64(pWorkStructure->nCacheMaxPerProcess);
3911
3912
0
    GDALRasterTileAlgorithmStandalone alg;
3913
0
    if (pWorkStructure->poMemSrcDS)
3914
0
    {
3915
0
        auto *inputArg = alg.GetArg(GDAL_ARG_NAME_INPUT);
3916
0
        std::vector<GDALArgDatasetValue> val;
3917
0
        val.resize(1);
3918
0
        val[0].Set(pWorkStructure->poMemSrcDS);
3919
0
        inputArg->Set(std::move(val));
3920
0
    }
3921
0
    return alg.ParseCommandLineArguments(pWorkStructure->aosArgv) && alg.Run()
3922
0
               ? 0
3923
0
               : 1;
3924
0
}
3925
3926
#endif  // FORK_ALLOWED
3927
3928
/************************************************************************/
3929
/*       GDALRasterTileAlgorithm::GenerateBaseTilesSpawnMethod()        */
3930
/************************************************************************/
3931
3932
bool GDALRasterTileAlgorithm::GenerateBaseTilesSpawnMethod(
3933
    int nBaseTilesPerCol, int nBaseTilesPerRow, int nMinTileX, int nMinTileY,
3934
    int nMaxTileX, int nMaxTileY, uint64_t nTotalTiles, uint64_t nBaseTiles,
3935
    GDALProgressFunc pfnProgress, void *pProgressData)
3936
0
{
3937
0
    if (m_parallelMethod == "spawn")
3938
0
    {
3939
0
        CPLAssert(!m_osGDALPath.empty());
3940
0
    }
3941
3942
0
    const int nMaxJobCount = GetMaxChildCount(std::max(
3943
0
        1, static_cast<int>(std::min<uint64_t>(
3944
0
               m_numThreads, nBaseTiles / GetThresholdMinTilesPerJob()))));
3945
3946
0
    double dfTilesYPerJob;
3947
0
    int nYOuterIterations;
3948
0
    double dfTilesXPerJob;
3949
0
    int nXOuterIterations;
3950
0
    ComputeJobChunkSize(nMaxJobCount, nBaseTilesPerCol, nBaseTilesPerRow,
3951
0
                        dfTilesYPerJob, nYOuterIterations, dfTilesXPerJob,
3952
0
                        nXOuterIterations);
3953
3954
0
    CPLDebugOnly("gdal_raster_tile",
3955
0
                 "nYOuterIterations=%d, dfTilesYPerJob=%g, "
3956
0
                 "nXOuterIterations=%d, dfTilesXPerJob=%g",
3957
0
                 nYOuterIterations, dfTilesYPerJob, nXOuterIterations,
3958
0
                 dfTilesXPerJob);
3959
3960
0
    std::vector<std::string> asCommandLines;
3961
0
    std::vector<CPLSpawnedProcess *> ahSpawnedProcesses;
3962
0
    std::vector<uint64_t> anRemainingTilesForProcess;
3963
3964
0
    const uint64_t nCacheMaxPerProcess = GDALGetCacheMax64() / nMaxJobCount;
3965
3966
0
    const auto poSrcDriver = m_poSrcDS->GetDriver();
3967
0
    const bool bIsMEMSource =
3968
0
        poSrcDriver && EQUAL(poSrcDriver->GetDescription(), "MEM");
3969
3970
0
    int nLastYEndIncluded = nMinTileY - 1;
3971
3972
0
#ifdef FORK_ALLOWED
3973
0
    std::vector<std::unique_ptr<ForkWorkStructure>> forkWorkStructures;
3974
0
#endif
3975
3976
0
    bool bRet = true;
3977
0
    for (int iYOuterIter = 0; bRet && iYOuterIter < nYOuterIterations &&
3978
0
                              nLastYEndIncluded < nMaxTileY;
3979
0
         ++iYOuterIter)
3980
0
    {
3981
0
        const int iYStart = nLastYEndIncluded + 1;
3982
0
        const int iYEndIncluded =
3983
0
            iYOuterIter + 1 == nYOuterIterations
3984
0
                ? nMaxTileY
3985
0
                : std::max(
3986
0
                      iYStart,
3987
0
                      static_cast<int>(std::floor(
3988
0
                          nMinTileY + (iYOuterIter + 1) * dfTilesYPerJob - 1)));
3989
3990
0
        nLastYEndIncluded = iYEndIncluded;
3991
3992
0
        int nLastXEndIncluded = nMinTileX - 1;
3993
0
        for (int iXOuterIter = 0; bRet && iXOuterIter < nXOuterIterations &&
3994
0
                                  nLastXEndIncluded < nMaxTileX;
3995
0
             ++iXOuterIter)
3996
0
        {
3997
0
            const int iXStart = nLastXEndIncluded + 1;
3998
0
            const int iXEndIncluded =
3999
0
                iXOuterIter + 1 == nXOuterIterations
4000
0
                    ? nMaxTileX
4001
0
                    : std::max(iXStart,
4002
0
                               static_cast<int>(std::floor(
4003
0
                                   nMinTileX +
4004
0
                                   (iXOuterIter + 1) * dfTilesXPerJob - 1)));
4005
4006
0
            nLastXEndIncluded = iXEndIncluded;
4007
4008
0
            anRemainingTilesForProcess.push_back(
4009
0
                static_cast<uint64_t>(iYEndIncluded - iYStart + 1) *
4010
0
                (iXEndIncluded - iXStart + 1));
4011
4012
0
            CPLStringList aosArgv;
4013
0
            if (m_parallelMethod == "spawn")
4014
0
            {
4015
0
                aosArgv.push_back(m_osGDALPath.c_str());
4016
0
                aosArgv.push_back("raster");
4017
0
                aosArgv.push_back("tile");
4018
0
                aosArgv.push_back("--config-options-in-stdin");
4019
0
                aosArgv.push_back("--config");
4020
0
                aosArgv.push_back("GDAL_NUM_THREADS=1");
4021
0
                aosArgv.push_back("--config");
4022
0
                aosArgv.push_back(
4023
0
                    CPLSPrintf("GDAL_CACHEMAX=%" PRIu64, nCacheMaxPerProcess));
4024
0
            }
4025
0
            aosArgv.push_back(
4026
0
                std::string("--").append(GDAL_ARG_NAME_NUM_THREADS).c_str());
4027
0
            aosArgv.push_back("1");
4028
0
            aosArgv.push_back("--min-x");
4029
0
            aosArgv.push_back(CPLSPrintf("%d", iXStart));
4030
0
            aosArgv.push_back("--max-x");
4031
0
            aosArgv.push_back(CPLSPrintf("%d", iXEndIncluded));
4032
0
            aosArgv.push_back("--min-y");
4033
0
            aosArgv.push_back(CPLSPrintf("%d", iYStart));
4034
0
            aosArgv.push_back("--max-y");
4035
0
            aosArgv.push_back(CPLSPrintf("%d", iYEndIncluded));
4036
0
            aosArgv.push_back("--webviewer");
4037
0
            aosArgv.push_back("none");
4038
0
            aosArgv.push_back(m_parallelMethod == "spawn" ? "--spawned"
4039
0
                                                          : "--forked");
4040
0
            if (!bIsMEMSource)
4041
0
            {
4042
0
                aosArgv.push_back("--input");
4043
0
                aosArgv.push_back(m_poSrcDS->GetDescription());
4044
0
            }
4045
0
            for (const auto &arg : GetArgs())
4046
0
            {
4047
0
                if (arg->IsExplicitlySet() && arg->GetName() != "min-x" &&
4048
0
                    arg->GetName() != "min-y" && arg->GetName() != "max-x" &&
4049
0
                    arg->GetName() != "max-y" && arg->GetName() != "min-zoom" &&
4050
0
                    arg->GetName() != "progress" &&
4051
0
                    arg->GetName() != "progress-forked" &&
4052
0
                    arg->GetName() != GDAL_ARG_NAME_INPUT &&
4053
0
                    arg->GetName() != GDAL_ARG_NAME_NUM_THREADS &&
4054
0
                    arg->GetName() != "webviewer" &&
4055
0
                    arg->GetName() != "parallel-method")
4056
0
                {
4057
0
                    if (!AddArgToArgv(arg.get(), aosArgv))
4058
0
                        return false;
4059
0
                }
4060
0
            }
4061
4062
0
            std::string cmdLine;
4063
0
            for (const char *arg : aosArgv)
4064
0
            {
4065
0
                if (!cmdLine.empty())
4066
0
                    cmdLine += ' ';
4067
0
                CPLString sArg(arg);
4068
0
                if (sArg.find_first_of(" \"") != std::string::npos)
4069
0
                {
4070
0
                    cmdLine += '"';
4071
0
                    cmdLine += sArg.replaceAll('"', "\\\"");
4072
0
                    cmdLine += '"';
4073
0
                }
4074
0
                else
4075
0
                    cmdLine += sArg;
4076
0
            }
4077
0
            CPLDebugOnly("gdal_raster_tile", "%s %s",
4078
0
                         m_parallelMethod == "spawn" ? "Spawning" : "Forking",
4079
0
                         cmdLine.c_str());
4080
0
            asCommandLines.push_back(std::move(cmdLine));
4081
4082
0
#ifdef FORK_ALLOWED
4083
0
            if (m_parallelMethod == "fork")
4084
0
            {
4085
0
                forkWorkStructures.push_back(
4086
0
                    std::make_unique<ForkWorkStructure>());
4087
0
                ForkWorkStructure *pData = forkWorkStructures.back().get();
4088
0
                pData->nCacheMaxPerProcess = nCacheMaxPerProcess;
4089
0
                pData->aosArgv = aosArgv;
4090
0
                if (bIsMEMSource)
4091
0
                    pData->poMemSrcDS = m_poSrcDS;
4092
0
            }
4093
0
            CPL_IGNORE_RET_VAL(aosArgv);
4094
0
#endif
4095
4096
0
            CPLSpawnedProcess *hSpawnedProcess = CPLSpawnAsync(
4097
0
#ifdef FORK_ALLOWED
4098
0
                m_parallelMethod == "fork" ? GenerateTilesForkMethod :
4099
0
#endif
4100
0
                                           nullptr,
4101
0
                m_parallelMethod == "fork" ? nullptr : aosArgv.List(),
4102
0
                /* bCreateInputPipe = */ true,
4103
0
                /* bCreateOutputPipe = */ true,
4104
0
                /* bCreateErrorPipe = */ false, nullptr);
4105
0
            if (!hSpawnedProcess)
4106
0
            {
4107
0
                ReportError(CE_Failure, CPLE_AppDefined,
4108
0
                            "Spawning child gdal process '%s' failed",
4109
0
                            asCommandLines.back().c_str());
4110
0
                bRet = false;
4111
0
                break;
4112
0
            }
4113
4114
0
            CPLDebugOnly("gdal_raster_tile",
4115
0
                         "Job for y in [%d,%d] and x in [%d,%d], "
4116
0
                         "run by process %" PRIu64,
4117
0
                         iYStart, iYEndIncluded, iXStart, iXEndIncluded,
4118
0
                         static_cast<uint64_t>(
4119
0
                             CPLSpawnAsyncGetChildProcessId(hSpawnedProcess)));
4120
4121
0
            ahSpawnedProcesses.push_back(hSpawnedProcess);
4122
4123
0
            if (m_parallelMethod == "spawn")
4124
0
            {
4125
0
                SendConfigOptions(hSpawnedProcess, bRet);
4126
0
            }
4127
4128
0
#ifdef FORK_ALLOWED
4129
0
            else
4130
0
            {
4131
0
                ForkWorkStructure *pData = forkWorkStructures.back().get();
4132
0
                auto handle = CPLSpawnAsyncGetOutputFileHandle(hSpawnedProcess);
4133
0
                bRet &= CPL_TO_BOOL(CPLPipeWrite(
4134
0
                    handle, &pData, static_cast<int>(sizeof(pData))));
4135
0
            }
4136
0
#endif
4137
4138
0
            if (!bRet)
4139
0
            {
4140
0
                ReportError(CE_Failure, CPLE_AppDefined,
4141
0
                            "Could not transmit config options to child gdal "
4142
0
                            "process '%s'",
4143
0
                            asCommandLines.back().c_str());
4144
0
                break;
4145
0
            }
4146
0
        }
4147
0
    }
4148
4149
0
    uint64_t nCurTile = 0;
4150
0
    GetProgressForChildProcesses(bRet, ahSpawnedProcesses,
4151
0
                                 anRemainingTilesForProcess, nCurTile,
4152
0
                                 nTotalTiles, pfnProgress, pProgressData);
4153
4154
0
    WaitForSpawnedProcesses(bRet, asCommandLines, ahSpawnedProcesses);
4155
4156
0
    if (bRet && nCurTile != nBaseTiles)
4157
0
    {
4158
0
        bRet = false;
4159
0
        ReportError(CE_Failure, CPLE_AppDefined,
4160
0
                    "Not all tiles at max zoom level have been "
4161
0
                    "generated. Got %" PRIu64 ", expected %" PRIu64,
4162
0
                    nCurTile, nBaseTiles);
4163
0
    }
4164
4165
0
    return bRet;
4166
0
}
4167
4168
/************************************************************************/
4169
/*     GDALRasterTileAlgorithm::GenerateOverviewTilesSpawnMethod()      */
4170
/************************************************************************/
4171
4172
bool GDALRasterTileAlgorithm::GenerateOverviewTilesSpawnMethod(
4173
    int iZ, int nOvrMinTileX, int nOvrMinTileY, int nOvrMaxTileX,
4174
    int nOvrMaxTileY, std::atomic<uint64_t> &nCurTile, uint64_t nTotalTiles,
4175
    GDALProgressFunc pfnProgress, void *pProgressData)
4176
0
{
4177
0
    if (m_parallelMethod == "spawn")
4178
0
    {
4179
0
        CPLAssert(!m_osGDALPath.empty());
4180
0
    }
4181
4182
0
    const int nOvrTilesPerCol = nOvrMaxTileY - nOvrMinTileY + 1;
4183
0
    const int nOvrTilesPerRow = nOvrMaxTileX - nOvrMinTileX + 1;
4184
0
    const uint64_t nExpectedOvrTileCount =
4185
0
        static_cast<uint64_t>(nOvrTilesPerCol) * nOvrTilesPerRow;
4186
4187
0
    const int nMaxJobCount = GetMaxChildCount(
4188
0
        std::max(1, static_cast<int>(std::min<uint64_t>(
4189
0
                        m_numThreads, nExpectedOvrTileCount /
4190
0
                                          GetThresholdMinTilesPerJob()))));
4191
4192
0
    double dfTilesYPerJob;
4193
0
    int nYOuterIterations;
4194
0
    double dfTilesXPerJob;
4195
0
    int nXOuterIterations;
4196
0
    ComputeJobChunkSize(nMaxJobCount, nOvrTilesPerCol, nOvrTilesPerRow,
4197
0
                        dfTilesYPerJob, nYOuterIterations, dfTilesXPerJob,
4198
0
                        nXOuterIterations);
4199
4200
0
    CPLDebugOnly("gdal_raster_tile",
4201
0
                 "z=%d, nYOuterIterations=%d, dfTilesYPerJob=%g, "
4202
0
                 "nXOuterIterations=%d, dfTilesXPerJob=%g",
4203
0
                 iZ, nYOuterIterations, dfTilesYPerJob, nXOuterIterations,
4204
0
                 dfTilesXPerJob);
4205
4206
0
    std::vector<std::string> asCommandLines;
4207
0
    std::vector<CPLSpawnedProcess *> ahSpawnedProcesses;
4208
0
    std::vector<uint64_t> anRemainingTilesForProcess;
4209
4210
0
#ifdef FORK_ALLOWED
4211
0
    std::vector<std::unique_ptr<ForkWorkStructure>> forkWorkStructures;
4212
0
#endif
4213
4214
0
    const uint64_t nCacheMaxPerProcess = GDALGetCacheMax64() / nMaxJobCount;
4215
4216
0
    const auto poSrcDriver = m_poSrcDS ? m_poSrcDS->GetDriver() : nullptr;
4217
0
    const bool bIsMEMSource =
4218
0
        poSrcDriver && EQUAL(poSrcDriver->GetDescription(), "MEM");
4219
4220
0
    int nLastYEndIncluded = nOvrMinTileY - 1;
4221
0
    bool bRet = true;
4222
0
    for (int iYOuterIter = 0; bRet && iYOuterIter < nYOuterIterations &&
4223
0
                              nLastYEndIncluded < nOvrMaxTileY;
4224
0
         ++iYOuterIter)
4225
0
    {
4226
0
        const int iYStart = nLastYEndIncluded + 1;
4227
0
        const int iYEndIncluded =
4228
0
            iYOuterIter + 1 == nYOuterIterations
4229
0
                ? nOvrMaxTileY
4230
0
                : std::max(iYStart,
4231
0
                           static_cast<int>(std::floor(
4232
0
                               nOvrMinTileY +
4233
0
                               (iYOuterIter + 1) * dfTilesYPerJob - 1)));
4234
4235
0
        nLastYEndIncluded = iYEndIncluded;
4236
4237
0
        int nLastXEndIncluded = nOvrMinTileX - 1;
4238
0
        for (int iXOuterIter = 0; bRet && iXOuterIter < nXOuterIterations &&
4239
0
                                  nLastXEndIncluded < nOvrMaxTileX;
4240
0
             ++iXOuterIter)
4241
0
        {
4242
0
            const int iXStart = nLastXEndIncluded + 1;
4243
0
            const int iXEndIncluded =
4244
0
                iXOuterIter + 1 == nXOuterIterations
4245
0
                    ? nOvrMaxTileX
4246
0
                    : std::max(iXStart,
4247
0
                               static_cast<int>(std::floor(
4248
0
                                   nOvrMinTileX +
4249
0
                                   (iXOuterIter + 1) * dfTilesXPerJob - 1)));
4250
4251
0
            nLastXEndIncluded = iXEndIncluded;
4252
4253
0
            anRemainingTilesForProcess.push_back(
4254
0
                static_cast<uint64_t>(iYEndIncluded - iYStart + 1) *
4255
0
                (iXEndIncluded - iXStart + 1));
4256
4257
0
            CPLStringList aosArgv;
4258
0
            if (m_parallelMethod == "spawn")
4259
0
            {
4260
0
                aosArgv.push_back(m_osGDALPath.c_str());
4261
0
                aosArgv.push_back("raster");
4262
0
                aosArgv.push_back("tile");
4263
0
                aosArgv.push_back("--config-options-in-stdin");
4264
0
                aosArgv.push_back("--config");
4265
0
                aosArgv.push_back("GDAL_NUM_THREADS=1");
4266
0
                aosArgv.push_back("--config");
4267
0
                aosArgv.push_back(
4268
0
                    CPLSPrintf("GDAL_CACHEMAX=%" PRIu64, nCacheMaxPerProcess));
4269
0
            }
4270
0
            aosArgv.push_back(
4271
0
                std::string("--").append(GDAL_ARG_NAME_NUM_THREADS).c_str());
4272
0
            aosArgv.push_back("1");
4273
0
            aosArgv.push_back("--ovr-zoom-level");
4274
0
            aosArgv.push_back(CPLSPrintf("%d", iZ));
4275
0
            aosArgv.push_back("--ovr-min-x");
4276
0
            aosArgv.push_back(CPLSPrintf("%d", iXStart));
4277
0
            aosArgv.push_back("--ovr-max-x");
4278
0
            aosArgv.push_back(CPLSPrintf("%d", iXEndIncluded));
4279
0
            aosArgv.push_back("--ovr-min-y");
4280
0
            aosArgv.push_back(CPLSPrintf("%d", iYStart));
4281
0
            aosArgv.push_back("--ovr-max-y");
4282
0
            aosArgv.push_back(CPLSPrintf("%d", iYEndIncluded));
4283
0
            aosArgv.push_back("--webviewer");
4284
0
            aosArgv.push_back("none");
4285
0
            aosArgv.push_back(m_parallelMethod == "spawn" ? "--spawned"
4286
0
                                                          : "--forked");
4287
0
            if (!bIsMEMSource)
4288
0
            {
4289
0
                aosArgv.push_back("--input");
4290
0
                aosArgv.push_back(m_inputDataset[0].GetName().c_str());
4291
0
            }
4292
0
            for (const auto &arg : GetArgs())
4293
0
            {
4294
0
                if (arg->IsExplicitlySet() && arg->GetName() != "progress" &&
4295
0
                    arg->GetName() != "progress-forked" &&
4296
0
                    arg->GetName() != GDAL_ARG_NAME_INPUT &&
4297
0
                    arg->GetName() != GDAL_ARG_NAME_NUM_THREADS &&
4298
0
                    arg->GetName() != "webviewer" &&
4299
0
                    arg->GetName() != "parallel-method")
4300
0
                {
4301
0
                    if (!AddArgToArgv(arg.get(), aosArgv))
4302
0
                        return false;
4303
0
                }
4304
0
            }
4305
4306
0
            std::string cmdLine;
4307
0
            for (const char *arg : aosArgv)
4308
0
            {
4309
0
                if (!cmdLine.empty())
4310
0
                    cmdLine += ' ';
4311
0
                CPLString sArg(arg);
4312
0
                if (sArg.find_first_of(" \"") != std::string::npos)
4313
0
                {
4314
0
                    cmdLine += '"';
4315
0
                    cmdLine += sArg.replaceAll('"', "\\\"");
4316
0
                    cmdLine += '"';
4317
0
                }
4318
0
                else
4319
0
                    cmdLine += sArg;
4320
0
            }
4321
0
            CPLDebugOnly("gdal_raster_tile", "%s %s",
4322
0
                         m_parallelMethod == "spawn" ? "Spawning" : "Forking",
4323
0
                         cmdLine.c_str());
4324
0
            asCommandLines.push_back(std::move(cmdLine));
4325
4326
0
#ifdef FORK_ALLOWED
4327
0
            if (m_parallelMethod == "fork")
4328
0
            {
4329
0
                forkWorkStructures.push_back(
4330
0
                    std::make_unique<ForkWorkStructure>());
4331
0
                ForkWorkStructure *pData = forkWorkStructures.back().get();
4332
0
                pData->nCacheMaxPerProcess = nCacheMaxPerProcess;
4333
0
                pData->aosArgv = aosArgv;
4334
0
                if (bIsMEMSource)
4335
0
                    pData->poMemSrcDS = m_poSrcDS;
4336
0
            }
4337
0
            CPL_IGNORE_RET_VAL(aosArgv);
4338
0
#endif
4339
4340
0
            CPLSpawnedProcess *hSpawnedProcess = CPLSpawnAsync(
4341
0
#ifdef FORK_ALLOWED
4342
0
                m_parallelMethod == "fork" ? GenerateTilesForkMethod :
4343
0
#endif
4344
0
                                           nullptr,
4345
0
                m_parallelMethod == "fork" ? nullptr : aosArgv.List(),
4346
0
                /* bCreateInputPipe = */ true,
4347
0
                /* bCreateOutputPipe = */ true,
4348
0
                /* bCreateErrorPipe = */ true, nullptr);
4349
0
            if (!hSpawnedProcess)
4350
0
            {
4351
0
                ReportError(CE_Failure, CPLE_AppDefined,
4352
0
                            "Spawning child gdal process '%s' failed",
4353
0
                            asCommandLines.back().c_str());
4354
0
                bRet = false;
4355
0
                break;
4356
0
            }
4357
4358
0
            CPLDebugOnly("gdal_raster_tile",
4359
0
                         "Job for z = %d, y in [%d,%d] and x in [%d,%d], "
4360
0
                         "run by process %" PRIu64,
4361
0
                         iZ, iYStart, iYEndIncluded, iXStart, iXEndIncluded,
4362
0
                         static_cast<uint64_t>(
4363
0
                             CPLSpawnAsyncGetChildProcessId(hSpawnedProcess)));
4364
4365
0
            ahSpawnedProcesses.push_back(hSpawnedProcess);
4366
4367
0
            if (m_parallelMethod == "spawn")
4368
0
            {
4369
0
                SendConfigOptions(hSpawnedProcess, bRet);
4370
0
            }
4371
4372
0
#ifdef FORK_ALLOWED
4373
0
            else
4374
0
            {
4375
0
                ForkWorkStructure *pData = forkWorkStructures.back().get();
4376
0
                auto handle = CPLSpawnAsyncGetOutputFileHandle(hSpawnedProcess);
4377
0
                bRet &= CPL_TO_BOOL(CPLPipeWrite(
4378
0
                    handle, &pData, static_cast<int>(sizeof(pData))));
4379
0
            }
4380
0
#endif
4381
0
            if (!bRet)
4382
0
            {
4383
0
                ReportError(CE_Failure, CPLE_AppDefined,
4384
0
                            "Could not transmit config options to child gdal "
4385
0
                            "process '%s'",
4386
0
                            asCommandLines.back().c_str());
4387
0
                break;
4388
0
            }
4389
0
        }
4390
0
    }
4391
4392
0
    uint64_t nCurTileLocal = nCurTile;
4393
0
    GetProgressForChildProcesses(bRet, ahSpawnedProcesses,
4394
0
                                 anRemainingTilesForProcess, nCurTileLocal,
4395
0
                                 nTotalTiles, pfnProgress, pProgressData);
4396
4397
0
    WaitForSpawnedProcesses(bRet, asCommandLines, ahSpawnedProcesses);
4398
4399
0
    if (bRet && nCurTileLocal - nCurTile != nExpectedOvrTileCount)
4400
0
    {
4401
0
        bRet = false;
4402
0
        ReportError(CE_Failure, CPLE_AppDefined,
4403
0
                    "Not all tiles at zoom level %d have been "
4404
0
                    "generated. Got %" PRIu64 ", expected %" PRIu64,
4405
0
                    iZ, nCurTileLocal - nCurTile, nExpectedOvrTileCount);
4406
0
    }
4407
4408
0
    nCurTile = nCurTileLocal;
4409
4410
0
    return bRet;
4411
0
}
4412
4413
/************************************************************************/
4414
/*                  GDALRasterTileAlgorithm::RunImpl()                  */
4415
/************************************************************************/
4416
4417
bool GDALRasterTileAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
4418
                                      void *pProgressData)
4419
0
{
4420
0
    GDALPipelineStepRunContext stepCtxt;
4421
0
    stepCtxt.m_pfnProgress = pfnProgress;
4422
0
    stepCtxt.m_pProgressData = pProgressData;
4423
0
    return RunStep(stepCtxt);
4424
0
}
4425
4426
/************************************************************************/
4427
/*                        SpawnedErrorHandler()                         */
4428
/************************************************************************/
4429
4430
static void CPL_STDCALL SpawnedErrorHandler(CPLErr eErr, CPLErrorNum eNum,
4431
                                            const char *pszMsg)
4432
0
{
4433
0
    fwrite(ERROR_START_MARKER, sizeof(ERROR_START_MARKER), 1, stdout);
4434
0
    uint32_t nErr = eErr;
4435
0
    fwrite(&nErr, sizeof(nErr), 1, stdout);
4436
0
    uint32_t nNum = eNum;
4437
0
    fwrite(&nNum, sizeof(nNum), 1, stdout);
4438
0
    uint16_t nLen = static_cast<uint16_t>(strlen(pszMsg));
4439
0
    fwrite(&nLen, sizeof(nLen), 1, stdout);
4440
0
    fwrite(pszMsg, nLen, 1, stdout);
4441
0
    fflush(stdout);
4442
0
}
4443
4444
/************************************************************************/
4445
/*                  GDALRasterTileAlgorithm::RunStep()                  */
4446
/************************************************************************/
4447
4448
bool GDALRasterTileAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
4449
0
{
4450
0
    auto pfnProgress = ctxt.m_pfnProgress;
4451
0
    auto pProgressData = ctxt.m_pProgressData;
4452
0
    CPLAssert(m_inputDataset.size() == 1);
4453
0
    m_poSrcDS = m_inputDataset[0].GetDatasetRef();
4454
0
    CPLAssert(m_poSrcDS);
4455
4456
0
    const int nSrcWidth = m_poSrcDS->GetRasterXSize();
4457
0
    const int nSrcHeight = m_poSrcDS->GetRasterYSize();
4458
0
    if (m_poSrcDS->GetRasterCount() == 0 || nSrcWidth == 0 || nSrcHeight == 0)
4459
0
    {
4460
0
        ReportError(CE_Failure, CPLE_AppDefined, "Invalid source dataset");
4461
0
        return false;
4462
0
    }
4463
4464
0
    const bool bIsNamedSource = m_poSrcDS->GetDescription()[0] != 0;
4465
0
    auto poSrcDriver = m_poSrcDS->GetDriver();
4466
0
    const bool bIsMEMSource =
4467
0
        poSrcDriver && EQUAL(poSrcDriver->GetDescription(), "MEM");
4468
0
    m_bIsNamedNonMemSrcDS = bIsNamedSource && !bIsMEMSource;
4469
0
    const bool bSrcIsFineForFork = bIsNamedSource || bIsMEMSource;
4470
4471
0
    if (m_parallelMethod == "spawn")
4472
0
    {
4473
0
        const char *pszErrorMsg = "";
4474
0
        if (!IsCompatibleOfSpawn(pszErrorMsg))
4475
0
        {
4476
0
            if (pszErrorMsg[0])
4477
0
                ReportError(CE_Failure, CPLE_AppDefined, "%s", pszErrorMsg);
4478
0
            return false;
4479
0
        }
4480
0
    }
4481
0
#ifdef FORK_ALLOWED
4482
0
    else if (m_parallelMethod == "fork")
4483
0
    {
4484
0
        if (!bSrcIsFineForFork)
4485
0
        {
4486
0
            ReportError(CE_Failure, CPLE_AppDefined,
4487
0
                        "Unnamed non-MEM source are not supported "
4488
0
                        "with fork parallelization method");
4489
0
            return false;
4490
0
        }
4491
0
        if (cpl::starts_with(m_outputDir, "/vsimem/"))
4492
0
        {
4493
0
            ReportError(CE_Failure, CPLE_AppDefined,
4494
0
                        "/vsimem/ output directory not supported with fork "
4495
0
                        "parallelization method");
4496
0
            return false;
4497
0
        }
4498
0
    }
4499
0
#endif
4500
4501
0
    if (m_resampling == "near")
4502
0
        m_resampling = "nearest";
4503
0
    if (m_overviewResampling == "near")
4504
0
        m_overviewResampling = "nearest";
4505
0
    else if (m_overviewResampling.empty())
4506
0
        m_overviewResampling = m_resampling;
4507
4508
0
    CPLStringList aosWarpOptions;
4509
0
    if (!m_excludedValues.empty() || m_nodataValuesPctThreshold < 100)
4510
0
    {
4511
0
        aosWarpOptions.SetNameValue(
4512
0
            "NODATA_VALUES_PCT_THRESHOLD",
4513
0
            CPLSPrintf("%g", m_nodataValuesPctThreshold));
4514
0
        if (!m_excludedValues.empty())
4515
0
        {
4516
0
            aosWarpOptions.SetNameValue("EXCLUDED_VALUES",
4517
0
                                        m_excludedValues.c_str());
4518
0
            aosWarpOptions.SetNameValue(
4519
0
                "EXCLUDED_VALUES_PCT_THRESHOLD",
4520
0
                CPLSPrintf("%g", m_excludedValuesPctThreshold));
4521
0
        }
4522
0
    }
4523
4524
0
    if (m_poSrcDS->GetRasterBand(1)->GetColorInterpretation() ==
4525
0
            GCI_PaletteIndex &&
4526
0
        ((m_resampling != "nearest" && m_resampling != "mode") ||
4527
0
         (m_overviewResampling != "nearest" && m_overviewResampling != "mode")))
4528
0
    {
4529
0
        ReportError(CE_Failure, CPLE_NotSupported,
4530
0
                    "Datasets with color table not supported with non-nearest "
4531
0
                    "or non-mode resampling. Run 'gdal raster "
4532
0
                    "color-map' before or set the 'resampling' argument to "
4533
0
                    "'nearest' or 'mode'.");
4534
0
        return false;
4535
0
    }
4536
4537
0
    const auto eSrcDT = m_poSrcDS->GetRasterBand(1)->GetRasterDataType();
4538
0
    m_poDstDriver = GetGDALDriverManager()->GetDriverByName(m_format.c_str());
4539
0
    if (!m_poDstDriver)
4540
0
    {
4541
0
        ReportError(CE_Failure, CPLE_AppDefined,
4542
0
                    "Invalid value for argument 'output-format'. Driver '%s' "
4543
0
                    "does not exist",
4544
0
                    m_format.c_str());
4545
0
        return false;
4546
0
    }
4547
4548
0
    if (!ValidateOutputFormat(eSrcDT))
4549
0
        return false;
4550
4551
0
    const char *pszExtensions =
4552
0
        m_poDstDriver->GetMetadataItem(GDAL_DMD_EXTENSIONS);
4553
0
    CPLAssert(pszExtensions && pszExtensions[0] != 0);
4554
0
    const CPLStringList aosExtensions(
4555
0
        CSLTokenizeString2(pszExtensions, " ", 0));
4556
0
    const char *pszExtension = aosExtensions[0];
4557
0
    GDALGeoTransform srcGT;
4558
0
    const bool bHasSrcGT = m_poSrcDS->GetGeoTransform(srcGT) == CE_None;
4559
0
    const bool bHasNorthUpSrcGT =
4560
0
        bHasSrcGT && srcGT[2] == 0 && srcGT[4] == 0 && srcGT[5] < 0;
4561
0
    OGRSpatialReference oSRS_TMS;
4562
4563
0
    if (m_tilingScheme == "raster")
4564
0
    {
4565
0
        if (const auto poSRS = m_poSrcDS->GetSpatialRef())
4566
0
            oSRS_TMS = *poSRS;
4567
0
    }
4568
0
    else
4569
0
    {
4570
0
        if (!bHasSrcGT && m_poSrcDS->GetGCPCount() == 0 &&
4571
0
            m_poSrcDS->GetMetadata(GDAL_MDD_GEOLOCATION) == nullptr &&
4572
0
            m_poSrcDS->GetMetadata(GDAL_MDD_RPC) == nullptr)
4573
0
        {
4574
0
            ReportError(CE_Failure, CPLE_NotSupported,
4575
0
                        "Ungeoreferenced datasets are not supported, unless "
4576
0
                        "'tiling-scheme' is set to 'raster'");
4577
0
            return false;
4578
0
        }
4579
4580
0
        if (m_poSrcDS->GetMetadata(GDAL_MDD_GEOLOCATION) == nullptr &&
4581
0
            m_poSrcDS->GetMetadata(GDAL_MDD_RPC) == nullptr &&
4582
0
            m_poSrcDS->GetSpatialRef() == nullptr &&
4583
0
            m_poSrcDS->GetGCPSpatialRef() == nullptr)
4584
0
        {
4585
0
            ReportError(CE_Failure, CPLE_NotSupported,
4586
0
                        "Ungeoreferenced datasets are not supported, unless "
4587
0
                        "'tiling-scheme' is set to 'raster'");
4588
0
            return false;
4589
0
        }
4590
0
    }
4591
4592
0
    if (m_copySrcMetadata)
4593
0
    {
4594
0
        CPLStringList aosMD(CSLDuplicate(m_poSrcDS->GetMetadata()));
4595
0
        const CPLStringList aosNewMD(m_metadata);
4596
0
        for (const auto [key, value] : cpl::IterateNameValue(aosNewMD))
4597
0
        {
4598
0
            aosMD.SetNameValue(key, value);
4599
0
        }
4600
0
        m_metadata = aosMD;
4601
0
    }
4602
4603
0
    std::vector<BandMetadata> aoBandMetadata;
4604
0
    for (int i = 1; i <= m_poSrcDS->GetRasterCount(); ++i)
4605
0
    {
4606
0
        auto poBand = m_poSrcDS->GetRasterBand(i);
4607
0
        BandMetadata bm;
4608
0
        bm.osDescription = poBand->GetDescription();
4609
0
        bm.eDT = poBand->GetRasterDataType();
4610
0
        bm.eColorInterp = poBand->GetColorInterpretation();
4611
0
        if (const char *pszCenterWavelength = poBand->GetMetadataItem(
4612
0
                GDALMD_CENTRAL_WAVELENGTH_UM, GDAL_MDD_IMAGERY))
4613
0
            bm.osCenterWaveLength = pszCenterWavelength;
4614
0
        if (const char *pszFWHM =
4615
0
                poBand->GetMetadataItem(GDALMD_FWHM_UM, GDAL_MDD_IMAGERY))
4616
0
            bm.osFWHM = pszFWHM;
4617
0
        aoBandMetadata.emplace_back(std::move(bm));
4618
0
    }
4619
4620
0
    GDALGeoTransform srcGTModif{0, 1, 0, 0, 0, -1};
4621
4622
0
    if (m_tilingScheme == "mercator")
4623
0
        m_tilingScheme = "WebMercatorQuad";
4624
0
    else if (m_tilingScheme == "raster")
4625
0
    {
4626
0
        if (m_tileSize == 0)
4627
0
            m_tileSize = 256;
4628
0
        if (m_maxZoomLevel < 0)
4629
0
        {
4630
0
            m_maxZoomLevel = static_cast<int>(std::ceil(std::log2(
4631
0
                std::max(1, std::max(nSrcWidth, nSrcHeight) / m_tileSize))));
4632
0
        }
4633
0
        if (bHasNorthUpSrcGT)
4634
0
        {
4635
0
            srcGTModif = srcGT;
4636
0
        }
4637
0
    }
4638
4639
0
    auto poTMS =
4640
0
        m_tilingScheme == "raster"
4641
0
            ? gdal::TileMatrixSet::createRaster(
4642
0
                  nSrcWidth, nSrcHeight, m_tileSize, 1 + m_maxZoomLevel,
4643
0
                  srcGTModif[0], srcGTModif[3], srcGTModif[1], -srcGTModif[5],
4644
0
                  oSRS_TMS.IsEmpty() ? std::string() : oSRS_TMS.exportToWkt())
4645
0
            : gdal::TileMatrixSet::parse(
4646
0
                  m_mapTileMatrixIdentifierToScheme[m_tilingScheme].c_str());
4647
    // Enforced by SetChoices() on the m_tilingScheme argument
4648
0
    CPLAssert(poTMS && !poTMS->hasVariableMatrixWidth());
4649
4650
0
    CPLStringList aosTO;
4651
0
    if (m_tilingScheme == "raster")
4652
0
    {
4653
0
        aosTO.SetNameValue("SRC_METHOD", "GEOTRANSFORM");
4654
0
    }
4655
0
    else
4656
0
    {
4657
0
        CPL_IGNORE_RET_VAL(oSRS_TMS.SetFromUserInput(poTMS->crs().c_str()));
4658
0
        aosTO.SetNameValue("DST_SRS", oSRS_TMS.exportToWkt().c_str());
4659
0
    }
4660
4661
0
    const char *pszAuthName = oSRS_TMS.GetAuthorityName();
4662
0
    const char *pszAuthCode = oSRS_TMS.GetAuthorityCode();
4663
0
    const int nEPSGCode =
4664
0
        (pszAuthName && pszAuthCode && EQUAL(pszAuthName, "EPSG"))
4665
0
            ? atoi(pszAuthCode)
4666
0
            : 0;
4667
4668
0
    const bool bInvertAxisTMS =
4669
0
        m_tilingScheme != "raster" &&
4670
0
        (oSRS_TMS.EPSGTreatsAsLatLong() != FALSE ||
4671
0
         oSRS_TMS.EPSGTreatsAsNorthingEasting() != FALSE);
4672
4673
0
    oSRS_TMS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
4674
4675
0
    std::unique_ptr<void, decltype(&GDALDestroyTransformer)> hTransformArg(
4676
0
        nullptr, GDALDestroyTransformer);
4677
4678
    // Hack to compensate for GDALSuggestedWarpOutput2() failure (or not
4679
    // ideal suggestion with PROJ 8) when reprojecting latitude = +/- 90 to
4680
    // EPSG:3857.
4681
0
    std::unique_ptr<GDALDataset> poTmpDS;
4682
0
    bool bEPSG3857Adjust = false;
4683
0
    if (nEPSGCode == 3857 && bHasNorthUpSrcGT)
4684
0
    {
4685
0
        const auto poSrcSRS = m_poSrcDS->GetSpatialRef();
4686
0
        if (poSrcSRS && poSrcSRS->IsGeographic())
4687
0
        {
4688
0
            double maxLat = srcGT[3];
4689
0
            double minLat = srcGT[3] + nSrcHeight * srcGT[5];
4690
            // Corresponds to the latitude of below MAX_GM
4691
0
            constexpr double MAX_LAT = 85.0511287798066;
4692
0
            bool bModified = false;
4693
0
            if (maxLat > MAX_LAT)
4694
0
            {
4695
0
                maxLat = MAX_LAT;
4696
0
                bModified = true;
4697
0
            }
4698
0
            if (minLat < -MAX_LAT)
4699
0
            {
4700
0
                minLat = -MAX_LAT;
4701
0
                bModified = true;
4702
0
            }
4703
0
            if (bModified)
4704
0
            {
4705
0
                CPLStringList aosOptions;
4706
0
                aosOptions.AddString("-of");
4707
0
                aosOptions.AddString("VRT");
4708
0
                aosOptions.AddString("-projwin");
4709
0
                aosOptions.AddString(srcGT[0]);
4710
0
                aosOptions.AddString(maxLat);
4711
0
                aosOptions.AddString(srcGT[0] + nSrcWidth * srcGT[1]);
4712
0
                aosOptions.AddString(minLat);
4713
0
                auto psOptions =
4714
0
                    GDALTranslateOptionsNew(aosOptions.List(), nullptr);
4715
0
                poTmpDS.reset(GDALDataset::FromHandle(GDALTranslate(
4716
0
                    "", GDALDataset::ToHandle(m_poSrcDS), psOptions, nullptr)));
4717
0
                GDALTranslateOptionsFree(psOptions);
4718
0
                if (poTmpDS)
4719
0
                {
4720
0
                    bEPSG3857Adjust = true;
4721
0
                    hTransformArg.reset(GDALCreateGenImgProjTransformer2(
4722
0
                        GDALDataset::FromHandle(poTmpDS.get()), nullptr,
4723
0
                        aosTO.List()));
4724
0
                }
4725
0
            }
4726
0
        }
4727
0
    }
4728
4729
0
    GDALGeoTransform dstGT;
4730
0
    double adfExtent[4];
4731
0
    int nXSize, nYSize;
4732
4733
0
    bool bSuggestOK;
4734
0
    if (m_tilingScheme == "raster")
4735
0
    {
4736
0
        bSuggestOK = true;
4737
0
        nXSize = nSrcWidth;
4738
0
        nYSize = nSrcHeight;
4739
0
        dstGT = srcGTModif;
4740
0
        adfExtent[0] = dstGT[0];
4741
0
        adfExtent[1] = dstGT[3] + nSrcHeight * dstGT[5];
4742
0
        adfExtent[2] = dstGT[0] + nSrcWidth * dstGT[1];
4743
0
        adfExtent[3] = dstGT[3];
4744
0
    }
4745
0
    else
4746
0
    {
4747
0
        if (!hTransformArg)
4748
0
        {
4749
0
            hTransformArg.reset(GDALCreateGenImgProjTransformer2(
4750
0
                m_poSrcDS, nullptr, aosTO.List()));
4751
0
        }
4752
0
        if (!hTransformArg)
4753
0
        {
4754
0
            return false;
4755
0
        }
4756
0
        CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
4757
0
        bSuggestOK =
4758
0
            (GDALSuggestedWarpOutput2(
4759
0
                 m_poSrcDS,
4760
0
                 static_cast<GDALTransformerInfo *>(hTransformArg.get())
4761
0
                     ->pfnTransform,
4762
0
                 hTransformArg.get(), dstGT.data(), &nXSize, &nYSize, adfExtent,
4763
0
                 0) == CE_None);
4764
0
    }
4765
0
    if (!bSuggestOK)
4766
0
    {
4767
0
        ReportError(CE_Failure, CPLE_AppDefined,
4768
0
                    "Cannot determine extent of raster in target CRS");
4769
0
        return false;
4770
0
    }
4771
4772
0
    poTmpDS.reset();
4773
4774
0
    if (bEPSG3857Adjust)
4775
0
    {
4776
0
        constexpr double SPHERICAL_RADIUS = 6378137.0;
4777
0
        constexpr double MAX_GM =
4778
0
            SPHERICAL_RADIUS * M_PI;  // 20037508.342789244
4779
0
        double maxNorthing = dstGT[3];
4780
0
        double minNorthing = dstGT[3] + dstGT[5] * nYSize;
4781
0
        bool bChanged = false;
4782
0
        if (maxNorthing > MAX_GM)
4783
0
        {
4784
0
            bChanged = true;
4785
0
            maxNorthing = MAX_GM;
4786
0
        }
4787
0
        if (minNorthing < -MAX_GM)
4788
0
        {
4789
0
            bChanged = true;
4790
0
            minNorthing = -MAX_GM;
4791
0
        }
4792
0
        if (bChanged)
4793
0
        {
4794
0
            dstGT[3] = maxNorthing;
4795
0
            nYSize = int((maxNorthing - minNorthing) / (-dstGT[5]) + 0.5);
4796
0
            adfExtent[1] = maxNorthing + nYSize * dstGT[5];
4797
0
            adfExtent[3] = maxNorthing;
4798
0
        }
4799
0
    }
4800
4801
0
    const auto &tileMatrixList = poTMS->tileMatrixList();
4802
0
    if (m_maxZoomLevel >= 0)
4803
0
    {
4804
0
        if (m_maxZoomLevel >= static_cast<int>(tileMatrixList.size()))
4805
0
        {
4806
0
            ReportError(CE_Failure, CPLE_AppDefined,
4807
0
                        "max-zoom = %d is invalid. It must be in [0,%d] range",
4808
0
                        m_maxZoomLevel,
4809
0
                        static_cast<int>(tileMatrixList.size()) - 1);
4810
0
            return false;
4811
0
        }
4812
0
    }
4813
0
    else
4814
0
    {
4815
0
        const double dfComputedRes = dstGT[1];
4816
0
        double dfPrevRes = 0.0;
4817
0
        double dfRes = 0.0;
4818
0
        constexpr double EPSILON = 1e-8;
4819
4820
0
        if (m_minZoomLevel >= 0)
4821
0
            m_maxZoomLevel = m_minZoomLevel;
4822
0
        else
4823
0
            m_maxZoomLevel = 0;
4824
4825
0
        for (; m_maxZoomLevel < static_cast<int>(tileMatrixList.size());
4826
0
             m_maxZoomLevel++)
4827
0
        {
4828
0
            dfRes = tileMatrixList[m_maxZoomLevel].mResX;
4829
0
            if (dfComputedRes > dfRes ||
4830
0
                fabs(dfComputedRes - dfRes) / dfRes <= EPSILON)
4831
0
                break;
4832
0
            dfPrevRes = dfRes;
4833
0
        }
4834
0
        if (m_maxZoomLevel >= static_cast<int>(tileMatrixList.size()))
4835
0
        {
4836
0
            ReportError(CE_Failure, CPLE_AppDefined,
4837
0
                        "Could not find an appropriate zoom level. Perhaps "
4838
0
                        "min-zoom is too large?");
4839
0
            return false;
4840
0
        }
4841
4842
0
        if (m_maxZoomLevel > 0 && fabs(dfComputedRes - dfRes) / dfRes > EPSILON)
4843
0
        {
4844
            // Round to closest resolution
4845
0
            if (dfPrevRes / dfComputedRes < dfComputedRes / dfRes)
4846
0
                m_maxZoomLevel--;
4847
0
        }
4848
0
    }
4849
4850
0
    auto tileMatrix = tileMatrixList[m_maxZoomLevel];
4851
0
    int nMinTileX = 0;
4852
0
    int nMinTileY = 0;
4853
0
    int nMaxTileX = 0;
4854
0
    int nMaxTileY = 0;
4855
0
    bool bIntersects = false;
4856
0
    if (!GetTileIndices(tileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
4857
0
                        nMinTileX, nMinTileY, nMaxTileX, nMaxTileY,
4858
0
                        m_noIntersectionIsOK, bIntersects,
4859
0
                        /* checkRasterOverflow = */ false))
4860
0
    {
4861
0
        return false;
4862
0
    }
4863
0
    if (!bIntersects)
4864
0
        return true;
4865
4866
    // Potentially restrict tiling to user specified coordinates
4867
0
    if (m_minTileX >= tileMatrix.mMatrixWidth)
4868
0
    {
4869
0
        ReportError(CE_Failure, CPLE_IllegalArg,
4870
0
                    "'min-x' value must be in [0,%d] range",
4871
0
                    tileMatrix.mMatrixWidth - 1);
4872
0
        return false;
4873
0
    }
4874
0
    if (m_maxTileX >= tileMatrix.mMatrixWidth)
4875
0
    {
4876
0
        ReportError(CE_Failure, CPLE_IllegalArg,
4877
0
                    "'max-x' value must be in [0,%d] range",
4878
0
                    tileMatrix.mMatrixWidth - 1);
4879
0
        return false;
4880
0
    }
4881
0
    if (m_minTileY >= tileMatrix.mMatrixHeight)
4882
0
    {
4883
0
        ReportError(CE_Failure, CPLE_IllegalArg,
4884
0
                    "'min-y' value must be in [0,%d] range",
4885
0
                    tileMatrix.mMatrixHeight - 1);
4886
0
        return false;
4887
0
    }
4888
0
    if (m_maxTileY >= tileMatrix.mMatrixHeight)
4889
0
    {
4890
0
        ReportError(CE_Failure, CPLE_IllegalArg,
4891
0
                    "'max-y' value must be in [0,%d] range",
4892
0
                    tileMatrix.mMatrixHeight - 1);
4893
0
        return false;
4894
0
    }
4895
4896
0
    if ((m_minTileX >= 0 && m_minTileX > nMaxTileX) ||
4897
0
        (m_minTileY >= 0 && m_minTileY > nMaxTileY) ||
4898
0
        (m_maxTileX >= 0 && m_maxTileX < nMinTileX) ||
4899
0
        (m_maxTileY >= 0 && m_maxTileY < nMinTileY))
4900
0
    {
4901
0
        ReportError(
4902
0
            m_noIntersectionIsOK ? CE_Warning : CE_Failure, CPLE_AppDefined,
4903
0
            "Dataset extent not intersecting specified min/max X/Y tile "
4904
0
            "coordinates");
4905
0
        return m_noIntersectionIsOK;
4906
0
    }
4907
0
    if (m_minTileX >= 0 && m_minTileX > nMinTileX)
4908
0
    {
4909
0
        nMinTileX = m_minTileX;
4910
0
        adfExtent[0] = tileMatrix.mTopLeftX +
4911
0
                       nMinTileX * tileMatrix.mResX * tileMatrix.mTileWidth;
4912
0
    }
4913
0
    if (m_minTileY >= 0 && m_minTileY > nMinTileY)
4914
0
    {
4915
0
        nMinTileY = m_minTileY;
4916
0
        adfExtent[3] = tileMatrix.mTopLeftY -
4917
0
                       nMinTileY * tileMatrix.mResY * tileMatrix.mTileHeight;
4918
0
    }
4919
0
    if (m_maxTileX >= 0 && m_maxTileX < nMaxTileX)
4920
0
    {
4921
0
        nMaxTileX = m_maxTileX;
4922
0
        adfExtent[2] = tileMatrix.mTopLeftX + (nMaxTileX + 1) *
4923
0
                                                  tileMatrix.mResX *
4924
0
                                                  tileMatrix.mTileWidth;
4925
0
    }
4926
0
    if (m_maxTileY >= 0 && m_maxTileY < nMaxTileY)
4927
0
    {
4928
0
        nMaxTileY = m_maxTileY;
4929
0
        adfExtent[1] = tileMatrix.mTopLeftY - (nMaxTileY + 1) *
4930
0
                                                  tileMatrix.mResY *
4931
0
                                                  tileMatrix.mTileHeight;
4932
0
    }
4933
4934
0
    if (nMaxTileX - nMinTileX + 1 > INT_MAX / tileMatrix.mTileWidth ||
4935
0
        nMaxTileY - nMinTileY + 1 > INT_MAX / tileMatrix.mTileHeight)
4936
0
    {
4937
0
        ReportError(CE_Failure, CPLE_AppDefined, "Too large zoom level");
4938
0
        return false;
4939
0
    }
4940
4941
0
    dstGT[0] = tileMatrix.mTopLeftX +
4942
0
               nMinTileX * tileMatrix.mResX * tileMatrix.mTileWidth;
4943
0
    dstGT[1] = tileMatrix.mResX;
4944
0
    dstGT[2] = 0;
4945
0
    dstGT[3] = tileMatrix.mTopLeftY -
4946
0
               nMinTileY * tileMatrix.mResY * tileMatrix.mTileHeight;
4947
0
    dstGT[4] = 0;
4948
0
    dstGT[5] = -tileMatrix.mResY;
4949
4950
0
    if (m_minZoomLevelSingleTile)
4951
0
    {
4952
0
        const int nMaxDim = std::max(nXSize, nYSize);
4953
0
        const int nOvrCount = static_cast<int>(
4954
0
            std::ceil(std::max(0.0, std::log2(static_cast<double>(nMaxDim) /
4955
0
                                              tileMatrix.mTileWidth))));
4956
0
        m_minZoomLevel = std::max(0, m_maxZoomLevel - nOvrCount);
4957
0
    }
4958
0
    else if (m_minZoomLevel < 0)
4959
0
        m_minZoomLevel = m_maxZoomLevel;
4960
4961
    /* -------------------------------------------------------------------- */
4962
    /*      Setup warp options.                                             */
4963
    /* -------------------------------------------------------------------- */
4964
0
    std::unique_ptr<GDALWarpOptions, decltype(&GDALDestroyWarpOptions)> psWO(
4965
0
        GDALCreateWarpOptions(), GDALDestroyWarpOptions);
4966
4967
0
    psWO->papszWarpOptions = CSLSetNameValue(nullptr, "OPTIMIZE_SIZE", "YES");
4968
0
    psWO->papszWarpOptions =
4969
0
        CSLSetNameValue(psWO->papszWarpOptions, "SAMPLE_GRID", "YES");
4970
0
    psWO->papszWarpOptions =
4971
0
        CSLMerge(psWO->papszWarpOptions, aosWarpOptions.List());
4972
4973
0
    int bHasSrcNoData = false;
4974
0
    const double dfSrcNoDataValue =
4975
0
        m_poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHasSrcNoData);
4976
4977
0
    const bool bLastSrcBandIsAlpha =
4978
0
        (m_poSrcDS->GetRasterCount() > 1 &&
4979
0
         m_poSrcDS->GetRasterBand(m_poSrcDS->GetRasterCount())
4980
0
                 ->GetColorInterpretation() == GCI_AlphaBand);
4981
4982
0
    const bool bOutputSupportsAlpha = !EQUAL(m_format.c_str(), "JPEG");
4983
0
    const bool bOutputSupportsNoData = EQUAL(m_format.c_str(), "GTiff");
4984
0
    const bool bDstNoDataSpecified = GetArg("dst-nodata")->IsExplicitlySet();
4985
0
    auto poColorTable = std::unique_ptr<GDALColorTable>(
4986
0
        [this]()
4987
0
        {
4988
0
            auto poCT = m_poSrcDS->GetRasterBand(1)->GetColorTable();
4989
0
            return poCT ? poCT->Clone() : nullptr;
4990
0
        }());
4991
4992
0
    const bool bUserAskedForAlpha = m_addalpha;
4993
0
    if (!m_noalpha && !m_addalpha)
4994
0
    {
4995
0
        m_addalpha = !(bHasSrcNoData && bOutputSupportsNoData) &&
4996
0
                     !bDstNoDataSpecified && poColorTable == nullptr;
4997
0
    }
4998
0
    m_addalpha &= bOutputSupportsAlpha;
4999
5000
0
    psWO->nBandCount = m_poSrcDS->GetRasterCount();
5001
0
    if (bLastSrcBandIsAlpha)
5002
0
    {
5003
0
        --psWO->nBandCount;
5004
0
        psWO->nSrcAlphaBand = m_poSrcDS->GetRasterCount();
5005
0
    }
5006
5007
0
    if (bHasSrcNoData)
5008
0
    {
5009
0
        psWO->padfSrcNoDataReal =
5010
0
            static_cast<double *>(CPLCalloc(psWO->nBandCount, sizeof(double)));
5011
0
        for (int i = 0; i < psWO->nBandCount; ++i)
5012
0
        {
5013
0
            psWO->padfSrcNoDataReal[i] = dfSrcNoDataValue;
5014
0
        }
5015
0
    }
5016
5017
0
    if ((bHasSrcNoData && !m_addalpha && bOutputSupportsNoData) ||
5018
0
        bDstNoDataSpecified)
5019
0
    {
5020
0
        psWO->padfDstNoDataReal =
5021
0
            static_cast<double *>(CPLCalloc(psWO->nBandCount, sizeof(double)));
5022
0
        for (int i = 0; i < psWO->nBandCount; ++i)
5023
0
        {
5024
0
            psWO->padfDstNoDataReal[i] =
5025
0
                bDstNoDataSpecified ? m_dstNoData : dfSrcNoDataValue;
5026
0
        }
5027
0
    }
5028
5029
0
    psWO->eWorkingDataType = eSrcDT;
5030
5031
0
    GDALGetWarpResampleAlg(m_resampling.c_str(), psWO->eResampleAlg);
5032
5033
    /* -------------------------------------------------------------------- */
5034
    /*      Setup band mapping.                                             */
5035
    /* -------------------------------------------------------------------- */
5036
5037
0
    psWO->panSrcBands =
5038
0
        static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
5039
0
    psWO->panDstBands =
5040
0
        static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
5041
5042
0
    for (int i = 0; i < psWO->nBandCount; i++)
5043
0
    {
5044
0
        psWO->panSrcBands[i] = i + 1;
5045
0
        psWO->panDstBands[i] = i + 1;
5046
0
    }
5047
5048
0
    if (m_addalpha)
5049
0
        psWO->nDstAlphaBand = psWO->nBandCount + 1;
5050
5051
0
    const int nDstBands =
5052
0
        psWO->nDstAlphaBand ? psWO->nDstAlphaBand : psWO->nBandCount;
5053
5054
0
    std::vector<GByte> dstBuffer;
5055
0
    const bool bIsPNGOutput = EQUAL(pszExtension, "png");
5056
0
    uint64_t dstBufferSize =
5057
0
        (static_cast<uint64_t>(tileMatrix.mTileWidth) *
5058
             // + 1 for PNG filter type / row byte
5059
0
             nDstBands * GDALGetDataTypeSizeBytes(psWO->eWorkingDataType) +
5060
0
         (bIsPNGOutput ? 1 : 0)) *
5061
0
        tileMatrix.mTileHeight;
5062
0
    if (bIsPNGOutput)
5063
0
    {
5064
        // Security margin for deflate compression
5065
0
        dstBufferSize += dstBufferSize / 10;
5066
0
    }
5067
0
    const uint64_t nUsableRAM =
5068
0
        std::min<uint64_t>(INT_MAX, CPLGetUsablePhysicalRAM() / 4);
5069
0
    if (dstBufferSize <=
5070
0
        (nUsableRAM ? nUsableRAM : static_cast<uint64_t>(INT_MAX)))
5071
0
    {
5072
0
        try
5073
0
        {
5074
0
            dstBuffer.resize(static_cast<size_t>(dstBufferSize));
5075
0
        }
5076
0
        catch (const std::exception &)
5077
0
        {
5078
0
        }
5079
0
    }
5080
0
    if (dstBuffer.size() < dstBufferSize)
5081
0
    {
5082
0
        ReportError(CE_Failure, CPLE_AppDefined,
5083
0
                    "Tile size and/or number of bands too large compared to "
5084
0
                    "available RAM");
5085
0
        return false;
5086
0
    }
5087
5088
    /* -------------------------------------------------------------------- */
5089
    /*      Select source overview                                          */
5090
    /* -------------------------------------------------------------------- */
5091
5092
0
    const int nDstXSize = (nMaxTileX - nMinTileX + 1) * tileMatrix.mTileWidth;
5093
0
    const int nDstYSize = (nMaxTileY - nMinTileY + 1) * tileMatrix.mTileHeight;
5094
5095
0
    const int nSrcOvrCount = m_poSrcDS->GetRasterBand(1)->GetOverviewCount();
5096
0
    if (nSrcOvrCount > 0 &&
5097
0
        m_poSrcDS->GetRasterXSize() > tileMatrix.mTileWidth &&
5098
0
        m_poSrcDS->GetRasterYSize() > tileMatrix.mTileHeight)
5099
0
    {
5100
0
        const double dfTargetRatioX =
5101
0
            static_cast<double>(m_poSrcDS->GetRasterXSize()) / nDstXSize;
5102
0
        const double dfTargetRatioY =
5103
0
            static_cast<double>(m_poSrcDS->GetRasterYSize()) / nDstYSize;
5104
        // take the minimum of these ratios #7019
5105
0
        const double dfTargetRatio = std::min(dfTargetRatioX, dfTargetRatioY);
5106
0
        if (dfTargetRatio > 1.0)
5107
0
        {
5108
0
            const int iBestOvr = GDALBandGetBestOverviewLevel(
5109
0
                m_poSrcDS->GetRasterBand(1), dfTargetRatio,
5110
0
                /* dfOversamplingThreshold = */ 1.0);
5111
0
            if (iBestOvr >= 0)
5112
0
            {
5113
0
                CPLDebug("WARP", "Selecting overview level %d", iBestOvr);
5114
0
                m_poSrcOvrDS =
5115
0
                    GDALCreateOverviewDataset(m_poSrcDS, iBestOvr,
5116
0
                                              /* bThisLevelOnly = */ false);
5117
0
                m_poSrcDS = m_poSrcOvrDS;
5118
0
            }
5119
0
        }
5120
0
    }
5121
5122
0
    FakeMaxZoomDataset oFakeMaxZoomDS(
5123
0
        nDstXSize, nDstYSize, nDstBands, tileMatrix.mTileWidth,
5124
0
        tileMatrix.mTileHeight, psWO->eWorkingDataType, dstGT, oSRS_TMS,
5125
0
        dstBuffer);
5126
0
    CPL_IGNORE_RET_VAL(oFakeMaxZoomDS.GetSpatialRef());
5127
5128
0
    psWO->hSrcDS = GDALDataset::ToHandle(m_poSrcDS);
5129
0
    psWO->hDstDS = GDALDataset::ToHandle(&oFakeMaxZoomDS);
5130
5131
0
    std::unique_ptr<GDALDataset> tmpSrcDS;
5132
0
    if (m_tilingScheme == "raster" && !bHasNorthUpSrcGT)
5133
0
    {
5134
0
        CPLStringList aosOptions;
5135
0
        aosOptions.AddString("-of");
5136
0
        aosOptions.AddString("VRT");
5137
0
        aosOptions.AddString("-a_ullr");
5138
0
        aosOptions.AddString(srcGTModif[0]);
5139
0
        aosOptions.AddString(srcGTModif[3]);
5140
0
        aosOptions.AddString(srcGTModif[0] + nSrcWidth * srcGTModif[1]);
5141
0
        aosOptions.AddString(srcGTModif[3] + nSrcHeight * srcGTModif[5]);
5142
0
        if (oSRS_TMS.IsEmpty())
5143
0
        {
5144
0
            aosOptions.AddString("-a_srs");
5145
0
            aosOptions.AddString("none");
5146
0
        }
5147
5148
0
        GDALTranslateOptions *psOptions =
5149
0
            GDALTranslateOptionsNew(aosOptions.List(), nullptr);
5150
5151
0
        tmpSrcDS.reset(GDALDataset::FromHandle(GDALTranslate(
5152
0
            "", GDALDataset::ToHandle(m_poSrcDS), psOptions, nullptr)));
5153
0
        GDALTranslateOptionsFree(psOptions);
5154
0
        if (!tmpSrcDS)
5155
0
            return false;
5156
0
    }
5157
0
    hTransformArg.reset(GDALCreateGenImgProjTransformer2(
5158
0
        tmpSrcDS ? tmpSrcDS.get() : m_poSrcDS, &oFakeMaxZoomDS, aosTO.List()));
5159
0
    CPLAssert(hTransformArg);
5160
5161
    /* -------------------------------------------------------------------- */
5162
    /*      Warp the transformer with a linear approximator                 */
5163
    /* -------------------------------------------------------------------- */
5164
0
    hTransformArg.reset(GDALCreateApproxTransformer(
5165
0
        GDALGenImgProjTransform, hTransformArg.release(), 0.125));
5166
0
    GDALApproxTransformerOwnsSubtransformer(hTransformArg.get(), TRUE);
5167
5168
0
    psWO->pfnTransformer = GDALApproxTransform;
5169
0
    psWO->pTransformerArg = hTransformArg.get();
5170
5171
    /* -------------------------------------------------------------------- */
5172
    /*      Determine total number of tiles                                 */
5173
    /* -------------------------------------------------------------------- */
5174
0
    const int nBaseTilesPerRow = nMaxTileX - nMinTileX + 1;
5175
0
    const int nBaseTilesPerCol = nMaxTileY - nMinTileY + 1;
5176
0
    const uint64_t nBaseTiles =
5177
0
        static_cast<uint64_t>(nBaseTilesPerCol) * nBaseTilesPerRow;
5178
0
    uint64_t nTotalTiles = nBaseTiles;
5179
0
    std::atomic<uint64_t> nCurTile = 0;
5180
0
    bool bRet = true;
5181
5182
0
    for (int iZ = m_maxZoomLevel - 1;
5183
0
         bRet && bIntersects && iZ >= m_minZoomLevel; --iZ)
5184
0
    {
5185
0
        auto ovrTileMatrix = tileMatrixList[iZ];
5186
0
        int nOvrMinTileX = 0;
5187
0
        int nOvrMinTileY = 0;
5188
0
        int nOvrMaxTileX = 0;
5189
0
        int nOvrMaxTileY = 0;
5190
0
        bRet =
5191
0
            GetTileIndices(ovrTileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
5192
0
                           nOvrMinTileX, nOvrMinTileY, nOvrMaxTileX,
5193
0
                           nOvrMaxTileY, m_noIntersectionIsOK, bIntersects);
5194
0
        if (bIntersects)
5195
0
        {
5196
0
            nTotalTiles +=
5197
0
                static_cast<uint64_t>(nOvrMaxTileY - nOvrMinTileY + 1) *
5198
0
                (nOvrMaxTileX - nOvrMinTileX + 1);
5199
0
        }
5200
0
    }
5201
5202
    /* -------------------------------------------------------------------- */
5203
    /*      Generate tiles at max zoom level                                */
5204
    /* -------------------------------------------------------------------- */
5205
0
    GDALWarpOperation oWO;
5206
5207
0
    bRet = oWO.Initialize(psWO.get()) == CE_None && bRet;
5208
5209
0
    const auto GetUpdatedCreationOptions =
5210
0
        [this](const gdal::TileMatrixSet::TileMatrix &oTM)
5211
0
    {
5212
0
        CPLStringList aosCreationOptions(m_creationOptions);
5213
0
        if (m_format == "GTiff")
5214
0
        {
5215
0
            if (aosCreationOptions.FetchNameValue("TILED") == nullptr &&
5216
0
                aosCreationOptions.FetchNameValue("BLOCKYSIZE") == nullptr)
5217
0
            {
5218
0
                if (oTM.mTileWidth <= 512 && oTM.mTileHeight <= 512)
5219
0
                {
5220
0
                    aosCreationOptions.SetNameValue(
5221
0
                        "BLOCKYSIZE", CPLSPrintf("%d", oTM.mTileHeight));
5222
0
                }
5223
0
                else
5224
0
                {
5225
0
                    aosCreationOptions.SetNameValue("TILED", "YES");
5226
0
                }
5227
0
            }
5228
0
            if (aosCreationOptions.FetchNameValue("COMPRESS") == nullptr)
5229
0
                aosCreationOptions.SetNameValue("COMPRESS", "LZW");
5230
0
        }
5231
0
        else if (m_format == "COG")
5232
0
        {
5233
0
            if (aosCreationOptions.FetchNameValue("OVERVIEW_RESAMPLING") ==
5234
0
                nullptr)
5235
0
            {
5236
0
                aosCreationOptions.SetNameValue("OVERVIEW_RESAMPLING",
5237
0
                                                m_overviewResampling.c_str());
5238
0
            }
5239
0
            if (aosCreationOptions.FetchNameValue("BLOCKSIZE") == nullptr &&
5240
0
                oTM.mTileWidth <= 512 && oTM.mTileWidth == oTM.mTileHeight)
5241
0
            {
5242
0
                aosCreationOptions.SetNameValue(
5243
0
                    "BLOCKSIZE", CPLSPrintf("%d", oTM.mTileWidth));
5244
0
            }
5245
0
        }
5246
0
        return aosCreationOptions;
5247
0
    };
5248
5249
0
    VSIMkdir(m_outputDir.c_str(), 0755);
5250
0
    VSIStatBufL sStat;
5251
0
    if (VSIStatL(m_outputDir.c_str(), &sStat) != 0 || !VSI_ISDIR(sStat.st_mode))
5252
0
    {
5253
0
        ReportError(CE_Failure, CPLE_FileIO,
5254
0
                    "Cannot create output directory %s", m_outputDir.c_str());
5255
0
        return false;
5256
0
    }
5257
5258
0
    OGRSpatialReference oWGS84;
5259
0
    oWGS84.importFromEPSG(4326);
5260
0
    oWGS84.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
5261
5262
0
    std::unique_ptr<OGRCoordinateTransformation> poCTToWGS84;
5263
0
    if (!oSRS_TMS.IsEmpty())
5264
0
    {
5265
0
        CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
5266
0
        poCTToWGS84.reset(
5267
0
            OGRCreateCoordinateTransformation(&oSRS_TMS, &oWGS84));
5268
0
    }
5269
5270
0
    const bool kmlCompatible = m_kml &&
5271
0
                               [this, &poTMS, &poCTToWGS84, bInvertAxisTMS]()
5272
0
    {
5273
0
        CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
5274
0
        double dfX = poTMS->tileMatrixList()[0].mTopLeftX;
5275
0
        double dfY = poTMS->tileMatrixList()[0].mTopLeftY;
5276
0
        if (bInvertAxisTMS)
5277
0
            std::swap(dfX, dfY);
5278
0
        return (m_minZoomLevel == m_maxZoomLevel ||
5279
0
                (poTMS->haveAllLevelsSameTopLeft() &&
5280
0
                 poTMS->haveAllLevelsSameTileSize() &&
5281
0
                 poTMS->hasOnlyPowerOfTwoVaryingScales())) &&
5282
0
               poCTToWGS84 && poCTToWGS84->Transform(1, &dfX, &dfY);
5283
0
    }();
5284
0
    const int kmlTileSize =
5285
0
        m_tileSize > 0 ? m_tileSize : poTMS->tileMatrixList()[0].mTileWidth;
5286
0
    if (m_kml && !kmlCompatible)
5287
0
    {
5288
0
        ReportError(CE_Failure, CPLE_NotSupported,
5289
0
                    "Tiling scheme not compatible with KML output");
5290
0
        return false;
5291
0
    }
5292
5293
0
    if (m_title.empty())
5294
0
        m_title = CPLGetFilename(m_inputDataset[0].GetName().c_str());
5295
5296
0
    if (!m_url.empty())
5297
0
    {
5298
0
        if (m_url.back() != '/')
5299
0
            m_url += '/';
5300
0
        std::string out_path = m_outputDir;
5301
0
        if (m_outputDir.back() == '/')
5302
0
            out_path.pop_back();
5303
0
        m_url += CPLGetFilename(out_path.c_str());
5304
0
    }
5305
5306
0
    CPLWorkerThreadPool oThreadPool;
5307
5308
0
    bool bThreadPoolInitialized = false;
5309
0
    const auto InitThreadPool =
5310
0
        [this, &oThreadPool, &bRet, &bThreadPoolInitialized]()
5311
0
    {
5312
0
        if (!bThreadPoolInitialized)
5313
0
        {
5314
0
            bThreadPoolInitialized = true;
5315
5316
0
            if (bRet && m_numThreads > 1)
5317
0
            {
5318
0
                CPLDebug("gdal_raster_tile", "Using %d threads", m_numThreads);
5319
0
                bRet = oThreadPool.Setup(m_numThreads, nullptr, nullptr);
5320
0
            }
5321
0
        }
5322
5323
0
        return bRet;
5324
0
    };
5325
5326
    // Just for unit test purposes
5327
0
    const bool bEmitSpuriousCharsOnStdout = CPLTestBool(
5328
0
        CPLGetConfigOption("GDAL_RASTER_TILE_EMIT_SPURIOUS_CHARS", "NO"));
5329
5330
0
    const auto IsCompatibleOfSpawnSilent = [bSrcIsFineForFork, this]()
5331
0
    {
5332
0
        const char *pszErrorMsg = "";
5333
0
        {
5334
0
            CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
5335
0
            if (IsCompatibleOfSpawn(pszErrorMsg))
5336
0
            {
5337
0
                m_parallelMethod = "spawn";
5338
0
                return true;
5339
0
            }
5340
0
        }
5341
0
        (void)bSrcIsFineForFork;
5342
0
#ifdef FORK_ALLOWED
5343
0
        if (bSrcIsFineForFork && !cpl::starts_with(m_outputDir, "/vsimem/"))
5344
0
        {
5345
0
            if (CPLGetCurrentThreadCount() == 1)
5346
0
            {
5347
0
                CPLDebugOnce(
5348
0
                    "gdal_raster_tile",
5349
0
                    "'gdal' binary not found. Using instead "
5350
0
                    "parallel-method=fork. If causing instability issues, set "
5351
0
                    "parallel-method to 'thread' or 'spawn'");
5352
0
                m_parallelMethod = "fork";
5353
0
                return true;
5354
0
            }
5355
0
        }
5356
0
#endif
5357
0
        return false;
5358
0
    };
5359
5360
0
    m_numThreads = std::max(
5361
0
        1, static_cast<int>(std::min<uint64_t>(
5362
0
               m_numThreads, nBaseTiles / GetThresholdMinTilesPerJob())));
5363
5364
0
    std::atomic<bool> bParentAskedForStop = false;
5365
0
    std::thread threadWaitForParentStop;
5366
0
    std::unique_ptr<CPLErrorHandlerPusher> poErrorHandlerPusher;
5367
0
    if (m_spawned)
5368
0
    {
5369
        // Redirect errors to stdout so the parent listens on a single
5370
        // file descriptor.
5371
0
        poErrorHandlerPusher =
5372
0
            std::make_unique<CPLErrorHandlerPusher>(SpawnedErrorHandler);
5373
5374
0
        threadWaitForParentStop = std::thread(
5375
0
            [&bParentAskedForStop]()
5376
0
            {
5377
0
                char szBuffer[81] = {0};
5378
0
                while (fgets(szBuffer, 80, stdin))
5379
0
                {
5380
0
                    if (strcmp(szBuffer, STOP_MARKER) == 0)
5381
0
                    {
5382
0
                        bParentAskedForStop = true;
5383
0
                        break;
5384
0
                    }
5385
0
                    else
5386
0
                    {
5387
0
                        CPLError(CE_Failure, CPLE_AppDefined,
5388
0
                                 "Got unexpected input from parent '%s'",
5389
0
                                 szBuffer);
5390
0
                    }
5391
0
                }
5392
0
            });
5393
0
    }
5394
0
#ifdef FORK_ALLOWED
5395
0
    else if (m_forked)
5396
0
    {
5397
0
        threadWaitForParentStop = std::thread(
5398
0
            [&bParentAskedForStop]()
5399
0
            {
5400
0
                std::string buffer;
5401
0
                buffer.resize(strlen(STOP_MARKER));
5402
0
                if (CPLPipeRead(pipeIn, buffer.data(),
5403
0
                                static_cast<int>(strlen(STOP_MARKER))) &&
5404
0
                    buffer == STOP_MARKER)
5405
0
                {
5406
0
                    bParentAskedForStop = true;
5407
0
                }
5408
0
                else
5409
0
                {
5410
0
                    CPLError(CE_Failure, CPLE_AppDefined,
5411
0
                             "Got unexpected input from parent '%s'",
5412
0
                             buffer.c_str());
5413
0
                }
5414
0
            });
5415
0
    }
5416
0
#endif
5417
5418
0
    if (m_ovrZoomLevel >= 0)
5419
0
    {
5420
        // do not generate base tiles if called as a child process with
5421
        // --ovr-zoom-level
5422
0
    }
5423
0
    else if (m_numThreads > 1 && nBaseTiles > 1 &&
5424
0
             ((m_parallelMethod.empty() &&
5425
0
               m_numThreads >= GetThresholdMinThreadsForSpawn() &&
5426
0
               IsCompatibleOfSpawnSilent()) ||
5427
0
              (m_parallelMethod == "spawn" || m_parallelMethod == "fork")))
5428
0
    {
5429
0
        if (!GenerateBaseTilesSpawnMethod(nBaseTilesPerCol, nBaseTilesPerRow,
5430
0
                                          nMinTileX, nMinTileY, nMaxTileX,
5431
0
                                          nMaxTileY, nTotalTiles, nBaseTiles,
5432
0
                                          pfnProgress, pProgressData))
5433
0
        {
5434
0
            return false;
5435
0
        }
5436
0
        nCurTile = nBaseTiles;
5437
0
    }
5438
0
    else
5439
0
    {
5440
        // Branch for multi-threaded or single-threaded max zoom level tile
5441
        // generation
5442
5443
0
        PerThreadMaxZoomResourceManager oResourceManager(
5444
0
            m_poSrcDS, psWO.get(), hTransformArg.get(), oFakeMaxZoomDS,
5445
0
            dstBuffer.size());
5446
5447
0
        const CPLStringList aosCreationOptions(
5448
0
            GetUpdatedCreationOptions(tileMatrix));
5449
5450
0
        CPLDebug("gdal_raster_tile",
5451
0
                 "Generating tiles z=%d, y=%d...%d, x=%d...%d", m_maxZoomLevel,
5452
0
                 nMinTileY, nMaxTileY, nMinTileX, nMaxTileX);
5453
5454
0
        bRet &= InitThreadPool();
5455
5456
0
        if (bRet && m_numThreads > 1)
5457
0
        {
5458
0
            std::atomic<bool> bFailure = false;
5459
0
            std::atomic<int> nQueuedJobs = 0;
5460
5461
0
            double dfTilesYPerJob;
5462
0
            int nYOuterIterations;
5463
0
            double dfTilesXPerJob;
5464
0
            int nXOuterIterations;
5465
0
            ComputeJobChunkSize(m_numThreads, nBaseTilesPerCol,
5466
0
                                nBaseTilesPerRow, dfTilesYPerJob,
5467
0
                                nYOuterIterations, dfTilesXPerJob,
5468
0
                                nXOuterIterations);
5469
5470
0
            CPLDebugOnly("gdal_raster_tile",
5471
0
                         "nYOuterIterations=%d, dfTilesYPerJob=%g, "
5472
0
                         "nXOuterIterations=%d, dfTilesXPerJob=%g",
5473
0
                         nYOuterIterations, dfTilesYPerJob, nXOuterIterations,
5474
0
                         dfTilesXPerJob);
5475
5476
0
            int nLastYEndIncluded = nMinTileY - 1;
5477
0
            for (int iYOuterIter = 0; bRet && iYOuterIter < nYOuterIterations &&
5478
0
                                      nLastYEndIncluded < nMaxTileY;
5479
0
                 ++iYOuterIter)
5480
0
            {
5481
0
                const int iYStart = nLastYEndIncluded + 1;
5482
0
                const int iYEndIncluded =
5483
0
                    iYOuterIter + 1 == nYOuterIterations
5484
0
                        ? nMaxTileY
5485
0
                        : std::max(
5486
0
                              iYStart,
5487
0
                              static_cast<int>(std::floor(
5488
0
                                  nMinTileY +
5489
0
                                  (iYOuterIter + 1) * dfTilesYPerJob - 1)));
5490
5491
0
                nLastYEndIncluded = iYEndIncluded;
5492
5493
0
                int nLastXEndIncluded = nMinTileX - 1;
5494
0
                for (int iXOuterIter = 0;
5495
0
                     bRet && iXOuterIter < nXOuterIterations &&
5496
0
                     nLastXEndIncluded < nMaxTileX;
5497
0
                     ++iXOuterIter)
5498
0
                {
5499
0
                    const int iXStart = nLastXEndIncluded + 1;
5500
0
                    const int iXEndIncluded =
5501
0
                        iXOuterIter + 1 == nXOuterIterations
5502
0
                            ? nMaxTileX
5503
0
                            : std::max(
5504
0
                                  iXStart,
5505
0
                                  static_cast<int>(std::floor(
5506
0
                                      nMinTileX +
5507
0
                                      (iXOuterIter + 1) * dfTilesXPerJob - 1)));
5508
5509
0
                    nLastXEndIncluded = iXEndIncluded;
5510
5511
0
                    CPLDebugOnly("gdal_raster_tile",
5512
0
                                 "Job for y in [%d,%d] and x in [%d,%d]",
5513
0
                                 iYStart, iYEndIncluded, iXStart,
5514
0
                                 iXEndIncluded);
5515
5516
0
                    auto job = [this, &oThreadPool, &oResourceManager,
5517
0
                                &bFailure, &bParentAskedForStop, &nCurTile,
5518
0
                                &nQueuedJobs, pszExtension, &aosCreationOptions,
5519
0
                                &psWO, &tileMatrix, nDstBands, iXStart,
5520
0
                                iXEndIncluded, iYStart, iYEndIncluded,
5521
0
                                nMinTileX, nMinTileY, &poColorTable,
5522
0
                                bUserAskedForAlpha]()
5523
0
                    {
5524
0
                        CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
5525
5526
0
                        auto resources = oResourceManager.AcquireResources();
5527
0
                        if (resources)
5528
0
                        {
5529
0
                            std::vector<GByte> tmpBuffer;
5530
0
                            for (int iY = iYStart;
5531
0
                                 iY <= iYEndIncluded && !bParentAskedForStop;
5532
0
                                 ++iY)
5533
0
                            {
5534
0
                                for (int iX = iXStart; iX <= iXEndIncluded &&
5535
0
                                                       !bParentAskedForStop;
5536
0
                                     ++iX)
5537
0
                                {
5538
0
                                    if (!GenerateTile(
5539
0
                                            resources->poSrcDS.get(),
5540
0
                                            m_poDstDriver, pszExtension,
5541
0
                                            aosCreationOptions.List(),
5542
0
                                            *(resources->poWO.get()),
5543
0
                                            *(resources->poFakeMaxZoomDS
5544
0
                                                  ->GetSpatialRef()),
5545
0
                                            psWO->eWorkingDataType, tileMatrix,
5546
0
                                            m_outputDir, nDstBands,
5547
0
                                            psWO->padfDstNoDataReal
5548
0
                                                ? &(psWO->padfDstNoDataReal[0])
5549
0
                                                : nullptr,
5550
0
                                            m_maxZoomLevel, iX, iY,
5551
0
                                            m_convention, nMinTileX, nMinTileY,
5552
0
                                            m_skipBlank, bUserAskedForAlpha,
5553
0
                                            m_auxXML, m_resume, m_metadata,
5554
0
                                            poColorTable.get(),
5555
0
                                            resources->dstBuffer, tmpBuffer))
5556
0
                                    {
5557
0
                                        oResourceManager.SetError();
5558
0
                                        bFailure = true;
5559
0
                                        --nQueuedJobs;
5560
0
                                        return;
5561
0
                                    }
5562
0
                                    ++nCurTile;
5563
0
                                    oThreadPool.WakeUpWaitEvent();
5564
0
                                }
5565
0
                            }
5566
0
                            oResourceManager.ReleaseResources(
5567
0
                                std::move(resources));
5568
0
                        }
5569
0
                        else
5570
0
                        {
5571
0
                            oResourceManager.SetError();
5572
0
                            bFailure = true;
5573
0
                        }
5574
5575
0
                        --nQueuedJobs;
5576
0
                    };
5577
5578
0
                    ++nQueuedJobs;
5579
0
                    oThreadPool.SubmitJob(std::move(job));
5580
0
                }
5581
0
            }
5582
5583
            // Wait for completion of all jobs
5584
0
            while (bRet && nQueuedJobs > 0)
5585
0
            {
5586
0
                oThreadPool.WaitEvent();
5587
0
                bRet &= !bFailure;
5588
0
                if (bRet && pfnProgress &&
5589
0
                    !pfnProgress(static_cast<double>(nCurTile) /
5590
0
                                     static_cast<double>(nTotalTiles),
5591
0
                                 "", pProgressData))
5592
0
                {
5593
0
                    bParentAskedForStop = true;
5594
0
                    bRet = false;
5595
0
                    CPLError(CE_Failure, CPLE_UserInterrupt,
5596
0
                             "Process interrupted by user");
5597
0
                }
5598
0
            }
5599
0
            oThreadPool.WaitCompletion();
5600
0
            bRet &=
5601
0
                !bFailure && (!pfnProgress ||
5602
0
                              pfnProgress(static_cast<double>(nCurTile) /
5603
0
                                              static_cast<double>(nTotalTiles),
5604
0
                                          "", pProgressData));
5605
5606
0
            if (!oResourceManager.GetErrorMsg().empty())
5607
0
            {
5608
                // Re-emit error message from worker thread to main thread
5609
0
                ReportError(CE_Failure, CPLE_AppDefined, "%s",
5610
0
                            oResourceManager.GetErrorMsg().c_str());
5611
0
            }
5612
0
        }
5613
0
        else
5614
0
        {
5615
            // Branch for single-thread max zoom level tile generation
5616
0
            std::vector<GByte> tmpBuffer;
5617
0
            for (int iY = nMinTileY;
5618
0
                 bRet && !bParentAskedForStop && iY <= nMaxTileY; ++iY)
5619
0
            {
5620
0
                for (int iX = nMinTileX;
5621
0
                     bRet && !bParentAskedForStop && iX <= nMaxTileX; ++iX)
5622
0
                {
5623
0
                    bRet = GenerateTile(
5624
0
                        m_poSrcDS, m_poDstDriver, pszExtension,
5625
0
                        aosCreationOptions.List(), oWO, oSRS_TMS,
5626
0
                        psWO->eWorkingDataType, tileMatrix, m_outputDir,
5627
0
                        nDstBands,
5628
0
                        psWO->padfDstNoDataReal ? &(psWO->padfDstNoDataReal[0])
5629
0
                                                : nullptr,
5630
0
                        m_maxZoomLevel, iX, iY, m_convention, nMinTileX,
5631
0
                        nMinTileY, m_skipBlank, bUserAskedForAlpha, m_auxXML,
5632
0
                        m_resume, m_metadata, poColorTable.get(), dstBuffer,
5633
0
                        tmpBuffer);
5634
5635
0
                    if (m_spawned)
5636
0
                    {
5637
0
                        if (bEmitSpuriousCharsOnStdout)
5638
0
                            fwrite(&PROGRESS_MARKER[0], 1, 1, stdout);
5639
0
                        fwrite(PROGRESS_MARKER, sizeof(PROGRESS_MARKER), 1,
5640
0
                               stdout);
5641
0
                        fflush(stdout);
5642
0
                    }
5643
0
#ifdef FORK_ALLOWED
5644
0
                    else if (m_forked)
5645
0
                    {
5646
0
                        CPLPipeWrite(pipeOut, PROGRESS_MARKER,
5647
0
                                     sizeof(PROGRESS_MARKER));
5648
0
                    }
5649
0
#endif
5650
0
                    else
5651
0
                    {
5652
0
                        ++nCurTile;
5653
0
                        if (bRet && pfnProgress &&
5654
0
                            !pfnProgress(static_cast<double>(nCurTile) /
5655
0
                                             static_cast<double>(nTotalTiles),
5656
0
                                         "", pProgressData))
5657
0
                        {
5658
0
                            bRet = false;
5659
0
                            CPLError(CE_Failure, CPLE_UserInterrupt,
5660
0
                                     "Process interrupted by user");
5661
0
                        }
5662
0
                    }
5663
0
                }
5664
0
            }
5665
0
        }
5666
5667
0
        if (m_kml && bRet)
5668
0
        {
5669
0
            for (int iY = nMinTileY; iY <= nMaxTileY; ++iY)
5670
0
            {
5671
0
                for (int iX = nMinTileX; iX <= nMaxTileX; ++iX)
5672
0
                {
5673
0
                    const int nFileY =
5674
0
                        GetFileY(iY, poTMS->tileMatrixList()[m_maxZoomLevel],
5675
0
                                 m_convention);
5676
0
                    std::string osFilename = CPLFormFilenameSafe(
5677
0
                        m_outputDir.c_str(), CPLSPrintf("%d", m_maxZoomLevel),
5678
0
                        nullptr);
5679
0
                    osFilename = CPLFormFilenameSafe(
5680
0
                        osFilename.c_str(), CPLSPrintf("%d", iX), nullptr);
5681
0
                    osFilename = CPLFormFilenameSafe(
5682
0
                        osFilename.c_str(),
5683
0
                        CPLSPrintf("%d.%s", nFileY, pszExtension), nullptr);
5684
0
                    if (VSIStatL(osFilename.c_str(), &sStat) == 0)
5685
0
                    {
5686
0
                        GenerateKML(m_outputDir, m_title, iX, iY,
5687
0
                                    m_maxZoomLevel, kmlTileSize, pszExtension,
5688
0
                                    m_url, poTMS.get(), bInvertAxisTMS,
5689
0
                                    m_convention, poCTToWGS84.get(), {});
5690
0
                    }
5691
0
                }
5692
0
            }
5693
0
        }
5694
0
    }
5695
5696
    // Close source dataset if we have opened it (in GDALAlgorithm core code),
5697
    // to free file descriptors, particularly if it is a VRT file.
5698
0
    std::vector<GDALColorInterp> aeColorInterp;
5699
0
    for (int i = 1; i <= m_poSrcDS->GetRasterCount(); ++i)
5700
0
        aeColorInterp.push_back(
5701
0
            m_poSrcDS->GetRasterBand(i)->GetColorInterpretation());
5702
0
    if (m_poSrcOvrDS)
5703
0
    {
5704
0
        m_poSrcOvrDS->ReleaseRef();
5705
0
        m_poSrcOvrDS = nullptr;
5706
0
    }
5707
0
    if (m_inputDataset[0].HasDatasetBeenOpenedByAlgorithm())
5708
0
    {
5709
0
        m_inputDataset[0].Close();
5710
0
        m_poSrcDS = nullptr;
5711
0
    }
5712
5713
    /* -------------------------------------------------------------------- */
5714
    /*      Generate tiles at lower zoom levels                             */
5715
    /* -------------------------------------------------------------------- */
5716
0
    const int iZStart =
5717
0
        m_ovrZoomLevel >= 0 ? m_ovrZoomLevel : m_maxZoomLevel - 1;
5718
0
    const int iZEnd = m_ovrZoomLevel >= 0 ? m_ovrZoomLevel : m_minZoomLevel;
5719
0
    for (int iZ = iZStart; bRet && iZ >= iZEnd; --iZ)
5720
0
    {
5721
0
        int nOvrMinTileX = 0;
5722
0
        int nOvrMinTileY = 0;
5723
0
        int nOvrMaxTileX = 0;
5724
0
        int nOvrMaxTileY = 0;
5725
5726
0
        auto ovrTileMatrix = tileMatrixList[iZ];
5727
0
        CPL_IGNORE_RET_VAL(
5728
0
            GetTileIndices(ovrTileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
5729
0
                           nOvrMinTileX, nOvrMinTileY, nOvrMaxTileX,
5730
0
                           nOvrMaxTileY, m_noIntersectionIsOK, bIntersects));
5731
5732
0
        bRet = bIntersects;
5733
5734
0
        if (m_minOvrTileX >= 0)
5735
0
        {
5736
0
            bRet = true;
5737
0
            nOvrMinTileX = m_minOvrTileX;
5738
0
            nOvrMinTileY = m_minOvrTileY;
5739
0
            nOvrMaxTileX = m_maxOvrTileX;
5740
0
            nOvrMaxTileY = m_maxOvrTileY;
5741
0
        }
5742
5743
0
        if (bRet)
5744
0
        {
5745
0
            CPLDebug("gdal_raster_tile",
5746
0
                     "Generating overview tiles z=%d, y=%d...%d, x=%d...%d", iZ,
5747
0
                     nOvrMinTileY, nOvrMaxTileY, nOvrMinTileX, nOvrMaxTileX);
5748
0
        }
5749
5750
0
        const int nOvrTilesPerCol = nOvrMaxTileY - nOvrMinTileY + 1;
5751
0
        const int nOvrTilesPerRow = nOvrMaxTileX - nOvrMinTileX + 1;
5752
0
        const uint64_t nOvrTileCount =
5753
0
            static_cast<uint64_t>(nOvrTilesPerCol) * nOvrTilesPerRow;
5754
5755
0
        m_numThreads = std::max(
5756
0
            1,
5757
0
            static_cast<int>(std::min<uint64_t>(
5758
0
                m_numThreads, nOvrTileCount / GetThresholdMinTilesPerJob())));
5759
5760
0
        if (m_numThreads > 1 && nOvrTileCount > 1 &&
5761
0
            ((m_parallelMethod.empty() &&
5762
0
              m_numThreads >= GetThresholdMinThreadsForSpawn() &&
5763
0
              IsCompatibleOfSpawnSilent()) ||
5764
0
             (m_parallelMethod == "spawn" || m_parallelMethod == "fork")))
5765
0
        {
5766
0
            bRet &= GenerateOverviewTilesSpawnMethod(
5767
0
                iZ, nOvrMinTileX, nOvrMinTileY, nOvrMaxTileX, nOvrMaxTileY,
5768
0
                nCurTile, nTotalTiles, pfnProgress, pProgressData);
5769
0
        }
5770
0
        else
5771
0
        {
5772
0
            bRet &= InitThreadPool();
5773
5774
0
            auto srcTileMatrix = tileMatrixList[iZ + 1];
5775
0
            int nSrcMinTileX = 0;
5776
0
            int nSrcMinTileY = 0;
5777
0
            int nSrcMaxTileX = 0;
5778
0
            int nSrcMaxTileY = 0;
5779
5780
0
            CPL_IGNORE_RET_VAL(GetTileIndices(
5781
0
                srcTileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
5782
0
                nSrcMinTileX, nSrcMinTileY, nSrcMaxTileX, nSrcMaxTileY,
5783
0
                m_noIntersectionIsOK, bIntersects));
5784
5785
0
            constexpr double EPSILON = 1e-3;
5786
0
            int maxCacheTileSizePerThread = static_cast<int>(
5787
0
                (1 + std::ceil(
5788
0
                         (ovrTileMatrix.mResY * ovrTileMatrix.mTileHeight) /
5789
0
                             (srcTileMatrix.mResY * srcTileMatrix.mTileHeight) -
5790
0
                         EPSILON)) *
5791
0
                (1 + std::ceil(
5792
0
                         (ovrTileMatrix.mResX * ovrTileMatrix.mTileWidth) /
5793
0
                             (srcTileMatrix.mResX * srcTileMatrix.mTileWidth) -
5794
0
                         EPSILON)));
5795
5796
0
            CPLDebugOnly("gdal_raster_tile",
5797
0
                         "Ideal maxCacheTileSizePerThread = %d",
5798
0
                         maxCacheTileSizePerThread);
5799
5800
0
#ifndef _WIN32
5801
0
            const int remainingFileDescriptorCount =
5802
0
                CPLGetRemainingFileDescriptorCount();
5803
0
            CPLDebugOnly("gdal_raster_tile",
5804
0
                         "remainingFileDescriptorCount = %d",
5805
0
                         remainingFileDescriptorCount);
5806
0
            if (remainingFileDescriptorCount >= 0 &&
5807
0
                remainingFileDescriptorCount <
5808
0
                    (1 + maxCacheTileSizePerThread) * m_numThreads)
5809
0
            {
5810
0
                const int newNumThreads =
5811
0
                    std::max(1, remainingFileDescriptorCount /
5812
0
                                    (1 + maxCacheTileSizePerThread));
5813
0
                if (newNumThreads < m_numThreads)
5814
0
                {
5815
0
                    CPLError(CE_Warning, CPLE_AppDefined,
5816
0
                             "Not enough file descriptors available given the "
5817
0
                             "number of "
5818
0
                             "threads. Reducing the number of threads %d to %d",
5819
0
                             m_numThreads, newNumThreads);
5820
0
                    m_numThreads = newNumThreads;
5821
0
                }
5822
0
            }
5823
0
#endif
5824
5825
0
            MosaicDataset oSrcDS(
5826
0
                CPLFormFilenameSafe(m_outputDir.c_str(),
5827
0
                                    CPLSPrintf("%d", iZ + 1), nullptr),
5828
0
                pszExtension, m_format, aeColorInterp, srcTileMatrix, oSRS_TMS,
5829
0
                nSrcMinTileX, nSrcMinTileY, nSrcMaxTileX, nSrcMaxTileY,
5830
0
                m_convention, nDstBands, psWO->eWorkingDataType,
5831
0
                psWO->padfDstNoDataReal ? &(psWO->padfDstNoDataReal[0])
5832
0
                                        : nullptr,
5833
0
                m_metadata, poColorTable.get(), maxCacheTileSizePerThread);
5834
5835
0
            const CPLStringList aosCreationOptions(
5836
0
                GetUpdatedCreationOptions(ovrTileMatrix));
5837
5838
0
            PerThreadLowerZoomResourceManager oResourceManager(oSrcDS);
5839
0
            std::atomic<bool> bFailure = false;
5840
0
            std::atomic<int> nQueuedJobs = 0;
5841
5842
0
            const bool bUseThreads = m_numThreads > 1 && nOvrTileCount > 1;
5843
5844
0
            if (bUseThreads)
5845
0
            {
5846
0
                double dfTilesYPerJob;
5847
0
                int nYOuterIterations;
5848
0
                double dfTilesXPerJob;
5849
0
                int nXOuterIterations;
5850
0
                ComputeJobChunkSize(m_numThreads, nOvrTilesPerCol,
5851
0
                                    nOvrTilesPerRow, dfTilesYPerJob,
5852
0
                                    nYOuterIterations, dfTilesXPerJob,
5853
0
                                    nXOuterIterations);
5854
5855
0
                CPLDebugOnly("gdal_raster_tile",
5856
0
                             "z=%d, nYOuterIterations=%d, dfTilesYPerJob=%g, "
5857
0
                             "nXOuterIterations=%d, dfTilesXPerJob=%g",
5858
0
                             iZ, nYOuterIterations, dfTilesYPerJob,
5859
0
                             nXOuterIterations, dfTilesXPerJob);
5860
5861
0
                int nLastYEndIncluded = nOvrMinTileY - 1;
5862
0
                for (int iYOuterIter = 0;
5863
0
                     bRet && iYOuterIter < nYOuterIterations &&
5864
0
                     nLastYEndIncluded < nOvrMaxTileY;
5865
0
                     ++iYOuterIter)
5866
0
                {
5867
0
                    const int iYStart = nLastYEndIncluded + 1;
5868
0
                    const int iYEndIncluded =
5869
0
                        iYOuterIter + 1 == nYOuterIterations
5870
0
                            ? nOvrMaxTileY
5871
0
                            : std::max(
5872
0
                                  iYStart,
5873
0
                                  static_cast<int>(std::floor(
5874
0
                                      nOvrMinTileY +
5875
0
                                      (iYOuterIter + 1) * dfTilesYPerJob - 1)));
5876
5877
0
                    nLastYEndIncluded = iYEndIncluded;
5878
5879
0
                    int nLastXEndIncluded = nOvrMinTileX - 1;
5880
0
                    for (int iXOuterIter = 0;
5881
0
                         bRet && iXOuterIter < nXOuterIterations &&
5882
0
                         nLastXEndIncluded < nOvrMaxTileX;
5883
0
                         ++iXOuterIter)
5884
0
                    {
5885
0
                        const int iXStart = nLastXEndIncluded + 1;
5886
0
                        const int iXEndIncluded =
5887
0
                            iXOuterIter + 1 == nXOuterIterations
5888
0
                                ? nOvrMaxTileX
5889
0
                                : std::max(iXStart, static_cast<int>(std::floor(
5890
0
                                                        nOvrMinTileX +
5891
0
                                                        (iXOuterIter + 1) *
5892
0
                                                            dfTilesXPerJob -
5893
0
                                                        1)));
5894
5895
0
                        nLastXEndIncluded = iXEndIncluded;
5896
5897
0
                        CPLDebugOnly(
5898
0
                            "gdal_raster_tile",
5899
0
                            "Job for z=%d, y in [%d,%d] and x in [%d,%d]", iZ,
5900
0
                            iYStart, iYEndIncluded, iXStart, iXEndIncluded);
5901
0
                        auto job =
5902
0
                            [this, &oThreadPool, &oResourceManager, &bFailure,
5903
0
                             &bParentAskedForStop, &nCurTile, &nQueuedJobs,
5904
0
                             pszExtension, &aosCreationOptions, &aosWarpOptions,
5905
0
                             &ovrTileMatrix, iZ, iXStart, iXEndIncluded,
5906
0
                             iYStart, iYEndIncluded, bUserAskedForAlpha]()
5907
0
                        {
5908
0
                            CPLErrorStateBackuper oBackuper(
5909
0
                                CPLQuietErrorHandler);
5910
5911
0
                            auto resources =
5912
0
                                oResourceManager.AcquireResources();
5913
0
                            if (resources)
5914
0
                            {
5915
0
                                for (int iY = iYStart; iY <= iYEndIncluded &&
5916
0
                                                       !bParentAskedForStop;
5917
0
                                     ++iY)
5918
0
                                {
5919
0
                                    for (int iX = iXStart;
5920
0
                                         iX <= iXEndIncluded &&
5921
0
                                         !bParentAskedForStop;
5922
0
                                         ++iX)
5923
0
                                    {
5924
0
                                        if (!GenerateOverviewTile(
5925
0
                                                *(resources->poSrcDS.get()),
5926
0
                                                m_poDstDriver, m_format,
5927
0
                                                pszExtension,
5928
0
                                                aosCreationOptions.List(),
5929
0
                                                aosWarpOptions.List(),
5930
0
                                                m_overviewResampling,
5931
0
                                                ovrTileMatrix, m_outputDir, iZ,
5932
0
                                                iX, iY, m_convention,
5933
0
                                                m_skipBlank, bUserAskedForAlpha,
5934
0
                                                m_auxXML, m_resume))
5935
0
                                        {
5936
0
                                            oResourceManager.SetError();
5937
0
                                            bFailure = true;
5938
0
                                            --nQueuedJobs;
5939
0
                                            return;
5940
0
                                        }
5941
5942
0
                                        ++nCurTile;
5943
0
                                        oThreadPool.WakeUpWaitEvent();
5944
0
                                    }
5945
0
                                }
5946
0
                                oResourceManager.ReleaseResources(
5947
0
                                    std::move(resources));
5948
0
                            }
5949
0
                            else
5950
0
                            {
5951
0
                                oResourceManager.SetError();
5952
0
                                bFailure = true;
5953
0
                            }
5954
0
                            --nQueuedJobs;
5955
0
                        };
5956
5957
0
                        ++nQueuedJobs;
5958
0
                        oThreadPool.SubmitJob(std::move(job));
5959
0
                    }
5960
0
                }
5961
5962
                // Wait for completion of all jobs
5963
0
                while (bRet && nQueuedJobs > 0)
5964
0
                {
5965
0
                    oThreadPool.WaitEvent();
5966
0
                    bRet &= !bFailure;
5967
0
                    if (bRet && pfnProgress &&
5968
0
                        !pfnProgress(static_cast<double>(nCurTile) /
5969
0
                                         static_cast<double>(nTotalTiles),
5970
0
                                     "", pProgressData))
5971
0
                    {
5972
0
                        bParentAskedForStop = true;
5973
0
                        bRet = false;
5974
0
                        CPLError(CE_Failure, CPLE_UserInterrupt,
5975
0
                                 "Process interrupted by user");
5976
0
                    }
5977
0
                }
5978
0
                oThreadPool.WaitCompletion();
5979
0
                bRet &= !bFailure &&
5980
0
                        (!pfnProgress ||
5981
0
                         pfnProgress(static_cast<double>(nCurTile) /
5982
0
                                         static_cast<double>(nTotalTiles),
5983
0
                                     "", pProgressData));
5984
5985
0
                if (!oResourceManager.GetErrorMsg().empty())
5986
0
                {
5987
                    // Re-emit error message from worker thread to main thread
5988
0
                    ReportError(CE_Failure, CPLE_AppDefined, "%s",
5989
0
                                oResourceManager.GetErrorMsg().c_str());
5990
0
                }
5991
0
            }
5992
0
            else
5993
0
            {
5994
                // Branch for single-thread overview generation
5995
5996
0
                for (int iY = nOvrMinTileY;
5997
0
                     bRet && !bParentAskedForStop && iY <= nOvrMaxTileY; ++iY)
5998
0
                {
5999
0
                    for (int iX = nOvrMinTileX;
6000
0
                         bRet && !bParentAskedForStop && iX <= nOvrMaxTileX;
6001
0
                         ++iX)
6002
0
                    {
6003
0
                        bRet = GenerateOverviewTile(
6004
0
                            oSrcDS, m_poDstDriver, m_format, pszExtension,
6005
0
                            aosCreationOptions.List(), aosWarpOptions.List(),
6006
0
                            m_overviewResampling, ovrTileMatrix, m_outputDir,
6007
0
                            iZ, iX, iY, m_convention, m_skipBlank,
6008
0
                            bUserAskedForAlpha, m_auxXML, m_resume);
6009
6010
0
                        if (m_spawned)
6011
0
                        {
6012
0
                            if (bEmitSpuriousCharsOnStdout)
6013
0
                                fwrite(&PROGRESS_MARKER[0], 1, 1, stdout);
6014
0
                            fwrite(PROGRESS_MARKER, sizeof(PROGRESS_MARKER), 1,
6015
0
                                   stdout);
6016
0
                            fflush(stdout);
6017
0
                        }
6018
0
#ifdef FORK_ALLOWED
6019
0
                        else if (m_forked)
6020
0
                        {
6021
0
                            CPLPipeWrite(pipeOut, PROGRESS_MARKER,
6022
0
                                         sizeof(PROGRESS_MARKER));
6023
0
                        }
6024
0
#endif
6025
0
                        else
6026
0
                        {
6027
0
                            ++nCurTile;
6028
0
                            if (bRet && pfnProgress &&
6029
0
                                !pfnProgress(
6030
0
                                    static_cast<double>(nCurTile) /
6031
0
                                        static_cast<double>(nTotalTiles),
6032
0
                                    "", pProgressData))
6033
0
                            {
6034
0
                                bRet = false;
6035
0
                                CPLError(CE_Failure, CPLE_UserInterrupt,
6036
0
                                         "Process interrupted by user");
6037
0
                            }
6038
0
                        }
6039
0
                    }
6040
0
                }
6041
0
            }
6042
0
        }
6043
6044
0
        if (m_kml && bRet)
6045
0
        {
6046
0
            for (int iY = nOvrMinTileY; bRet && iY <= nOvrMaxTileY; ++iY)
6047
0
            {
6048
0
                for (int iX = nOvrMinTileX; bRet && iX <= nOvrMaxTileX; ++iX)
6049
0
                {
6050
0
                    int nFileY =
6051
0
                        GetFileY(iY, poTMS->tileMatrixList()[iZ], m_convention);
6052
0
                    std::string osFilename = CPLFormFilenameSafe(
6053
0
                        m_outputDir.c_str(), CPLSPrintf("%d", iZ), nullptr);
6054
0
                    osFilename = CPLFormFilenameSafe(
6055
0
                        osFilename.c_str(), CPLSPrintf("%d", iX), nullptr);
6056
0
                    osFilename = CPLFormFilenameSafe(
6057
0
                        osFilename.c_str(),
6058
0
                        CPLSPrintf("%d.%s", nFileY, pszExtension), nullptr);
6059
0
                    if (VSIStatL(osFilename.c_str(), &sStat) == 0)
6060
0
                    {
6061
0
                        std::vector<TileCoordinates> children;
6062
6063
0
                        for (int iChildY = 0; iChildY <= 1; ++iChildY)
6064
0
                        {
6065
0
                            for (int iChildX = 0; iChildX <= 1; ++iChildX)
6066
0
                            {
6067
0
                                nFileY =
6068
0
                                    GetFileY(iY * 2 + iChildY,
6069
0
                                             poTMS->tileMatrixList()[iZ + 1],
6070
0
                                             m_convention);
6071
0
                                osFilename = CPLFormFilenameSafe(
6072
0
                                    m_outputDir.c_str(),
6073
0
                                    CPLSPrintf("%d", iZ + 1), nullptr);
6074
0
                                osFilename = CPLFormFilenameSafe(
6075
0
                                    osFilename.c_str(),
6076
0
                                    CPLSPrintf("%d", iX * 2 + iChildX),
6077
0
                                    nullptr);
6078
0
                                osFilename = CPLFormFilenameSafe(
6079
0
                                    osFilename.c_str(),
6080
0
                                    CPLSPrintf("%d.%s", nFileY, pszExtension),
6081
0
                                    nullptr);
6082
0
                                if (VSIStatL(osFilename.c_str(), &sStat) == 0)
6083
0
                                {
6084
0
                                    TileCoordinates tc;
6085
0
                                    tc.nTileX = iX * 2 + iChildX;
6086
0
                                    tc.nTileY = iY * 2 + iChildY;
6087
0
                                    tc.nTileZ = iZ + 1;
6088
0
                                    children.push_back(std::move(tc));
6089
0
                                }
6090
0
                            }
6091
0
                        }
6092
6093
0
                        GenerateKML(m_outputDir, m_title, iX, iY, iZ,
6094
0
                                    kmlTileSize, pszExtension, m_url,
6095
0
                                    poTMS.get(), bInvertAxisTMS, m_convention,
6096
0
                                    poCTToWGS84.get(), children);
6097
0
                    }
6098
0
                }
6099
0
            }
6100
0
        }
6101
0
    }
6102
6103
0
    const auto IsWebViewerEnabled = [this](const char *name)
6104
0
    {
6105
0
        return std::find_if(m_webviewers.begin(), m_webviewers.end(),
6106
0
                            [name](const std::string &s)
6107
0
                            { return s == "all" || s == name; }) !=
6108
0
               m_webviewers.end();
6109
0
    };
6110
6111
0
    if (m_ovrZoomLevel < 0 && bRet &&
6112
0
        poTMS->identifier() == "GoogleMapsCompatible" &&
6113
0
        IsWebViewerEnabled("leaflet"))
6114
0
    {
6115
0
        double dfSouthLat = -90;
6116
0
        double dfWestLon = -180;
6117
0
        double dfNorthLat = 90;
6118
0
        double dfEastLon = 180;
6119
6120
0
        if (poCTToWGS84)
6121
0
        {
6122
0
            poCTToWGS84->TransformBounds(
6123
0
                adfExtent[0], adfExtent[1], adfExtent[2], adfExtent[3],
6124
0
                &dfWestLon, &dfSouthLat, &dfEastLon, &dfNorthLat, 21);
6125
0
        }
6126
6127
0
        GenerateLeaflet(m_outputDir, m_title, dfSouthLat, dfWestLon, dfNorthLat,
6128
0
                        dfEastLon, m_minZoomLevel, m_maxZoomLevel,
6129
0
                        tileMatrix.mTileWidth, pszExtension, m_url, m_copyright,
6130
0
                        m_convention == "xyz");
6131
0
    }
6132
6133
0
    if (m_ovrZoomLevel < 0 && bRet && IsWebViewerEnabled("openlayers"))
6134
0
    {
6135
0
        GenerateOpenLayers(m_outputDir, m_title, adfExtent[0], adfExtent[1],
6136
0
                           adfExtent[2], adfExtent[3], m_minZoomLevel,
6137
0
                           m_maxZoomLevel, tileMatrix.mTileWidth, pszExtension,
6138
0
                           m_url, m_copyright, *(poTMS.get()), bInvertAxisTMS,
6139
0
                           oSRS_TMS, m_convention == "xyz");
6140
0
    }
6141
6142
0
    if (m_ovrZoomLevel < 0 && bRet && IsWebViewerEnabled("mapml") &&
6143
0
        poTMS->identifier() != "raster" && m_convention == "xyz")
6144
0
    {
6145
0
        GenerateMapML(m_outputDir, m_mapmlTemplate, m_title, nMinTileX,
6146
0
                      nMinTileY, nMaxTileX, nMaxTileY, m_minZoomLevel,
6147
0
                      m_maxZoomLevel, pszExtension, m_url, m_copyright,
6148
0
                      *(poTMS.get()));
6149
0
    }
6150
6151
0
    if (m_ovrZoomLevel < 0 && bRet && IsWebViewerEnabled("stac") &&
6152
0
        m_convention == "xyz")
6153
0
    {
6154
0
        OGRCoordinateTransformation *poCT = poCTToWGS84.get();
6155
0
        std::unique_ptr<OGRCoordinateTransformation> poCTToLongLat;
6156
0
        if (!poCTToWGS84)
6157
0
        {
6158
0
            CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
6159
0
            OGRSpatialReference oLongLat;
6160
0
            oLongLat.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
6161
0
            oLongLat.CopyGeogCSFrom(&oSRS_TMS);
6162
0
            poCTToLongLat.reset(
6163
0
                OGRCreateCoordinateTransformation(&oSRS_TMS, &oLongLat));
6164
0
            poCT = poCTToLongLat.get();
6165
0
        }
6166
6167
0
        double dfSouthLat = -90;
6168
0
        double dfWestLon = -180;
6169
0
        double dfNorthLat = 90;
6170
0
        double dfEastLon = 180;
6171
0
        if (poCT)
6172
0
        {
6173
0
            poCT->TransformBounds(adfExtent[0], adfExtent[1], adfExtent[2],
6174
0
                                  adfExtent[3], &dfWestLon, &dfSouthLat,
6175
0
                                  &dfEastLon, &dfNorthLat, 21);
6176
0
        }
6177
6178
0
        GenerateSTAC(m_outputDir, m_title, dfWestLon, dfSouthLat, dfEastLon,
6179
0
                     dfNorthLat, m_metadata, aoBandMetadata, m_minZoomLevel,
6180
0
                     m_maxZoomLevel, pszExtension, m_format, m_url, m_copyright,
6181
0
                     oSRS_TMS, *(poTMS.get()), bInvertAxisTMS, m_tileSize,
6182
0
                     adfExtent, m_inputDataset[0]);
6183
0
    }
6184
6185
0
    if (m_ovrZoomLevel < 0 && bRet && m_kml)
6186
0
    {
6187
0
        std::vector<TileCoordinates> children;
6188
6189
0
        auto ovrTileMatrix = tileMatrixList[m_minZoomLevel];
6190
0
        int nOvrMinTileX = 0;
6191
0
        int nOvrMinTileY = 0;
6192
0
        int nOvrMaxTileX = 0;
6193
0
        int nOvrMaxTileY = 0;
6194
0
        CPL_IGNORE_RET_VAL(
6195
0
            GetTileIndices(ovrTileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
6196
0
                           nOvrMinTileX, nOvrMinTileY, nOvrMaxTileX,
6197
0
                           nOvrMaxTileY, m_noIntersectionIsOK, bIntersects));
6198
6199
0
        for (int iY = nOvrMinTileY; bRet && iY <= nOvrMaxTileY; ++iY)
6200
0
        {
6201
0
            for (int iX = nOvrMinTileX; bRet && iX <= nOvrMaxTileX; ++iX)
6202
0
            {
6203
0
                int nFileY = GetFileY(
6204
0
                    iY, poTMS->tileMatrixList()[m_minZoomLevel], m_convention);
6205
0
                std::string osFilename = CPLFormFilenameSafe(
6206
0
                    m_outputDir.c_str(), CPLSPrintf("%d", m_minZoomLevel),
6207
0
                    nullptr);
6208
0
                osFilename = CPLFormFilenameSafe(osFilename.c_str(),
6209
0
                                                 CPLSPrintf("%d", iX), nullptr);
6210
0
                osFilename = CPLFormFilenameSafe(
6211
0
                    osFilename.c_str(),
6212
0
                    CPLSPrintf("%d.%s", nFileY, pszExtension), nullptr);
6213
0
                if (VSIStatL(osFilename.c_str(), &sStat) == 0)
6214
0
                {
6215
0
                    TileCoordinates tc;
6216
0
                    tc.nTileX = iX;
6217
0
                    tc.nTileY = iY;
6218
0
                    tc.nTileZ = m_minZoomLevel;
6219
0
                    children.push_back(std::move(tc));
6220
0
                }
6221
0
            }
6222
0
        }
6223
0
        GenerateKML(m_outputDir, m_title, -1, -1, -1, kmlTileSize, pszExtension,
6224
0
                    m_url, poTMS.get(), bInvertAxisTMS, m_convention,
6225
0
                    poCTToWGS84.get(), children);
6226
0
    }
6227
6228
0
    if (!bRet && CPLGetLastErrorType() == CE_None)
6229
0
    {
6230
        // If that happens, this is a programming error
6231
0
        ReportError(CE_Failure, CPLE_AppDefined,
6232
0
                    "Bug: process failed without returning an error message");
6233
0
    }
6234
6235
0
    if (m_spawned)
6236
0
    {
6237
        // Uninstall he custom error handler, before we close stdout.
6238
0
        poErrorHandlerPusher.reset();
6239
6240
0
        fwrite(END_MARKER, sizeof(END_MARKER), 1, stdout);
6241
0
        fflush(stdout);
6242
0
        fclose(stdout);
6243
0
        threadWaitForParentStop.join();
6244
0
    }
6245
0
#ifdef FORK_ALLOWED
6246
0
    else if (m_forked)
6247
0
    {
6248
0
        CPLPipeWrite(pipeOut, END_MARKER, sizeof(END_MARKER));
6249
0
        threadWaitForParentStop.join();
6250
0
    }
6251
0
#endif
6252
6253
0
    return bRet;
6254
0
}
6255
6256
GDALRasterTileAlgorithmStandalone::~GDALRasterTileAlgorithmStandalone() =
6257
    default;
6258
6259
//! @endcond