• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 16038479760

03 Jul 2025 12:12AM UTC coverage: 71.106% (-0.004%) from 71.11%
16038479760

Pull #12692

github

web-flow
Merge efeee3602 into b5d2a80d4
Pull Request #12692: C/C++/Python band algebra: add gdal.abs(), sqrt(), log(), log10() and pow()

80 of 87 new or added lines in 3 files covered. (91.95%)

8848 existing lines in 54 files now uncovered.

574863 of 808463 relevant lines covered (71.11%)

255001.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.02
/apps/gdalalg_raster_tile.cpp
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_mem_cache.h"
17
#include "cpl_spawn.h"
18
#include "cpl_worker_thread_pool.h"
19
#include "gdal_alg_priv.h"
20
#include "gdal_priv.h"
21
#include "gdalgetgdalpath.h"
22
#include "gdalwarper.h"
23
#include "gdal_utils.h"
24
#include "ogr_spatialref.h"
25
#include "memdataset.h"
26
#include "tilematrixset.hpp"
27

28
#include <algorithm>
29
#include <array>
30
#include <atomic>
31
#include <cinttypes>
32
#include <cmath>
33
#include <mutex>
34

35
//! @cond Doxygen_Suppress
36

37
#ifndef _
38
#define _(x) (x)
39
#endif
40

41
// Unlikely substring to appear in stdout. We do that in case some GDAL
42
// driver would output on stdout.
43
constexpr const char PROGRESS_MARKER[] = {'!', '.', 'x'};
44

45
// Minimum number of threads for automatic switch to spawning
46
constexpr int THRESHOLD_MIN_THREADS_FOR_SPAWN = 8;
47

48
// Minimum number of tiles per job to decide for automatic switch to spawning
49
constexpr int THRESHOLD_TILES_PER_JOB = 100;
50

51
/************************************************************************/
52
/*           GDALRasterTileAlgorithm::GDALRasterTileAlgorithm()         */
53
/************************************************************************/
54

55
GDALRasterTileAlgorithm::GDALRasterTileAlgorithm()
117✔
56
    : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
117✔
57
{
58
    AddProgressArg();
117✔
59
    AddArg("progress-forked", 0, _("Report progress as a forked child"),
60
           &m_progressForked)
234✔
61
        .SetHidden();  // Used in spawn mode
117✔
62
    AddArg("config-options-in-stdin", 0, _(""), &m_dummy)
234✔
63
        .SetHidden();  // Used in spawn mode
117✔
64
    AddArg("ovr-zoom-level", 0, _("Overview zoom level to compute"),
65
           &m_ovrZoomLevel)
234✔
66
        .SetMinValueIncluded(0)
117✔
67
        .SetHidden();  // Used in spawn mode
117✔
68
    AddArg("ovr-min-x", 0, _("Minimum tile X coordinate"), &m_minOvrTileX)
234✔
69
        .SetMinValueIncluded(0)
117✔
70
        .SetHidden();  // Used in spawn mode
117✔
71
    AddArg("ovr-max-x", 0, _("Maximum tile X coordinate"), &m_maxOvrTileX)
234✔
72
        .SetMinValueIncluded(0)
117✔
73
        .SetHidden();  // Used in spawn mode
117✔
74
    AddArg("ovr-min-y", 0, _("Minimum tile Y coordinate"), &m_minOvrTileY)
234✔
75
        .SetMinValueIncluded(0)
117✔
76
        .SetHidden();  // Used in spawn mode
117✔
77
    AddArg("ovr-max-y", 0, _("Maximum tile Y coordinate"), &m_maxOvrTileY)
234✔
78
        .SetMinValueIncluded(0)
117✔
79
        .SetHidden();  // Used in spawn mode
117✔
80

81
    AddOpenOptionsArg(&m_openOptions);
117✔
82
    AddInputFormatsArg(&m_inputFormats)
117✔
83
        .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_RASTER});
234✔
84
    AddInputDatasetArg(&m_dataset, GDAL_OF_RASTER);
117✔
85
    AddOutputFormatArg(&m_outputFormat)
117✔
86
        .SetDefault(m_outputFormat)
117✔
87
        .AddMetadataItem(
88
            GAAMDI_REQUIRED_CAPABILITIES,
89
            {GDAL_DCAP_RASTER, GDAL_DCAP_CREATECOPY, GDAL_DMD_EXTENSIONS})
585✔
90
        .AddMetadataItem(GAAMDI_VRT_COMPATIBLE, {"false"});
234✔
91
    AddCreationOptionsArg(&m_creationOptions);
117✔
92

93
    AddArg("output", 'o', _("Output directory"), &m_outputDirectory)
234✔
94
        .SetRequired()
117✔
95
        .SetMinCharCount(1)
117✔
96
        .SetPositional();
117✔
97

98
    std::vector<std::string> tilingSchemes{"raster"};
468✔
99
    for (const std::string &scheme :
1,053✔
100
         gdal::TileMatrixSet::listPredefinedTileMatrixSets())
2,223✔
101
    {
102
        auto poTMS = gdal::TileMatrixSet::parse(scheme.c_str());
2,106✔
103
        OGRSpatialReference oSRS_TMS;
2,106✔
104
        if (poTMS && !poTMS->hasVariableMatrixWidth() &&
2,106✔
105
            oSRS_TMS.SetFromUserInput(poTMS->crs().c_str()) == OGRERR_NONE)
1,053✔
106
        {
107
            std::string identifier = scheme == "GoogleMapsCompatible"
1,053✔
108
                                         ? "WebMercatorQuad"
109
                                         : poTMS->identifier();
2,106✔
110
            m_mapTileMatrixIdentifierToScheme[identifier] = scheme;
1,053✔
111
            tilingSchemes.push_back(std::move(identifier));
1,053✔
112
        }
113
    }
114
    AddArg("tiling-scheme", 0, _("Tiling scheme"), &m_tilingScheme)
234✔
115
        .SetDefault("WebMercatorQuad")
117✔
116
        .SetChoices(tilingSchemes)
117✔
117
        .SetHiddenChoices(
118
            "GoogleMapsCompatible",  // equivalent of WebMercatorQuad
119
            "mercator",              // gdal2tiles equivalent of WebMercatorQuad
120
            "geodetic"  // gdal2tiles (not totally) equivalent of WorldCRS84Quad
121
        );
117✔
122

123
    AddArg("min-zoom", 0, _("Minimum zoom level"), &m_minZoomLevel)
234✔
124
        .SetMinValueIncluded(0);
117✔
125
    AddArg("max-zoom", 0, _("Maximum zoom level"), &m_maxZoomLevel)
234✔
126
        .SetMinValueIncluded(0);
117✔
127

128
    AddArg("min-x", 0, _("Minimum tile X coordinate"), &m_minTileX)
234✔
129
        .SetMinValueIncluded(0);
117✔
130
    AddArg("max-x", 0, _("Maximum tile X coordinate"), &m_maxTileX)
234✔
131
        .SetMinValueIncluded(0);
117✔
132
    AddArg("min-y", 0, _("Minimum tile Y coordinate"), &m_minTileY)
234✔
133
        .SetMinValueIncluded(0);
117✔
134
    AddArg("max-y", 0, _("Maximum tile Y coordinate"), &m_maxTileY)
234✔
135
        .SetMinValueIncluded(0);
117✔
136
    AddArg("no-intersection-ok", 0,
137
           _("Whether dataset extent not intersecting tile matrix is only a "
138
             "warning"),
139
           &m_noIntersectionIsOK);
117✔
140

141
    AddArg("resampling", 'r', _("Resampling method for max zoom"),
142
           &m_resampling)
234✔
143
        .SetChoices("nearest", "bilinear", "cubic", "cubicspline", "lanczos",
144
                    "average", "rms", "mode", "min", "max", "med", "q1", "q3",
145
                    "sum")
117✔
146
        .SetDefault("cubic")
117✔
147
        .SetHiddenChoices("near");
117✔
148
    AddArg("overview-resampling", 0, _("Resampling method for overviews"),
149
           &m_overviewResampling)
234✔
150
        .SetChoices("nearest", "bilinear", "cubic", "cubicspline", "lanczos",
151
                    "average", "rms", "mode", "min", "max", "med", "q1", "q3",
152
                    "sum")
117✔
153
        .SetHiddenChoices("near");
117✔
154

155
    AddArg("convention", 0,
156
           _("Tile numbering convention: xyz (from top) or tms (from bottom)"),
157
           &m_convention)
234✔
158
        .SetDefault(m_convention)
117✔
159
        .SetChoices("xyz", "tms");
117✔
160
    AddArg("tile-size", 0, _("Override default tile size"), &m_tileSize)
234✔
161
        .SetMinValueIncluded(64)
117✔
162
        .SetMaxValueIncluded(32768);
117✔
163
    AddArg("add-alpha", 0, _("Whether to force adding an alpha channel"),
164
           &m_addalpha)
234✔
165
        .SetMutualExclusionGroup("alpha");
117✔
166
    AddArg("no-alpha", 0, _("Whether to disable adding an alpha channel"),
167
           &m_noalpha)
234✔
168
        .SetMutualExclusionGroup("alpha");
117✔
169
    auto &dstNoDataArg =
170
        AddArg("dst-nodata", 0, _("Destination nodata value"), &m_dstNoData);
117✔
171
    AddArg("skip-blank", 0, _("Do not generate blank tiles"), &m_skipBlank);
117✔
172

173
    {
174
        auto &arg = AddArg("metadata", 0,
175
                           _("Add metadata item to output tiles"), &m_metadata)
234✔
176
                        .SetMetaVar("<KEY>=<VALUE>")
234✔
177
                        .SetPackedValuesAllowed(false);
117✔
178
        arg.AddValidationAction([this, &arg]()
17✔
179
                                { return ParseAndValidateKeyValue(arg); });
134✔
180
        arg.AddHiddenAlias("mo");
117✔
181
    }
182
    AddArg("copy-src-metadata", 0,
183
           _("Whether to copy metadata from source dataset"),
184
           &m_copySrcMetadata);
117✔
185

186
    AddArg("aux-xml", 0, _("Generate .aux.xml sidecar files when needed"),
187
           &m_auxXML);
117✔
188
    AddArg("kml", 0, _("Generate KML files"), &m_kml);
117✔
189
    AddArg("resume", 0, _("Generate only missing files"), &m_resume);
117✔
190

191
    AddNumThreadsArg(&m_numThreads, &m_numThreadsStr);
117✔
192
    AddArg("parallel-method", 0, _("Parallelization method (thread / spawn)"),
193
           &m_parallelMethod)
234✔
194
        .SetChoices("thread", "spawn");
117✔
195

196
    constexpr const char *ADVANCED_RESAMPLING_CATEGORY = "Advanced Resampling";
117✔
197
    auto &excludedValuesArg =
198
        AddArg("excluded-values", 0,
199
               _("Tuples of values (e.g. <R>,<G>,<B> or (<R1>,<G1>,<B1>),"
200
                 "(<R2>,<G2>,<B2>)) that must beignored as contributing source "
201
                 "pixels during (average) resampling"),
202
               &m_excludedValues)
234✔
203
            .SetCategory(ADVANCED_RESAMPLING_CATEGORY);
117✔
204
    auto &excludedValuesPctThresholdArg =
205
        AddArg(
206
            "excluded-values-pct-threshold", 0,
207
            _("Minimum percentage of source pixels that must be set at one of "
208
              "the --excluded-values to cause the excluded value to be used as "
209
              "the target pixel value"),
210
            &m_excludedValuesPctThreshold)
234✔
211
            .SetDefault(m_excludedValuesPctThreshold)
117✔
212
            .SetMinValueIncluded(0)
117✔
213
            .SetMaxValueIncluded(100)
117✔
214
            .SetCategory(ADVANCED_RESAMPLING_CATEGORY);
117✔
215
    auto &nodataValuesPctThresholdArg =
216
        AddArg(
217
            "nodata-values-pct-threshold", 0,
218
            _("Minimum percentage of source pixels that must be set at one of "
219
              "nodata (or alpha=0 or any other way to express transparent pixel"
220
              "to cause the target pixel value to be transparent"),
221
            &m_nodataValuesPctThreshold)
234✔
222
            .SetDefault(m_nodataValuesPctThreshold)
117✔
223
            .SetMinValueIncluded(0)
117✔
224
            .SetMaxValueIncluded(100)
117✔
225
            .SetCategory(ADVANCED_RESAMPLING_CATEGORY);
117✔
226

227
    constexpr const char *PUBLICATION_CATEGORY = "Publication";
117✔
228
    AddArg("webviewer", 0, _("Web viewer to generate"), &m_webviewers)
234✔
229
        .SetDefault("all")
117✔
230
        .SetChoices("none", "all", "leaflet", "openlayers", "mapml")
117✔
231
        .SetCategory(PUBLICATION_CATEGORY);
117✔
232
    AddArg("url", 0,
233
           _("URL address where the generated tiles are going to be published"),
234
           &m_url)
234✔
235
        .SetCategory(PUBLICATION_CATEGORY);
117✔
236
    AddArg("title", 0, _("Title of the map"), &m_title)
234✔
237
        .SetCategory(PUBLICATION_CATEGORY);
117✔
238
    AddArg("copyright", 0, _("Copyright for the map"), &m_copyright)
234✔
239
        .SetCategory(PUBLICATION_CATEGORY);
117✔
240
    AddArg("mapml-template", 0,
241
           _("Filename of a template mapml file where variables will be "
242
             "substituted"),
243
           &m_mapmlTemplate)
234✔
244
        .SetMinCharCount(1)
117✔
245
        .SetCategory(PUBLICATION_CATEGORY);
117✔
246

247
    AddValidationAction(
117✔
248
        [this, &dstNoDataArg, &excludedValuesArg,
126✔
249
         &excludedValuesPctThresholdArg, &nodataValuesPctThresholdArg]()
811✔
250
        {
251
            if (m_minTileX >= 0 && m_maxTileX >= 0 && m_minTileX > m_maxTileX)
126✔
252
            {
253
                ReportError(CE_Failure, CPLE_IllegalArg,
1✔
254
                            "'min-x' must be lesser or equal to 'max-x'");
255
                return false;
1✔
256
            }
257

258
            if (m_minTileY >= 0 && m_maxTileY >= 0 && m_minTileY > m_maxTileY)
125✔
259
            {
260
                ReportError(CE_Failure, CPLE_IllegalArg,
1✔
261
                            "'min-y' must be lesser or equal to 'max-y'");
262
                return false;
1✔
263
            }
264

265
            if (m_minZoomLevel >= 0 && m_maxZoomLevel >= 0 &&
124✔
266
                m_minZoomLevel > m_maxZoomLevel)
32✔
267
            {
268
                ReportError(CE_Failure, CPLE_IllegalArg,
1✔
269
                            "'min-zoom' must be lesser or equal to 'max-zoom'");
270
                return false;
1✔
271
            }
272

273
            if (m_addalpha && dstNoDataArg.IsExplicitlySet())
123✔
274
            {
275
                ReportError(
1✔
276
                    CE_Failure, CPLE_IllegalArg,
277
                    "'add-alpha' and 'dst-nodata' are mutually exclusive");
278
                return false;
1✔
279
            }
280

281
            for (const auto *arg :
360✔
282
                 {&excludedValuesArg, &excludedValuesPctThresholdArg,
283
                  &nodataValuesPctThresholdArg})
482✔
284
            {
285
                if (arg->IsExplicitlySet() && m_resampling != "average")
362✔
286
                {
287
                    ReportError(CE_Failure, CPLE_AppDefined,
1✔
288
                                "'%s' can only be specified if 'resampling' is "
289
                                "set to 'average'",
290
                                arg->GetName().c_str());
1✔
291
                    return false;
2✔
292
                }
293
                if (arg->IsExplicitlySet() && !m_overviewResampling.empty() &&
362✔
294
                    m_overviewResampling != "average")
1✔
295
                {
296
                    ReportError(CE_Failure, CPLE_AppDefined,
1✔
297
                                "'%s' can only be specified if "
298
                                "'overview-resampling' is set to 'average'",
299
                                arg->GetName().c_str());
1✔
300
                    return false;
1✔
301
                }
302
            }
303

304
            return true;
120✔
305
        });
306
}
117✔
307

308
/************************************************************************/
309
/*                          GetTileIndices()                            */
310
/************************************************************************/
311

312
static bool GetTileIndices(gdal::TileMatrixSet::TileMatrix &tileMatrix,
240✔
313
                           bool bInvertAxisTMS, int tileSize,
314
                           const double adfExtent[4], int &nMinTileX,
315
                           int &nMinTileY, int &nMaxTileX, int &nMaxTileY,
316
                           bool noIntersectionIsOK, bool &bIntersects,
317
                           bool checkRasterOverflow = true)
318
{
319
    if (tileSize > 0)
240✔
320
    {
321
        tileMatrix.mResX *=
18✔
322
            static_cast<double>(tileMatrix.mTileWidth) / tileSize;
18✔
323
        tileMatrix.mResY *=
18✔
324
            static_cast<double>(tileMatrix.mTileHeight) / tileSize;
18✔
325
        tileMatrix.mTileWidth = tileSize;
18✔
326
        tileMatrix.mTileHeight = tileSize;
18✔
327
    }
328

329
    if (bInvertAxisTMS)
240✔
330
        std::swap(tileMatrix.mTopLeftX, tileMatrix.mTopLeftY);
2✔
331

332
    const double dfTileWidth = tileMatrix.mResX * tileMatrix.mTileWidth;
240✔
333
    const double dfTileHeight = tileMatrix.mResY * tileMatrix.mTileHeight;
240✔
334

335
    constexpr double EPSILON = 1e-3;
240✔
336
    const double dfMinTileX =
240✔
337
        (adfExtent[0] - tileMatrix.mTopLeftX) / dfTileWidth;
240✔
338
    nMinTileX = static_cast<int>(
240✔
339
        std::clamp(std::floor(dfMinTileX + EPSILON), 0.0,
480✔
340
                   static_cast<double>(tileMatrix.mMatrixWidth - 1)));
240✔
341
    const double dfMinTileY =
240✔
342
        (tileMatrix.mTopLeftY - adfExtent[3]) / dfTileHeight;
240✔
343
    nMinTileY = static_cast<int>(
240✔
344
        std::clamp(std::floor(dfMinTileY + EPSILON), 0.0,
480✔
345
                   static_cast<double>(tileMatrix.mMatrixHeight - 1)));
240✔
346
    const double dfMaxTileX =
240✔
347
        (adfExtent[2] - tileMatrix.mTopLeftX) / dfTileWidth;
240✔
348
    nMaxTileX = static_cast<int>(
240✔
349
        std::clamp(std::floor(dfMaxTileX + EPSILON), 0.0,
480✔
350
                   static_cast<double>(tileMatrix.mMatrixWidth - 1)));
240✔
351
    const double dfMaxTileY =
240✔
352
        (tileMatrix.mTopLeftY - adfExtent[1]) / dfTileHeight;
240✔
353
    nMaxTileY = static_cast<int>(
240✔
354
        std::clamp(std::floor(dfMaxTileY + EPSILON), 0.0,
480✔
355
                   static_cast<double>(tileMatrix.mMatrixHeight - 1)));
240✔
356

357
    bIntersects = (dfMinTileX <= tileMatrix.mMatrixWidth && dfMaxTileX >= 0 &&
240✔
358
                   dfMinTileY <= tileMatrix.mMatrixHeight && dfMaxTileY >= 0);
480✔
359
    if (!bIntersects)
240✔
360
    {
361
        CPLDebug("gdal_raster_tile",
2✔
362
                 "dfMinTileX=%g dfMinTileY=%g dfMaxTileX=%g dfMaxTileY=%g",
363
                 dfMinTileX, dfMinTileY, dfMaxTileX, dfMaxTileY);
364
        CPLError(noIntersectionIsOK ? CE_Warning : CE_Failure, CPLE_AppDefined,
2✔
365
                 "Extent of source dataset is not compatible with extent of "
366
                 "tile matrix %s",
367
                 tileMatrix.mId.c_str());
368
        return noIntersectionIsOK;
2✔
369
    }
370
    if (checkRasterOverflow)
238✔
371
    {
372
        if (nMaxTileX - nMinTileX + 1 > INT_MAX / tileMatrix.mTileWidth ||
154✔
373
            nMaxTileY - nMinTileY + 1 > INT_MAX / tileMatrix.mTileHeight)
154✔
374
        {
375
            CPLError(CE_Failure, CPLE_AppDefined, "Too large zoom level");
×
376
            return false;
×
377
        }
378
    }
379
    return true;
238✔
380
}
381

382
/************************************************************************/
383
/*                           GetFileY()                                 */
384
/************************************************************************/
385

386
static int GetFileY(int iY, const gdal::TileMatrixSet::TileMatrix &tileMatrix,
2,125✔
387
                    const std::string &convention)
388
{
389
    return convention == "xyz" ? iY : tileMatrix.mMatrixHeight - 1 - iY;
2,125✔
390
}
391

392
/************************************************************************/
393
/*                          GenerateTile()                              */
394
/************************************************************************/
395

396
static bool GenerateTile(
314✔
397
    GDALDataset *poSrcDS, GDALDriver *m_poDstDriver, const char *pszExtension,
398
    CSLConstList creationOptions, GDALWarpOperation &oWO,
399
    const OGRSpatialReference &oSRS_TMS, GDALDataType eWorkingDataType,
400
    const gdal::TileMatrixSet::TileMatrix &tileMatrix,
401
    const std::string &outputDirectory, int nBands, const double *pdfDstNoData,
402
    int nZoomLevel, int iX, int iY, const std::string &convention,
403
    int nMinTileX, int nMinTileY, bool bSkipBlank, bool bUserAskedForAlpha,
404
    bool bAuxXML, bool bResume, const std::vector<std::string> &metadata,
405
    const GDALColorTable *poColorTable, std::vector<GByte> &dstBuffer)
406
{
407
    const std::string osDirZ = CPLFormFilenameSafe(
408
        outputDirectory.c_str(), CPLSPrintf("%d", nZoomLevel), nullptr);
628✔
409
    const std::string osDirX =
410
        CPLFormFilenameSafe(osDirZ.c_str(), CPLSPrintf("%d", iX), nullptr);
628✔
411
    const int iFileY = GetFileY(iY, tileMatrix, convention);
314✔
412
    const std::string osFilename = CPLFormFilenameSafe(
413
        osDirX.c_str(), CPLSPrintf("%d", iFileY), pszExtension);
628✔
414

415
    if (bResume)
314✔
416
    {
417
        VSIStatBufL sStat;
418
        if (VSIStatL(osFilename.c_str(), &sStat) == 0)
5✔
419
            return true;
5✔
420
    }
421

422
    const int nDstXOff = (iX - nMinTileX) * tileMatrix.mTileWidth;
309✔
423
    const int nDstYOff = (iY - nMinTileY) * tileMatrix.mTileHeight;
309✔
424
    memset(dstBuffer.data(), 0, dstBuffer.size());
309✔
425
    const CPLErr eErr = oWO.WarpRegionToBuffer(
618✔
426
        nDstXOff, nDstYOff, tileMatrix.mTileWidth, tileMatrix.mTileHeight,
309✔
427
        dstBuffer.data(), eWorkingDataType);
309✔
428
    if (eErr != CE_None)
309✔
429
        return false;
2✔
430

431
    const bool bDstHasAlpha =
432
        nBands > poSrcDS->GetRasterCount() ||
336✔
433
        (nBands == poSrcDS->GetRasterCount() &&
29✔
434
         poSrcDS->GetRasterBand(nBands)->GetColorInterpretation() ==
28✔
435
             GCI_AlphaBand);
307✔
436
    const size_t nBytesPerBand = static_cast<size_t>(tileMatrix.mTileWidth) *
307✔
437
                                 tileMatrix.mTileHeight *
307✔
438
                                 GDALGetDataTypeSizeBytes(eWorkingDataType);
307✔
439
    if (bDstHasAlpha && bSkipBlank)
307✔
440
    {
441
        bool bBlank = true;
9✔
442
        for (size_t i = 0; i < nBytesPerBand && bBlank; ++i)
327,706✔
443
        {
444
            bBlank = (dstBuffer[(nBands - 1) * nBytesPerBand + i] == 0);
327,697✔
445
        }
446
        if (bBlank)
9✔
447
            return true;
5✔
448
    }
449
    if (bDstHasAlpha && !bUserAskedForAlpha)
302✔
450
    {
451
        bool bAllOpaque = true;
287✔
452
        for (size_t i = 0; i < nBytesPerBand && bAllOpaque; ++i)
14,483,900✔
453
        {
454
            bAllOpaque = (dstBuffer[(nBands - 1) * nBytesPerBand + i] == 255);
14,531,900✔
455
        }
UNCOV
456
        if (bAllOpaque)
×
457
            nBands--;
212✔
458
    }
459

UNCOV
460
    VSIMkdir(osDirZ.c_str(), 0755);
×
461
    VSIMkdir(osDirX.c_str(), 0755);
302✔
462

463
    auto memDS = std::unique_ptr<GDALDataset>(
464
        MEMDataset::Create("", tileMatrix.mTileWidth, tileMatrix.mTileHeight, 0,
302✔
465
                           eWorkingDataType, nullptr));
604✔
466
    for (int i = 0; i < nBands; ++i)
1,219✔
467
    {
468
        char szBuffer[32] = {'\0'};
917✔
469
        int nRet = CPLPrintPointer(
1,834✔
470
            szBuffer, dstBuffer.data() + i * nBytesPerBand, sizeof(szBuffer));
917✔
471
        szBuffer[nRet] = 0;
917✔
472

473
        char szOption[64] = {'\0'};
917✔
474
        snprintf(szOption, sizeof(szOption), "DATAPOINTER=%s", szBuffer);
917✔
475

476
        char *apszOptions[] = {szOption, nullptr};
917✔
477

478
        memDS->AddBand(eWorkingDataType, apszOptions);
917✔
479
        auto poDstBand = memDS->GetRasterBand(i + 1);
917✔
480
        if (i + 1 <= poSrcDS->GetRasterCount())
917✔
481
            poDstBand->SetColorInterpretation(
842✔
482
                poSrcDS->GetRasterBand(i + 1)->GetColorInterpretation());
842✔
483
        else
484
            poDstBand->SetColorInterpretation(GCI_AlphaBand);
75✔
485
        if (pdfDstNoData)
917✔
486
            poDstBand->SetNoDataValue(*pdfDstNoData);
9✔
487
        if (i == 0 && poColorTable)
917✔
488
            poDstBand->SetColorTable(
1✔
489
                const_cast<GDALColorTable *>(poColorTable));
1✔
490
    }
491
    const CPLStringList aosMD(metadata);
604✔
492
    for (const auto [key, value] : cpl::IterateNameValue(aosMD))
374✔
493
    {
494
        memDS->SetMetadataItem(key, value);
72✔
495
    }
496

497
    GDALGeoTransform gt;
302✔
498
    gt[0] =
604✔
499
        tileMatrix.mTopLeftX + iX * tileMatrix.mResX * tileMatrix.mTileWidth;
302✔
500
    gt[1] = tileMatrix.mResX;
302✔
501
    gt[2] = 0;
302✔
502
    gt[3] =
604✔
503
        tileMatrix.mTopLeftY - iY * tileMatrix.mResY * tileMatrix.mTileHeight;
302✔
504
    gt[4] = 0;
302✔
505
    gt[5] = -tileMatrix.mResY;
302✔
506
    memDS->SetGeoTransform(gt);
302✔
507

508
    memDS->SetSpatialRef(&oSRS_TMS);
302✔
509

510
    CPLConfigOptionSetter oSetter("GDAL_PAM_ENABLED", bAuxXML ? "YES" : "NO",
511
                                  false);
604✔
512
    CPLConfigOptionSetter oSetter2("GDAL_DISABLE_READDIR_ON_OPEN", "YES",
513
                                   false);
603✔
514

515
    std::unique_ptr<CPLConfigOptionSetter> poSetter;
302✔
516
    // No need to reopen the dataset at end of CreateCopy() (for PNG
517
    // and JPEG) if we don't need to generate .aux.xml
518
    if (!bAuxXML)
302✔
519
        poSetter = std::make_unique<CPLConfigOptionSetter>(
298✔
520
            "GDAL_OPEN_AFTER_COPY", "NO", false);
595✔
521
    CPL_IGNORE_RET_VAL(poSetter);
301✔
522

523
    const std::string osTmpFilename = osFilename + ".tmp." + pszExtension;
603✔
524

525
    std::unique_ptr<GDALDataset> poOutDS(
526
        m_poDstDriver->CreateCopy(osTmpFilename.c_str(), memDS.get(), false,
527
                                  creationOptions, nullptr, nullptr));
302✔
528
    bool bRet = poOutDS && poOutDS->Close() == CE_None;
302✔
529
    poOutDS.reset();
302✔
530
    if (bRet)
302✔
531
    {
532
        bRet = VSIRename(osTmpFilename.c_str(), osFilename.c_str()) == 0;
300✔
533
        if (bAuxXML)
300✔
534
        {
535
            VSIRename((osTmpFilename + ".aux.xml").c_str(),
4✔
536
                      (osFilename + ".aux.xml").c_str());
8✔
537
        }
538
    }
539
    else
540
    {
541
        VSIUnlink(osTmpFilename.c_str());
2✔
542
    }
543
    return bRet;
302✔
544
}
545

