Coverage Report

Created: 2026-02-14 06:52

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