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