546
/************************************************************************/
547
/*                    GenerateOverviewTile()                            */
548
/************************************************************************/
549

550
static bool
551
GenerateOverviewTile(GDALDataset &oSrcDS, GDALDriver *m_poDstDriver,
92✔
552
                     const std::string &outputFormat, const char *pszExtension,
553
                     CSLConstList creationOptions,
554
                     CSLConstList papszWarpOptions,
555
                     const std::string &resampling,
556
                     const gdal::TileMatrixSet::TileMatrix &tileMatrix,
557
                     const std::string &outputDirectory, int nZoomLevel, int iX,
558
                     int iY, const std::string &convention, bool bSkipBlank,
559
                     bool bUserAskedForAlpha, bool bAuxXML, bool bResume)
560
{
561
    const std::string osDirZ = CPLFormFilenameSafe(
562
        outputDirectory.c_str(), CPLSPrintf("%d", nZoomLevel), nullptr);
184✔
563
    const std::string osDirX =
564
        CPLFormFilenameSafe(osDirZ.c_str(), CPLSPrintf("%d", iX), nullptr);
184✔
565

566
    const int iFileY = GetFileY(iY, tileMatrix, convention);
92✔
567
    const std::string osFilename = CPLFormFilenameSafe(
568
        osDirX.c_str(), CPLSPrintf("%d", iFileY), pszExtension);
184✔
569

570
    if (bResume)
92✔
571
    {
572
        VSIStatBufL sStat;
573
        if (VSIStatL(osFilename.c_str(), &sStat) == 0)
2✔
574
            return true;
1✔
575
    }
576

577
    VSIMkdir(osDirZ.c_str(), 0755);
91✔
578
    VSIMkdir(osDirX.c_str(), 0755);
91✔
579

580
    CPLStringList aosOptions;
182✔
581

582
    aosOptions.AddString("-of");
91✔
583
    aosOptions.AddString(outputFormat.c_str());
91✔
584

585
    for (const char *pszCO : cpl::Iterate(creationOptions))
115✔
586
    {
587
        aosOptions.AddString("-co");
24✔
588
        aosOptions.AddString(pszCO);
24✔
589
    }
590
    CPLConfigOptionSetter oSetter("GDAL_PAM_ENABLED", bAuxXML ? "YES" : "NO",
591
                                  false);
182✔
592
    CPLConfigOptionSetter oSetter2("GDAL_DISABLE_READDIR_ON_OPEN", "YES",
593
                                   false);
182✔
594

595
    aosOptions.AddString("-r");
91✔
596
    aosOptions.AddString(resampling.c_str());
91✔
597

598
    std::unique_ptr<GDALDataset> poOutDS;
91✔
599
    const double dfMinX =
91✔
600
        tileMatrix.mTopLeftX + iX * tileMatrix.mResX * tileMatrix.mTileWidth;
91✔
601
    const double dfMaxY =
91✔
602
        tileMatrix.mTopLeftY - iY * tileMatrix.mResY * tileMatrix.mTileHeight;
91✔
603
    const double dfMaxX = dfMinX + tileMatrix.mResX * tileMatrix.mTileWidth;
91✔
604
    const double dfMinY = dfMaxY - tileMatrix.mResY * tileMatrix.mTileHeight;
91✔
605

606
    const bool resamplingCompatibleOfTranslate =
607
        papszWarpOptions == nullptr &&
262✔
608
        (resampling == "nearest" || resampling == "average" ||
253✔
609
         resampling == "bilinear" || resampling == "cubic" ||
167✔
610
         resampling == "cubicspline" || resampling == "lanczos" ||
9✔
611
         resampling == "mode");
3✔
612

613
    const std::string osTmpFilename = osFilename + ".tmp." + pszExtension;
182✔
614

615
    if (resamplingCompatibleOfTranslate)
91✔
616
    {
617
        GDALGeoTransform upperGT;
85✔
618
        oSrcDS.GetGeoTransform(upperGT);
85✔
619
        const double dfMinXUpper = upperGT[0];
85✔
620
        const double dfMaxXUpper =
621
            dfMinXUpper + upperGT[1] * oSrcDS.GetRasterXSize();
85✔
622
        const double dfMaxYUpper = upperGT[3];
85✔
623
        const double dfMinYUpper =
624
            dfMaxYUpper + upperGT[5] * oSrcDS.GetRasterYSize();
85✔
625
        if (dfMinX >= dfMinXUpper && dfMaxX <= dfMaxXUpper &&
85✔
626
            dfMinY >= dfMinYUpper && dfMaxY <= dfMaxYUpper)
69✔
627
        {
628
            // If the overview tile is fully within the extent of the
629
            // upper zoom level, we can use GDALDataset::RasterIO() directly.
630

631
            const auto eDT = oSrcDS.GetRasterBand(1)->GetRasterDataType();
69✔
632
            const size_t nBytesPerBand =
633
                static_cast<size_t>(tileMatrix.mTileWidth) *
69✔
634
                tileMatrix.mTileHeight * GDALGetDataTypeSizeBytes(eDT);
69✔
635
            std::vector<GByte> dstBuffer(nBytesPerBand *
636
                                         oSrcDS.GetRasterCount());
69✔
637

638
            const double dfXOff = (dfMinX - dfMinXUpper) / upperGT[1];
69✔
639
            const double dfYOff = (dfMaxYUpper - dfMaxY) / -upperGT[5];
69✔
640
            const double dfXSize = (dfMaxX - dfMinX) / upperGT[1];
69✔
641
            const double dfYSize = (dfMaxY - dfMinY) / -upperGT[5];
69✔
642
            GDALRasterIOExtraArg sExtraArg;
643
            INIT_RASTERIO_EXTRA_ARG(sExtraArg);
69✔
644
            CPL_IGNORE_RET_VAL(sExtraArg.eResampleAlg);
69✔
645
            sExtraArg.eResampleAlg =
69✔
646
                GDALRasterIOGetResampleAlg(resampling.c_str());
69✔
647
            sExtraArg.dfXOff = dfXOff;
69✔
648
            sExtraArg.dfYOff = dfYOff;
69✔
649
            sExtraArg.dfXSize = dfXSize;
69✔
650
            sExtraArg.dfYSize = dfYSize;
69✔
651
            sExtraArg.bFloatingPointWindowValidity =
69✔
652
                sExtraArg.eResampleAlg != GRIORA_NearestNeighbour;
69✔
653
            constexpr double EPSILON = 1e-3;
69✔
654
            if (oSrcDS.RasterIO(GF_Read, static_cast<int>(dfXOff + EPSILON),
69✔
655
                                static_cast<int>(dfYOff + EPSILON),
69✔
656
                                static_cast<int>(dfXSize + 0.5),
69✔
657
                                static_cast<int>(dfYSize + 0.5),
69✔
658
                                dstBuffer.data(), tileMatrix.mTileWidth,
69✔
659
                                tileMatrix.mTileHeight, eDT,
69✔
660
                                oSrcDS.GetRasterCount(), nullptr, 0, 0, 0,
661
                                &sExtraArg) == CE_None)
69✔
662
            {
663
                int nDstBands = oSrcDS.GetRasterCount();
68✔
664
                const bool bDstHasAlpha =
665
                    oSrcDS.GetRasterBand(nDstBands)->GetColorInterpretation() ==
68✔
666
                    GCI_AlphaBand;
68✔
667
                if (bDstHasAlpha && bSkipBlank)
68✔
668
                {
669
                    bool bBlank = true;
2✔
670
                    for (size_t i = 0; i < nBytesPerBand && bBlank; ++i)
65,545✔
671
                    {
672
                        bBlank =
65,543✔
673
                            (dstBuffer[(nDstBands - 1) * nBytesPerBand + i] ==
65,543✔
674
                             0);
675
                    }
676
                    if (bBlank)
2✔
677
                        return true;
1✔
678
                    bSkipBlank = false;
1✔
679
                }
680
                if (bDstHasAlpha && !bUserAskedForAlpha)
67✔
681
                {
682
                    bool bAllOpaque = true;
67✔
683
                    for (size_t i = 0; i < nBytesPerBand && bAllOpaque; ++i)
4,194,370✔
684
                    {
685
                        bAllOpaque =
4,194,310✔
686
                            (dstBuffer[(nDstBands - 1) * nBytesPerBand + i] ==
4,194,310✔
687
                             255);
688
                    }
689
                    if (bAllOpaque)
67✔
690
                        nDstBands--;
64✔
691
                }
692

693
                auto memDS = std::unique_ptr<GDALDataset>(MEMDataset::Create(
134✔
694
                    "", tileMatrix.mTileWidth, tileMatrix.mTileHeight, 0, eDT,
67✔
695
                    nullptr));
134✔
696
                for (int i = 0; i < nDstBands; ++i)
271✔
697
                {
698
                    char szBuffer[32] = {'\0'};
204✔
699
                    int nRet = CPLPrintPointer(
408✔
700
                        szBuffer, dstBuffer.data() + i * nBytesPerBand,
204✔
701
                        sizeof(szBuffer));
702
                    szBuffer[nRet] = 0;
204✔
703

704
                    char szOption[64] = {'\0'};
204✔
705
                    snprintf(szOption, sizeof(szOption), "DATAPOINTER=%s",
204✔
706
                             szBuffer);
707

708
                    char *apszOptions[] = {szOption, nullptr};
204✔
709

710
                    memDS->AddBand(eDT, apszOptions);
204✔
711
                    auto poSrcBand = oSrcDS.GetRasterBand(i + 1);
204✔
712
                    auto poDstBand = memDS->GetRasterBand(i + 1);
204✔
713
                    poDstBand->SetColorInterpretation(
204✔
714
                        poSrcBand->GetColorInterpretation());
204✔
715
                    int bHasNoData = false;
204✔
716
                    const double dfNoData =
717
                        poSrcBand->GetNoDataValue(&bHasNoData);
204✔
718
                    if (bHasNoData)
204✔
719
                        poDstBand->SetNoDataValue(dfNoData);
×
720
                    if (auto poCT = poSrcBand->GetColorTable())
204✔
721
                        poDstBand->SetColorTable(poCT);
×
722
                }
723
                memDS->SetMetadata(oSrcDS.GetMetadata());
67✔
724
                memDS->SetGeoTransform(GDALGeoTransform(
134✔
725
                    dfMinX, tileMatrix.mResX, 0, dfMaxY, 0, -tileMatrix.mResY));
67✔
726

727
                memDS->SetSpatialRef(oSrcDS.GetSpatialRef());
67✔
728

729
                std::unique_ptr<CPLConfigOptionSetter> poSetter;
67✔
730
                // No need to reopen the dataset at end of CreateCopy() (for PNG
731
                // and JPEG) if we don't need to generate .aux.xml
732
                if (!bAuxXML)
67✔
733
                    poSetter = std::make_unique<CPLConfigOptionSetter>(
67✔
734
                        "GDAL_OPEN_AFTER_COPY", "NO", false);
134✔
735
                CPL_IGNORE_RET_VAL(poSetter);
67✔
736

737
                poOutDS.reset(m_poDstDriver->CreateCopy(
67✔
738
                    osTmpFilename.c_str(), memDS.get(), false, creationOptions,
739
                    nullptr, nullptr));
740
            }
68✔
741
        }
742
        else
743
        {
744
            // If the overview tile is not fully within the extent of the
745
            // upper zoom level, use GDALTranslate() to use VRT padding
746

747
            aosOptions.AddString("-q");
16✔
748

749
            aosOptions.AddString("-projwin");
16✔
750
            aosOptions.AddString(CPLSPrintf("%.17g", dfMinX));
16✔
751
            aosOptions.AddString(CPLSPrintf("%.17g", dfMaxY));
16✔
752
            aosOptions.AddString(CPLSPrintf("%.17g", dfMaxX));
16✔
753
            aosOptions.AddString(CPLSPrintf("%.17g", dfMinY));
16✔
754

755
            aosOptions.AddString("-outsize");
16✔
756
            aosOptions.AddString(CPLSPrintf("%d", tileMatrix.mTileWidth));
16✔
757
            aosOptions.AddString(CPLSPrintf("%d", tileMatrix.mTileHeight));
16✔
758

759
            GDALTranslateOptions *psOptions =
760
                GDALTranslateOptionsNew(aosOptions.List(), nullptr);
16✔
761
            poOutDS.reset(GDALDataset::FromHandle(GDALTranslate(
16✔
762
                osTmpFilename.c_str(), GDALDataset::ToHandle(&oSrcDS),
763
                psOptions, nullptr)));
764
            GDALTranslateOptionsFree(psOptions);
16✔
765
        }
766
    }
767
    else
768
    {
769
        aosOptions.AddString("-te");
6✔
770
        aosOptions.AddString(CPLSPrintf("%.17g", dfMinX));
6✔
771
        aosOptions.AddString(CPLSPrintf("%.17g", dfMinY));
6✔
772
        aosOptions.AddString(CPLSPrintf("%.17g", dfMaxX));
6✔
773
        aosOptions.AddString(CPLSPrintf("%.17g", dfMaxY));
6✔
774

775
        aosOptions.AddString("-ts");
6✔
776
        aosOptions.AddString(CPLSPrintf("%d", tileMatrix.mTileWidth));
6✔
777
        aosOptions.AddString(CPLSPrintf("%d", tileMatrix.mTileHeight));
6✔
778

779
        for (int i = 0; papszWarpOptions && papszWarpOptions[i]; ++i)
11✔
780
        {
781
            aosOptions.AddString("-wo");
5✔
782
            aosOptions.AddString(papszWarpOptions[i]);
5✔
783
        }
784

785
        GDALWarpAppOptions *psOptions =
786
            GDALWarpAppOptionsNew(aosOptions.List(), nullptr);
6✔
787
        GDALDatasetH hSrcDS = GDALDataset::ToHandle(&oSrcDS);
6✔
788
        poOutDS.reset(GDALDataset::FromHandle(GDALWarp(
6✔
789
            osTmpFilename.c_str(), nullptr, 1, &hSrcDS, psOptions, nullptr)));
790
        GDALWarpAppOptionsFree(psOptions);
6✔
791
    }
792

793
    bool bRet = poOutDS != nullptr;
90✔
794
    if (bRet && bSkipBlank)
90✔
795
    {
796
        auto poLastBand = poOutDS->GetRasterBand(poOutDS->GetRasterCount());
10✔
797
        if (poLastBand->GetColorInterpretation() == GCI_AlphaBand)
10✔
798
        {
799
            std::vector<GByte> buffer(
800
                static_cast<size_t>(tileMatrix.mTileWidth) *
1✔
801
                tileMatrix.mTileHeight *
1✔
802
                GDALGetDataTypeSizeBytes(poLastBand->GetRasterDataType()));
1✔
803
            CPL_IGNORE_RET_VAL(poLastBand->RasterIO(
2✔
804
                GF_Read, 0, 0, tileMatrix.mTileWidth, tileMatrix.mTileHeight,
1✔
805
                buffer.data(), tileMatrix.mTileWidth, tileMatrix.mTileHeight,
1✔
806
                poLastBand->GetRasterDataType(), 0, 0, nullptr));
807
            bool bBlank = true;
1✔
808
            for (size_t i = 0; i < buffer.size() && bBlank; ++i)
65,537✔
809
            {
810
                bBlank = (buffer[i] == 0);
65,536✔
811
            }
812
            if (bBlank)
1✔
813
            {
814
                poOutDS.reset();
1✔
815
                VSIUnlink(osTmpFilename.c_str());
1✔
816
                if (bAuxXML)
1✔
817
                    VSIUnlink((osTmpFilename + ".aux.xml").c_str());
×
818
                return true;
1✔
819
            }
820
        }
821
    }
822
    bRet = bRet && poOutDS->Close() == CE_None;
89✔
823
    poOutDS.reset();
89✔
824
    if (bRet)
89✔
825
    {
826
        bRet = VSIRename(osTmpFilename.c_str(), osFilename.c_str()) == 0;
88✔
827
        if (bAuxXML)
88✔
828
        {
829
            VSIRename((osTmpFilename + ".aux.xml").c_str(),
4✔
830
                      (osFilename + ".aux.xml").c_str());
8✔
831
        }
832
    }
833
    else
834
    {
835
        VSIUnlink(osTmpFilename.c_str());
1✔
836
    }
837
    return bRet;
89✔
838
}
839

840
namespace
841
{
842

843
/************************************************************************/
844
/*                     FakeMaxZoomRasterBand                            */
845
/************************************************************************/
846

847
class FakeMaxZoomRasterBand : public GDALRasterBand
848
{
849
    void *m_pDstBuffer = nullptr;
850
    CPL_DISALLOW_COPY_ASSIGN(FakeMaxZoomRasterBand)
851

852
  public:
853
    FakeMaxZoomRasterBand(int nBandIn, int nWidth, int nHeight,
249✔
854
                          int nBlockXSizeIn, int nBlockYSizeIn,
855
                          GDALDataType eDT, void *pDstBuffer)
856
        : m_pDstBuffer(pDstBuffer)
249✔
857
    {
858
        nBand = nBandIn;
249✔
859
        nRasterXSize = nWidth;
249✔
860
        nRasterYSize = nHeight;
249✔
861
        nBlockXSize = nBlockXSizeIn;
249✔
862
        nBlockYSize = nBlockYSizeIn;
249✔
863
        eDataType = eDT;
249✔
864
    }
249✔
865

866
    CPLErr IReadBlock(int, int, void *) override
×
867
    {
868
        CPLAssert(false);
×
869
        return CE_Failure;
870
    }
871

872
#ifdef DEBUG
873
    CPLErr IWriteBlock(int, int, void *) override
×
874
    {
875
        CPLAssert(false);
×
876
        return CE_Failure;
877
    }
878
#endif
879

880
    CPLErr IRasterIO(GDALRWFlag eRWFlag, [[maybe_unused]] int nXOff,
588✔
881
                     [[maybe_unused]] int nYOff, [[maybe_unused]] int nXSize,
882
                     [[maybe_unused]] int nYSize, void *pData,
883
                     [[maybe_unused]] int nBufXSize,
884
                     [[maybe_unused]] int nBufYSize, GDALDataType eBufType,
885
                     GSpacing nPixelSpace, [[maybe_unused]] GSpacing nLineSpace,
886
                     GDALRasterIOExtraArg *) override
887
    {
888
        // For sake of implementation simplicity, check various assumptions of
889
        // how GDALAlphaMask code does I/O
890
        CPLAssert((nXOff % nBlockXSize) == 0);
588✔
891
        CPLAssert((nYOff % nBlockYSize) == 0);
588✔
892
        CPLAssert(nXSize == nBufXSize);
588✔
893
        CPLAssert(nXSize == nBlockXSize);
588✔
894
        CPLAssert(nYSize == nBufYSize);
588✔
895
        CPLAssert(nYSize == nBlockYSize);
588✔
896
        CPLAssert(nLineSpace == nBlockXSize * nPixelSpace);
588✔
897
        CPLAssert(
588✔
898
            nBand ==
899
            poDS->GetRasterCount());  // only alpha band is accessed this way
900
        if (eRWFlag == GF_Read)
588✔
901
        {
902
            double dfZero = 0;
294✔
903
            GDALCopyWords64(&dfZero, GDT_Float64, 0, pData, eBufType,
294✔
904
                            static_cast<int>(nPixelSpace),
905
                            static_cast<size_t>(nBlockXSize) * nBlockYSize);
294✔
906
        }
907
        else
908
        {
909
            GDALCopyWords64(pData, eBufType, static_cast<int>(nPixelSpace),
294✔
910
                            m_pDstBuffer, eDataType,
911
                            GDALGetDataTypeSizeBytes(eDataType),
912
                            static_cast<size_t>(nBlockXSize) * nBlockYSize);
294✔
913
        }
914
        return CE_None;
588✔
915
    }
916
};
917

918
/************************************************************************/
919
/*                       FakeMaxZoomDataset                             */
920
/************************************************************************/
921

922
// This class is used to create a fake output dataset for GDALWarpOperation.
923
// In particular we need to implement GDALRasterBand::IRasterIO(GF_Write, ...)
924
// to catch writes (of one single tile) to the alpha band and redirect them
925
// to the dstBuffer passed to FakeMaxZoomDataset constructor.
926

927
class FakeMaxZoomDataset : public GDALDataset
928
{
929
    const int m_nBlockXSize;
930
    const int m_nBlockYSize;
931
    const OGRSpatialReference m_oSRS;
932
    const GDALGeoTransform m_gt{};
933

934
  public:
935
    FakeMaxZoomDataset(int nWidth, int nHeight, int nBandsIn, int nBlockXSize,
80✔
936
                       int nBlockYSize, GDALDataType eDT,
937
                       const GDALGeoTransform &gt,
938
                       const OGRSpatialReference &oSRS,
939
                       std::vector<GByte> &dstBuffer)
940
        : m_nBlockXSize(nBlockXSize), m_nBlockYSize(nBlockYSize), m_oSRS(oSRS),
80✔
941
          m_gt(gt)
80✔
942
    {
943
        eAccess = GA_Update;
80✔
944
        nRasterXSize = nWidth;
80✔
945
        nRasterYSize = nHeight;
80✔
946
        for (int i = 1; i <= nBandsIn; ++i)
329✔
947
        {
948
            SetBand(i,
249✔
949
                    new FakeMaxZoomRasterBand(
950
                        i, nWidth, nHeight, nBlockXSize, nBlockYSize, eDT,
951
                        dstBuffer.data() + static_cast<size_t>(i - 1) *
249✔
952
                                               nBlockXSize * nBlockYSize *
498✔
953
                                               GDALGetDataTypeSizeBytes(eDT)));
249✔
954
        }
955
    }
80✔
956

957
    const OGRSpatialReference *GetSpatialRef() const override
144✔
958
    {
959
        return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
144✔
960
    }
961

962
    CPLErr GetGeoTransform(GDALGeoTransform &gt) const override
76✔
963
    {
964
        gt = m_gt;
76✔
965
        return CE_None;
76✔
966
    }
967

968
    using GDALDataset::Clone;
969

970
    std::unique_ptr<FakeMaxZoomDataset>
971
    Clone(std::vector<GByte> &dstBuffer) const
4✔
972
    {
973
        return std::make_unique<FakeMaxZoomDataset>(
974
            nRasterXSize, nRasterYSize, nBands, m_nBlockXSize, m_nBlockYSize,
4✔
975
            GetRasterBand(1)->GetRasterDataType(), m_gt, m_oSRS, dstBuffer);
8✔
976
    }
977
};
978

979
/************************************************************************/
980
/*                          MosaicRasterBand                            */
981
/************************************************************************/
982

983
class MosaicRasterBand : public GDALRasterBand
984
{
985
    const int m_tileMinX;
986
    const int m_tileMinY;
987
    const GDALColorInterp m_eColorInterp;
988
    const gdal::TileMatrixSet::TileMatrix m_oTM;
989
    const std::string m_convention;
990
    const std::string m_directory;
991
    const std::string m_extension;
992
    const bool m_hasNoData;
993
    const double m_noData;
994
    std::unique_ptr<GDALColorTable> m_poColorTable{};
995

996
  public:
997
    MosaicRasterBand(GDALDataset *poDSIn, int nBandIn, int nWidth, int nHeight,
162✔
998
                     int nBlockXSizeIn, int nBlockYSizeIn, GDALDataType eDT,
999
                     GDALColorInterp eColorInterp, int nTileMinX, int nTileMinY,
1000
                     const gdal::TileMatrixSet::TileMatrix &oTM,
1001
                     const std::string &convention,
1002
                     const std::string &directory, const std::string &extension,
1003
                     const double *pdfDstNoData,
1004
                     const GDALColorTable *poColorTable)
1005
        : m_tileMinX(nTileMinX), m_tileMinY(nTileMinY),
162✔
1006
          m_eColorInterp(eColorInterp), m_oTM(oTM), m_convention(convention),
1007
          m_directory(directory), m_extension(extension),
1008
          m_hasNoData(pdfDstNoData != nullptr),
162✔
1009
          m_noData(pdfDstNoData ? *pdfDstNoData : 0),
162✔
1010
          m_poColorTable(poColorTable ? poColorTable->Clone() : nullptr)
324✔
1011
    {
1012
        poDS = poDSIn;
162✔
1013
        nBand = nBandIn;
162✔
1014
        nRasterXSize = nWidth;
162✔
1015
        nRasterYSize = nHeight;
162✔
1016
        nBlockXSize = nBlockXSizeIn;
162✔
1017
        nBlockYSize = nBlockYSizeIn;
162✔
1018
        eDataType = eDT;
162✔
1019
    }
162✔
1020

1021
    CPLErr IReadBlock(int nXBlock, int nYBlock, void *pData) override;
1022

1023
    GDALColorTable *GetColorTable() override
2,213✔
1024
    {
1025
        return m_poColorTable.get();
2,213✔
1026
    }
1027

1028
    GDALColorInterp GetColorInterpretation() override
418✔
1029
    {
1030
        return m_eColorInterp;
418✔
1031
    }
1032

1033
    double GetNoDataValue(int *pbHasNoData) override
1,773✔
1034
    {
1035
        if (pbHasNoData)
1,773✔
1036
            *pbHasNoData = m_hasNoData;
1,764✔
1037
        return m_noData;
1,773✔
1038
    }
1039
};
1040

1041
/************************************************************************/
1042
/*                         MosaicDataset                                */
1043
/************************************************************************/
1044

1045
// This class is to expose the tiles of a given level as a mosaic that
1046
// can be used as a source to generate the immediately below zoom level.
1047

1048
class MosaicDataset : public GDALDataset
1049
{
1050
    friend class MosaicRasterBand;
1051

1052
    const std::string m_directory;
1053
    const std::string m_extension;
1054
    const std::string m_format;
1055
    const std::vector<GDALColorInterp> m_aeColorInterp;
1056
    const gdal::TileMatrixSet::TileMatrix &m_oTM;
1057
    const OGRSpatialReference m_oSRS;
1058
    const int m_nTileMinX;
1059
    const int m_nTileMinY;
1060
    const int m_nTileMaxX;
1061
    const int m_nTileMaxY;
1062
    const std::string m_convention;
1063
    const GDALDataType m_eDT;
1064
    const double *const m_pdfDstNoData;
1065
    const std::vector<std::string> &m_metadata;
1066
    const GDALColorTable *const m_poCT;
1067

1068
    GDALGeoTransform m_gt{};
1069
    const int m_nMaxCacheTileSize;
1070
    lru11::Cache<std::string, std::shared_ptr<GDALDataset>> m_oCacheTile;
1071

1072
    CPL_DISALLOW_COPY_ASSIGN(MosaicDataset)
1073

1074
  public:
1075
    MosaicDataset(const std::string &directory, const std::string &extension,
52✔
1076
                  const std::string &format,
1077
                  const std::vector<GDALColorInterp> &aeColorInterp,
1078
                  const gdal::TileMatrixSet::TileMatrix &oTM,
1079
                  const OGRSpatialReference &oSRS, int nTileMinX, int nTileMinY,
1080
                  int nTileMaxX, int nTileMaxY, const std::string &convention,
1081
                  int nBandsIn, GDALDataType eDT, const double *pdfDstNoData,
1082
                  const std::vector<std::string> &metadata,
1083
                  const GDALColorTable *poCT, int maxCacheTileSize)
1084
        : m_directory(directory), m_extension(extension), m_format(format),
52✔
1085
          m_aeColorInterp(aeColorInterp), m_oTM(oTM), m_oSRS(oSRS),
1086
          m_nTileMinX(nTileMinX), m_nTileMinY(nTileMinY),
1087
          m_nTileMaxX(nTileMaxX), m_nTileMaxY(nTileMaxY),
1088
          m_convention(convention), m_eDT(eDT), m_pdfDstNoData(pdfDstNoData),
1089
          m_metadata(metadata), m_poCT(poCT),
1090
          m_nMaxCacheTileSize(maxCacheTileSize),
1091
          m_oCacheTile(/* max_size = */ maxCacheTileSize, /* elasticity = */ 0)
52✔
1092
    {
1093
        nRasterXSize = (nTileMaxX - nTileMinX + 1) * oTM.mTileWidth;
52✔
1094
        nRasterYSize = (nTileMaxY - nTileMinY + 1) * oTM.mTileHeight;
52✔
1095
        m_gt[0] = oTM.mTopLeftX + nTileMinX * oTM.mResX * oTM.mTileWidth;
52✔
1096
        m_gt[1] = oTM.mResX;
52✔
1097
        m_gt[2] = 0;
52✔
1098
        m_gt[3] = oTM.mTopLeftY - nTileMinY * oTM.mResY * oTM.mTileHeight;
52✔
1099
        m_gt[4] = 0;
52✔
1100
        m_gt[5] = -oTM.mResY;
52✔
1101
        for (int i = 1; i <= nBandsIn; ++i)
214✔
1102
        {
1103
            const GDALColorInterp eColorInterp =
1104
                (i <= static_cast<int>(m_aeColorInterp.size()))
162✔
1105
                    ? m_aeColorInterp[i - 1]
162✔
1106
                    : GCI_AlphaBand;
162✔
1107
            SetBand(i, new MosaicRasterBand(
162✔
1108
                           this, i, nRasterXSize, nRasterYSize, oTM.mTileWidth,
162✔
1109
                           oTM.mTileHeight, eDT, eColorInterp, nTileMinX,
162✔
1110
                           nTileMinY, oTM, convention, directory, extension,
1111
                           pdfDstNoData, poCT));
162✔
1112
        }
1113
        SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
52✔
1114
        const CPLStringList aosMD(metadata);
104✔
1115
        for (const auto [key, value] : cpl::IterateNameValue(aosMD))
69✔
1116
        {
1117
            SetMetadataItem(key, value);
17✔
1118
        }
1119
    }
52✔
1120

1121
    const OGRSpatialReference *GetSpatialRef() const override
113✔
1122
    {
1123
        return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
113✔
1124
    }
1125

1126
    CPLErr GetGeoTransform(GDALGeoTransform &gt) const override
123✔
1127
    {
1128
        gt = m_gt;
123✔
1129
        return CE_None;
123✔
1130
    }
1131

1132
    using GDALDataset::Clone;
1133

1134
    std::unique_ptr<MosaicDataset> Clone() const
8✔
1135
    {
1136
        return std::make_unique<MosaicDataset>(
1137
            m_directory, m_extension, m_format, m_aeColorInterp, m_oTM, m_oSRS,
8✔
1138
            m_nTileMinX, m_nTileMinY, m_nTileMaxX, m_nTileMaxY, m_convention,
8✔
1139
            nBands, m_eDT, m_pdfDstNoData, m_metadata, m_poCT,
8✔
1140
            m_nMaxCacheTileSize);
8✔
1141
    }
1142
};
1143

1144
/************************************************************************/
1145
/*                   MosaicRasterBand::IReadBlock()                     */
1146
/************************************************************************/
1147

1148
CPLErr MosaicRasterBand::IReadBlock(int nXBlock, int nYBlock, void *pData)
1,704✔
1149
{
1150
    auto poThisDS = cpl::down_cast<MosaicDataset *>(poDS);
1,704✔
1151
    std::string filename = CPLFormFilenameSafe(
1152
        m_directory.c_str(), CPLSPrintf("%d", m_tileMinX + nXBlock), nullptr);
3,407✔
1153
    const int iFileY = GetFileY(m_tileMinY + nYBlock, m_oTM, m_convention);
1,704✔
1154
    filename = CPLFormFilenameSafe(filename.c_str(), CPLSPrintf("%d", iFileY),
3,407✔
1155
                                   m_extension.c_str());
1,704✔
1156

1157
    std::shared_ptr<GDALDataset> poTileDS;
1,703✔
1158
    if (!poThisDS->m_oCacheTile.tryGet(filename, poTileDS))
1,704✔
1159
    {
1160
        const char *const apszAllowedDrivers[] = {poThisDS->m_format.c_str(),
488✔
1161
                                                  nullptr};
488✔
1162
        const char *const apszAllowedDriversForCOG[] = {"GTiff", "LIBERTIFF",
488✔
1163
                                                        nullptr};
1164
        // CPLDebugOnly("gdal_raster_tile", "Opening %s", filename.c_str());
1165
        poTileDS.reset(GDALDataset::Open(
489✔
1166
            filename.c_str(), GDAL_OF_RASTER | GDAL_OF_INTERNAL,
1167
            EQUAL(poThisDS->m_format.c_str(), "COG") ? apszAllowedDriversForCOG
488✔
1168
                                                     : apszAllowedDrivers));
1169
        if (!poTileDS)
486✔
1170
        {
1171
            VSIStatBufL sStat;
1172
            if (VSIStatL(filename.c_str(), &sStat) == 0)
6✔
1173
            {
1174
                CPLError(CE_Failure, CPLE_AppDefined,
1✔
1175
                         "File %s exists but cannot be opened with %s driver",
1176
                         filename.c_str(), poThisDS->m_format.c_str());
1177
                return CE_Failure;
1✔
1178
            }
1179
        }
1180
        poThisDS->m_oCacheTile.insert(filename, poTileDS);
487✔
1181
    }
1182
    if (!poTileDS || nBand > poTileDS->GetRasterCount())
1,701✔
1183
    {
1184
        memset(pData,
801✔
1185
               (poTileDS && (nBand == poTileDS->GetRasterCount() + 1)) ? 255
398✔
1186
                                                                       : 0,
1187
               static_cast<size_t>(nBlockXSize) * nBlockYSize *
401✔
1188
                   GDALGetDataTypeSizeBytes(eDataType));
401✔
1189
        return CE_None;
403✔
1190
    }
1191
    else
1192
    {
1193
        return poTileDS->GetRasterBand(nBand)->RasterIO(
1,299✔
1194
            GF_Read, 0, 0, nBlockXSize, nBlockYSize, pData, nBlockXSize,
1195
            nBlockYSize, eDataType, 0, 0, nullptr);
1,299✔
1196
    }
1197
}
1198

1199
}  // namespace
1200

