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