Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: gdal "raster pipeline" subcommand
5 : * Author: Even Rouault <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "gdalalg_raster_pipeline.h"
14 : #include "gdalalg_materialize.h"
15 : #include "gdalalg_raster_read.h"
16 : #include "gdalalg_raster_calc.h"
17 : #include "gdalalg_raster_aspect.h"
18 : #include "gdalalg_raster_blend.h"
19 : #include "gdalalg_raster_clip.h"
20 : #include "gdalalg_raster_color_map.h"
21 : #include "gdalalg_raster_compare.h"
22 : #include "gdalalg_raster_edit.h"
23 : #include "gdalalg_raster_fill_nodata.h"
24 : #include "gdalalg_raster_hillshade.h"
25 : #include "gdalalg_raster_info.h"
26 : #include "gdalalg_raster_mosaic.h"
27 : #include "gdalalg_raster_neighbors.h"
28 : #include "gdalalg_raster_nodata_to_alpha.h"
29 : #include "gdalalg_raster_overview.h"
30 : #include "gdalalg_raster_pansharpen.h"
31 : #include "gdalalg_raster_proximity.h"
32 : #include "gdalalg_raster_reclassify.h"
33 : #include "gdalalg_raster_reproject.h"
34 : #include "gdalalg_raster_resize.h"
35 : #include "gdalalg_raster_rgb_to_palette.h"
36 : #include "gdalalg_raster_roughness.h"
37 : #include "gdalalg_raster_scale.h"
38 : #include "gdalalg_raster_select.h"
39 : #include "gdalalg_raster_set_type.h"
40 : #include "gdalalg_raster_sieve.h"
41 : #include "gdalalg_raster_slope.h"
42 : #include "gdalalg_raster_stack.h"
43 : #include "gdalalg_raster_tile.h"
44 : #include "gdalalg_raster_write.h"
45 : #include "gdalalg_raster_tpi.h"
46 : #include "gdalalg_raster_tri.h"
47 : #include "gdalalg_raster_unscale.h"
48 : #include "gdalalg_raster_update.h"
49 : #include "gdalalg_raster_viewshed.h"
50 : #include "gdalalg_tee.h"
51 :
52 : #include "cpl_conv.h"
53 : #include "cpl_progress.h"
54 : #include "cpl_string.h"
55 : #include "cpl_vsi.h"
56 : #include "gdal_priv.h"
57 : #include "gdal_utils.h"
58 :
59 : #include <algorithm>
60 : #include <array>
61 : #include <cassert>
62 :
63 : //! @cond Doxygen_Suppress
64 :
65 : #ifndef _
66 : #define _(x) (x)
67 : #endif
68 :
69 : GDALRasterAlgorithmStepRegistry::~GDALRasterAlgorithmStepRegistry() = default;
70 :
71 : /************************************************************************/
72 : /* GDALRasterPipelineStepAlgorithm::GDALRasterPipelineStepAlgorithm() */
73 : /************************************************************************/
74 :
75 1792 : GDALRasterPipelineStepAlgorithm::GDALRasterPipelineStepAlgorithm(
76 : const std::string &name, const std::string &description,
77 1792 : const std::string &helpURL, bool standaloneStep)
78 : : GDALRasterPipelineStepAlgorithm(
79 : name, description, helpURL,
80 1792 : ConstructorOptions().SetStandaloneStep(standaloneStep))
81 : {
82 1792 : }
83 :
84 : /************************************************************************/
85 : /* GDALRasterPipelineStepAlgorithm::GDALRasterPipelineStepAlgorithm() */
86 : /************************************************************************/
87 :
88 4381 : GDALRasterPipelineStepAlgorithm::GDALRasterPipelineStepAlgorithm(
89 : const std::string &name, const std::string &description,
90 4381 : const std::string &helpURL, const ConstructorOptions &options)
91 4381 : : GDALPipelineStepAlgorithm(name, description, helpURL, options)
92 : {
93 4381 : if (m_standaloneStep)
94 : {
95 1313 : m_supportsStreamedOutput = true;
96 :
97 1313 : if (m_constructorOptions.addDefaultArguments)
98 : {
99 472 : AddRasterInputArgs(false, false);
100 472 : AddProgressArg();
101 472 : AddRasterOutputArgs(false);
102 : }
103 : }
104 3068 : else if (m_constructorOptions.addDefaultArguments)
105 : {
106 1402 : AddRasterHiddenInputDatasetArg();
107 : }
108 4381 : }
109 :
110 : GDALRasterPipelineStepAlgorithm::~GDALRasterPipelineStepAlgorithm() = default;
111 :
112 : /************************************************************************/
113 : /* GDALRasterPipelineStepAlgorithm::SetOutputVRTCompatible() */
114 : /************************************************************************/
115 :
116 344 : void GDALRasterPipelineStepAlgorithm::SetOutputVRTCompatible(bool b)
117 : {
118 344 : m_outputVRTCompatible = b;
119 344 : if (m_outputFormatArg)
120 : {
121 116 : m_outputFormatArg->AddMetadataItem(GAAMDI_VRT_COMPATIBLE,
122 232 : {b ? "true" : "false"});
123 : }
124 344 : }
125 :
126 : /************************************************************************/
127 : /* GDALRasterPipelineAlgorithm::GDALRasterPipelineAlgorithm() */
128 : /************************************************************************/
129 :
130 137 : GDALRasterPipelineAlgorithm::GDALRasterPipelineAlgorithm(
131 137 : bool openForMixedRasterVector)
132 : : GDALAbstractPipelineAlgorithm(NAME, DESCRIPTION, HELP_URL,
133 0 : ConstructorOptions()
134 137 : .SetAddDefaultArguments(false)
135 274 : .SetInputDatasetMaxCount(INT_MAX))
136 : {
137 137 : m_supportsStreamedOutput = true;
138 :
139 137 : AddRasterInputArgs(openForMixedRasterVector, /* hiddenForCLI = */ true);
140 137 : AddProgressArg();
141 274 : AddArg("pipeline", 0, _("Pipeline string"), &m_pipeline)
142 137 : .SetHiddenForCLI()
143 137 : .SetPositional();
144 137 : AddRasterOutputArgs(/* hiddenForCLI = */ true);
145 :
146 137 : AddOutputStringArg(&m_output).SetHiddenForCLI();
147 137 : AddStdoutArg(&m_stdout);
148 :
149 137 : RegisterAlgorithms(m_stepRegistry, false);
150 137 : }
151 :
152 : /************************************************************************/
153 : /* GDALRasterPipelineAlgorithm::RegisterAlgorithms() */
154 : /************************************************************************/
155 :
156 : /* static */
157 345 : void GDALRasterPipelineAlgorithm::RegisterAlgorithms(
158 : GDALRasterAlgorithmStepRegistry ®istry, bool forMixedPipeline)
159 : {
160 345 : GDALAlgorithmRegistry::AlgInfo algInfo;
161 :
162 : const auto addSuffixIfNeeded =
163 3450 : [forMixedPipeline](const char *name) -> std::string
164 : {
165 5530 : return forMixedPipeline ? std::string(name).append(RASTER_SUFFIX)
166 8980 : : std::string(name);
167 345 : };
168 :
169 345 : registry.Register<GDALRasterReadAlgorithm>(
170 690 : addSuffixIfNeeded(GDALRasterReadAlgorithm::NAME));
171 :
172 345 : registry.Register<GDALRasterCalcAlgorithm>();
173 :
174 345 : registry.Register<GDALRasterNeighborsAlgorithm>();
175 :
176 345 : registry.Register<GDALRasterWriteAlgorithm>(
177 690 : addSuffixIfNeeded(GDALRasterWriteAlgorithm::NAME));
178 :
179 345 : registry.Register<GDALRasterInfoAlgorithm>(
180 690 : addSuffixIfNeeded(GDALRasterInfoAlgorithm::NAME));
181 :
182 345 : registry.Register<GDALRasterAspectAlgorithm>();
183 345 : registry.Register<GDALRasterBlendAlgorithm>();
184 :
185 345 : registry.Register<GDALRasterClipAlgorithm>(
186 690 : addSuffixIfNeeded(GDALRasterClipAlgorithm::NAME));
187 :
188 345 : registry.Register<GDALRasterColorMapAlgorithm>();
189 345 : registry.Register<GDALRasterCompareAlgorithm>();
190 :
191 345 : registry.Register<GDALRasterEditAlgorithm>(
192 690 : addSuffixIfNeeded(GDALRasterEditAlgorithm::NAME));
193 :
194 345 : registry.Register<GDALRasterNoDataToAlphaAlgorithm>();
195 345 : registry.Register<GDALRasterFillNodataAlgorithm>();
196 345 : registry.Register<GDALRasterHillshadeAlgorithm>();
197 :
198 345 : registry.Register<GDALMaterializeRasterAlgorithm>(
199 690 : addSuffixIfNeeded(GDALMaterializeRasterAlgorithm::NAME));
200 :
201 345 : registry.Register<GDALRasterMosaicAlgorithm>();
202 345 : registry.Register<GDALRasterOverviewAlgorithm>();
203 345 : registry.Register<GDALRasterPansharpenAlgorithm>();
204 345 : registry.Register<GDALRasterProximityAlgorithm>();
205 345 : registry.Register<GDALRasterReclassifyAlgorithm>();
206 :
207 345 : registry.Register<GDALRasterReprojectAlgorithm>(
208 690 : addSuffixIfNeeded(GDALRasterReprojectAlgorithm::NAME));
209 :
210 345 : registry.Register<GDALRasterResizeAlgorithm>();
211 345 : registry.Register<GDALRasterRGBToPaletteAlgorithm>();
212 345 : registry.Register<GDALRasterRoughnessAlgorithm>();
213 345 : registry.Register<GDALRasterScaleAlgorithm>();
214 :
215 345 : registry.Register<GDALRasterSelectAlgorithm>(
216 690 : addSuffixIfNeeded(GDALRasterSelectAlgorithm::NAME));
217 :
218 345 : registry.Register<GDALRasterSetTypeAlgorithm>();
219 345 : registry.Register<GDALRasterSieveAlgorithm>();
220 345 : registry.Register<GDALRasterSlopeAlgorithm>();
221 345 : registry.Register<GDALRasterStackAlgorithm>();
222 345 : registry.Register<GDALRasterTileAlgorithm>();
223 345 : registry.Register<GDALRasterTPIAlgorithm>();
224 345 : registry.Register<GDALRasterTRIAlgorithm>();
225 345 : registry.Register<GDALRasterUnscaleAlgorithm>();
226 345 : registry.Register<GDALRasterUpdateAlgorithm>(
227 690 : addSuffixIfNeeded(GDALRasterUpdateAlgorithm::NAME));
228 345 : registry.Register<GDALRasterViewshedAlgorithm>();
229 345 : registry.Register<GDALTeeRasterAlgorithm>(
230 690 : addSuffixIfNeeded(GDALTeeRasterAlgorithm::NAME));
231 345 : }
232 :
233 : /************************************************************************/
234 : /* GDALRasterPipelineAlgorithm::GetUsageForCLI() */
235 : /************************************************************************/
236 :
237 8 : std::string GDALRasterPipelineAlgorithm::GetUsageForCLI(
238 : bool shortUsage, const UsageOptions &usageOptions) const
239 : {
240 8 : UsageOptions stepUsageOptions;
241 8 : stepUsageOptions.isPipelineStep = true;
242 :
243 8 : if (!m_helpDocCategory.empty() && m_helpDocCategory != "main")
244 : {
245 4 : auto alg = GetStepAlg(m_helpDocCategory);
246 2 : if (alg)
247 : {
248 2 : alg->SetCallPath({m_helpDocCategory});
249 1 : alg->GetArg("help-doc")->Set(true);
250 1 : return alg->GetUsageForCLI(shortUsage, stepUsageOptions);
251 : }
252 : else
253 : {
254 1 : fprintf(stderr, "ERROR: unknown pipeline step '%s'\n",
255 : m_helpDocCategory.c_str());
256 : return CPLSPrintf("ERROR: unknown pipeline step '%s'\n",
257 1 : m_helpDocCategory.c_str());
258 : }
259 : }
260 :
261 6 : UsageOptions usageOptionsMain(usageOptions);
262 6 : usageOptionsMain.isPipelineMain = true;
263 : std::string ret =
264 12 : GDALAlgorithm::GetUsageForCLI(shortUsage, usageOptionsMain);
265 6 : if (shortUsage)
266 2 : return ret;
267 :
268 : ret += "\n<PIPELINE> is of the form: read|mosaic|stack [READ-OPTIONS] "
269 : "( ! <STEP-NAME> [STEP-OPTIONS] )* ! info|compare|tile|write "
270 4 : "[WRITE-OPTIONS]\n";
271 :
272 4 : if (m_helpDocCategory == "main")
273 : {
274 1 : return ret;
275 : }
276 :
277 3 : ret += '\n';
278 3 : ret += "Example: 'gdal raster pipeline --progress ! read in.tif ! \\\n";
279 3 : ret += " reproject --dst-crs=EPSG:32632 ! ";
280 3 : ret += "write out.tif --overwrite'\n";
281 3 : ret += '\n';
282 3 : ret += "Potential steps are:\n";
283 :
284 114 : for (const std::string &name : m_stepRegistry.GetNames())
285 : {
286 222 : auto alg = GetStepAlg(name);
287 111 : auto [options, maxOptLen] = alg->GetArgNamesForCLI();
288 111 : stepUsageOptions.maxOptLen =
289 111 : std::max(stepUsageOptions.maxOptLen, maxOptLen);
290 : }
291 :
292 : {
293 3 : const auto name = GDALRasterReadAlgorithm::NAME;
294 3 : ret += '\n';
295 6 : auto alg = GetStepAlg(name);
296 6 : alg->SetCallPath({name});
297 3 : ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
298 : }
299 114 : for (const std::string &name : m_stepRegistry.GetNames())
300 : {
301 222 : auto alg = GetStepAlg(name);
302 111 : assert(alg);
303 123 : if (alg->CanBeFirstStep() && !alg->CanBeMiddleStep() &&
304 123 : !alg->IsHidden() && name != GDALRasterReadAlgorithm::NAME)
305 : {
306 9 : ret += '\n';
307 18 : alg->SetCallPath({name});
308 9 : ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
309 : }
310 : }
311 114 : for (const std::string &name : m_stepRegistry.GetNames())
312 : {
313 222 : auto alg = GetStepAlg(name);
314 111 : assert(alg);
315 111 : if (alg->CanBeMiddleStep() && !alg->IsHidden())
316 : {
317 87 : ret += '\n';
318 174 : alg->SetCallPath({name});
319 87 : ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
320 : }
321 : }
322 114 : for (const std::string &name : m_stepRegistry.GetNames())
323 : {
324 222 : auto alg = GetStepAlg(name);
325 111 : assert(alg);
326 129 : if (alg->CanBeLastStep() && !alg->CanBeMiddleStep() &&
327 129 : !alg->IsHidden() && name != GDALRasterWriteAlgorithm::NAME)
328 : {
329 9 : ret += '\n';
330 18 : alg->SetCallPath({name});
331 9 : ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
332 : }
333 : }
334 : {
335 3 : const auto name = GDALRasterWriteAlgorithm::NAME;
336 3 : ret += '\n';
337 6 : auto alg = GetStepAlg(name);
338 6 : alg->SetCallPath({name});
339 3 : ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
340 : }
341 3 : ret += GetUsageForCLIEnd();
342 :
343 3 : return ret;
344 : }
345 :
346 : /************************************************************************/
347 : /* GDALRasterPipelineNonNativelyStreamingAlgorithm() */
348 : /************************************************************************/
349 :
350 288 : GDALRasterPipelineNonNativelyStreamingAlgorithm::
351 : GDALRasterPipelineNonNativelyStreamingAlgorithm(
352 : const std::string &name, const std::string &description,
353 288 : const std::string &helpURL, bool standaloneStep)
354 : : GDALRasterPipelineStepAlgorithm(name, description, helpURL,
355 288 : standaloneStep)
356 : {
357 288 : }
358 :
359 : /************************************************************************/
360 : /* IsNativelyStreamingCompatible() */
361 : /************************************************************************/
362 :
363 46 : bool GDALRasterPipelineNonNativelyStreamingAlgorithm::
364 : IsNativelyStreamingCompatible() const
365 : {
366 46 : return false;
367 : }
368 :
369 : /************************************************************************/
370 : /* MustCreateOnDiskTempDataset() */
371 : /************************************************************************/
372 :
373 55 : static bool MustCreateOnDiskTempDataset(int nWidth, int nHeight, int nBands,
374 : GDALDataType eDT)
375 : {
376 : // Config option mostly for autotest purposes
377 55 : if (CPLTestBool(CPLGetConfigOption(
378 : "GDAL_RASTER_PIPELINE_USE_GTIFF_FOR_TEMP_DATASET", "NO")))
379 5 : return true;
380 :
381 : // Allow up to 10% of RAM usage for temporary dataset
382 50 : const auto nRAM = CPLGetUsablePhysicalRAM() / 10;
383 50 : const int nDTSize = GDALGetDataTypeSizeBytes(eDT);
384 50 : const bool bOnDisk =
385 100 : nBands > 0 && nDTSize > 0 && nRAM > 0 &&
386 50 : static_cast<int64_t>(nWidth) * nHeight > nRAM / (nBands * nDTSize);
387 50 : return bOnDisk;
388 : }
389 :
390 : /************************************************************************/
391 : /* CreateTemporaryDataset() */
392 : /************************************************************************/
393 :
394 : std::unique_ptr<GDALDataset>
395 30 : GDALRasterPipelineNonNativelyStreamingAlgorithm::CreateTemporaryDataset(
396 : int nWidth, int nHeight, int nBands, GDALDataType eDT,
397 : bool bTiledIfPossible, GDALDataset *poSrcDSForMetadata, bool bCopyMetadata)
398 : {
399 : const bool bOnDisk =
400 30 : MustCreateOnDiskTempDataset(nWidth, nHeight, nBands, eDT);
401 30 : const char *pszDriverName = bOnDisk ? "GTIFF" : "MEM";
402 : GDALDriver *poDriver =
403 30 : GetGDALDriverManager()->GetDriverByName(pszDriverName);
404 60 : CPLStringList aosOptions;
405 60 : std::string osTmpFilename;
406 30 : if (bOnDisk)
407 : {
408 : osTmpFilename =
409 4 : CPLGenerateTempFilenameSafe(
410 : poSrcDSForMetadata
411 4 : ? CPLGetBasenameSafe(poSrcDSForMetadata->GetDescription())
412 2 : .c_str()
413 4 : : "") +
414 2 : ".tif";
415 2 : if (bTiledIfPossible)
416 2 : aosOptions.SetNameValue("TILED", "YES");
417 : const char *pszCOList =
418 2 : poDriver->GetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST);
419 : aosOptions.SetNameValue("COMPRESS",
420 2 : pszCOList && strstr(pszCOList, "ZSTD") ? "ZSTD"
421 4 : : "LZW");
422 2 : aosOptions.SetNameValue("SPARSE_OK", "YES");
423 : }
424 : std::unique_ptr<GDALDataset> poOutDS(
425 30 : poDriver ? poDriver->Create(osTmpFilename.c_str(), nWidth, nHeight,
426 30 : nBands, eDT, aosOptions.List())
427 60 : : nullptr);
428 30 : if (poOutDS && bOnDisk)
429 : {
430 : // In file systems that allow it (all but Windows...), we want to
431 : // delete the temporary file as soon as soon as possible after
432 : // having open it, so that if someone kills the process there are
433 : // no temp files left over. If that unlink() doesn't succeed
434 : // (on Windows), then the file will eventually be deleted when
435 : // poTmpDS is cleaned due to MarkSuppressOnClose().
436 0 : VSIUnlink(osTmpFilename.c_str());
437 0 : poOutDS->MarkSuppressOnClose();
438 : }
439 :
440 30 : if (poOutDS && poSrcDSForMetadata)
441 : {
442 28 : poOutDS->SetSpatialRef(poSrcDSForMetadata->GetSpatialRef());
443 28 : GDALGeoTransform gt;
444 28 : if (poSrcDSForMetadata->GetGeoTransform(gt) == CE_None)
445 27 : poOutDS->SetGeoTransform(gt);
446 28 : if (const int nGCPCount = poSrcDSForMetadata->GetGCPCount())
447 : {
448 0 : const auto apsGCPs = poSrcDSForMetadata->GetGCPs();
449 0 : if (apsGCPs)
450 : {
451 0 : poOutDS->SetGCPs(nGCPCount, apsGCPs,
452 0 : poSrcDSForMetadata->GetGCPSpatialRef());
453 : }
454 : }
455 28 : if (bCopyMetadata)
456 : {
457 14 : poOutDS->SetMetadata(poSrcDSForMetadata->GetMetadata());
458 : }
459 : }
460 :
461 60 : return poOutDS;
462 : }
463 :
464 : /************************************************************************/
465 : /* CreateTemporaryCopy() */
466 : /************************************************************************/
467 :
468 : std::unique_ptr<GDALDataset>
469 25 : GDALRasterPipelineNonNativelyStreamingAlgorithm::CreateTemporaryCopy(
470 : GDALAlgorithm *poAlg, GDALDataset *poSrcDS, int nSingleBand,
471 : bool bTiledIfPossible, GDALProgressFunc pfnProgress, void *pProgressData)
472 : {
473 25 : const int nBands = nSingleBand > 0 ? 1 : poSrcDS->GetRasterCount();
474 : const auto eDT =
475 25 : nBands ? poSrcDS->GetRasterBand(1)->GetRasterDataType() : GDT_Unknown;
476 25 : const bool bOnDisk = MustCreateOnDiskTempDataset(
477 : poSrcDS->GetRasterXSize(), poSrcDS->GetRasterYSize(), nBands, eDT);
478 25 : const char *pszDriverName = bOnDisk ? "GTIFF" : "MEM";
479 :
480 50 : CPLStringList options;
481 25 : if (nSingleBand > 0)
482 : {
483 25 : options.AddString("-b");
484 25 : options.AddString(CPLSPrintf("%d", nSingleBand));
485 : }
486 :
487 25 : options.AddString("-of");
488 25 : options.AddString(pszDriverName);
489 :
490 50 : std::string osTmpFilename;
491 25 : if (bOnDisk)
492 : {
493 : osTmpFilename =
494 3 : CPLGenerateTempFilenameSafe(
495 9 : CPLGetBasenameSafe(poSrcDS->GetDescription()).c_str()) +
496 3 : ".tif";
497 3 : if (bTiledIfPossible)
498 : {
499 3 : options.AddString("-co");
500 3 : options.AddString("TILED=YES");
501 : }
502 :
503 : GDALDriver *poDriver =
504 3 : GetGDALDriverManager()->GetDriverByName(pszDriverName);
505 : const char *pszCOList =
506 3 : poDriver ? poDriver->GetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST)
507 3 : : nullptr;
508 3 : options.AddString("-co");
509 3 : options.AddString(pszCOList && strstr(pszCOList, "ZSTD")
510 : ? "COMPRESS=ZSTD"
511 6 : : "COMPRESS=LZW");
512 : }
513 :
514 : GDALTranslateOptions *translateOptions =
515 25 : GDALTranslateOptionsNew(options.List(), nullptr);
516 :
517 25 : if (pfnProgress)
518 12 : GDALTranslateOptionsSetProgress(translateOptions, pfnProgress,
519 : pProgressData);
520 :
521 : std::unique_ptr<GDALDataset> poOutDS(GDALDataset::FromHandle(
522 : GDALTranslate(osTmpFilename.c_str(), GDALDataset::ToHandle(poSrcDS),
523 25 : translateOptions, nullptr)));
524 25 : GDALTranslateOptionsFree(translateOptions);
525 :
526 25 : if (!poOutDS)
527 : {
528 2 : poAlg->ReportError(CE_Failure, CPLE_AppDefined,
529 : "Failed to create temporary dataset");
530 : }
531 23 : else if (bOnDisk)
532 : {
533 : // In file systems that allow it (all but Windows...), we want to
534 : // delete the temporary file as soon as soon as possible after
535 : // having open it, so that if someone kills the process there are
536 : // no temp files left over. If that unlink() doesn't succeed
537 : // (on Windows), then the file will eventually be deleted when
538 : // poTmpDS is cleaned due to MarkSuppressOnClose().
539 1 : VSIUnlink(osTmpFilename.c_str());
540 1 : poOutDS->MarkSuppressOnClose();
541 : }
542 50 : return poOutDS;
543 : }
544 :
545 : //! @endcond
|