1201
/************************************************************************/
1202
/*                         ApplySubstitutions()                         */
1203
/************************************************************************/
1204

1205
static void ApplySubstitutions(CPLString &s,
62✔
1206
                               const std::map<std::string, std::string> &substs)
1207
{
1208
    for (const auto &[key, value] : substs)
978✔
1209
    {
1210
        s.replaceAll("%(" + key + ")s", value);
916✔
1211
        s.replaceAll("%(" + key + ")d", value);
916✔
1212
        s.replaceAll("%(" + key + ")f", value);
916✔
1213
        s.replaceAll("${" + key + "}", value);
916✔
1214
    }
1215
}
62✔
1216

1217
/************************************************************************/
1218
/*                           GenerateLeaflet()                          */
1219
/************************************************************************/
1220

1221
static void GenerateLeaflet(const std::string &osDirectory,
14✔
1222
                            const std::string &osTitle, double dfSouthLat,
1223
                            double dfWestLon, double dfNorthLat,
1224
                            double dfEastLon, int nMinZoom, int nMaxZoom,
1225
                            int nTileSize, const std::string &osExtension,
1226
                            const std::string &osURL,
1227
                            const std::string &osCopyright, bool bXYZ)
1228
{
1229
    if (const char *pszTemplate = CPLFindFile("gdal", "leaflet_template.html"))
14✔
1230
    {
1231
        const std::string osFilename(pszTemplate);
28✔
1232
        std::map<std::string, std::string> substs;
28✔
1233

1234
        // For tests
1235
        const char *pszFmt =
1236
            atoi(CPLGetConfigOption("GDAL_RASTER_TILE_HTML_PREC", "17")) == 10
14✔
1237
                ? "%.10g"
1238
                : "%.17g";
14✔
1239

1240
        substs["double_quote_escaped_title"] =
28✔
1241
            CPLString(osTitle).replaceAll('"', "\\\"");
42✔
1242
        char *pszStr = CPLEscapeString(osTitle.c_str(), -1, CPLES_XML);
14✔
1243
        substs["xml_escaped_title"] = pszStr;
14✔
1244
        CPLFree(pszStr);
14✔
1245
        substs["south"] = CPLSPrintf(pszFmt, dfSouthLat);
14✔
1246
        substs["west"] = CPLSPrintf(pszFmt, dfWestLon);
14✔
1247
        substs["north"] = CPLSPrintf(pszFmt, dfNorthLat);
14✔
1248
        substs["east"] = CPLSPrintf(pszFmt, dfEastLon);
14✔
1249
        substs["centerlon"] = CPLSPrintf(pszFmt, (dfNorthLat + dfSouthLat) / 2);
14✔
1250
        substs["centerlat"] = CPLSPrintf(pszFmt, (dfWestLon + dfEastLon) / 2);
14✔
1251
        substs["minzoom"] = CPLSPrintf("%d", nMinZoom);
14✔
1252
        substs["maxzoom"] = CPLSPrintf("%d", nMaxZoom);
14✔
1253
        substs["beginzoom"] = CPLSPrintf("%d", nMaxZoom);
14✔
1254
        substs["tile_size"] = CPLSPrintf("%d", nTileSize);  // not used
14✔
1255
        substs["tileformat"] = osExtension;
14✔
1256
        substs["publishurl"] = osURL;  // not used
14✔
1257
        substs["copyright"] = CPLString(osCopyright).replaceAll('"', "\\\"");
14✔
1258
        substs["tms"] = bXYZ ? "0" : "1";
14✔
1259

1260
        GByte *pabyRet = nullptr;
14✔
1261
        CPL_IGNORE_RET_VAL(VSIIngestFile(nullptr, osFilename.c_str(), &pabyRet,
14✔
1262
                                         nullptr, 10 * 1024 * 1024));
1263
        if (pabyRet)
14✔
1264
        {
1265
            CPLString osHTML(reinterpret_cast<char *>(pabyRet));
28✔
1266
            CPLFree(pabyRet);
14✔
1267

1268
            ApplySubstitutions(osHTML, substs);
14✔
1269

1270
            VSILFILE *f = VSIFOpenL(CPLFormFilenameSafe(osDirectory.c_str(),
14✔
1271
                                                        "leaflet.html", nullptr)
1272
                                        .c_str(),
1273
                                    "wb");
1274
            if (f)
14✔
1275
            {
1276
                VSIFWriteL(osHTML.data(), 1, osHTML.size(), f);
14✔
1277
                VSIFCloseL(f);
14✔
1278
            }
1279
        }
1280
    }
1281
}
14✔
1282

1283
/************************************************************************/
1284
/*                           GenerateMapML()                            */
1285
/************************************************************************/
1286

1287
static void
1288
GenerateMapML(const std::string &osDirectory, const std::string &mapmlTemplate,
15✔
1289
              const std::string &osTitle, int nMinTileX, int nMinTileY,
1290
              int nMaxTileX, int nMaxTileY, int nMinZoom, int nMaxZoom,
1291
              const std::string &osExtension, const std::string &osURL,
1292
              const std::string &osCopyright, const gdal::TileMatrixSet &tms)
1293
{
1294
    if (const char *pszTemplate =
15✔
1295
            (mapmlTemplate.empty() ? CPLFindFile("gdal", "template_tiles.mapml")
15✔
1296
                                   : mapmlTemplate.c_str()))
15✔
1297
    {
1298
        const std::string osFilename(pszTemplate);
30✔
1299
        std::map<std::string, std::string> substs;
30✔
1300

1301
        if (tms.identifier() == "GoogleMapsCompatible")
15✔
1302
            substs["TILING_SCHEME"] = "OSMTILE";
14✔
1303
        else if (tms.identifier() == "WorldCRS84Quad")
1✔
1304
            substs["TILING_SCHEME"] = "WGS84";
1✔
1305
        else
1306
            substs["TILING_SCHEME"] = tms.identifier();
×
1307

1308
        substs["URL"] = osURL.empty() ? "./" : osURL;
15✔
1309
        substs["MINTILEX"] = CPLSPrintf("%d", nMinTileX);
15✔
1310
        substs["MINTILEY"] = CPLSPrintf("%d", nMinTileY);
15✔
1311
        substs["MAXTILEX"] = CPLSPrintf("%d", nMaxTileX);
15✔
1312
        substs["MAXTILEY"] = CPLSPrintf("%d", nMaxTileY);
15✔
1313
        substs["CURZOOM"] = CPLSPrintf("%d", nMaxZoom);
15✔
1314
        substs["MINZOOM"] = CPLSPrintf("%d", nMinZoom);
15✔
1315
        substs["MAXZOOM"] = CPLSPrintf("%d", nMaxZoom);
15✔
1316
        substs["TILEEXT"] = osExtension;
15✔
1317
        char *pszStr = CPLEscapeString(osTitle.c_str(), -1, CPLES_XML);
15✔
1318
        substs["TITLE"] = pszStr;
15✔
1319
        CPLFree(pszStr);
15✔
1320
        substs["COPYRIGHT"] = osCopyright;
15✔
1321

1322
        GByte *pabyRet = nullptr;
15✔
1323
        CPL_IGNORE_RET_VAL(VSIIngestFile(nullptr, osFilename.c_str(), &pabyRet,
15✔
1324
                                         nullptr, 10 * 1024 * 1024));
1325
        if (pabyRet)
15✔
1326
        {
1327
            CPLString osMAPML(reinterpret_cast<char *>(pabyRet));
30✔
1328
            CPLFree(pabyRet);
15✔
1329

1330
            ApplySubstitutions(osMAPML, substs);
15✔
1331

1332
            VSILFILE *f = VSIFOpenL(
15✔
1333
                CPLFormFilenameSafe(osDirectory.c_str(), "mapml.mapml", nullptr)
30✔
1334
                    .c_str(),
1335
                "wb");
1336
            if (f)
15✔
1337
            {
1338
                VSIFWriteL(osMAPML.data(), 1, osMAPML.size(), f);
15✔
1339
                VSIFCloseL(f);
15✔
1340
            }
1341
        }
1342
    }
1343
}
15✔
1344

1345
/************************************************************************/
1346
/*                           GenerateOpenLayers()                       */
1347
/************************************************************************/
1348

1349
static void GenerateOpenLayers(
22✔
1350
    const std::string &osDirectory, const std::string &osTitle, double dfMinX,
1351
    double dfMinY, double dfMaxX, double dfMaxY, int nMinZoom, int nMaxZoom,
1352
    int nTileSize, const std::string &osExtension, const std::string &osURL,
1353
    const std::string &osCopyright, const gdal::TileMatrixSet &tms,
1354
    bool bInvertAxisTMS, const OGRSpatialReference &oSRS_TMS, bool bXYZ)
1355
{
1356
    std::map<std::string, std::string> substs;
44✔
1357

1358
    // For tests
1359
    const char *pszFmt =
1360
        atoi(CPLGetConfigOption("GDAL_RASTER_TILE_HTML_PREC", "17")) == 10
22✔
1361
            ? "%.10g"
1362
            : "%.17g";
22✔
1363

1364
    char *pszStr = CPLEscapeString(osTitle.c_str(), -1, CPLES_XML);
22✔
1365
    substs["xml_escaped_title"] = pszStr;
22✔
1366
    CPLFree(pszStr);
22✔
1367
    substs["ominx"] = CPLSPrintf(pszFmt, dfMinX);
22✔
1368
    substs["ominy"] = CPLSPrintf(pszFmt, dfMinY);
22✔
1369
    substs["omaxx"] = CPLSPrintf(pszFmt, dfMaxX);
22✔
1370
    substs["omaxy"] = CPLSPrintf(pszFmt, dfMaxY);
22✔
1371
    substs["center_x"] = CPLSPrintf(pszFmt, (dfMinX + dfMaxX) / 2);
22✔
1372
    substs["center_y"] = CPLSPrintf(pszFmt, (dfMinY + dfMaxY) / 2);
22✔
1373
    substs["minzoom"] = CPLSPrintf("%d", nMinZoom);
22✔
1374
    substs["maxzoom"] = CPLSPrintf("%d", nMaxZoom);
22✔
1375
    substs["tile_size"] = CPLSPrintf("%d", nTileSize);
22✔
1376
    substs["tileformat"] = osExtension;
22✔
1377
    substs["publishurl"] = osURL;
22✔
1378
    substs["copyright"] = osCopyright;
22✔
1379
    substs["sign_y"] = bXYZ ? "" : "-";
22✔
1380

1381
    CPLString s(R"raw(<!DOCTYPE html>
1382
<html>
1383
<head>
1384
    <title>%(xml_escaped_title)s</title>
1385
    <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
1386
    <meta http-equiv='imagetoolbar' content='no'/>
1387
    <style type="text/css"> v\:* {behavior:url(#default#VML);}
1388
        html, body { overflow: hidden; padding: 0; height: 100%; width: 100%; font-family: 'Lucida Grande',Geneva,Arial,Verdana,sans-serif; }
1389
        body { margin: 10px; background: #fff; }
1390
        h1 { margin: 0; padding: 6px; border:0; font-size: 20pt; }
1391
        #header { height: 43px; padding: 0; background-color: #eee; border: 1px solid #888; }
1392
        #subheader { height: 12px; text-align: right; font-size: 10px; color: #555;}
1393
        #map { height: 90%; border: 1px solid #888; }
1394
    </style>
1395
    <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">
1396
    <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@main/dist/en/v7.0.0/legacy/ol.js"></script>
1397
    <script src="https://unpkg.com/ol-layerswitcher@4.1.1"></script>
1398
    <link rel="stylesheet" href="https://unpkg.com/ol-layerswitcher@4.1.1/src/ol-layerswitcher.css" />
1399
</head>
1400
<body>
1401
    <div id="header"><h1>%(xml_escaped_title)s</h1></div>
1402
    <div id="subheader">Generated by <a href="https://gdal.org/programs/gdal_raster_tile.html">gdal raster tile</a>&nbsp;&nbsp;&nbsp;&nbsp;</div>
1403
    <div id="map" class="map"></div>
1404
    <div id="mouse-position"></div>
1405
    <script type="text/javascript">
1406
        var mousePositionControl = new ol.control.MousePosition({
1407
            className: 'custom-mouse-position',
1408
            target: document.getElementById('mouse-position'),
1409
            undefinedHTML: '&nbsp;'
1410
        });
1411
        var map = new ol.Map({
1412
            controls: ol.control.defaults.defaults().extend([mousePositionControl]),
1413
            target: 'map',)raw");
44✔
1414

1415
    if (tms.identifier() == "GoogleMapsCompatible" ||
30✔
1416
        tms.identifier() == "WorldCRS84Quad")
8✔
1417
    {
1418
        s += R"raw(
16✔
1419
            layers: [
1420
                new ol.layer.Group({
1421
                        title: 'Base maps',
1422
                        layers: [
1423
                            new ol.layer.Tile({
1424
                                title: 'OpenStreetMap',
1425
                                type: 'base',
1426
                                visible: true,
1427
                                source: new ol.source.OSM()
1428
                            }),
1429
                        ]
1430
                }),)raw";
1431
    }
1432

1433
    if (tms.identifier() == "GoogleMapsCompatible")
22✔
1434
    {
1435
        s += R"raw(new ol.layer.Group({
14✔
1436
                    title: 'Overlay',
1437
                    layers: [
1438
                        new ol.layer.Tile({
1439
                            title: 'Overlay',
1440
                            // opacity: 0.7,
1441
                            extent: [%(ominx)f, %(ominy)f,%(omaxx)f, %(omaxy)f],
1442
                            source: new ol.source.XYZ({
1443
                                attributions: '%(copyright)s',
1444
                                minZoom: %(minzoom)d,
1445
                                maxZoom: %(maxzoom)d,
1446
                                url: './{z}/{x}/{%(sign_y)sy}.%(tileformat)s',
1447
                                tileSize: [%(tile_size)d, %(tile_size)d]
1448
                            })
1449
                        }),
1450
                    ]
1451
                }),)raw";
1452
    }
1453
    else if (tms.identifier() == "WorldCRS84Quad")
8✔
1454
    {
1455
        const double base_res = 180.0 / nTileSize;
2✔
1456
        std::string resolutions = "[";
4✔
1457
        for (int i = 0; i <= nMaxZoom; ++i)
4✔
1458
        {
1459
            if (i > 0)
2✔
1460
                resolutions += ",";
×
1461
            resolutions += CPLSPrintf(pszFmt, base_res / (1 << i));
2✔
1462
        }
1463
        resolutions += "]";
2✔
1464
        substs["resolutions"] = std::move(resolutions);
2✔
1465

1466
        if (bXYZ)
2✔
1467
        {
1468
            substs["origin"] = "[-180,90]";
1✔
1469
            substs["y_formula"] = "tileCoord[2]";
1✔
1470
        }
1471
        else
1472
        {
1473
            substs["origin"] = "[-180,-90]";
1✔
1474
            substs["y_formula"] = "- 1 - tileCoord[2]";
1✔
1475
        }
1476

1477
        s += R"raw(
2✔
1478
                new ol.layer.Group({
1479
                    title: 'Overlay',
1480
                    layers: [
1481
                        new ol.layer.Tile({
1482
                            title: 'Overlay',
1483
                            // opacity: 0.7,
1484
                            extent: [%(ominx)f, %(ominy)f,%(omaxx)f, %(omaxy)f],
1485
                            source: new ol.source.TileImage({
1486
                                attributions: '%(copyright)s',
1487
                                projection: 'EPSG:4326',
1488
                                minZoom: %(minzoom)d,
1489
                                maxZoom: %(maxzoom)d,
1490
                                tileGrid: new ol.tilegrid.TileGrid({
1491
                                    extent: [-180,-90,180,90],
1492
                                    origin: %(origin)s,
1493
                                    resolutions: %(resolutions)s,
1494
                                    tileSize: [%(tile_size)d, %(tile_size)d]
1495
                                }),
1496
                                tileUrlFunction: function(tileCoord) {
1497
                                    return ('./{z}/{x}/{y}.%(tileformat)s'
1498
                                        .replace('{z}', String(tileCoord[0]))
1499
                                        .replace('{x}', String(tileCoord[1]))
1500
                                        .replace('{y}', String(%(y_formula)s)));
1501
                                },
1502
                            })
1503
                        }),
1504
                    ]
1505
                }),)raw";
1506
    }
1507
    else
1508
    {
1509
        substs["maxres"] =
12✔
1510
            CPLSPrintf(pszFmt, tms.tileMatrixList()[nMinZoom].mResX);
12✔
1511
        std::string resolutions = "[";
12✔
1512
        for (int i = 0; i <= nMaxZoom; ++i)
16✔
1513
        {
1514
            if (i > 0)
10✔
1515
                resolutions += ",";
4✔
1516
            resolutions += CPLSPrintf(pszFmt, tms.tileMatrixList()[i].mResX);
10✔
1517
        }
1518
        resolutions += "]";
6✔
1519
        substs["resolutions"] = std::move(resolutions);
6✔
1520

1521
        std::string matrixsizes = "[";
12✔
1522
        for (int i = 0; i <= nMaxZoom; ++i)
16✔
1523
        {
1524
            if (i > 0)
10✔
1525
                matrixsizes += ",";
4✔
1526
            matrixsizes +=
1527
                CPLSPrintf("[%d,%d]", tms.tileMatrixList()[i].mMatrixWidth,
10✔
1528
                           tms.tileMatrixList()[i].mMatrixHeight);
20✔
1529
        }
1530
        matrixsizes += "]";
6✔
1531
        substs["matrixsizes"] = std::move(matrixsizes);
6✔
1532

1533
        double dfTopLeftX = tms.tileMatrixList()[0].mTopLeftX;
6✔
1534
        double dfTopLeftY = tms.tileMatrixList()[0].mTopLeftY;
6✔
1535
        if (bInvertAxisTMS)
6✔
1536
            std::swap(dfTopLeftX, dfTopLeftY);
×
1537

1538
        if (bXYZ)
6✔
1539
        {
1540
            substs["origin"] =
10✔
1541
                CPLSPrintf("[%.17g,%.17g]", dfTopLeftX, dfTopLeftY);
10✔
1542
            substs["y_formula"] = "tileCoord[2]";
5✔
1543
        }
1544
        else
1545
        {
1546
            substs["origin"] = CPLSPrintf(
2✔
1547
                "[%.17g,%.17g]", dfTopLeftX,
1548
                dfTopLeftY - tms.tileMatrixList()[0].mResY *
1✔
1549
                                 tms.tileMatrixList()[0].mTileHeight);
3✔
1550
            substs["y_formula"] = "- 1 - tileCoord[2]";
1✔
1551
        }
1552

1553
        substs["tilegrid_extent"] =
12✔
1554
            CPLSPrintf("[%.17g,%.17g,%.17g,%.17g]", dfTopLeftX,
1555
                       dfTopLeftY - tms.tileMatrixList()[0].mMatrixHeight *
6✔
1556
                                        tms.tileMatrixList()[0].mResY *
6✔
1557
                                        tms.tileMatrixList()[0].mTileHeight,
6✔
1558
                       dfTopLeftX + tms.tileMatrixList()[0].mMatrixWidth *
6✔
1559
                                        tms.tileMatrixList()[0].mResX *
6✔
1560
                                        tms.tileMatrixList()[0].mTileWidth,
6✔
1561
                       dfTopLeftY);
24✔
1562

1563
        s += R"raw(
6✔
1564
            layers: [
1565
                new ol.layer.Group({
1566
                    title: 'Overlay',
1567
                    layers: [
1568
                        new ol.layer.Tile({
1569
                            title: 'Overlay',
1570
                            // opacity: 0.7,
1571
                            extent: [%(ominx)f, %(ominy)f,%(omaxx)f, %(omaxy)f],
1572
                            source: new ol.source.TileImage({
1573
                                attributions: '%(copyright)s',
1574
                                minZoom: %(minzoom)d,
1575
                                maxZoom: %(maxzoom)d,
1576
                                tileGrid: new ol.tilegrid.TileGrid({
1577
                                    extent: %(tilegrid_extent)s,
1578
                                    origin: %(origin)s,
1579
                                    resolutions: %(resolutions)s,
1580
                                    sizes: %(matrixsizes)s,
1581
                                    tileSize: [%(tile_size)d, %(tile_size)d]
1582
                                }),
1583
                                tileUrlFunction: function(tileCoord) {
1584
                                    return ('./{z}/{x}/{y}.%(tileformat)s'
1585
                                        .replace('{z}', String(tileCoord[0]))
1586
                                        .replace('{x}', String(tileCoord[1]))
1587
                                        .replace('{y}', String(%(y_formula)s)));
1588
                                },
1589
                            })
1590
                        }),
1591
                    ]
1592
                }),)raw";
1593
    }
1594

1595
    s += R"raw(
22✔
1596
            ],
1597
            view: new ol.View({
1598
                center: [%(center_x)f, %(center_y)f],)raw";
1599

1600
    if (tms.identifier() == "GoogleMapsCompatible" ||
30✔
1601
        tms.identifier() == "WorldCRS84Quad")
8✔
1602
    {
1603
        substs["view_zoom"] = substs["minzoom"];
16✔
1604
        if (tms.identifier() == "WorldCRS84Quad")
16✔
1605
        {
1606
            substs["view_zoom"] = CPLSPrintf("%d", nMinZoom + 1);
2✔
1607
        }
1608

1609
        s += R"raw(
16✔
1610
                zoom: %(view_zoom)d,)raw";
1611
    }
1612
    else
1613
    {
1614
        s += R"raw(
6✔
1615
                resolution: %(maxres)f,)raw";
1616
    }
1617

1618
    if (tms.identifier() == "WorldCRS84Quad")
22✔
1619
    {
1620
        s += R"raw(
2✔
1621
                projection: 'EPSG:4326',)raw";
1622
    }
1623
    else if (!oSRS_TMS.IsEmpty() && tms.identifier() != "GoogleMapsCompatible")
