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