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