20✔
1624
    {
1625
        const char *pszAuthName = oSRS_TMS.GetAuthorityName(nullptr);
5✔
1626
        const char *pszAuthCode = oSRS_TMS.GetAuthorityCode(nullptr);
5✔
1627
        if (pszAuthName && pszAuthCode && EQUAL(pszAuthName, "EPSG"))
5✔
1628
        {
1629
            substs["epsg_code"] = pszAuthCode;
3✔
1630
            if (oSRS_TMS.IsGeographic())
3✔
1631
            {
1632
                substs["units"] = "deg";
1✔
1633
            }
1634
            else
1635
            {
1636
                const char *pszUnits = "";
2✔
1637
                if (oSRS_TMS.GetLinearUnits(&pszUnits) == 1.0)
2✔
1638
                    substs["units"] = "m";
2✔
1639
                else
1640
                    substs["units"] = pszUnits;
×
1641
            }
1642
            s += R"raw(
3✔
1643
                projection: new ol.proj.Projection({code: 'EPSG:%(epsg_code)s', units:'%(units)s'}),)raw";
1644
        }
1645
    }
1646

1647
    s += R"raw(
22✔
1648
            })
1649
        });)raw";
1650

1651
    if (tms.identifier() == "GoogleMapsCompatible" ||
30✔
1652
        tms.identifier() == "WorldCRS84Quad")
8✔
1653
    {
1654
        s += R"raw(
16✔
1655
        map.addControl(new ol.control.LayerSwitcher());)raw";
1656
    }
1657

1658
    s += R"raw(
22✔
1659
    </script>
1660
</body>
1661
</html>)raw";
1662

1663
    ApplySubstitutions(s, substs);
22✔
1664

1665
    VSILFILE *f = VSIFOpenL(
22✔
1666
        CPLFormFilenameSafe(osDirectory.c_str(), "openlayers.html", nullptr)
44✔
1667
            .c_str(),
1668
        "wb");
1669
    if (f)
22✔
1670
    {
1671
        VSIFWriteL(s.data(), 1, s.size(), f);
22✔
1672
        VSIFCloseL(f);
22✔
1673
    }
1674
}
22✔
1675

1676
/************************************************************************/
1677
/*                           GetTileBoundingBox()                       */
1678
/************************************************************************/
1679

1680
static void GetTileBoundingBox(int nTileX, int nTileY, int nTileZ,
6✔
1681
                               const gdal::TileMatrixSet *poTMS,
1682
                               bool bInvertAxisTMS,
1683
                               OGRCoordinateTransformation *poCTToWGS84,
1684
                               double &dfTLX, double &dfTLY, double &dfTRX,
1685
                               double &dfTRY, double &dfLLX, double &dfLLY,
1686
                               double &dfLRX, double &dfLRY)
1687
{
1688
    gdal::TileMatrixSet::TileMatrix tileMatrix =
1689
        poTMS->tileMatrixList()[nTileZ];
12✔
1690
    if (bInvertAxisTMS)
6✔
1691
        std::swap(tileMatrix.mTopLeftX, tileMatrix.mTopLeftY);
×
1692

1693
    dfTLX = tileMatrix.mTopLeftX +
6✔
1694
            nTileX * tileMatrix.mResX * tileMatrix.mTileWidth;
6✔
1695
    dfTLY = tileMatrix.mTopLeftY -
6✔
1696
            nTileY * tileMatrix.mResY * tileMatrix.mTileHeight;
6✔
1697
    poCTToWGS84->Transform(1, &dfTLX, &dfTLY);
6✔
1698

1699
    dfTRX = tileMatrix.mTopLeftX +
6✔
1700
            (nTileX + 1) * tileMatrix.mResX * tileMatrix.mTileWidth;
6✔
1701
    dfTRY = tileMatrix.mTopLeftY -
6✔
1702
            nTileY * tileMatrix.mResY * tileMatrix.mTileHeight;
6✔
1703
    poCTToWGS84->Transform(1, &dfTRX, &dfTRY);
6✔
1704

1705
    dfLLX = tileMatrix.mTopLeftX +
6✔
1706
            nTileX * tileMatrix.mResX * tileMatrix.mTileWidth;
6✔
1707
    dfLLY = tileMatrix.mTopLeftY -
6✔
1708
            (nTileY + 1) * tileMatrix.mResY * tileMatrix.mTileHeight;
6✔
1709
    poCTToWGS84->Transform(1, &dfLLX, &dfLLY);
6✔
1710

1711
    dfLRX = tileMatrix.mTopLeftX +
6✔
1712
            (nTileX + 1) * tileMatrix.mResX * tileMatrix.mTileWidth;
6✔
1713
    dfLRY = tileMatrix.mTopLeftY -
6✔
1714
            (nTileY + 1) * tileMatrix.mResY * tileMatrix.mTileHeight;
6✔
1715
    poCTToWGS84->Transform(1, &dfLRX, &dfLRY);
6✔
1716
}
6✔
1717

1718
/************************************************************************/
1719
/*                           GenerateKML()                              */
1720
/************************************************************************/
1721

1722
namespace
1723
{
1724
struct TileCoordinates
1725
{
1726
    int nTileX = 0;
1727
    int nTileY = 0;
1728
    int nTileZ = 0;
1729
};
1730
}  // namespace
1731

1732
static void GenerateKML(const std::string &osDirectory,
5✔
1733
                        const std::string &osTitle, int nTileX, int nTileY,
1734
                        int nTileZ, int nTileSize,
1735
                        const std::string &osExtension,
1736
                        const std::string &osURL,
1737
                        const gdal::TileMatrixSet *poTMS, bool bInvertAxisTMS,
1738
                        const std::string &convention,
1739
                        OGRCoordinateTransformation *poCTToWGS84,
1740
                        const std::vector<TileCoordinates> &children)
1741
{
1742
    std::map<std::string, std::string> substs;
10✔
1743

1744
    const bool bIsTileKML = nTileX >= 0;
5✔
1745

1746
    // For tests
1747
    const char *pszFmt =
1748
        atoi(CPLGetConfigOption("GDAL_RASTER_TILE_KML_PREC", "14")) == 10
5✔
1749
            ? "%.10f"
1750
            : "%.14f";
5✔
1751

1752
    substs["tx"] = CPLSPrintf("%d", nTileX);
5✔
1753
    substs["tz"] = CPLSPrintf("%d", nTileZ);
5✔
1754
    substs["tileformat"] = osExtension;
5✔
1755
    substs["minlodpixels"] = CPLSPrintf("%d", nTileSize / 2);
5✔
1756
    substs["maxlodpixels"] =
10✔
1757
        children.empty() ? "-1" : CPLSPrintf("%d", nTileSize * 8);
10✔
1758

1759
    double dfTLX = 0;
5✔
1760
    double dfTLY = 0;
5✔
1761
    double dfTRX = 0;
5✔
1762
    double dfTRY = 0;
5✔
1763
    double dfLLX = 0;
5✔
1764
    double dfLLY = 0;
5✔
1765
    double dfLRX = 0;
5✔
1766
    double dfLRY = 0;
5✔
1767

1768
    int nFileY = -1;
5✔
1769
    if (!bIsTileKML)
5✔
1770
    {
1771
        char *pszStr = CPLEscapeString(osTitle.c_str(), -1, CPLES_XML);
2✔
1772
        substs["xml_escaped_title"] = pszStr;
2✔
1773
        CPLFree(pszStr);
2✔
1774
    }
1775
    else
1776
    {
1777
        nFileY = GetFileY(nTileY, poTMS->tileMatrixList()[nTileZ], convention);
3✔
1778
        substs["realtiley"] = CPLSPrintf("%d", nFileY);
3✔
1779
        substs["xml_escaped_title"] =
6✔
1780
            CPLSPrintf("%d/%d/%d.kml", nTileZ, nTileX, nFileY);
6✔
1781

1782
        GetTileBoundingBox(nTileX, nTileY, nTileZ, poTMS, bInvertAxisTMS,
3✔
1783
                           poCTToWGS84, dfTLX, dfTLY, dfTRX, dfTRY, dfLLX,
1784
                           dfLLY, dfLRX, dfLRY);
1785
    }
1786

1787
    substs["drawOrder"] = CPLSPrintf("%d", nTileX == 0  ? 2 * nTileZ + 1
10✔
1788
                                           : nTileX > 0 ? 2 * nTileZ
4✔
1789
                                                        : 0);
14✔
1790

1791
    substs["url"] = osURL.empty() && bIsTileKML ? "../../" : "";
5✔
1792

1793
    const bool bIsRectangle =
5✔
1794
        (dfTLX == dfLLX && dfTRX == dfLRX && dfTLY == dfTRY && dfLLY == dfLRY);
5✔
1795
    const bool bUseGXNamespace = bIsTileKML && !bIsRectangle;
5✔
1796

1797
    substs["xmlns_gx"] = bUseGXNamespace
10✔
1798
                             ? " xmlns:gx=\"http://www.google.com/kml/ext/2.2\""
1799
                             : "";
10✔
1800

1801
    CPLString s(R"raw(<?xml version="1.0" encoding="utf-8"?>
1802
<kml xmlns="http://www.opengis.net/kml/2.2"%(xmlns_gx)s>
1803
  <Document>
1804
    <name>%(xml_escaped_title)s</name>
1805
    <description></description>
1806
    <Style>
1807
      <ListStyle id="hideChildren">
1808
        <listItemType>checkHideChildren</listItemType>
1809
      </ListStyle>
1810
    </Style>
1811
)raw");
10✔
1812
    ApplySubstitutions(s, substs);
5✔
1813

1814
    if (bIsTileKML)
5✔
1815
    {
1816
        CPLString s2(R"raw(    <Region>
1817
      <LatLonAltBox>
1818
        <north>%(north)f</north>
1819
        <south>%(south)f</south>
1820
        <east>%(east)f</east>
1821
        <west>%(west)f</west>
1822
      </LatLonAltBox>
1823
      <Lod>
1824
        <minLodPixels>%(minlodpixels)d</minLodPixels>
1825
        <maxLodPixels>%(maxlodpixels)d</maxLodPixels>
1826
      </Lod>
1827
    </Region>
1828
    <GroundOverlay>
1829
      <drawOrder>%(drawOrder)d</drawOrder>
1830
      <Icon>
1831
        <href>%(realtiley)d.%(tileformat)s</href>
1832
      </Icon>
1833
      <LatLonBox>
1834
        <north>%(north)f</north>
1835
        <south>%(south)f</south>
1836
        <east>%(east)f</east>
1837
        <west>%(west)f</west>
1838
      </LatLonBox>
1839
)raw");
6✔
1840

1841
        if (!bIsRectangle)
3✔
1842
        {
1843
            s2 +=
1844
                R"raw(      <gx:LatLonQuad><coordinates>%(LLX)f,%(LLY)f %(LRX)f,%(LRY)f %(TRX)f,%(TRY)f %(TLX)f,%(TLY)f</coordinates></gx:LatLonQuad>
1✔
1845
)raw";
1846
        }
1847

1848
        s2 += R"raw(    </GroundOverlay>
3✔
1849
)raw";
1850
        substs["north"] = CPLSPrintf(pszFmt, std::max(dfTLY, dfTRY));
3✔
1851
        substs["south"] = CPLSPrintf(pszFmt, std::min(dfLLY, dfLRY));
3✔
1852
        substs["east"] = CPLSPrintf(pszFmt, std::max(dfTRX, dfLRX));
3✔
1853
        substs["west"] = CPLSPrintf(pszFmt, std::min(dfLLX, dfTLX));
3✔
1854

1855
        if (!bIsRectangle)
3✔
1856
        {
1857
            substs["TLX"] = CPLSPrintf(pszFmt, dfTLX);
1✔
1858
            substs["TLY"] = CPLSPrintf(pszFmt, dfTLY);
1✔
1859
            substs["TRX"] = CPLSPrintf(pszFmt, dfTRX);
1✔
1860
            substs["TRY"] = CPLSPrintf(pszFmt, dfTRY);
1✔
1861
            substs["LRX"] = CPLSPrintf(pszFmt, dfLRX);
1✔
1862
            substs["LRY"] = CPLSPrintf(pszFmt, dfLRY);
1✔
1863
            substs["LLX"] = CPLSPrintf(pszFmt, dfLLX);
1✔
1864
            substs["LLY"] = CPLSPrintf(pszFmt, dfLLY);
1✔
1865
        }
1866

1867
        ApplySubstitutions(s2, substs);
3✔
1868
        s += s2;
3✔
1869
    }
1870

1871
    for (const auto &child : children)
8✔
1872
    {
1873
        substs["tx"] = CPLSPrintf("%d", child.nTileX);
3✔
1874
        substs["tz"] = CPLSPrintf("%d", child.nTileZ);
3✔
1875
        substs["realtiley"] = CPLSPrintf(
6✔
1876
            "%d", GetFileY(child.nTileY, poTMS->tileMatrixList()[child.nTileZ],
3✔
1877
                           convention));
6✔
1878

1879
        GetTileBoundingBox(child.nTileX, child.nTileY, child.nTileZ, poTMS,
3✔
1880
                           bInvertAxisTMS, poCTToWGS84, dfTLX, dfTLY, dfTRX,
1881
                           dfTRY, dfLLX, dfLLY, dfLRX, dfLRY);
1882

1883
        CPLString s2(R"raw(    <NetworkLink>
1884
      <name>%(tz)d/%(tx)d/%(realtiley)d.%(tileformat)s</name>
1885
      <Region>
1886
        <LatLonAltBox>
1887
          <north>%(north)f</north>
1888
          <south>%(south)f</south>
1889
          <east>%(east)f</east>
1890
          <west>%(west)f</west>
1891
        </LatLonAltBox>
1892
        <Lod>
1893
          <minLodPixels>%(minlodpixels)d</minLodPixels>
1894
          <maxLodPixels>-1</maxLodPixels>
1895
        </Lod>
1896
      </Region>
1897
      <Link>
1898
        <href>%(url)s%(tz)d/%(tx)d/%(realtiley)d.kml</href>
1899
        <viewRefreshMode>onRegion</viewRefreshMode>
1900
        <viewFormat/>
1901
      </Link>
1902
    </NetworkLink>
1903
)raw");
6✔
1904
        substs["north"] = CPLSPrintf(pszFmt, std::max(dfTLY, dfTRY));
3✔
1905
        substs["south"] = CPLSPrintf(pszFmt, std::min(dfLLY, dfLRY));
3✔
1906
        substs["east"] = CPLSPrintf(pszFmt, std::max(dfTRX, dfLRX));
3✔
1907
        substs["west"] = CPLSPrintf(pszFmt, std::min(dfLLX, dfTLX));
3✔
1908
        ApplySubstitutions(s2, substs);
3✔
1909
        s += s2;
3✔
1910
    }
1911

1912
    s += R"raw(</Document>
5✔
1913
</kml>)raw";
1914

1915
    std::string osFilename(osDirectory);
10✔
1916
    if (!bIsTileKML)
5✔
1917
    {
1918
        osFilename =
1919
            CPLFormFilenameSafe(osFilename.c_str(), "doc.kml", nullptr);
2✔
1920
    }
1921
    else
1922
    {
1923
        osFilename = CPLFormFilenameSafe(osFilename.c_str(),
6✔
1924
                                         CPLSPrintf("%d", nTileZ), nullptr);
3✔
1925
        osFilename = CPLFormFilenameSafe(osFilename.c_str(),
6✔
1926
                                         CPLSPrintf("%d", nTileX), nullptr);
3✔
1927
        osFilename = CPLFormFilenameSafe(osFilename.c_str(),
6✔
1928
                                         CPLSPrintf("%d.kml", nFileY), nullptr);
3✔
1929
    }
1930

1931
    VSILFILE *f = VSIFOpenL(osFilename.c_str(), "wb");
5✔
1932
    if (f)
5✔
1933
    {
1934
        VSIFWriteL(s.data(), 1, s.size(), f);
5✔
1935
        VSIFCloseL(f);
5✔
1936
    }
1937
}
5✔
1938

1939
namespace
1940
{
1941

1942
/************************************************************************/
1943
/*                            ResourceManager                           */
1944
/************************************************************************/
1945

1946
// Generic cache managing resources
1947
template <class Resource> class ResourceManager /* non final */
1948
{
1949
  public:
1950
    virtual ~ResourceManager() = default;
109✔
1951

1952
    std::unique_ptr<Resource> AcquireResources()
12✔
1953
    {
1954
        std::lock_guard oLock(m_oMutex);
24✔
1955
        if (!m_oResources.empty())
12✔
1956
        {
1957
            auto ret = std::move(m_oResources.back());
×
1958
            m_oResources.pop_back();
×
1959
            return ret;
×
1960
        }
1961

1962
        return CreateResources();
12✔
1963
    }
1964

1965
    void ReleaseResources(std::unique_ptr<Resource> resources)
12✔
1966
    {
1967
        std::lock_guard oLock(m_oMutex);
24✔
1968
        m_oResources.push_back(std::move(resources));
12✔
1969
    }
12✔
1970

1971
    void SetError()
×
1972
    {
1973
        std::lock_guard oLock(m_oMutex);
×
1974
        if (m_errorMsg.empty())
×
1975
            m_errorMsg = CPLGetLastErrorMsg();
×
1976
    }
×
1977

1978
    const std::string &GetErrorMsg() const
3✔
1979
    {
1980
        std::lock_guard oLock(m_oMutex);
3✔
1981
        return m_errorMsg;
6✔
1982
    }
1983

1984
  protected:
1985
    virtual std::unique_ptr<Resource> CreateResources() = 0;
1986

1987
  private:
1988
    mutable std::mutex m_oMutex{};
1989
    std::vector<std::unique_ptr<Resource>> m_oResources{};
1990
    std::string m_errorMsg{};
1991
};
1992

1993
/************************************************************************/
1994
/*                         PerThreadMaxZoomResources                    */
1995
/************************************************************************/
1996

1997
// Per-thread resources for generation of tiles at full resolution
1998
struct PerThreadMaxZoomResources
1999
{
2000
    struct GDALDatasetReleaser
2001
    {
2002
        void operator()(GDALDataset *poDS)
4✔
2003
        {
2004
            if (poDS)
4✔
2005
                poDS->ReleaseRef();
4✔
2006
        }
4✔
2007
    };
2008

2009
    std::unique_ptr<GDALDataset, GDALDatasetReleaser> poSrcDS{};
2010
    std::vector<GByte> dstBuffer{};
2011
    std::unique_ptr<FakeMaxZoomDataset> poFakeMaxZoomDS{};
2012
    std::unique_ptr<void, decltype(&GDALDestroyTransformer)> poTransformer{
2013
        nullptr, GDALDestroyTransformer};
2014
    std::unique_ptr<GDALWarpOperation> poWO{};
2015
};
2016

2017
/************************************************************************/
2018
/*                      PerThreadMaxZoomResourceManager                 */
2019
/************************************************************************/
2020

2021
// Manage a cache of PerThreadMaxZoomResources instances
2022
class PerThreadMaxZoomResourceManager final
2023
    : public ResourceManager<PerThreadMaxZoomResources>
2024
{
2025
  public:
2026
    PerThreadMaxZoomResourceManager(GDALDataset *poSrcDS,
65✔
2027
                                    const GDALWarpOptions *psWO,
2028
                                    void *pTransformerArg,
2029
                                    const FakeMaxZoomDataset &oFakeMaxZoomDS,
2030
                                    size_t nBufferSize)
2031
        : m_poSrcDS(poSrcDS), m_psWOSource(psWO),
65✔
2032
          m_pTransformerArg(pTransformerArg), m_oFakeMaxZoomDS(oFakeMaxZoomDS),
2033
          m_nBufferSize(nBufferSize)
65✔
2034
    {
2035
    }
65✔
2036

2037
  protected:
2038
    std::unique_ptr<PerThreadMaxZoomResources> CreateResources() override
4✔
2039
    {
2040
        auto ret = std::make_unique<PerThreadMaxZoomResources>();
8✔
2041

2042
        ret->poSrcDS.reset(GDALGetThreadSafeDataset(m_poSrcDS, GDAL_OF_RASTER));
4✔
2043
        if (!ret->poSrcDS)
4✔
2044
            return nullptr;
×
2045

2046
        try
2047
        {
2048
            ret->dstBuffer.resize(m_nBufferSize);
4✔
2049
        }
2050
        catch (const std::exception &)
×
2051
        {
2052
            CPLError(CE_Failure, CPLE_OutOfMemory,
×
2053
                     "Out of memory allocating temporary buffer");
2054
            return nullptr;
×
2055
        }
2056

2057
        ret->poFakeMaxZoomDS = m_oFakeMaxZoomDS.Clone(ret->dstBuffer);
4✔
2058

2059
        ret->poTransformer.reset(GDALCloneTransformer(m_pTransformerArg));
4✔
2060
        if (!ret->poTransformer)
4✔
2061
            return nullptr;
×
2062

2063
        auto psWO =
2064
            std::unique_ptr<GDALWarpOptions, decltype(&GDALDestroyWarpOptions)>(
2065
                GDALCloneWarpOptions(m_psWOSource), GDALDestroyWarpOptions);
8✔
2066
        if (!psWO)
4✔
2067
            return nullptr;
×
2068

2069
        psWO->hSrcDS = GDALDataset::ToHandle(ret->poSrcDS.get());
4✔
2070
        psWO->hDstDS = GDALDataset::ToHandle(ret->poFakeMaxZoomDS.get());
4✔
2071
        psWO->pTransformerArg = ret->poTransformer.get();
4✔
2072
        psWO->pfnTransformer = m_psWOSource->pfnTransformer;
4✔
2073

2074
        ret->poWO = std::make_unique<GDALWarpOperation>();
4✔
2075
        if (ret->poWO->Initialize(psWO.get()) != CE_None)
4✔
2076
            return nullptr;
×
2077

2078
        return ret;
4✔
2079
    }
2080

2081
  private:
2082
    GDALDataset *const m_poSrcDS;
2083
    const GDALWarpOptions *const m_psWOSource;
2084
    void *const m_pTransformerArg;
2085
    const FakeMaxZoomDataset &m_oFakeMaxZoomDS;
2086
    const size_t m_nBufferSize;
2087

2088
    CPL_DISALLOW_COPY_ASSIGN(PerThreadMaxZoomResourceManager)
2089
};
2090

2091
/************************************************************************/
2092
/*                       PerThreadLowerZoomResources                    */
2093
/************************************************************************/
2094

2095
// Per-thread resources for generation of tiles at zoom level < max
2096
struct PerThreadLowerZoomResources
2097
{
2098
    std::unique_ptr<GDALDataset> poSrcDS{};
2099
};
2100

2101
/************************************************************************/
2102
/*                   PerThreadLowerZoomResourceManager                  */
2103
/************************************************************************/
2104

2105
// Manage a cache of PerThreadLowerZoomResources instances
2106
class PerThreadLowerZoomResourceManager final
2107
    : public ResourceManager<PerThreadLowerZoomResources>
2108
{
2109
  public:
2110
    explicit PerThreadLowerZoomResourceManager(const MosaicDataset &oSrcDS)
44✔
2111
        : m_oSrcDS(oSrcDS)
44✔
2112
    {
2113
    }
44✔
2114

2115
  protected:
2116
    std::unique_ptr<PerThreadLowerZoomResources> CreateResources() override
8✔
2117
    {
2118
        auto ret = std::make_unique<PerThreadLowerZoomResources>();
8✔
2119
        ret->poSrcDS = m_oSrcDS.Clone();
8✔
2120
        return ret;
8✔
2121
    }
2122

2123
  private:
2124
    const MosaicDataset &m_oSrcDS;
2125
};
2126

2127
}  // namespace
2128

2129
/************************************************************************/
2130
/*            GDALRasterTileAlgorithm::ValidateOutputFormat()           */
2131
/************************************************************************/
2132

2133
bool GDALRasterTileAlgorithm::ValidateOutputFormat(GDALDataType eSrcDT) const
100✔
2134
{
2135
    if (m_outputFormat == "PNG")
100✔
2136
    {
2137
        if (m_poSrcDS->GetRasterCount() > 4)
76✔
2138
        {
2139
            ReportError(CE_Failure, CPLE_NotSupported,
1✔
2140
                        "Only up to 4 bands supported for PNG.");
2141
            return false;
1✔
2142
        }
2143
        if (eSrcDT != GDT_Byte && eSrcDT != GDT_UInt16)
75✔
2144
        {
2145
            ReportError(CE_Failure, CPLE_NotSupported,
1✔
2146
                        "Only Byte and UInt16 data types supported for PNG.");
2147
            return false;
1✔
2148
        }
2149
    }
2150
    else if (m_outputFormat == "JPEG")
24✔
2151
    {
2152
        if (m_poSrcDS->GetRasterCount() > 4)
5✔
2153
        {
2154
            ReportError(
1✔
2155
                CE_Failure, CPLE_NotSupported,
2156
                "Only up to 4 bands supported for JPEG (with alpha ignored).");
2157
            return false;
1✔
2158
        }
2159
        const bool bUInt16Supported =
2160
            strstr(m_poDstDriver->GetMetadataItem(GDAL_DMD_CREATIONDATATYPES),
4✔
2161
                   "UInt16");
4✔
2162
        if (eSrcDT != GDT_Byte && !(eSrcDT == GDT_UInt16 && bUInt16Supported))
4✔
2163
        {
2164
            ReportError(
1✔
2165
                CE_Failure, CPLE_NotSupported,
2166
                bUInt16Supported
2167
                    ? "Only Byte and UInt16 data types supported for JPEG."
2168
                    : "Only Byte data type supported for JPEG.");
2169
            return false;
1✔
2170
        }
2171
        if (eSrcDT == GDT_UInt16)
3✔
2172
        {
2173
            if (const char *pszNBITS =
3✔
2174
                    m_poSrcDS->GetRasterBand(1)->GetMetadataItem(
6✔
2175
                        "NBITS", "IMAGE_STRUCTURE"))
3✔
2176
            {
2177
                if (atoi(pszNBITS) > 12)
1✔
2178
                {
2179
                    ReportError(CE_Failure, CPLE_NotSupported,
1✔
2180
                                "JPEG output only supported up to 12 bits");
2181
                    return false;
1✔
2182
                }
2183
            }
2184
            else
2185
            {
2186
                double adfMinMax[2] = {0, 0};
2✔
2187
                m_poSrcDS->GetRasterBand(1)->ComputeRasterMinMax(
2✔
2188
                    /* bApproxOK = */ true, adfMinMax);
2✔
2189
                if (adfMinMax[1] >= (1 << 12))
2✔
2190
                {
2191
                    ReportError(CE_Failure, CPLE_NotSupported,
1✔
2192
                                "JPEG output only supported up to 12 bits");
2193
                    return false;
1✔
2194
                }
2195
            }
2196
        }
2197
    }
2198
    else if (m_outputFormat == "WEBP")
19✔
2199
    {
2200
        if (m_poSrcDS->GetRasterCount() != 3 &&
3✔
2201
            m_poSrcDS->GetRasterCount() != 4)
1✔
2202
        {
2203
            ReportError(CE_Failure, CPLE_NotSupported,
1✔
2204
                        "Only 3 or 4 bands supported for WEBP.");
2205
            return false;
1✔
2206
        }
2207
        if (eSrcDT != GDT_Byte)
1✔
2208
        {
2209
            ReportError(CE_Failure, CPLE_NotSupported,
1✔
2210
                        "Only Byte data type supported for WEBP.");
2211
            return false;
1✔
2212
        }
2213
    }
2214
    return true;
92✔
2215
}
2216

2217
/************************************************************************/
2218
/*            GDALRasterTileAlgorithm::ComputeJobChunkSize()            */
2219
/************************************************************************/
2220

2221
// Given a number of tiles in the Y dimension being nTilesPerCol and
2222
// in the X dimension being nTilesPerRow, compute the (upper bound of)
2223
// number of jobs needed to be nYOuterIterations x nXOuterIterations,
2224
// with each job processing in average dfTilesYPerJob x dfTilesXPerJob
2225
// tiles.
2226
/* static */
2227
void GDALRasterTileAlgorithm::ComputeJobChunkSize(
7✔
2228
    int nMaxJobCount, int nTilesPerCol, int nTilesPerRow,
2229
    double &dfTilesYPerJob, int &nYOuterIterations, double &dfTilesXPerJob,
2230
    int &nXOuterIterations)
2231
{
2232
    CPLAssert(nMaxJobCount >= 1);
7✔
2233
    dfTilesYPerJob = static_cast<double>(nTilesPerCol) / nMaxJobCount;
7✔
2234
    nYOuterIterations = dfTilesYPerJob >= 1 ? nMaxJobCount : 1;
7✔
2235

2236
    dfTilesXPerJob = dfTilesYPerJob >= 1
14✔
2237
                         ? nTilesPerRow
7✔
2238
                         : static_cast<double>(nTilesPerRow) / nMaxJobCount;
2✔
2239
    nXOuterIterations = dfTilesYPerJob >= 1 ? 1 : nMaxJobCount;
7✔
2240

2241
    if (dfTilesYPerJob < 1 && dfTilesXPerJob < 1 &&
7✔
2242
        nTilesPerCol <= nMaxJobCount / nTilesPerRow)
2✔
2243
    {
2244
        dfTilesYPerJob = 1;
2✔
2245
        dfTilesXPerJob = 1;
2✔
2246
        nYOuterIterations = nTilesPerCol;
2✔
2247
        nXOuterIterations = nTilesPerRow;
2✔
2248
    }
2249
}
7✔
2250

2251
/************************************************************************/
2252
/*               GDALRasterTileAlgorithm::AddArgToArgv()                */
2253
/************************************************************************/
2254

2255
bool GDALRasterTileAlgorithm::AddArgToArgv(const GDALAlgorithmArg *arg,
48✔
2256
                                           CPLStringList &aosArgv) const
2257
{
2258
    aosArgv.push_back(CPLSPrintf("--%s", arg->GetName().c_str()));
48✔
2259
    if (arg->GetType() == GAAT_STRING)
48✔
2260
    {
2261
        aosArgv.push_back(arg->Get<std::string>().c_str());
14✔
2262
    }
2263
    else if (arg->GetType() == GAAT_STRING_LIST)
34✔
2264
    {
2265
        bool bFirst = true;
12✔
2266
        for (const std::string &s : arg->Get<std::vector<std::string>>())
24✔
2267
        {
2268
            if (!bFirst)
12✔
2269
            {
2270
                aosArgv.push_back(CPLSPrintf("--%s", arg->GetName().c_str()));
×
2271
            }
2272
            bFirst = false;
12✔
2273
            aosArgv.push_back(s.c_str());
12✔
2274
        }
2275
    }
2276
    else if (arg->GetType() == GAAT_REAL)
22✔
2277
    {
2278
        aosArgv.push_back(CPLSPrintf("%.17g", arg->Get<double>()));
×
2279
    }
2280
    else if (arg->GetType() == GAAT_INTEGER)
22✔
2281
    {
2282
        aosArgv.push_back(CPLSPrintf("%d", arg->Get<int>()));
22✔
2283
    }
2284
    else if (arg->GetType() != GAAT_BOOLEAN)
×
2285
    {
2286
        ReportError(CE_Failure, CPLE_AppDefined,
×
2287
                    "Bug: argument of type %d not handled "
2288
                    "by gdal raster tile!",
2289
                    static_cast<int>(arg->GetType()));
×
2290
        return false;
×
2291
    }
2292
    return true;
48✔
2293
}
2294

2295
/************************************************************************/
2296
/*            GDALRasterTileAlgorithm::IsCompatibleOfSpawn()            */
2297
/************************************************************************/
2298

2299
bool GDALRasterTileAlgorithm::IsCompatibleOfSpawn(const char *&pszErrorMsg)
5✔
2300
{
2301
    pszErrorMsg = "";
5✔
2302
    auto poSrcDriver = m_poSrcDS->GetDriver();
5✔
2303
    if (m_poSrcDS->GetDescription()[0] == 0 || !poSrcDriver ||
9✔
2304
        EQUAL(poSrcDriver->GetDescription(), "MEM"))
4✔
2305
    {
2306
        pszErrorMsg = "Unnamed or memory dataset sources are not supported "
1✔
2307
                      "with spawn parallelization method";
2308
        return false;
1✔
2309
    }
2310
    if (cpl::starts_with(m_outputDirectory, "/vsimem/"))
4✔
2311
    {
2312
        pszErrorMsg = "/vsimem/ output directory not supported with spawn "
1✔
2313
                      "parallelization method";
2314
        return false;
1✔
2315
    }
2316

2317
    if (m_osGDALPath.empty())
3✔
2318
        m_osGDALPath = GDALGetGDALPath();
3✔
2319
    return !(m_osGDALPath.empty());
3✔
2320
}
2321

2322
/************************************************************************/
2323
/*                      GetProgressForChildProcesses()                  */
2324
/************************************************************************/
2325

2326
static void GetProgressForChildProcesses(
4✔
2327
    bool &bRet, std::vector<CPLSpawnedProcess *> &ahSpawnedProcesses,
2328
    std::vector<uint64_t> &anRemainingTilesForProcess, uint64_t &nCurTile,
2329
    uint64_t nTotalTiles, GDALProgressFunc pfnProgress, void *pProgressData)
2330
{
2331
    std::vector<unsigned int> anProgressState(ahSpawnedProcesses.size(), 0);
8✔
2332

2333
    while (bRet)
91✔
2334
    {
2335
        size_t iProcess = 0;
91✔
2336
        size_t nFinished = 0;
91✔
2337
        for (CPLSpawnedProcess *hSpawnedProcess : ahSpawnedProcesses)
447✔
2338
        {
2339
            char ch = 0;
356✔
2340
            if (anRemainingTilesForProcess[iProcess] == 0 ||
700✔
2341
                !CPLPipeRead(CPLSpawnAsyncGetInputFileHandle(hSpawnedProcess),
344✔
2342
                             &ch, 1))
2343
            {
2344
                ++nFinished;
14✔
2345
            }
2346
            else if (ch == PROGRESS_MARKER[anProgressState[iProcess]])
342✔
2347
            {
2348
                ++anProgressState[iProcess];
258✔
2349
                if (anProgressState[iProcess] == sizeof(PROGRESS_MARKER))
258✔
2350
                {
2351
                    anProgressState[iProcess] = 0;
86✔
2352
                    --anRemainingTilesForProcess[iProcess];
86✔
2353
                    ++nCurTile;
86✔
2354
                    bRet &= (!pfnProgress ||
170✔
2355
                             pfnProgress(static_cast<double>(nCurTile) /
84✔
2356
                                             static_cast<double>(nTotalTiles),
84✔
2357
                                         "", pProgressData));
86✔
2358
                }
2359
            }
2360
            else
2361
            {
2362
                CPLErrorOnce(
84✔
2363
                    CE_Warning, CPLE_AppDefined,
2364
                    "Spurious character detected on stdout of child process");
2365
                anProgressState[iProcess] = 0;
84✔
2366
                if (ch == PROGRESS_MARKER[anProgressState[iProcess]])
84✔
2367
                {
2368
                    ++anProgressState[iProcess];
84✔
2369
                }
2370
            }
2371
            ++iProcess;
356✔
2372
        }
2373
        if (!bRet || nFinished == ahSpawnedProcesses.size())
91✔
2374
            break;
4✔
2375
    }
2376
}
4✔
2377

2378
/************************************************************************/
2379
/*                       WaitForSpawnedProcesses()                      */
2380
/************************************************************************/
2381

2382
void GDALRasterTileAlgorithm::WaitForSpawnedProcesses(
4✔
2383
    bool &bRet, const std::vector<std::string> &asCommandLines,
2384
    std::vector<CPLSpawnedProcess *> &ahSpawnedProcesses) const
2385
{
2386
    size_t iProcess = 0;
4✔
2387
    for (CPLSpawnedProcess *hSpawnedProcess : ahSpawnedProcesses)
18✔
2388
    {
2389
        CPLSpawnAsyncCloseInputFileHandle(hSpawnedProcess);
14✔
2390

2391
        char ch = 0;
14✔
2392
        std::string errorMsg;
14✔
2393
        while (CPLPipeRead(CPLSpawnAsyncGetErrorFileHandle(hSpawnedProcess),
483✔
2394
                           &ch, 1))
483✔
2395
        {
2396
            if (ch == '\n')
469✔
2397
            {
2398
                if (!errorMsg.empty())
6✔
2399
                {
2400
                    if (cpl::starts_with(errorMsg, "ERROR "))
6✔
2401
                    {
2402
                        const auto nPos = errorMsg.find(": ");
6✔
2403
                        if (nPos != std::string::npos)
6✔
2404
                            errorMsg = errorMsg.substr(nPos + 1);
6✔
2405
                        ReportError(CE_Failure, CPLE_AppDefined, "%s",
6✔
2406
                                    errorMsg.c_str());
2407
                    }
2408
                    else
2409
                    {
2410
                        std::string osComp = "GDAL";
×
2411
                        const auto nPos = errorMsg.find(": ");
×
2412
                        if (nPos != std::string::npos)
×
2413
                        {
2414
                            osComp = errorMsg.substr(0, nPos);
×
2415
                            errorMsg = errorMsg.substr(nPos + 1);
×
2416
                        }
2417
                        CPLDebug(osComp.c_str(), "%s", errorMsg.c_str());
×
2418
                    }
2419
                    errorMsg.clear();
6✔
2420
                }
2421
            }
2422
            else
2423
            {
2424
                errorMsg += ch;
463✔
2425
            }
2426
        }
2427
        CPLSpawnAsyncCloseErrorFileHandle(hSpawnedProcess);
14✔
2428

2429
        if (CPLSpawnAsyncFinish(hSpawnedProcess, /* bWait = */ true,
14✔
2430
                                /* bKill = */ false) != 0)
14✔
2431
        {
2432
            bRet = false;
2✔
2433
            ReportError(CE_Failure, CPLE_AppDefined,
2✔
2434
                        "Child process '%s' failed",
2435
                        asCommandLines[iProcess].c_str());
2✔
2436
        }
2437
        ++iProcess;
14✔
2438
    }
2439
}
4✔
2440

2441
/************************************************************************/
2442
/*               GDALRasterTileAlgorithm::GetMaxChildCount()            */
2443
/**********************************f**************************************/
2444

2445
int GDALRasterTileAlgorithm::GetMaxChildCount(int nMaxJobCount) const
4✔
2446
{
2447
#ifndef _WIN32
2448
    // Limit the number of jobs compared to how many file descriptors we have
2449
    // left
2450
    const int remainingFileDescriptorCount =
2451
        CPLGetRemainingFileDescriptorCount();
4✔
2452
    constexpr int SOME_MARGIN = 3;
4✔
2453
    constexpr int FD_PER_CHILD = 3; /* stdin, stdout and stderr */
4✔
2454
    if (FD_PER_CHILD * nMaxJobCount + SOME_MARGIN >
4✔
2455
        remainingFileDescriptorCount)
2456
    {
2457
        nMaxJobCount = std::max(
×
2458
            1, (remainingFileDescriptorCount - SOME_MARGIN) / FD_PER_CHILD);
×
2459
        ReportError(
×
2460
            CE_Warning, CPLE_AppDefined,
2461
            "Limiting the number of child workers to %d (instead of %d), "
2462
            "because there are not enough file descriptors left (%d)",
2463
            nMaxJobCount, m_numThreads, remainingFileDescriptorCount);
×
2464
    }
2465
#endif
2466
    return nMaxJobCount;
4✔
2467
}
2468

2469
/************************************************************************/
2470
/*                           SendConfigOptions()                        */
2471
/************************************************************************/
2472

2473
static void SendConfigOptions(CPLSpawnedProcess *hSpawnedProcess, bool &bRet)
14✔
2474
{
2475
    // Send most config options through pipe, to avoid leaking
2476
    // secrets when listing processes
2477
    auto handle = CPLSpawnAsyncGetOutputFileHandle(hSpawnedProcess);
14✔
2478
    for (auto pfnFunc : {&CPLGetConfigOptions, &CPLGetThreadLocalConfigOptions})
42✔
2479
    {
2480
        CPLStringList aosConfigOptions((*pfnFunc)());
56✔
2481
        for (const char *pszNameValue : aosConfigOptions)
78✔
2482
        {
2483
            if (!STARTS_WITH(pszNameValue, "GDAL_CACHEMAX") &&
50✔
2484
                !STARTS_WITH(pszNameValue, "GDAL_NUM_THREADS"))
50✔
2485
            {
2486
                constexpr const char *CONFIG_MARKER = "--config\n";
50✔
2487
                bRet &= CPL_TO_BOOL(
50✔
2488
                    CPLPipeWrite(handle, CONFIG_MARKER,
2489
                                 static_cast<int>(strlen(CONFIG_MARKER))));
50✔
2490
                char *pszEscaped = CPLEscapeString(pszNameValue, -1, CPLES_URL);
50✔
2491
                bRet &= CPL_TO_BOOL(CPLPipeWrite(
50✔
2492
                    handle, pszEscaped, static_cast<int>(strlen(pszEscaped))));
50✔
2493
                CPLFree(pszEscaped);
50✔
2494
                bRet &= CPL_TO_BOOL(CPLPipeWrite(handle, "\n", 1));
50✔
2495
            }
2496
        }
2497
    }
2498
    constexpr const char *END_MARKER = "END\n";
14✔
2499
    bRet &= CPL_TO_BOOL(
14✔
2500
        CPLPipeWrite(handle, END_MARKER, static_cast<int>(strlen(END_MARKER))));
14✔
2501
    CPLSpawnAsyncCloseOutputFileHandle(hSpawnedProcess);
14✔
2502
}
14✔
2503

2504
/************************************************************************/
2505
/*          GDALRasterTileAlgorithm::GenerateBaseTilesSpawnMethod()     */
2506
/************************************************************************/
2507

2508
bool GDALRasterTileAlgorithm::GenerateBaseTilesSpawnMethod(
2✔
2509
    int nBaseTilesPerCol, int nBaseTilesPerRow, int nMinTileX, int nMinTileY,
2510
    int nMaxTileX, int nMaxTileY, uint64_t nTotalTiles, uint64_t nBaseTiles,
2511
    GDALProgressFunc pfnProgress, void *pProgressData)
2512
{
2513
    CPLAssert(!m_osGDALPath.empty());
2✔
2514

2515
    // Config option for test only
2516
    const int nThreshold = std::max(
2517
        1, atoi(CPLGetConfigOption("GDAL_THRESHOLD_TILES_PER_JOB",
2✔
2518
                                   CPLSPrintf("%d", THRESHOLD_TILES_PER_JOB))));
2✔
2519
    const int nMaxJobCount = GetMaxChildCount(
2✔
2520
        std::max(1, static_cast<int>(std::min<uint64_t>(
×
2521
                        m_numThreads, nBaseTiles / nThreshold))));
2✔
2522

2523
    double dfTilesYPerJob;
2524
    int nYOuterIterations;
2525
    double dfTilesXPerJob;
2526
    int nXOuterIterations;
2527
    ComputeJobChunkSize(nMaxJobCount, nBaseTilesPerCol, nBaseTilesPerRow,
2✔
2528
                        dfTilesYPerJob, nYOuterIterations, dfTilesXPerJob,
2529
                        nXOuterIterations);
2530

2531
    CPLDebugOnly("gdal_raster_tile",
2✔
2532
                 "nYOuterIterations=%d, dfTilesYPerJob=%g, "
2533
                 "nXOuterIterations=%d, dfTilesXPerJob=%g",
2534
                 nYOuterIterations, dfTilesYPerJob, nXOuterIterations,
2535
                 dfTilesXPerJob);
2536

2537
    std::vector<std::string> asCommandLines;
4✔
2538
    std::vector<CPLSpawnedProcess *> ahSpawnedProcesses;
4✔
2539
    std::vector<uint64_t> anRemainingTilesForProcess;
4✔
2540

2541
    const uint64_t nCacheMaxPerProcess = GDALGetCacheMax64() / nMaxJobCount;
2✔
2542

2543
    int nLastYEndIncluded = nMinTileY - 1;
2✔
2544

2545
    bool bRet = true;
2✔
2546
    for (int iYOuterIter = 0; bRet && iYOuterIter < nYOuterIterations &&
8✔
2547
                              nLastYEndIncluded < nMaxTileY;
6✔
2548
         ++iYOuterIter)
2549
    {
2550
        const int iYStart = nLastYEndIncluded + 1;
6✔
2551
        const int iYEndIncluded =
2552
            iYOuterIter + 1 == nYOuterIterations
6✔
2553
                ? nMaxTileY
10✔
2554
                : std::max(
2555
                      iYStart,
2556
                      static_cast<int>(std::floor(
10✔
2557
                          nMinTileY + (iYOuterIter + 1) * dfTilesYPerJob - 1)));
4✔
2558

2559
        nLastYEndIncluded = iYEndIncluded;
6✔
2560

2561
        int nLastXEndIncluded = nMinTileX - 1;
6✔
2562
        for (int iXOuterIter = 0; bRet && iXOuterIter < nXOuterIterations &&
12✔
2563
                                  nLastXEndIncluded < nMaxTileX;
6✔
2564
             ++iXOuterIter)
2565
        {
2566
            const int iXStart = nLastXEndIncluded + 1;
6✔
2567
            const int iXEndIncluded =
2568
                iXOuterIter + 1 == nXOuterIterations
6✔
2569
                    ? nMaxTileX
6✔
2570
                    : std::max(iXStart,
2571
                               static_cast<int>(std::floor(
6✔
2572
                                   nMinTileX +
×
2573
                                   (iXOuterIter + 1) * dfTilesXPerJob - 1)));
×
2574

2575
            nLastXEndIncluded = iXEndIncluded;
6✔
2576

2577
            anRemainingTilesForProcess.push_back(
6✔
2578
                static_cast<uint64_t>(iYEndIncluded - iYStart + 1) *
×
2579
                (iXEndIncluded - iXStart + 1));
6✔
2580

2581
            CPLStringList aosArgv;
6✔
2582
            aosArgv.push_back(m_osGDALPath.c_str());
6✔
2583
            aosArgv.push_back("raster");
6✔
2584
            aosArgv.push_back("tile");
6✔
2585
            aosArgv.push_back("--config-options-in-stdin");
6✔
2586
            aosArgv.push_back("--config");
6✔
2587
            aosArgv.push_back("GDAL_NUM_THREADS=1");
6✔
2588
            aosArgv.push_back("--config");
6✔
2589
            aosArgv.push_back(
6✔
2590
                CPLSPrintf("GDAL_CACHEMAX=%" PRIu64, nCacheMaxPerProcess));
2591
            aosArgv.push_back("--num-threads");
6✔
2592
            aosArgv.push_back("1");
6✔
2593
            aosArgv.push_back("--min-x");
6✔
2594
            aosArgv.push_back(CPLSPrintf("%d", iXStart));
6✔
2595
            aosArgv.push_back("--max-x");
6✔
2596
            aosArgv.push_back(CPLSPrintf("%d", iXEndIncluded));
6✔
2597
            aosArgv.push_back("--min-y");
6✔
2598
            aosArgv.push_back(CPLSPrintf("%d", iYStart));
6✔
2599
            aosArgv.push_back("--max-y");
6✔
2600
            aosArgv.push_back(CPLSPrintf("%d", iYEndIncluded));
6✔
2601
            aosArgv.push_back("--webviewer");
6✔
2602
            aosArgv.push_back("none");
6✔
2603
            aosArgv.push_back("--progress-forked");
6✔
2604
            aosArgv.push_back("--input");
6✔
2605
            aosArgv.push_back(m_poSrcDS->GetDescription());
6✔
2606
            for (const auto &arg : GetArgs())
300✔
2607
            {
2608
                if (arg->IsExplicitlySet() && arg->GetName() != "min-x" &&
362✔
2609
                    arg->GetName() != "min-y" && arg->GetName() != "max-x" &&
102✔
2610
                    arg->GetName() != "max-y" && arg->GetName() != "min-zoom" &&
98✔
2611
                    arg->GetName() != "progress" &&
60✔
2612
                    arg->GetName() != "progress-forked" &&
60✔
2613
                    arg->GetName() != "input" &&
54✔
2614
                    arg->GetName() != "num-threads" &&
46✔
2615
                    arg->GetName() != "webviewer" &&
350✔
2616
                    arg->GetName() != "parallel-method")
22✔
2617
                {
2618
                    if (!AddArgToArgv(arg.get(), aosArgv))
16✔
2619
                        return false;
×
2620
                }
2621
            }
2622

2623
            std::string cmdLine;
6✔
2624
            for (const char *arg : aosArgv)
176✔
2625
            {
2626
                if (!cmdLine.empty())
170✔
2627
                    cmdLine += ' ';
164✔
2628
                cmdLine += arg;
170✔
2629
            }
2630
            CPLDebugOnly("gdal_raster_tile", "Spawning %s", cmdLine.c_str());
6✔
2631
            asCommandLines.push_back(std::move(cmdLine));
6✔
2632

2633
            CPLSpawnedProcess *hSpawnedProcess =
2634
                CPLSpawnAsync(nullptr, aosArgv.List(),
6✔
2635
                              /* bCreateInputPipe = */ true,
2636
                              /* bCreateOutputPipe = */ true,
2637
                              /* bCreateErrorPipe = */ true, nullptr);
6✔
2638
            if (!hSpawnedProcess)
6✔
2639
            {
2640
                ReportError(CE_Failure, CPLE_AppDefined,
×
2641
                            "Spawning child gdal process '%s' failed",
2642
                            asCommandLines.back().c_str());
×
2643
                bRet = false;
×
2644
                break;
×
2645
            }
2646

2647
            CPLDebugOnly("gdal_raster_tile",
6✔
2648
                         "Job for y in [%d,%d] and x in [%d,%d], "
2649
                         "run by process %" PRIu64,
2650
                         iYStart, iYEndIncluded, iXStart, iXEndIncluded,
2651
                         static_cast<uint64_t>(
2652
                             CPLSpawnAsyncGetChildProcessId(hSpawnedProcess)));
2653

2654
            ahSpawnedProcesses.push_back(hSpawnedProcess);
6✔
2655

2656
            SendConfigOptions(hSpawnedProcess, bRet);
6✔
2657
            if (!bRet)
6✔
2658
            {
2659
                ReportError(CE_Failure, CPLE_AppDefined,
×
2660
                            "Could not transmit config options to child gdal "
2661
                            "process '%s'",
2662
                            asCommandLines.back().c_str());
×
2663
                break;
×
2664
            }
2665
        }
2666
    }
2667

2668
    uint64_t nCurTile = 0;
2✔
2669
    GetProgressForChildProcesses(bRet, ahSpawnedProcesses,
2✔
2670
                                 anRemainingTilesForProcess, nCurTile,
2671
                                 nTotalTiles, pfnProgress, pProgressData);
2672

2673
    WaitForSpawnedProcesses(bRet, asCommandLines, ahSpawnedProcesses);
2✔
2674

2675
    if (bRet && nCurTile != nBaseTiles)
2✔
2676
    {
2677
        bRet = false;
×
2678
        ReportError(CE_Failure, CPLE_AppDefined,
×
2679
                    "Not all tiles at max zoom level have been "
2680
                    "generated. Got %" PRIu64 ", expected %" PRIu64,
2681
                    nCurTile, nBaseTiles);
2682
    }
2683

2684
    return bRet;
2✔
2685
}
2686

2687
/************************************************************************/
2688
/*      GDALRasterTileAlgorithm::GenerateOverviewTilesSpawnMethod()     */
2689
/************************************************************************/
2690

2691
bool GDALRasterTileAlgorithm::GenerateOverviewTilesSpawnMethod(
2✔
2692
    int iZ, int nOvrMinTileX, int nOvrMinTileY, int nOvrMaxTileX,
2693
    int nOvrMaxTileY, std::atomic<uint64_t> &nCurTile, uint64_t nTotalTiles,
2694
    GDALProgressFunc pfnProgress, void *pProgressData)
2695
{
2696
    CPLAssert(!m_osGDALPath.empty());
2✔
2697

2698
    const int nOvrTilesPerCol = nOvrMaxTileY - nOvrMinTileY + 1;
2✔
2699
    const int nOvrTilesPerRow = nOvrMaxTileX - nOvrMinTileX + 1;
2✔
2700
    const uint64_t nExpectedOvrTileCount =
2✔
2701
        static_cast<uint64_t>(nOvrTilesPerCol) * nOvrTilesPerRow;
2✔
2702

2703
    // Config option for test only
2704
    const int nThreshold = std::max(
2705
        1, atoi(CPLGetConfigOption("GDAL_THRESHOLD_TILES_PER_JOB",
2✔
2706
                                   CPLSPrintf("%d", THRESHOLD_TILES_PER_JOB))));
2✔
2707
    const int nMaxJobCount = GetMaxChildCount(
2✔
2708
        std::max(1, static_cast<int>(std::min<uint64_t>(
×
2709
                        m_numThreads, nExpectedOvrTileCount / nThreshold))));
2✔
2710

2711
    double dfTilesYPerJob;
2712
    int nYOuterIterations;
2713
    double dfTilesXPerJob;
2714
    int nXOuterIterations;
2715
    ComputeJobChunkSize(nMaxJobCount, nOvrTilesPerCol, nOvrTilesPerRow,
2✔
2716
                        dfTilesYPerJob, nYOuterIterations, dfTilesXPerJob,
2717
                        nXOuterIterations);
2718

2719
    CPLDebugOnly("gdal_raster_tile",
2✔
2720
                 "z=%d, nYOuterIterations=%d, dfTilesYPerJob=%g, "
2721
                 "nXOuterIterations=%d, dfTilesXPerJob=%g",
2722
                 iZ, nYOuterIterations, dfTilesYPerJob, nXOuterIterations,
2723
                 dfTilesXPerJob);
2724

2725
    std::vector<std::string> asCommandLines;
4✔
2726
    std::vector<CPLSpawnedProcess *> ahSpawnedProcesses;
4✔
2727
    std::vector<uint64_t> anRemainingTilesForProcess;
4✔
2728

2729
    const uint64_t nCacheMaxPerProcess = GDALGetCacheMax64() / nMaxJobCount;
2✔
2730

2731
    int nLastYEndIncluded = nOvrMinTileY - 1;
2✔
2732
    bool bRet = true;
2✔
2733
    for (int iYOuterIter = 0; bRet && iYOuterIter < nYOuterIterations &&
8✔
2734
                              nLastYEndIncluded < nOvrMaxTileY;
6✔
2735
         ++iYOuterIter)
2736
    {
2737
        const int iYStart = nLastYEndIncluded + 1;
6✔
2738
        const int iYEndIncluded =
2739
            iYOuterIter + 1 == nYOuterIterations
6✔
2740
                ? nOvrMaxTileY
10✔
2741
                : std::max(iYStart,
2742
                           static_cast<int>(std::floor(
10✔
2743
                               nOvrMinTileY +
4✔
2744
                               (iYOuterIter + 1) * dfTilesYPerJob - 1)));
4✔
2745

2746
        nLastYEndIncluded = iYEndIncluded;
6✔
2747

2748
        int nLastXEndIncluded = nOvrMinTileX - 1;
6✔
2749
        for (int iXOuterIter = 0; bRet && iXOuterIter < nXOuterIterations &&
14✔
2750
                                  nLastXEndIncluded < nOvrMaxTileX;
8✔
2751
             ++iXOuterIter)
2752
        {
2753
            const int iXStart = nLastXEndIncluded + 1;
8✔
2754
            const int iXEndIncluded =
2755
                iXOuterIter + 1 == nXOuterIterations
8✔
2756
                    ? nOvrMaxTileX
10✔
2757
                    : std::max(iXStart,
2758
                               static_cast<int>(std::floor(
10✔
2759
                                   nOvrMinTileX +
2✔
2760
                                   (iXOuterIter + 1) * dfTilesXPerJob - 1)));
2✔
2761

2762
            nLastXEndIncluded = iXEndIncluded;
8✔
2763

2764
            anRemainingTilesForProcess.push_back(
8✔
2765
                static_cast<uint64_t>(iYEndIncluded - iYStart + 1) *
×
2766
                (iXEndIncluded - iXStart + 1));
8✔
2767

2768
            CPLStringList aosArgv;
8✔
2769
            aosArgv.push_back(m_osGDALPath.c_str());
8✔
2770
            aosArgv.push_back("raster");
8✔
2771
            aosArgv.push_back("tile");
8✔
2772
            aosArgv.push_back("--config-options-in-stdin");
8✔
2773
            aosArgv.push_back("--config");
8✔
2774
            aosArgv.push_back("GDAL_NUM_THREADS=1");
8✔
2775
            aosArgv.push_back("--config");
8✔
2776
            aosArgv.push_back(
8✔
2777
                CPLSPrintf("GDAL_CACHEMAX=%" PRIu64, nCacheMaxPerProcess));
2778
            aosArgv.push_back("--num-threads");
8✔
2779
            aosArgv.push_back("1");
8✔
2780
            aosArgv.push_back("--ovr-zoom-level");
8✔
2781
            aosArgv.push_back(CPLSPrintf("%d", iZ));
8✔
2782
            aosArgv.push_back("--ovr-min-x");
8✔
2783
            aosArgv.push_back(CPLSPrintf("%d", iXStart));
8✔
2784
            aosArgv.push_back("--ovr-max-x");
8✔
2785
            aosArgv.push_back(CPLSPrintf("%d", iXEndIncluded));
8✔
2786
            aosArgv.push_back("--ovr-min-y");
8✔
2787
            aosArgv.push_back(CPLSPrintf("%d", iYStart));
8✔
2788
            aosArgv.push_back("--ovr-max-y");
8✔
2789
            aosArgv.push_back(CPLSPrintf("%d", iYEndIncluded));
8✔
2790
            aosArgv.push_back("--webviewer");
8✔
2791
            aosArgv.push_back("none");
8✔
2792
            aosArgv.push_back("--progress-forked");
8✔
2793
            aosArgv.push_back("--input");
8✔
2794
            aosArgv.push_back(m_dataset.GetName().c_str());
8✔
2795
            for (const auto &arg : GetArgs())
400✔
2796
            {
2797
                if (arg->IsExplicitlySet() && arg->GetName() != "progress" &&
488✔
2798
                    arg->GetName() != "progress-forked" &&
96✔
2799
                    arg->GetName() != "input" &&
88✔
2800
                    arg->GetName() != "num-threads" &&
80✔
2801
                    arg->GetName() != "webviewer" &&
480✔
2802
                    arg->GetName() != "parallel-method")
40✔
2803
                {
2804
                    if (!AddArgToArgv(arg.get(), aosArgv))
32✔
2805
                        return false;
×
2806
                }
2807
            }
2808

2809
            std::string cmdLine;
8✔
2810
            for (const char *arg : aosArgv)
272✔
2811
            {
2812
                if (!cmdLine.empty())
264✔
2813
                    cmdLine += ' ';
256✔
2814
                cmdLine += arg;
264✔
2815
            }
2816
            CPLDebugOnly("gdal_raster_tile", "Spawning %s", cmdLine.c_str());
8✔
2817
            asCommandLines.push_back(std::move(cmdLine));
8✔
2818

2819
            CPLSpawnedProcess *hSpawnedProcess =
2820
                CPLSpawnAsync(nullptr, aosArgv.List(),
8✔
2821
                              /* bCreateInputPipe = */ true,
2822
                              /* bCreateOutputPipe = */ true,
2823
                              /* bCreateErrorPipe = */ true, nullptr);
8✔
2824
            if (!hSpawnedProcess)
8✔
2825
            {
2826
                ReportError(CE_Failure, CPLE_AppDefined,
×
2827
                            "Spawning child gdal process '%s' failed",
2828
                            asCommandLines.back().c_str());
×
2829
                bRet = false;
×
2830
                break;
×
2831
            }
2832

2833
            CPLDebugOnly("gdal_raster_tile",
8✔
2834
                         "Job for z = %d, y in [%d,%d] and x in [%d,%d], "
2835
                         "run by process %" PRIu64,
2836
                         iZ, iYStart, iYEndIncluded, iXStart, iXEndIncluded,
2837
                         static_cast<uint64_t>(
2838
                             CPLSpawnAsyncGetChildProcessId(hSpawnedProcess)));
2839

2840
            ahSpawnedProcesses.push_back(hSpawnedProcess);
8✔
2841

2842
            SendConfigOptions(hSpawnedProcess, bRet);
8✔
2843
            if (!bRet)
8✔
2844
            {
2845
                ReportError(CE_Failure, CPLE_AppDefined,
×
2846
                            "Could not transmit config options to child gdal "
2847
                            "process '%s'",
2848
                            asCommandLines.back().c_str());
×
2849
                break;
×
2850
            }
2851
        }
2852
    }
2853

2854
    uint64_t nCurTileLocal = nCurTile;
2✔
2855
    GetProgressForChildProcesses(bRet, ahSpawnedProcesses,
2✔
2856
                                 anRemainingTilesForProcess, nCurTileLocal,
2857
                                 nTotalTiles, pfnProgress, pProgressData);
2858

2859
    WaitForSpawnedProcesses(bRet, asCommandLines, ahSpawnedProcesses);
2✔
2860

2861
    if (bRet && nCurTileLocal - nCurTile != nExpectedOvrTileCount)
2✔
2862
    {
2863
        bRet = false;
×
2864
        ReportError(CE_Failure, CPLE_AppDefined,
×
2865
                    "Not all tiles at zoom level %d have been "
2866
                    "generated. Got %" PRIu64 ", expected %" PRIu64,
2867
                    iZ, nCurTileLocal - nCurTile, nExpectedOvrTileCount);
×
2868
    }
2869

2870
    nCurTile = nCurTileLocal;
2✔
2871

2872
    return bRet;
2✔
2873
}
2874

2875
/************************************************************************/
2876
/*                  GDALRasterTileAlgorithm::RunImpl()                  */
2877
/************************************************************************/
2878

2879
bool GDALRasterTileAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
106✔
2880
                                      void *pProgressData)
2881
{
2882
    m_poSrcDS = m_dataset.GetDatasetRef();
106✔
2883
    CPLAssert(m_poSrcDS);
106✔
2884
    const int nSrcWidth = m_poSrcDS->GetRasterXSize();
106✔
2885
    const int nSrcHeight = m_poSrcDS->GetRasterYSize();
106✔
2886
    if (m_poSrcDS->GetRasterCount() == 0 || nSrcWidth == 0 || nSrcHeight == 0)
106✔
2887
    {
2888
        ReportError(CE_Failure, CPLE_AppDefined, "Invalid source dataset");
1✔
2889
        return false;
1✔
2890
    }
2891

2892
    if (m_parallelMethod == "spawn")
105✔
2893
    {
2894
        const char *pszErrorMsg = "";
5✔
2895
        if (!IsCompatibleOfSpawn(pszErrorMsg))
5✔
2896
        {
2897
            if (pszErrorMsg[0])
3✔
2898
                ReportError(CE_Failure, CPLE_AppDefined, "%s", pszErrorMsg);
2✔
2899
            return false;
3✔
2900
        }
2901
    }
2902

2903
    if (m_resampling == "near")
102✔
2904
        m_resampling = "nearest";
1✔
2905
    if (m_overviewResampling == "near")
102✔
2906
        m_overviewResampling = "nearest";
1✔
2907
    else if (m_overviewResampling.empty())
101✔
2908
        m_overviewResampling = m_resampling;
96✔
2909

2910
    CPLStringList aosWarpOptions;
204✔
2911
    if (!m_excludedValues.empty() || m_nodataValuesPctThreshold < 100)
102✔
2912
    {
2913
        aosWarpOptions.SetNameValue(
2914
            "NODATA_VALUES_PCT_THRESHOLD",
2915
            CPLSPrintf("%g", m_nodataValuesPctThreshold));
3✔
2916
        if (!m_excludedValues.empty())
3✔
2917
        {
2918
            aosWarpOptions.SetNameValue("EXCLUDED_VALUES",
2919
                                        m_excludedValues.c_str());
1✔
2920
            aosWarpOptions.SetNameValue(
2921
                "EXCLUDED_VALUES_PCT_THRESHOLD",
2922
                CPLSPrintf("%g", m_excludedValuesPctThreshold));
1✔
2923
        }
2924
    }
2925

2926
    if (m_poSrcDS->GetRasterBand(1)->GetColorInterpretation() ==
102✔
2927
            GCI_PaletteIndex &&
106✔
2928
        ((m_resampling != "nearest" && m_resampling != "mode") ||
4✔
2929
         (m_overviewResampling != "nearest" && m_overviewResampling != "mode")))
1✔
2930
    {
2931
        ReportError(CE_Failure, CPLE_NotSupported,
1✔
2932
                    "Datasets with color table not supported with non-nearest "
2933
                    "or non-mode resampling. Run 'gdal raster "
2934
                    "color-map' before or set the 'resampling' argument to "
2935
                    "'nearest' or 'mode'.");
2936
        return false;
1✔
2937
    }
2938

2939
    const auto eSrcDT = m_poSrcDS->GetRasterBand(1)->GetRasterDataType();
101✔
2940
    m_poDstDriver =
101✔
2941
        GetGDALDriverManager()->GetDriverByName(m_outputFormat.c_str());
101✔
2942
    if (!m_poDstDriver)
101✔
2943
    {
2944
        ReportError(CE_Failure, CPLE_AppDefined,
1✔
2945
                    "Invalid value for argument 'output-format'. Driver '%s' "
2946
                    "does not exist",
2947
                    m_outputFormat.c_str());
2948
        return false;
1✔
2949
    }
2950

2951
    if (!ValidateOutputFormat(eSrcDT))
100✔
2952
        return false;
8✔
2953

2954
    const char *pszExtensions =
2955
        m_poDstDriver->GetMetadataItem(GDAL_DMD_EXTENSIONS);
92✔
2956
    CPLAssert(pszExtensions && pszExtensions[0] != 0);
92✔
2957
    const CPLStringList aosExtensions(
2958
        CSLTokenizeString2(pszExtensions, " ", 0));
184✔
2959
    const char *pszExtension = aosExtensions[0];
92✔
2960
    GDALGeoTransform srcGT;
92✔
2961
    const bool bHasSrcGT = m_poSrcDS->GetGeoTransform(srcGT) == CE_None;
92✔
2962
    const bool bHasNorthUpSrcGT =
2963
        bHasSrcGT && srcGT[2] == 0 && srcGT[4] == 0 && srcGT[5] < 0;
92✔
2964
    OGRSpatialReference oSRS_TMS;
184✔
2965

2966
    if (m_tilingScheme == "raster")
92✔
2967
    {
2968
        if (const auto poSRS = m_poSrcDS->GetSpatialRef())
4✔
2969
            oSRS_TMS = *poSRS;
3✔
2970
    }
2971
    else
2972
    {
2973
        if (!bHasSrcGT && m_poSrcDS->GetGCPCount() == 0 &&
1✔
2974
            m_poSrcDS->GetMetadata("GEOLOCATION") == nullptr &&
90✔
2975
            m_poSrcDS->GetMetadata("RPC") == nullptr)
1✔
2976
        {
2977
            ReportError(CE_Failure, CPLE_NotSupported,
1✔
2978
                        "Ungeoreferenced datasets are not supported, unless "
2979
                        "'tiling-scheme' is set to 'raster'");
2980
            return false;
1✔
2981
        }
2982

2983
        if (m_poSrcDS->GetMetadata("GEOLOCATION") == nullptr &&
87✔
2984
            m_poSrcDS->GetMetadata("RPC") == nullptr &&
87✔
2985
            m_poSrcDS->GetSpatialRef() == nullptr &&
175✔
2986
            m_poSrcDS->GetGCPSpatialRef() == nullptr)
1✔
2987
        {
2988
            ReportError(CE_Failure, CPLE_NotSupported,
1✔
2989
                        "Ungeoreferenced datasets are not supported, unless "
2990
                        "'tiling-scheme' is set to 'raster'");
2991
            return false;
1✔
2992
        }
2993
    }
2994

2995
    if (m_copySrcMetadata)
90✔
2996
    {
2997
        CPLStringList aosMD(CSLDuplicate(m_poSrcDS->GetMetadata()));
8✔
2998
        const CPLStringList aosNewMD(m_metadata);
4✔
2999
        for (const auto [key, value] : cpl::IterateNameValue(aosNewMD))
8✔
3000
        {
3001
            aosMD.SetNameValue(key, value);
4✔
3002
        }
3003
        m_metadata = aosMD;
4✔
3004
    }
3005

3006
    GDALGeoTransform srcGTModif{0, 1, 0, 0, 0, -1};
90✔
3007

3008
    if (m_tilingScheme == "mercator")
90✔
3009
        m_tilingScheme = "WebMercatorQuad";
1✔
3010
    else if (m_tilingScheme == "geodetic")
89✔
3011
        m_tilingScheme = "WorldCRS84Quad";
1✔
3012
    else if (m_tilingScheme == "raster")
88✔
3013
    {
3014
        if (m_tileSize == 0)
4✔
3015
            m_tileSize = 256;
4✔
3016
        if (m_maxZoomLevel < 0)
4✔
3017
        {
3018
            m_maxZoomLevel = static_cast<int>(std::ceil(std::log2(
3✔
3019
                std::max(1, std::max(nSrcWidth, nSrcHeight) / m_tileSize))));
6✔
3020
        }
3021
        if (bHasNorthUpSrcGT)
4✔
3022
        {
3023
            srcGTModif = srcGT;
3✔
3024
        }
3025
    }
3026

3027
    auto poTMS =
3028
        m_tilingScheme == "raster"
90✔
3029
            ? gdal::TileMatrixSet::createRaster(
3030
                  nSrcWidth, nSrcHeight, m_tileSize, 1 + m_maxZoomLevel,
4✔
3031
                  srcGTModif[0], srcGTModif[3], srcGTModif[1], -srcGTModif[5],
16✔
3032
                  oSRS_TMS.IsEmpty() ? std::string() : oSRS_TMS.exportToWkt())
94✔
3033
            : gdal::TileMatrixSet::parse(
3034
                  m_mapTileMatrixIdentifierToScheme[m_tilingScheme].c_str());
184✔
3035
    // Enforced by SetChoices() on the m_tilingScheme argument
3036
    CPLAssert(poTMS && !poTMS->hasVariableMatrixWidth());
90✔
3037

3038
    CPLStringList aosTO;
180✔
3039
    if (m_tilingScheme == "raster")
90✔
3040
    {
3041
        aosTO.SetNameValue("SRC_METHOD", "GEOTRANSFORM");
4✔
3042
    }
3043
    else
3044
    {
3045
        CPL_IGNORE_RET_VAL(oSRS_TMS.SetFromUserInput(poTMS->crs().c_str()));
86✔
3046
        aosTO.SetNameValue("DST_SRS", oSRS_TMS.exportToWkt().c_str());
86✔
3047
    }
3048

3049
    const char *pszAuthName = oSRS_TMS.GetAuthorityName(nullptr);
90✔
3050
    const char *pszAuthCode = oSRS_TMS.GetAuthorityCode(nullptr);
90✔
3051
    const int nEPSGCode =
90✔
3052
        (pszAuthName && pszAuthCode && EQUAL(pszAuthName, "EPSG"))
89✔
3053
            ? atoi(pszAuthCode)
179✔
3054
            : 0;
3055

3056
    const bool bInvertAxisTMS =
3057
        m_tilingScheme != "raster" &&
176✔
3058
        (oSRS_TMS.EPSGTreatsAsLatLong() != FALSE ||
86✔
3059
         oSRS_TMS.EPSGTreatsAsNorthingEasting() != FALSE);
86✔
3060

3061
    oSRS_TMS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
90✔
3062

3063
    std::unique_ptr<void, decltype(&GDALDestroyTransformer)> hTransformArg(
3064
        nullptr, GDALDestroyTransformer);
180✔
3065

3066
    // Hack to compensate for GDALSuggestedWarpOutput2() failure (or not
3067
    // ideal suggestion with PROJ 8) when reprojecting latitude = +/- 90 to
3068
    // EPSG:3857.
3069
    std::unique_ptr<GDALDataset> poTmpDS;
90✔
3070
    bool bEPSG3857Adjust = false;
90✔
3071
    if (nEPSGCode == 3857 && bHasNorthUpSrcGT)
90✔
3072
    {
3073
        const auto poSrcSRS = m_poSrcDS->GetSpatialRef();
64✔
3074
        if (poSrcSRS && poSrcSRS->IsGeographic())
64✔
3075
        {
3076
            double maxLat = srcGT[3];
33✔
3077
            double minLat = srcGT[3] + nSrcHeight * srcGT[5];
33✔
3078
            // Corresponds to the latitude of below MAX_GM
3079
            constexpr double MAX_LAT = 85.0511287798066;
33✔
3080
            bool bModified = false;
33✔
3081
            if (maxLat > MAX_LAT)
33✔
3082
            {
3083
                maxLat = MAX_LAT;
32✔
3084
                bModified = true;
32✔
3085
            }
3086
            if (minLat < -MAX_LAT)
33✔
3087
            {
3088
                minLat = -MAX_LAT;
32✔
3089
                bModified = true;
32✔
3090
            }
3091
            if (bModified)
33✔
3092
            {
3093
                CPLStringList aosOptions;
64✔
3094
                aosOptions.AddString("-of");
32✔
3095
                aosOptions.AddString("VRT");
32✔
3096
                aosOptions.AddString("-projwin");
32✔
3097
                aosOptions.AddString(CPLSPrintf("%.17g", srcGT[0]));
32✔
3098
                aosOptions.AddString(CPLSPrintf("%.17g", maxLat));
32✔
3099
                aosOptions.AddString(
3100
                    CPLSPrintf("%.17g", srcGT[0] + nSrcWidth * srcGT[1]));
32✔
3101
                aosOptions.AddString(CPLSPrintf("%.17g", minLat));
32✔
3102
                auto psOptions =
3103
                    GDALTranslateOptionsNew(aosOptions.List(), nullptr);
32✔
3104
                poTmpDS.reset(GDALDataset::FromHandle(GDALTranslate(
32✔
3105
                    "", GDALDataset::ToHandle(m_poSrcDS), psOptions, nullptr)));
3106
                GDALTranslateOptionsFree(psOptions);
32✔
3107
                if (poTmpDS)
32✔
3108
                {
3109
                    bEPSG3857Adjust = true;
32✔
3110
                    hTransformArg.reset(GDALCreateGenImgProjTransformer2(
32✔
3111
                        GDALDataset::FromHandle(poTmpDS.get()), nullptr,
32✔
3112
                        aosTO.List()));
32✔
3113
                }
3114
            }
3115
        }
3116
    }
3117

3118
    GDALGeoTransform dstGT;
90✔
3119
    double adfExtent[4];
3120
    int nXSize, nYSize;
3121

3122
    bool bSuggestOK;
3123
    if (m_tilingScheme == "raster")
90✔
3124
    {
3125
        bSuggestOK = true;
4✔
3126
        nXSize = nSrcWidth;
4✔
3127
        nYSize = nSrcHeight;
4✔
3128
        dstGT = srcGTModif;
4✔
3129
        adfExtent[0] = dstGT[0];
4✔
3130
        adfExtent[1] = dstGT[3] + nSrcHeight * dstGT[5];
4✔
3131
        adfExtent[2] = dstGT[0] + nSrcWidth * dstGT[1];
4✔
3132
        adfExtent[3] = dstGT[3];
4✔
3133
    }
3134
    else
3135
    {
3136
        if (!hTransformArg)
86✔
3137
        {
3138
            hTransformArg.reset(GDALCreateGenImgProjTransformer2(
54✔
3139
                m_poSrcDS, nullptr, aosTO.List()));
54✔
3140
        }
3141
        if (!hTransformArg)
86✔
3142
        {
3143
            return false;
1✔
3144
        }
3145
        CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
85✔
3146
        bSuggestOK =
85✔
3147
            (GDALSuggestedWarpOutput2(
170✔
3148
                 m_poSrcDS,
85✔
3149
                 static_cast<GDALTransformerInfo *>(hTransformArg.get())
85✔
3150
                     ->pfnTransform,
3151
                 hTransformArg.get(), dstGT.data(), &nXSize, &nYSize, adfExtent,
3152
                 0) == CE_None);
3153
    }
3154
    if (!bSuggestOK)
89✔
3155
    {
3156
        ReportError(CE_Failure, CPLE_AppDefined,
1✔
3157
                    "Cannot determine extent of raster in target CRS");
3158
        return false;
1✔
3159
    }
3160

3161
    poTmpDS.reset();
88✔
3162

3163
    if (bEPSG3857Adjust)
88✔
3164
    {
3165
        constexpr double SPHERICAL_RADIUS = 6378137.0;
32✔
3166
        constexpr double MAX_GM =
32✔
3167
            SPHERICAL_RADIUS * M_PI;  // 20037508.342789244
3168
        double maxNorthing = dstGT[3];
32✔
3169
        double minNorthing = dstGT[3] + dstGT[5] * nYSize;
32✔
3170
        bool bChanged = false;
32✔
3171
        if (maxNorthing > MAX_GM)
32✔
3172
        {
3173
            bChanged = true;
29✔
3174
            maxNorthing = MAX_GM;
29✔
3175
        }
3176
        if (minNorthing < -MAX_GM)
32✔
3177
        {
3178
            bChanged = true;
29✔
3179
            minNorthing = -MAX_GM;
29✔
3180
        }
3181
        if (bChanged)
32✔
3182
        {
3183
            dstGT[3] = maxNorthing;
29✔
3184
            nYSize = int((maxNorthing - minNorthing) / (-dstGT[5]) + 0.5);
29✔
3185
            adfExtent[1] = maxNorthing + nYSize * dstGT[5];
29✔
3186
            adfExtent[3] = maxNorthing;
29✔
3187
        }
3188
    }
3189

3190
    const auto &tileMatrixList = poTMS->tileMatrixList();
88✔
3191
    if (m_maxZoomLevel >= 0)
88✔
3192
    {
3193
        if (m_maxZoomLevel >= static_cast<int>(tileMatrixList.size()))
39✔
3194
        {
3195
            ReportError(CE_Failure, CPLE_AppDefined,
1✔
3196
                        "max-zoom = %d is invalid. It must be in [0,%d] range",
3197
                        m_maxZoomLevel,
3198
                        static_cast<int>(tileMatrixList.size()) - 1);
1✔
3199
            return false;
1✔
3200
        }
3201
    }
3202
    else
3203
    {
3204
        const double dfComputedRes = dstGT[1];
49✔
3205
        double dfPrevRes = 0.0;
49✔
3206
        double dfRes = 0.0;
49✔
3207
        constexpr double EPSILON = 1e-8;
49✔
3208

3209
        if (m_minZoomLevel >= 0)
49✔
3210
            m_maxZoomLevel = m_minZoomLevel;
17✔
3211
        else
3212
            m_maxZoomLevel = 0;
32✔
3213

3214
        for (; m_maxZoomLevel < static_cast<int>(tileMatrixList.size());
164✔
3215
             m_maxZoomLevel++)
115✔
3216
        {
3217
            dfRes = tileMatrixList[m_maxZoomLevel].mResX;
163✔
3218
            if (dfComputedRes > dfRes ||
163✔
3219
                fabs(dfComputedRes - dfRes) / dfRes <= EPSILON)
115✔
3220
                break;
3221
            dfPrevRes = dfRes;
115✔
3222
        }
3223
        if (m_maxZoomLevel >= static_cast<int>(tileMatrixList.size()))
49✔
3224
        {
3225
            ReportError(CE_Failure, CPLE_AppDefined,
1✔
3226
                        "Could not find an appropriate zoom level. Perhaps "
3227
                        "min-zoom is too large?");
3228
            return false;
1✔
3229
        }
3230

3231
        if (m_maxZoomLevel > 0 && fabs(dfComputedRes - dfRes) / dfRes > EPSILON)
48✔
3232
        {
3233
            // Round to closest resolution
3234
            if (dfPrevRes / dfComputedRes < dfComputedRes / dfRes)
43✔
3235
                m_maxZoomLevel--;
23✔
3236
        }
3237
    }
3238
    if (m_minZoomLevel < 0)
86✔
3239
        m_minZoomLevel = m_maxZoomLevel;
47✔
3240

3241
    auto tileMatrix = tileMatrixList[m_maxZoomLevel];
172✔
3242
    int nMinTileX = 0;
86✔
3243
    int nMinTileY = 0;
86✔
3244
    int nMaxTileX = 0;
86✔
3245
    int nMaxTileY = 0;
86✔
3246
    bool bIntersects = false;
86✔
3247
    if (!GetTileIndices(tileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
86✔
3248
                        nMinTileX, nMinTileY, nMaxTileX, nMaxTileY,
3249
                        m_noIntersectionIsOK, bIntersects,
86✔
3250
                        /* checkRasterOverflow = */ false))
3251
    {
3252
        return false;
1✔
3253
    }
3254
    if (!bIntersects)
85✔
3255
        return true;
1✔
3256

3257
    // Potentially restrict tiling to user specified coordinates
3258
    if (m_minTileX >= tileMatrix.mMatrixWidth)
84✔
3259
    {
3260
        ReportError(CE_Failure, CPLE_IllegalArg,
1✔
3261
                    "'min-x' value must be in [0,%d] range",
3262
                    tileMatrix.mMatrixWidth - 1);
1✔
3263
        return false;
1✔
3264
    }
3265
    if (m_maxTileX >= tileMatrix.mMatrixWidth)
83✔
3266
    {
3267
        ReportError(CE_Failure, CPLE_IllegalArg,
1✔
3268
                    "'max-x' value must be in [0,%d] range",
3269
                    tileMatrix.mMatrixWidth - 1);
1✔
3270
        return false;
1✔
3271
    }
3272
    if (m_minTileY >= tileMatrix.mMatrixHeight)
82✔
3273
    {
3274
        ReportError(CE_Failure, CPLE_IllegalArg,
1✔
3275
                    "'min-y' value must be in [0,%d] range",
3276
                    tileMatrix.mMatrixHeight - 1);
1✔
3277
        return false;
1✔
3278
    }
3279
    if (m_maxTileY >= tileMatrix.mMatrixHeight)
81✔
3280
    {
3281
        ReportError(CE_Failure, CPLE_IllegalArg,
1✔
3282
                    "'max-y' value must be in [0,%d] range",
3283
                    tileMatrix.mMatrixHeight - 1);
1✔
3284
        return false;
1✔
3285
    }
3286

3287
    if ((m_minTileX >= 0 && m_minTileX > nMaxTileX) ||
80✔
3288
        (m_minTileY >= 0 && m_minTileY > nMaxTileY) ||
78✔
3289
        (m_maxTileX >= 0 && m_maxTileX < nMinTileX) ||
78✔
3290
        (m_maxTileY >= 0 && m_maxTileY < nMinTileY))
78✔
3291
    {
3292
        ReportError(
2✔
3293
            m_noIntersectionIsOK ? CE_Warning : CE_Failure, CPLE_AppDefined,
2✔
3294
            "Dataset extent not intersecting specified min/max X/Y tile "
3295
            "coordinates");
3296
        return m_noIntersectionIsOK;
2✔
3297
    }
3298
    if (m_minTileX >= 0 && m_minTileX > nMinTileX)
78✔
3299
    {
3300
        nMinTileX = m_minTileX;
2✔
3301
        adfExtent[0] = tileMatrix.mTopLeftX +
2✔
3302
                       nMinTileX * tileMatrix.mResX * tileMatrix.mTileWidth;
2✔
3303
    }
3304
    if (m_minTileY >= 0 && m_minTileY > nMinTileY)
78✔
3305
    {
3306
        nMinTileY = m_minTileY;
5✔
3307
        adfExtent[3] = tileMatrix.mTopLeftY -
5✔
3308
                       nMinTileY * tileMatrix.mResY * tileMatrix.mTileHeight;
5✔
3309
    }
3310
    if (m_maxTileX >= 0 && m_maxTileX < nMaxTileX)
78✔
3311
    {
3312
        nMaxTileX = m_maxTileX;
2✔
3313
        adfExtent[2] = tileMatrix.mTopLeftX + (nMaxTileX + 1) *
2✔
3314
                                                  tileMatrix.mResX *
2✔
3315
                                                  tileMatrix.mTileWidth;
2✔
3316
    }
3317
    if (m_maxTileY >= 0 && m_maxTileY < nMaxTileY)
78✔
3318
    {
3319
        nMaxTileY = m_maxTileY;
6✔
3320
        adfExtent[1] = tileMatrix.mTopLeftY - (nMaxTileY + 1) *
6✔
3321
                                                  tileMatrix.mResY *
6✔
3322
                                                  tileMatrix.mTileHeight;
6✔
3323
    }
3324

3325
    if (nMaxTileX - nMinTileX + 1 > INT_MAX / tileMatrix.mTileWidth ||
78✔
3326
        nMaxTileY - nMinTileY + 1 > INT_MAX / tileMatrix.mTileHeight)
77✔
3327
    {
3328
        ReportError(CE_Failure, CPLE_AppDefined, "Too large zoom level");
1✔
3329
        return false;
1✔
3330
    }
3331

3332
    dstGT[0] = tileMatrix.mTopLeftX +
154✔
3333
               nMinTileX * tileMatrix.mResX * tileMatrix.mTileWidth;
77✔
3334
    dstGT[1] = tileMatrix.mResX;
77✔
3335
    dstGT[2] = 0;
77✔
3336
    dstGT[3] = tileMatrix.mTopLeftY -
154✔
3337
               nMinTileY * tileMatrix.mResY * tileMatrix.mTileHeight;
77✔
3338
    dstGT[4] = 0;
77✔
3339
    dstGT[5] = -tileMatrix.mResY;
77✔
3340

3341
    /* -------------------------------------------------------------------- */
3342
    /*      Setup warp options.                                             */
3343
    /* -------------------------------------------------------------------- */
3344
    std::unique_ptr<GDALWarpOptions, decltype(&GDALDestroyWarpOptions)> psWO(
3345
        GDALCreateWarpOptions(), GDALDestroyWarpOptions);
154✔
3346

3347
    psWO->papszWarpOptions = CSLSetNameValue(nullptr, "OPTIMIZE_SIZE", "YES");
77✔
3348
    psWO->papszWarpOptions =
154✔
3349
        CSLSetNameValue(psWO->papszWarpOptions, "SAMPLE_GRID", "YES");
77✔
3350
    psWO->papszWarpOptions =
154✔
3351
        CSLMerge(psWO->papszWarpOptions, aosWarpOptions.List());
77✔
3352

3353
    int bHasSrcNoData = false;
77✔
3354
    const double dfSrcNoDataValue =
3355
        m_poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHasSrcNoData);
77✔
3356

3357
    const bool bLastSrcBandIsAlpha =
3358
        (m_poSrcDS->GetRasterCount() > 1 &&
125✔
3359
         m_poSrcDS->GetRasterBand(m_poSrcDS->GetRasterCount())
48✔
3360
                 ->GetColorInterpretation() == GCI_AlphaBand);
48✔
3361

3362
    const bool bOutputSupportsAlpha = !EQUAL(m_outputFormat.c_str(), "JPEG");
77✔
3363
    const bool bOutputSupportsNoData = EQUAL(m_outputFormat.c_str(), "GTiff");
77✔
3364
    const bool bDstNoDataSpecified = GetArg("dst-nodata")->IsExplicitlySet();
77✔
3365
    auto poColorTable = std::unique_ptr<GDALColorTable>(
3366
        [this]()
77✔
3367
        {
3368
            auto poCT = m_poSrcDS->GetRasterBand(1)->GetColorTable();
77✔
3369
            return poCT ? poCT->Clone() : nullptr;
77✔
3370
        }());
154✔
3371

3372
    const bool bUserAskedForAlpha = m_addalpha;
77✔
3373
    if (!m_noalpha && !m_addalpha)
77✔
3374
    {
3375
        m_addalpha = !(bHasSrcNoData && bOutputSupportsNoData) &&
87✔
3376
                     !bDstNoDataSpecified && poColorTable == nullptr;
87✔
3377
    }
3378
    m_addalpha &= bOutputSupportsAlpha;
77✔
3379

3380
    psWO->nBandCount = m_poSrcDS->GetRasterCount();
77✔
3381
    if (bLastSrcBandIsAlpha)
77✔
3382
    {
3383
        --psWO->nBandCount;
5✔
3384
        psWO->nSrcAlphaBand = m_poSrcDS->GetRasterCount();
5✔
3385
    }
3386

3387
    if (bHasSrcNoData)
77✔
3388
    {
3389
        psWO->padfSrcNoDataReal =
26✔
3390
            static_cast<double *>(CPLCalloc(psWO->nBandCount, sizeof(double)));
13✔
3391
        for (int i = 0; i < psWO->nBandCount; ++i)
32✔
3392
        {
3393
            psWO->padfSrcNoDataReal[i] = dfSrcNoDataValue;
19✔
3394
        }
3395
    }
3396

3397
    if ((bHasSrcNoData && !m_addalpha && bOutputSupportsNoData) ||
77✔
3398
        bDstNoDataSpecified)
3399
    {
3400
        psWO->padfDstNoDataReal =
18✔
3401
            static_cast<double *>(CPLCalloc(psWO->nBandCount, sizeof(double)));
9✔
3402
        for (int i = 0; i < psWO->nBandCount; ++i)
18✔
3403
        {
3404
            psWO->padfDstNoDataReal[i] =
9✔
3405
                bDstNoDataSpecified ? m_dstNoData : dfSrcNoDataValue;
9✔
3406
        }
3407
    }
3408

3409
    psWO->eWorkingDataType = eSrcDT;
77✔
3410

3411
    GDALGetWarpResampleAlg(m_resampling.c_str(), psWO->eResampleAlg);
77✔
3412

3413
    /* -------------------------------------------------------------------- */
3414
    /*      Setup band mapping.                                             */
3415
    /* -------------------------------------------------------------------- */
3416

3417
    psWO->panSrcBands =
154✔
3418
        static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
77✔
3419
    psWO->panDstBands =
154✔
3420
        static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
77✔
3421

3422
    for (int i = 0; i < psWO->nBandCount; i++)
65,783✔
3423
    {
3424
        psWO->panSrcBands[i] = i + 1;
65,706✔
3425
        psWO->panDstBands[i] = i + 1;
65,706✔
3426
    }
3427

3428
    if (m_addalpha)
77✔
3429
        psWO->nDstAlphaBand = psWO->nBandCount + 1;
64✔
3430

3431
    const int nDstBands =
3432
        psWO->nDstAlphaBand ? psWO->nDstAlphaBand : psWO->nBandCount;
77✔
3433

3434
    std::vector<GByte> dstBuffer;
154✔
3435
    const uint64_t dstBufferSize =
3436
        static_cast<uint64_t>(tileMatrix.mTileWidth) * tileMatrix.mTileHeight *
77✔
3437
        nDstBands * GDALGetDataTypeSizeBytes(psWO->eWorkingDataType);
77✔
3438
    const uint64_t nUsableRAM =
3439
        std::min<uint64_t>(INT_MAX, CPLGetUsablePhysicalRAM() / 4);
77✔
3440
    if (dstBufferSize <=
77✔
3441
        (nUsableRAM ? nUsableRAM : static_cast<uint64_t>(INT_MAX)))
77✔
3442
    {
3443
        try
3444
        {
3445
            dstBuffer.resize(static_cast<size_t>(dstBufferSize));
76✔
3446
        }
3447
        catch (const std::exception &)
×
3448
        {
3449
        }
3450
    }
3451
    if (dstBuffer.size() < dstBufferSize)
77✔
3452
    {
3453
        ReportError(CE_Failure, CPLE_AppDefined,
1✔
3454
                    "Tile size and/or number of bands too large compared to "
3455
                    "available RAM");
3456
        return false;
1✔
3457
    }
3458

3459
    FakeMaxZoomDataset oFakeMaxZoomDS(
3460
        (nMaxTileX - nMinTileX + 1) * tileMatrix.mTileWidth,
76✔
3461
        (nMaxTileY - nMinTileY + 1) * tileMatrix.mTileHeight, nDstBands,
76✔
3462
        tileMatrix.mTileWidth, tileMatrix.mTileHeight, psWO->eWorkingDataType,
76✔
3463
        dstGT, oSRS_TMS, dstBuffer);
152✔
3464
    CPL_IGNORE_RET_VAL(oFakeMaxZoomDS.GetSpatialRef());
76✔
3465

3466
    psWO->hSrcDS = GDALDataset::ToHandle(m_poSrcDS);
76✔
3467
    psWO->hDstDS = GDALDataset::ToHandle(&oFakeMaxZoomDS);
76✔
3468

3469
    std::unique_ptr<GDALDataset> tmpSrcDS;
76✔
3470
    if (m_tilingScheme == "raster" && !bHasNorthUpSrcGT)
76✔
3471
    {
3472
        CPLStringList aosOptions;
1✔
3473
        aosOptions.AddString("-of");
1✔
3474
        aosOptions.AddString("VRT");
1✔
3475
        aosOptions.AddString("-a_ullr");
1✔
3476
        aosOptions.AddString(CPLSPrintf("%.17g", srcGTModif[0]));
1✔
3477
        aosOptions.AddString(CPLSPrintf("%.17g", srcGTModif[3]));
1✔
3478
        aosOptions.AddString(
3479
            CPLSPrintf("%.17g", srcGTModif[0] + nSrcWidth * srcGTModif[1]));
1✔
3480
        aosOptions.AddString(
3481
            CPLSPrintf("%.17g", srcGTModif[3] + nSrcHeight * srcGTModif[5]));
1✔
3482
        if (oSRS_TMS.IsEmpty())
1✔
3483
        {
3484
            aosOptions.AddString("-a_srs");
1✔
3485
            aosOptions.AddString("none");
1✔
3486
        }
3487

3488
        GDALTranslateOptions *psOptions =
3489
            GDALTranslateOptionsNew(aosOptions.List(), nullptr);
1✔
3490

3491
        tmpSrcDS.reset(GDALDataset::FromHandle(GDALTranslate(
1✔
3492
            "", GDALDataset::ToHandle(m_poSrcDS), psOptions, nullptr)));
3493
        GDALTranslateOptionsFree(psOptions);
1✔
3494
        if (!tmpSrcDS)
1✔
3495
            return false;
×
3496
    }
3497
    hTransformArg.reset(GDALCreateGenImgProjTransformer2(
77✔
3498
        tmpSrcDS ? tmpSrcDS.get() : m_poSrcDS, &oFakeMaxZoomDS, aosTO.List()));
77✔
3499
    CPLAssert(hTransformArg);
76✔
3500

3501
    /* -------------------------------------------------------------------- */
3502
    /*      Warp the transformer with a linear approximator                 */
3503
    /* -------------------------------------------------------------------- */
3504
    hTransformArg.reset(GDALCreateApproxTransformer(
76✔
3505
        GDALGenImgProjTransform, hTransformArg.release(), 0.125));
3506
    GDALApproxTransformerOwnsSubtransformer(hTransformArg.get(), TRUE);
76✔
3507

3508
    psWO->pfnTransformer = GDALApproxTransform;
76✔
3509
    psWO->pTransformerArg = hTransformArg.get();
76✔
3510

3511
    /* -------------------------------------------------------------------- */
3512
    /*      Determine total number of tiles                                 */
3513
    /* -------------------------------------------------------------------- */
3514
    const int nBaseTilesPerRow = nMaxTileX - nMinTileX + 1;
76✔
3515
    const int nBaseTilesPerCol = nMaxTileY - nMinTileY + 1;
76✔
3516
    const uint64_t nBaseTiles =
76✔
3517
        static_cast<uint64_t>(nBaseTilesPerCol) * nBaseTilesPerRow;
76✔
3518
    uint64_t nTotalTiles = nBaseTiles;
76✔
3519
    std::atomic<uint64_t> nCurTile = 0;
76✔
3520
    bool bRet = true;
76✔
3521

3522
    for (int iZ = m_maxZoomLevel - 1;
138✔
3523
         bRet && bIntersects && iZ >= m_minZoomLevel; --iZ)
138✔
3524
    {
3525
        auto ovrTileMatrix = tileMatrixList[iZ];
124✔
3526
        int nOvrMinTileX = 0;
62✔
3527
        int nOvrMinTileY = 0;
62✔
3528
        int nOvrMaxTileX = 0;
62✔
3529
        int nOvrMaxTileY = 0;
62✔
3530
        bRet =
62✔
3531
            GetTileIndices(ovrTileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
124✔
3532
                           nOvrMinTileX, nOvrMinTileY, nOvrMaxTileX,
3533
                           nOvrMaxTileY, m_noIntersectionIsOK, bIntersects);
62✔
3534
        if (bIntersects)
62✔
3535
        {
3536
            nTotalTiles +=
62✔
3537
                static_cast<uint64_t>(nOvrMaxTileY - nOvrMinTileY + 1) *
62✔
3538
                (nOvrMaxTileX - nOvrMinTileX + 1);
62✔
3539
        }
3540
    }
3541

3542
    /* -------------------------------------------------------------------- */
3543
    /*      Generate tiles at max zoom level                                */
3544
    /* -------------------------------------------------------------------- */
3545
    GDALWarpOperation oWO;
152✔
3546

3547
    bRet = oWO.Initialize(psWO.get()) == CE_None && bRet;
76✔
3548

3549
    const auto GetUpdatedCreationOptions =
3550
        [this](const gdal::TileMatrixSet::TileMatrix &oTM)
305✔
3551
    {
3552
        CPLStringList aosCreationOptions(m_creationOptions);
109✔
3553
        if (m_outputFormat == "GTiff")
109✔
3554
        {
3555
            if (aosCreationOptions.FetchNameValue("TILED") == nullptr &&
48✔
3556
                aosCreationOptions.FetchNameValue("BLOCKYSIZE") == nullptr)
24✔
3557
            {
3558
                if (oTM.mTileWidth <= 512 && oTM.mTileHeight <= 512)
24✔
3559
                {
3560
                    aosCreationOptions.SetNameValue(
22✔
3561
                        "BLOCKYSIZE", CPLSPrintf("%d", oTM.mTileHeight));
22✔
3562
                }
3563
                else
3564
                {
3565
                    aosCreationOptions.SetNameValue("TILED", "YES");
2✔
3566
                }
3567
            }
3568
            if (aosCreationOptions.FetchNameValue("COMPRESS") == nullptr)
24✔
3569
                aosCreationOptions.SetNameValue("COMPRESS", "LZW");
24✔
3570
        }
3571
        else if (m_outputFormat == "COG")
85✔
3572
        {
3573
            if (aosCreationOptions.FetchNameValue("OVERVIEW_RESAMPLING") ==
2✔
3574
                nullptr)
3575
            {
3576
                aosCreationOptions.SetNameValue("OVERVIEW_RESAMPLING",
3577
                                                m_overviewResampling.c_str());
2✔
3578
            }
3579
            if (aosCreationOptions.FetchNameValue("BLOCKSIZE") == nullptr &&
2✔
3580
                oTM.mTileWidth <= 512 && oTM.mTileWidth == oTM.mTileHeight)
2✔
3581
            {
3582
                aosCreationOptions.SetNameValue(
3583
                    "BLOCKSIZE", CPLSPrintf("%d", oTM.mTileWidth));
2✔
3584
            }
3585
        }
3586
        return aosCreationOptions;
109✔
3587
    };
76✔
3588

3589
    VSIMkdir(m_outputDirectory.c_str(), 0755);
76✔
3590
    VSIStatBufL sStat;
3591
    if (VSIStatL(m_outputDirectory.c_str(), &sStat) != 0 ||
151✔
3592
        !VSI_ISDIR(sStat.st_mode))
75✔
3593
    {
3594
        ReportError(CE_Failure, CPLE_FileIO,
1✔
3595
                    "Cannot create output directory %s",
3596
                    m_outputDirectory.c_str());
3597
        return false;
1✔
3598
    }
3599

3600
    OGRSpatialReference oWGS84;
150✔
3601
    oWGS84.importFromEPSG(4326);
75✔
3602
    oWGS84.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
75✔
3603

3604
    std::unique_ptr<OGRCoordinateTransformation> poCTToWGS84;
75✔
3605
    if (!oSRS_TMS.IsEmpty())
75✔
3606
    {
3607
        CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
148✔
3608
        poCTToWGS84.reset(
74✔
3609
            OGRCreateCoordinateTransformation(&oSRS_TMS, &oWGS84));
3610
    }
3611

3612
    const bool kmlCompatible = m_kml &&
77✔
3613
                               [this, &poTMS, &poCTToWGS84, bInvertAxisTMS]()
17✔
3614
    {
3615
        CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
2✔
3616
        double dfX = poTMS->tileMatrixList()[0].mTopLeftX;
2✔
3617
        double dfY = poTMS->tileMatrixList()[0].mTopLeftY;
2✔
3618
        if (bInvertAxisTMS)
2✔
3619
            std::swap(dfX, dfY);
×
3620
        return (m_minZoomLevel == m_maxZoomLevel ||
3✔
3621
                (poTMS->haveAllLevelsSameTopLeft() &&
1✔
3622
                 poTMS->haveAllLevelsSameTileSize() &&
1✔
3623
                 poTMS->hasOnlyPowerOfTwoVaryingScales())) &&
3✔
3624
               poCTToWGS84 && poCTToWGS84->Transform(1, &dfX, &dfY);
7✔
3625
    }();
2✔
3626
    const int kmlTileSize =
3627
        m_tileSize > 0 ? m_tileSize : poTMS->tileMatrixList()[0].mTileWidth;
75✔
3628
    if (m_kml && !kmlCompatible)
75✔
3629
    {
3630
        ReportError(CE_Failure, CPLE_NotSupported,
×
3631
                    "Tiling scheme not compatible with KML output");
3632
        return false;
×
3633
    }
3634

3635
    if (m_title.empty())
75✔
3636
        m_title = CPLGetFilename(m_dataset.GetName().c_str());
73✔
3637

3638
    if (!m_url.empty())
75✔
3639
    {
3640
        if (m_url.back() != '/')
2✔
3641
            m_url += '/';
2✔
3642
        std::string out_path = m_outputDirectory;
4✔
3643
        if (m_outputDirectory.back() == '/')
2✔
3644
            out_path.pop_back();
×
3645
        m_url += CPLGetFilename(out_path.c_str());
2✔
3646
    }
3647

3648
    CPLWorkerThreadPool oThreadPool;
150✔
3649

3650
    bool bThreadPoolInitialized = false;
75✔
3651
    const auto InitThreadPool =
3652
        [this, &oThreadPool, &bRet, &bThreadPoolInitialized]()
368✔
3653
    {
3654
        if (!bThreadPoolInitialized)
109✔
3655
        {
3656
            bThreadPoolInitialized = true;
74✔
3657

3658
            if (bRet && m_numThreads > 1)
74✔
3659
            {
3660
                CPLDebug("gdal_raster_tile", "Using %d threads", m_numThreads);
1✔
3661
                bRet = oThreadPool.Setup(m_numThreads, nullptr, nullptr);
1✔
3662
            }
3663
        }
3664

3665
        return bRet;
109✔
3666
    };
75✔
3667

3668
    // Just for unit test purposes
3669
    const bool bEmitSpuriousCharsOnStdout = CPLTestBool(
75✔
3670
        CPLGetConfigOption("GDAL_RASTER_TILE_EMIT_SPURIOUS_CHARS", "NO"));
3671

3672
    const auto IsCompatibleOfSpawnSilent = [this]()
×
3673
    {
3674
        CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
×
3675
        const char *pszErrorMsg = "";
×
3676
        return IsCompatibleOfSpawn(pszErrorMsg);
×
3677
    };
75✔
3678

3679
    // Config option for test only
3680
    const int nThreshold = std::max(
3681
        1, atoi(CPLGetConfigOption("GDAL_THRESHOLD_TILES_PER_JOB",
75✔
3682
                                   CPLSPrintf("%d", THRESHOLD_TILES_PER_JOB))));
75✔
3683
    m_numThreads = std::max(1, static_cast<int>(std::min<uint64_t>(
150✔
3684
                                   m_numThreads, nBaseTiles / nThreshold)));
75✔
3685

3686
    if (m_ovrZoomLevel >= 0)
75✔
3687
    {
3688
        // do not generate base tiles if called as a child process with
3689
        // --ovr-zoom-level
3690
    }
3691
    else if (m_numThreads > 1 && nBaseTiles > 1 &&
73✔
3692
             ((m_parallelMethod.empty() &&
3✔
3693
               m_numThreads >= THRESHOLD_MIN_THREADS_FOR_SPAWN &&
1✔
3694
               IsCompatibleOfSpawnSilent()) ||
3✔
3695
              m_parallelMethod == "spawn"))
3✔
3696
    {
3697
        if (!GenerateBaseTilesSpawnMethod(nBaseTilesPerCol, nBaseTilesPerRow,
2✔
3698
                                          nMinTileX, nMinTileY, nMaxTileX,
3699
                                          nMaxTileY, nTotalTiles, nBaseTiles,
3700
                                          pfnProgress, pProgressData))
3701
        {
3702
            return false;
1✔
3703
        }
3704
        nCurTile = nBaseTiles;
1✔
3705
    }
3706
    else
3707
    {
3708
        // Branch for multi-threaded or single-threaded max zoom level tile
3709
        // generation
3710

3711
        PerThreadMaxZoomResourceManager oResourceManager(
3712
            m_poSrcDS, psWO.get(), hTransformArg.get(), oFakeMaxZoomDS,
65✔
3713
            dstBuffer.size());
195✔
3714

3715
        const CPLStringList aosCreationOptions(
3716
            GetUpdatedCreationOptions(tileMatrix));
130✔
3717

3718
        CPLDebug("gdal_raster_tile",
65✔
3719
                 "Generating tiles z=%d, y=%d...%d, x=%d...%d", m_maxZoomLevel,
3720
                 nMinTileY, nMaxTileY, nMinTileX, nMaxTileX);
3721

3722
        bRet &= InitThreadPool();
65✔
3723

3724
        if (bRet && m_numThreads > 1)
65✔
3725
        {
3726
            std::atomic<bool> bFailure = false;
1✔
3727
            std::atomic<int> nQueuedJobs = 0;
1✔
3728

3729
            double dfTilesYPerJob;
3730
            int nYOuterIterations;
3731
            double dfTilesXPerJob;
3732
            int nXOuterIterations;
3733
            ComputeJobChunkSize(m_numThreads, nBaseTilesPerCol,
1✔
3734
                                nBaseTilesPerRow, dfTilesYPerJob,
3735
                                nYOuterIterations, dfTilesXPerJob,
3736
                                nXOuterIterations);
3737

3738
            CPLDebugOnly("gdal_raster_tile",
1✔
3739
                         "nYOuterIterations=%d, dfTilesYPerJob=%g, "
3740
                         "nXOuterIterations=%d, dfTilesXPerJob=%g",
3741
                         nYOuterIterations, dfTilesYPerJob, nXOuterIterations,
3742
                         dfTilesXPerJob);
3743

3744
            int nLastYEndIncluded = nMinTileY - 1;
1✔
3745
            for (int iYOuterIter = 0; bRet && iYOuterIter < nYOuterIterations &&
5✔
3746
                                      nLastYEndIncluded < nMaxTileY;
4✔
3747
                 ++iYOuterIter)
3748
            {
3749
                const int iYStart = nLastYEndIncluded + 1;
4✔
3750
                const int iYEndIncluded =
3751
                    iYOuterIter + 1 == nYOuterIterations
4✔
3752
                        ? nMaxTileY
7✔
3753
                        : std::max(
3754
                              iYStart,
3755
                              static_cast<int>(std::floor(
7✔
3756
                                  nMinTileY +
3✔
3757
                                  (iYOuterIter + 1) * dfTilesYPerJob - 1)));
3✔
3758

3759
                nLastYEndIncluded = iYEndIncluded;
4✔
3760

3761
                int nLastXEndIncluded = nMinTileX - 1;
4✔
3762
                for (int iXOuterIter = 0;
4✔
3763
                     bRet && iXOuterIter < nXOuterIterations &&
8✔
3764
                     nLastXEndIncluded < nMaxTileX;
4✔
3765
                     ++iXOuterIter)
3766
                {
3767
                    const int iXStart = nLastXEndIncluded + 1;
4✔
3768
                    const int iXEndIncluded =
3769
                        iXOuterIter + 1 == nXOuterIterations
4✔
3770
                            ? nMaxTileX
4✔
3771
                            : std::max(
3772
                                  iXStart,
3773
                                  static_cast<int>(std::floor(
4✔
3774
                                      nMinTileX +
×
3775
                                      (iXOuterIter + 1) * dfTilesXPerJob - 1)));
×
3776

3777
                    nLastXEndIncluded = iXEndIncluded;
4✔
3778

3779
                    CPLDebugOnly("gdal_raster_tile",
4✔
3780
                                 "Job for y in [%d,%d] and x in [%d,%d]",
3781
                                 iYStart, iYEndIncluded, iXStart,
3782
                                 iXEndIncluded);
3783

3784
                    auto job = [this, &oThreadPool, &oResourceManager,
4✔
3785
                                &bFailure, &nCurTile, &nQueuedJobs,
3786
                                pszExtension, &aosCreationOptions, &psWO,
3787
                                &tileMatrix, nDstBands, iXStart, iXEndIncluded,
3788
                                iYStart, iYEndIncluded, nMinTileX, nMinTileY,
3789
                                &poColorTable, bUserAskedForAlpha]()
544✔
3790
                    {
3791
                        CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
4✔
3792

3793
                        auto resources = oResourceManager.AcquireResources();
4✔
3794
                        if (resources)
4✔
3795
                        {
3796
                            for (int iY = iYStart; iY <= iYEndIncluded; ++iY)
12✔
3797
                            {
3798
                                for (int iX = iXStart; iX <= iXEndIncluded;
72✔
3799
                                     ++iX)
3800
                                {
3801
                                    if (!GenerateTile(
192✔
3802
                                            resources->poSrcDS.get(),
64✔
3803
                                            m_poDstDriver, pszExtension,
3804
                                            aosCreationOptions.List(),
3805
                                            *(resources->poWO.get()),
64✔
3806
                                            *(resources->poFakeMaxZoomDS
64✔
3807
                                                  ->GetSpatialRef()),
64✔
3808
                                            psWO->eWorkingDataType, tileMatrix,
64✔
3809
                                            m_outputDirectory, nDstBands,
64✔
3810
                                            psWO->padfDstNoDataReal
64✔
3811
                                                ? &(psWO->padfDstNoDataReal[0])
×
3812
                                                : nullptr,
3813
                                            m_maxZoomLevel, iX, iY,
3814
                                            m_convention, nMinTileX, nMinTileY,
64✔
3815
                                            m_skipBlank, bUserAskedForAlpha,
64✔
3816
                                            m_auxXML, m_resume, m_metadata,
64✔
3817
                                            poColorTable.get(),
64✔
3818
                                            resources->dstBuffer))
64✔
3819
                                    {
3820
                                        oResourceManager.SetError();
×
3821
                                        bFailure = true;
×
3822
                                        --nQueuedJobs;
×
3823
                                        return;
×
3824
                                    }
3825
                                    ++nCurTile;
64✔
3826
                                    oThreadPool.WakeUpWaitEvent();
64✔
3827
                                }
3828
                            }
3829
                            oResourceManager.ReleaseResources(
4✔
3830
                                std::move(resources));
4✔
3831
                        }
3832
                        else
3833
                        {
3834
                            oResourceManager.SetError();
×
3835
                            bFailure = true;
×
3836
                        }
3837

3838
                        --nQueuedJobs;
4✔
3839
                    };
4✔
3840

3841
                    ++nQueuedJobs;
4✔
3842
                    oThreadPool.SubmitJob(std::move(job));
4✔
3843
                }
3844
            }
3845

3846
            // Wait for completion of all jobs
3847
            while (bRet && nQueuedJobs > 0)
65✔
3848
            {
3849
                oThreadPool.WaitEvent();
64✔
3850
                bRet &= !bFailure &&
128✔
3851
                        (!pfnProgress ||
64✔
3852
                         pfnProgress(static_cast<double>(nCurTile) /
128✔
3853
                                         static_cast<double>(nTotalTiles),
64✔
3854
                                     "", pProgressData));
64✔
3855
            }
3856
            oThreadPool.WaitCompletion();
1✔
3857
            bRet &=
1✔
3858
                !bFailure && (!pfnProgress ||
2✔
3859
                              pfnProgress(static_cast<double>(nCurTile) /
2✔
3860
                                              static_cast<double>(nTotalTiles),
1✔
3861
                                          "", pProgressData));
1✔
3862

3863
            if (!oResourceManager.GetErrorMsg().empty())
1✔
3864
            {
3865
                // Re-emit error message from worker thread to main thread
3866
                ReportError(CE_Failure, CPLE_AppDefined, "%s",
×
3867
                            oResourceManager.GetErrorMsg().c_str());
×
3868
            }
1✔
3869
        }
3870
        else
3871
        {
3872
            // Branch for single-thread max zoom level tile generation
3873
            for (int iY = nMinTileY; bRet && iY <= nMaxTileY; ++iY)
160✔
3874
            {
3875
                for (int iX = nMinTileX; bRet && iX <= nMaxTileX; ++iX)
346✔
3876
                {
3877
                    bRet = GenerateTile(
500✔
3878
                        m_poSrcDS, m_poDstDriver, pszExtension,
3879
                        aosCreationOptions.List(), oWO, oSRS_TMS,
3880
                        psWO->eWorkingDataType, tileMatrix, m_outputDirectory,
250✔
3881
                        nDstBands,
3882
                        psWO->padfDstNoDataReal ? &(psWO->padfDstNoDataReal[0])
250✔
3883
                                                : nullptr,
3884
                        m_maxZoomLevel, iX, iY, m_convention, nMinTileX,
250✔
3885
                        nMinTileY, m_skipBlank, bUserAskedForAlpha, m_auxXML,
250✔
3886
                        m_resume, m_metadata, poColorTable.get(), dstBuffer);
250✔
3887

3888
                    if (m_progressForked)
250✔
3889
                    {
3890
                        if (bEmitSpuriousCharsOnStdout)
66✔
3891
                            fwrite(&PROGRESS_MARKER[0], 1, 1, stdout);
64✔
3892
                        fwrite(PROGRESS_MARKER, sizeof(PROGRESS_MARKER), 1,
66✔
3893
                               stdout);
3894
                        fflush(stdout);
66✔
3895
                    }
3896
                    else
3897
                    {
3898
                        ++nCurTile;
184✔
3899
                        bRet &=
184✔
3900
                            (!pfnProgress ||
186✔
3901
                             pfnProgress(static_cast<double>(nCurTile) /
4✔
3902
                                             static_cast<double>(nTotalTiles),
2✔
3903
                                         "", pProgressData));
184✔
3904
                    }
3905
                }
3906
            }
3907
        }
3908

3909
        if (m_kml && bRet)
65✔
3910
        {
3911
            for (int iY = nMinTileY; iY <= nMaxTileY; ++iY)
4✔
3912
            {
3913
                for (int iX = nMinTileX; iX <= nMaxTileX; ++iX)
4✔
3914
                {
3915
                    const int nFileY =
3916
                        GetFileY(iY, poTMS->tileMatrixList()[m_maxZoomLevel],
2✔
3917
                                 m_convention);
2✔
3918
                    std::string osFilename = CPLFormFilenameSafe(
3919
                        m_outputDirectory.c_str(),
3920
                        CPLSPrintf("%d", m_maxZoomLevel), nullptr);
4✔
3921
                    osFilename = CPLFormFilenameSafe(
4✔
3922
                        osFilename.c_str(), CPLSPrintf("%d", iX), nullptr);
2✔
3923
                    osFilename = CPLFormFilenameSafe(
4✔
3924
                        osFilename.c_str(),
3925
                        CPLSPrintf("%d.%s", nFileY, pszExtension), nullptr);
2✔
3926
                    if (VSIStatL(osFilename.c_str(), &sStat) == 0)
2✔
3927
                    {
3928
                        GenerateKML(m_outputDirectory, m_title, iX, iY,
4✔
3929
                                    m_maxZoomLevel, kmlTileSize, pszExtension,
3930
                                    m_url, poTMS.get(), bInvertAxisTMS,
2✔
3931
                                    m_convention, poCTToWGS84.get(), {});
2✔
3932
                    }
3933
                }
3934
            }
3935
        }
3936
    }
3937

3938
    // Close source dataset if we have opened it (in GDALAlgorithm core code),
3939
    // to free file descriptors, particularly if it is a VRT file.
3940
    std::vector<GDALColorInterp> aeColorInterp;
74✔
3941
    for (int i = 1; i <= m_poSrcDS->GetRasterCount(); ++i)
243✔
3942
        aeColorInterp.push_back(
169✔
3943
            m_poSrcDS->GetRasterBand(i)->GetColorInterpretation());
169✔
3944
    if (m_dataset.HasDatasetBeenOpenedByAlgorithm())
74✔
3945
    {
3946
        m_dataset.Close();
36✔
3947
        m_poSrcDS = nullptr;
36✔
3948
    }
3949

3950
    /* -------------------------------------------------------------------- */
3951
    /*      Generate tiles at lower zoom levels                             */
3952
    /* -------------------------------------------------------------------- */
3953
    const int iZStart =
74✔
3954
        m_ovrZoomLevel >= 0 ? m_ovrZoomLevel : m_maxZoomLevel - 1;
74✔
3955
    const int iZEnd = m_ovrZoomLevel >= 0 ? m_ovrZoomLevel : m_minZoomLevel;
74✔
3956
    for (int iZ = iZStart; bRet && iZ >= iZEnd; --iZ)
120✔
3957
    {
3958
        int nOvrMinTileX = 0;
46✔
3959
        int nOvrMinTileY = 0;
46✔
3960
        int nOvrMaxTileX = 0;
46✔
3961
        int nOvrMaxTileY = 0;
46✔
3962

3963
        auto ovrTileMatrix = tileMatrixList[iZ];
92✔
3964
        CPL_IGNORE_RET_VAL(
46✔
3965
            GetTileIndices(ovrTileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
46✔
3966
                           nOvrMinTileX, nOvrMinTileY, nOvrMaxTileX,
3967
                           nOvrMaxTileY, m_noIntersectionIsOK, bIntersects));
46✔
3968

3969
        bRet = bIntersects;
46✔
3970

3971
        if (m_minOvrTileX >= 0)
46✔
3972
        {
3973
            bRet = true;
8✔
3974
            nOvrMinTileX = m_minOvrTileX;
8✔
3975
            nOvrMinTileY = m_minOvrTileY;
8✔
3976
            nOvrMaxTileX = m_maxOvrTileX;
8✔
3977
            nOvrMaxTileY = m_maxOvrTileY;
8✔
3978
        }
3979

3980
        if (bRet)
46✔
3981
        {
3982
            CPLDebug("gdal_raster_tile",
46✔
3983
                     "Generating overview tiles z=%d, y=%d...%d, x=%d...%d", iZ,
3984
                     nOvrMinTileY, nOvrMaxTileY, nOvrMinTileX, nOvrMaxTileX);
3985
        }
3986

3987
        const int nOvrTilesPerCol = nOvrMaxTileY - nOvrMinTileY + 1;
46✔
3988
        const int nOvrTilesPerRow = nOvrMaxTileX - nOvrMinTileX + 1;
46✔
3989
        const uint64_t nOvrTileCount =
46✔
3990
            static_cast<uint64_t>(nOvrTilesPerCol) * nOvrTilesPerRow;
46✔
3991

3992
        m_numThreads =
46✔
3993
            std::max(1, static_cast<int>(std::min<uint64_t>(
92✔
3994
                            m_numThreads, nOvrTileCount / nThreshold)));
46✔
3995

3996
        if (m_numThreads > 1 && nOvrTileCount > 1 &&
54✔
3997
            ((m_parallelMethod.empty() &&
4✔
3998
              m_numThreads >= THRESHOLD_MIN_THREADS_FOR_SPAWN &&
2✔
3999
              IsCompatibleOfSpawnSilent()) ||
4✔
4000
             m_parallelMethod == "spawn"))
4✔
4001
        {
4002
            bRet &= GenerateOverviewTilesSpawnMethod(
2✔
4003
                iZ, nOvrMinTileX, nOvrMinTileY, nOvrMaxTileX, nOvrMaxTileY,
4004
                nCurTile, nTotalTiles, pfnProgress, pProgressData);
2✔
4005
        }
4006
        else
4007
        {
4008
            bRet &= InitThreadPool();
44✔
4009

4010
            auto srcTileMatrix = tileMatrixList[iZ + 1];
88✔
4011
            int nSrcMinTileX = 0;
44✔
4012
            int nSrcMinTileY = 0;
44✔
4013
            int nSrcMaxTileX = 0;
44✔
4014
            int nSrcMaxTileY = 0;
44✔
4015

4016
            CPL_IGNORE_RET_VAL(GetTileIndices(
44✔
4017
                srcTileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
4018
                nSrcMinTileX, nSrcMinTileY, nSrcMaxTileX, nSrcMaxTileY,
4019
                m_noIntersectionIsOK, bIntersects));
44✔
4020

4021
            constexpr double EPSILON = 1e-3;
44✔
4022
            int maxCacheTileSizePerThread = static_cast<int>(
44✔
4023
                (1 + std::ceil(
44✔
4024
                         (ovrTileMatrix.mResY * ovrTileMatrix.mTileHeight) /
44✔
4025
                             (srcTileMatrix.mResY * srcTileMatrix.mTileHeight) -
44✔
4026
                         EPSILON)) *
44✔
4027
                (1 + std::ceil(
44✔
4028
                         (ovrTileMatrix.mResX * ovrTileMatrix.mTileWidth) /
44✔
4029
                             (srcTileMatrix.mResX * srcTileMatrix.mTileWidth) -
44✔
4030
                         EPSILON)));
4031

4032
            CPLDebugOnly("gdal_raster_tile",
44✔
4033
                         "Ideal maxCacheTileSizePerThread = %d",
4034
                         maxCacheTileSizePerThread);
4035

4036
#ifndef _WIN32
4037
            const int remainingFileDescriptorCount =
4038
                CPLGetRemainingFileDescriptorCount();
44✔
4039
            CPLDebugOnly("gdal_raster_tile",
44✔
4040
                         "remainingFileDescriptorCount = %d",
4041
                         remainingFileDescriptorCount);
4042
            if (remainingFileDescriptorCount >= 0 &&
44✔
4043
                remainingFileDescriptorCount <
4044
                    (1 + maxCacheTileSizePerThread) * m_numThreads)
44✔
4045
            {
4046
                const int newNumThreads =
4047
                    std::max(1, remainingFileDescriptorCount /
×
4048
                                    (1 + maxCacheTileSizePerThread));
×
4049
                if (newNumThreads < m_numThreads)
×
4050
                {
4051
                    CPLError(CE_Warning, CPLE_AppDefined,
×
4052
                             "Not enough file descriptors available given the "
4053
                             "number of "
4054
                             "threads. Reducing the number of threads %d to %d",
4055
                             m_numThreads, newNumThreads);
4056
                    m_numThreads = newNumThreads;
×
4057
                }
4058
            }
4059
#endif
4060

4061
            MosaicDataset oSrcDS(
4062
                CPLFormFilenameSafe(m_outputDirectory.c_str(),
88✔
4063
                                    CPLSPrintf("%d", iZ + 1), nullptr),
4064
                pszExtension, m_outputFormat, aeColorInterp, srcTileMatrix,
44✔
4065
                oSRS_TMS, nSrcMinTileX, nSrcMinTileY, nSrcMaxTileX,
4066
                nSrcMaxTileY, m_convention, nDstBands, psWO->eWorkingDataType,
44✔
4067
                psWO->padfDstNoDataReal ? &(psWO->padfDstNoDataReal[0])
9✔
4068
                                        : nullptr,
4069
                m_metadata, poColorTable.get(), maxCacheTileSizePerThread);
229✔
4070

4071
            const CPLStringList aosCreationOptions(
4072
                GetUpdatedCreationOptions(ovrTileMatrix));
88✔
4073

4074
            PerThreadLowerZoomResourceManager oResourceManager(oSrcDS);
88✔
4075
            std::atomic<bool> bFailure = false;
44✔
4076
            std::atomic<int> nQueuedJobs = 0;
44✔
4077

4078
            const bool bUseThreads = m_numThreads > 1 && nOvrTileCount > 1;
44✔
4079

4080
            if (bUseThreads)
44✔
4081
            {
4082
                double dfTilesYPerJob;
4083
                int nYOuterIterations;
4084
                double dfTilesXPerJob;
4085
                int nXOuterIterations;
4086
                ComputeJobChunkSize(m_numThreads, nOvrTilesPerCol,
2✔
4087
                                    nOvrTilesPerRow, dfTilesYPerJob,
4088
                                    nYOuterIterations, dfTilesXPerJob,
4089
                                    nXOuterIterations);
4090

4091
                CPLDebugOnly("gdal_raster_tile",
2✔
4092
                             "z=%d, nYOuterIterations=%d, dfTilesYPerJob=%g, "
4093
                             "nXOuterIterations=%d, dfTilesXPerJob=%g",
4094
                             iZ, nYOuterIterations, dfTilesYPerJob,
4095
                             nXOuterIterations, dfTilesXPerJob);
4096

4097
                int nLastYEndIncluded = nOvrMinTileY - 1;
2✔
4098
                for (int iYOuterIter = 0;
8✔
4099
                     bRet && iYOuterIter < nYOuterIterations &&
8✔
4100
                     nLastYEndIncluded < nOvrMaxTileY;
6✔
4101
                     ++iYOuterIter)
4102
                {
4103
                    const int iYStart = nLastYEndIncluded + 1;
6✔
4104
                    const int iYEndIncluded =
4105
                        iYOuterIter + 1 == nYOuterIterations
6✔
4106
                            ? nOvrMaxTileY
10✔
4107
                            : std::max(
4108
                                  iYStart,
4109
                                  static_cast<int>(std::floor(
10✔
4110
                                      nOvrMinTileY +
4✔
4111
                                      (iYOuterIter + 1) * dfTilesYPerJob - 1)));
4✔
4112

4113
                    nLastYEndIncluded = iYEndIncluded;
6✔
4114

4115
                    int nLastXEndIncluded = nOvrMinTileX - 1;
6✔
4116
                    for (int iXOuterIter = 0;
6✔
4117
                         bRet && iXOuterIter < nXOuterIterations &&
14✔
4118
                         nLastXEndIncluded < nOvrMaxTileX;
8✔
4119
                         ++iXOuterIter)
4120
                    {
4121
                        const int iXStart = nLastXEndIncluded + 1;
8✔
4122
                        const int iXEndIncluded =
4123
                            iXOuterIter + 1 == nXOuterIterations
8✔
4124
                                ? nOvrMaxTileX
10✔
4125
                                : std::max(iXStart, static_cast<int>(std::floor(
10✔
4126
                                                        nOvrMinTileX +
2✔
4127
                                                        (iXOuterIter + 1) *
2✔
4128
                                                            dfTilesXPerJob -
4129
                                                        1)));
2✔
4130

4131
                        nLastXEndIncluded = iXEndIncluded;
8✔
4132

4133
                        CPLDebugOnly(
8✔
4134
                            "gdal_raster_tile",
4135
                            "Job for z=%d, y in [%d,%d] and x in [%d,%d]", iZ,
4136
                            iYStart, iYEndIncluded, iXStart, iXEndIncluded);
4137
                        auto job = [this, &oThreadPool, &oResourceManager,
8✔
4138
                                    &bFailure, &nCurTile, &nQueuedJobs,
4139
                                    pszExtension, &aosCreationOptions,
4140
                                    &aosWarpOptions, &ovrTileMatrix, iZ,
4141
                                    iXStart, iXEndIncluded, iYStart,
4142
                                    iYEndIncluded, bUserAskedForAlpha]()
168✔
4143
                        {
4144
                            CPLErrorStateBackuper oBackuper(
4145
                                CPLQuietErrorHandler);
8✔
4146

4147
                            auto resources =
4148
                                oResourceManager.AcquireResources();
8✔
4149
                            if (resources)
8✔
4150
                            {
4151
                                for (int iY = iYStart; iY <= iYEndIncluded;
16✔
4152
                                     ++iY)
4153
                                {
4154
                                    for (int iX = iXStart; iX <= iXEndIncluded;
28✔
4155
                                         ++iX)
4156
                                    {
4157
                                        if (!GenerateOverviewTile(
40✔
4158
                                                *(resources->poSrcDS.get()),
20✔
4159
                                                m_poDstDriver, m_outputFormat,
20✔
4160
                                                pszExtension,
4161
                                                aosCreationOptions.List(),
4162
                                                aosWarpOptions.List(),
20✔
4163
                                                m_overviewResampling,
20✔
4164
                                                ovrTileMatrix,
4165
                                                m_outputDirectory, iZ, iX, iY,
20✔
4166
                                                m_convention, m_skipBlank,
20✔
4167
                                                bUserAskedForAlpha, m_auxXML,
20✔
4168
                                                m_resume))
20✔
4169
                                        {
4170
                                            oResourceManager.SetError();
×
4171
                                            bFailure = true;
×
4172
                                            --nQueuedJobs;
×
4173
                                            return;
×
4174
                                        }
4175

4176
                                        ++nCurTile;
20✔
4177
                                        oThreadPool.WakeUpWaitEvent();
20✔
4178
                                    }
4179
                                }
4180
                                oResourceManager.ReleaseResources(
8✔
4181
                                    std::move(resources));
8✔
4182
                            }
4183
                            else
4184
                            {
4185
                                oResourceManager.SetError();
×
4186
                                bFailure = true;
×
4187
                            }
4188
                            --nQueuedJobs;
8✔
4189
                        };
8✔
4190

4191
                        ++nQueuedJobs;
8✔
4192
                        oThreadPool.SubmitJob(std::move(job));
8✔
4193
                    }
4194
                }
4195

4196
                // Wait for completion of all jobs
4197
                while (bRet && nQueuedJobs > 0)
24✔
4198
                {
4199
                    oThreadPool.WaitEvent();
22✔
4200
                    bRet &= !bFailure &&
44✔
4201
                            (!pfnProgress ||
22✔
4202
                             pfnProgress(static_cast<double>(nCurTile) /
44✔
4203
                                             static_cast<double>(nTotalTiles),
22✔
4204
                                         "", pProgressData));
22✔
4205
                }
4206
                oThreadPool.WaitCompletion();
2✔
4207
                bRet &= !bFailure &&
4✔
4208
                        (!pfnProgress ||
2✔
4209
                         pfnProgress(static_cast<double>(nCurTile) /
4✔
4210
                                         static_cast<double>(nTotalTiles),
2✔
4211
                                     "", pProgressData));
2✔
4212

4213
                if (!oResourceManager.GetErrorMsg().empty())
2✔
4214
                {
4215
                    // Re-emit error message from worker thread to main thread
4216
                    ReportError(CE_Failure, CPLE_AppDefined, "%s",
×
4217
                                oResourceManager.GetErrorMsg().c_str());
×
4218
                }
4219
            }
4220
            else
4221
            {
4222
                // Branch for single-thread overview generation
4223

4224
                for (int iY = nOvrMinTileY; bRet && iY <= nOvrMaxTileY; ++iY)
88✔
4225
                {
4226
                    for (int iX = nOvrMinTileX; bRet && iX <= nOvrMaxTileX;
118✔
4227
                         ++iX)
4228
                    {
4229
                        bRet = GenerateOverviewTile(
72✔
4230
                            oSrcDS, m_poDstDriver, m_outputFormat, pszExtension,
72✔
4231
                            aosCreationOptions.List(), aosWarpOptions.List(),
72✔
4232
                            m_overviewResampling, ovrTileMatrix,
72✔
4233
                            m_outputDirectory, iZ, iX, iY, m_convention,
72✔
4234
                            m_skipBlank, bUserAskedForAlpha, m_auxXML,
72✔
4235
                            m_resume);
72✔
4236

4237
                        if (m_progressForked)
72✔
4238
                        {
4239
                            if (bEmitSpuriousCharsOnStdout)
20✔
4240
                                fwrite(&PROGRESS_MARKER[0], 1, 1, stdout);
20✔
4241
                            fwrite(PROGRESS_MARKER, sizeof(PROGRESS_MARKER), 1,
20✔
4242
                                   stdout);
4243
                            fflush(stdout);
20✔
4244
                        }
4245
                        else
4246
                        {
4247
                            ++nCurTile;
52✔
4248
                            bRet &= (!pfnProgress ||
54✔
4249
                                     pfnProgress(
2✔
4250
                                         static_cast<double>(nCurTile) /
2✔
4251
                                             static_cast<double>(nTotalTiles),
2✔
4252
                                         "", pProgressData));
52✔
4253
                        }
4254
                    }
4255
                }
4256
            }
4257
        }
4258

4259
        if (m_kml && bRet)
46✔
4260
        {
4261
            for (int iY = nOvrMinTileY; bRet && iY <= nOvrMaxTileY; ++iY)
2✔
4262
            {
4263
                for (int iX = nOvrMinTileX; bRet && iX <= nOvrMaxTileX; ++iX)
2✔
4264
                {
4265
                    int nFileY =
4266
                        GetFileY(iY, poTMS->tileMatrixList()[iZ], m_convention);
1✔
4267
                    std::string osFilename =
4268
                        CPLFormFilenameSafe(m_outputDirectory.c_str(),
4269
                                            CPLSPrintf("%d", iZ), nullptr);
2✔
4270
                    osFilename = CPLFormFilenameSafe(
2✔
4271
                        osFilename.c_str(), CPLSPrintf("%d", iX), nullptr);
1✔
4272
                    osFilename = CPLFormFilenameSafe(
2✔
4273
                        osFilename.c_str(),
4274
                        CPLSPrintf("%d.%s", nFileY, pszExtension), nullptr);
1✔
4275
                    if (VSIStatL(osFilename.c_str(), &sStat) == 0)
1✔
4276
                    {
4277
                        std::vector<TileCoordinates> children;
1✔
4278

4279
                        for (int iChildY = 0; iChildY <= 1; ++iChildY)
3✔
4280
                        {
4281
                            for (int iChildX = 0; iChildX <= 1; ++iChildX)
6✔
4282
                            {
4283
                                nFileY =
4284
                                    GetFileY(iY * 2 + iChildY,
4✔
4285
                                             poTMS->tileMatrixList()[iZ + 1],
4✔
4286
                                             m_convention);
4✔
4287
                                osFilename = CPLFormFilenameSafe(
8✔
4288
                                    m_outputDirectory.c_str(),
4289
                                    CPLSPrintf("%d", iZ + 1), nullptr);
4✔
4290
                                osFilename = CPLFormFilenameSafe(
8✔
4291
                                    osFilename.c_str(),
4292
                                    CPLSPrintf("%d", iX * 2 + iChildX),
4✔
4293
                                    nullptr);
4✔
4294
                                osFilename = CPLFormFilenameSafe(
8✔
4295
                                    osFilename.c_str(),
4296
                                    CPLSPrintf("%d.%s", nFileY, pszExtension),
4297
                                    nullptr);
4✔
4298
                                if (VSIStatL(osFilename.c_str(), &sStat) == 0)
4✔
4299
                                {
4300
                                    TileCoordinates tc;
1✔
4301
                                    tc.nTileX = iX * 2 + iChildX;
1✔
4302
                                    tc.nTileY = iY * 2 + iChildY;
1✔
4303
                                    tc.nTileZ = iZ + 1;
1✔
4304
                                    children.push_back(std::move(tc));
1✔
4305
                                }
4306
                            }
4307
                        }
4308

4309
                        GenerateKML(m_outputDirectory, m_title, iX, iY, iZ,
2✔
4310
                                    kmlTileSize, pszExtension, m_url,
1✔
4311
                                    poTMS.get(), bInvertAxisTMS, m_convention,
1✔
4312
                                    poCTToWGS84.get(), children);
4313
                    }
4314
                }
4315
            }
4316
        }
4317
    }
4318

4319
    const auto IsWebViewerEnabled = [this](const char *name)
495✔
4320
    {
4321
        return std::find_if(m_webviewers.begin(), m_webviewers.end(),
165✔
4322
                            [name](const std::string &s) {
280✔
4323
                                return s == "all" || s == name;
167✔
4324
                            }) != m_webviewers.end();
165✔
4325
    };
74✔
4326

4327
    if (m_ovrZoomLevel < 0 && bRet &&
127✔
4328
        poTMS->identifier() == "GoogleMapsCompatible" &&
201✔
4329
        IsWebViewerEnabled("leaflet"))
43✔
4330
    {
4331
        double dfSouthLat = -90;
14✔
4332
        double dfWestLon = -180;
14✔
4333
        double dfNorthLat = 90;
14✔
4334
        double dfEastLon = 180;
14✔
4335

4336
        if (poCTToWGS84)
14✔
4337
        {
4338
            poCTToWGS84->TransformBounds(
14✔
4339
                adfExtent[0], adfExtent[1], adfExtent[2], adfExtent[3],
4340
                &dfWestLon, &dfSouthLat, &dfEastLon, &dfNorthLat, 21);
14✔
4341
        }
4342

4343
        GenerateLeaflet(m_outputDirectory, m_title, dfSouthLat, dfWestLon,
14✔
4344
                        dfNorthLat, dfEastLon, m_minZoomLevel, m_maxZoomLevel,
4345
                        tileMatrix.mTileWidth, pszExtension, m_url, m_copyright,
14✔
4346
                        m_convention == "xyz");
14✔
4347
    }
4348

4349
    if (m_ovrZoomLevel < 0 && bRet && IsWebViewerEnabled("openlayers"))
74✔
4350
    {
4351
        GenerateOpenLayers(
44✔
4352
            m_outputDirectory, m_title, adfExtent[0], adfExtent[1],
22✔
4353
            adfExtent[2], adfExtent[3], m_minZoomLevel, m_maxZoomLevel,
4354
            tileMatrix.mTileWidth, pszExtension, m_url, m_copyright,
22✔
4355
            *(poTMS.get()), bInvertAxisTMS, oSRS_TMS, m_convention == "xyz");
22✔
4356
    }
4357

4358
    if (m_ovrZoomLevel < 0 && bRet && IsWebViewerEnabled("mapml") &&
86✔
4359
        poTMS->identifier() != "raster" && m_convention == "xyz")
160✔
4360
    {
4361
        GenerateMapML(m_outputDirectory, m_mapmlTemplate, m_title, nMinTileX,
15✔
4362
                      nMinTileY, nMaxTileX, nMaxTileY, m_minZoomLevel,
4363
                      m_maxZoomLevel, pszExtension, m_url, m_copyright,
15✔
4364
                      *(poTMS.get()));
15✔
4365
    }
4366

4367
    if (m_ovrZoomLevel < 0 && bRet && m_kml)
74✔
4368
    {
4369
        std::vector<TileCoordinates> children;
4✔
4370

4371
        auto ovrTileMatrix = tileMatrixList[m_minZoomLevel];
2✔
4372
        int nOvrMinTileX = 0;
2✔
4373
        int nOvrMinTileY = 0;
2✔
4374
        int nOvrMaxTileX = 0;
2✔
4375
        int nOvrMaxTileY = 0;
2✔
4376
        CPL_IGNORE_RET_VAL(
2✔
4377
            GetTileIndices(ovrTileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
2✔
4378
                           nOvrMinTileX, nOvrMinTileY, nOvrMaxTileX,
4379
                           nOvrMaxTileY, m_noIntersectionIsOK, bIntersects));
2✔
4380

4381
        for (int iY = nOvrMinTileY; bRet && iY <= nOvrMaxTileY; ++iY)
4✔
4382
        {
4383
            for (int iX = nOvrMinTileX; bRet && iX <= nOvrMaxTileX; ++iX)
4✔
4384
            {
4385
                int nFileY = GetFileY(
2✔
4386
                    iY, poTMS->tileMatrixList()[m_minZoomLevel], m_convention);
2✔
4387
                std::string osFilename = CPLFormFilenameSafe(
4388
                    m_outputDirectory.c_str(), CPLSPrintf("%d", m_minZoomLevel),
4389
                    nullptr);
4✔
4390
                osFilename = CPLFormFilenameSafe(osFilename.c_str(),
4✔
4391
                                                 CPLSPrintf("%d", iX), nullptr);
2✔
4392
                osFilename = CPLFormFilenameSafe(
4✔
4393
                    osFilename.c_str(),
4394
                    CPLSPrintf("%d.%s", nFileY, pszExtension), nullptr);
2✔
4395
                if (VSIStatL(osFilename.c_str(), &sStat) == 0)
2✔
4396
                {
4397
                    TileCoordinates tc;
2✔
4398
                    tc.nTileX = iX;
2✔
4399
                    tc.nTileY = iY;
2✔
4400
                    tc.nTileZ = m_minZoomLevel;
2✔
4401
                    children.push_back(std::move(tc));
2✔
4402
                }
4403
            }
4404
        }
4405
        GenerateKML(m_outputDirectory, m_title, -1, -1, -1, kmlTileSize,
4✔
4406
                    pszExtension, m_url, poTMS.get(), bInvertAxisTMS,
2✔
4407
                    m_convention, poCTToWGS84.get(), children);
2✔
4408
    }
4409

4410
    if (!bRet && CPLGetLastErrorType() == CE_None)
74✔
4411
    {
4412
        // If that happens, this is a programming error
4413
        ReportError(CE_Failure, CPLE_AppDefined,
×
4414
                    "Bug: process failed without returning an error message");
4415
    }
4416

4417
    return bRet;
74✔
4418
}
4419

4420
//! @endcond
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc