Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: MapServer
4 : * Purpose: Commandline App to build tile index for raster files.
5 : * Author: Frank Warmerdam, warmerdam@pobox.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2001, Frank Warmerdam, DM Solutions Group Inc
9 : * Copyright (c) 2007-2023, Even Rouault <even dot rouault at spatialys.com>
10 : *
11 : * SPDX-License-Identifier: MIT
12 : ****************************************************************************/
13 :
14 : #include "cpl_port.h"
15 : #include "cpl_conv.h"
16 : #include "cpl_minixml.h"
17 : #include "cpl_string.h"
18 : #include "gdal_utils.h"
19 : #include "gdal_priv.h"
20 : #include "gdal_utils_priv.h"
21 : #include "ogr_api.h"
22 : #include "ogrsf_frmts.h"
23 : #include "ogr_spatialref.h"
24 : #include "commonutils.h"
25 : #include "gdalargumentparser.h"
26 :
27 : #include <ctype.h>
28 :
29 : #include <algorithm>
30 : #include <cmath>
31 : #include <limits>
32 : #include <set>
33 :
34 : typedef enum
35 : {
36 : FORMAT_AUTO,
37 : FORMAT_WKT,
38 : FORMAT_EPSG,
39 : FORMAT_PROJ
40 : } SrcSRSFormat;
41 :
42 : /************************************************************************/
43 : /* GDALTileIndexRasterMetadata */
44 : /************************************************************************/
45 :
46 : struct GDALTileIndexRasterMetadata
47 : {
48 : OGRFieldType eType = OFTString;
49 : std::string osFieldName{};
50 : std::string osRasterItemName{};
51 : };
52 :
53 : /************************************************************************/
54 : /* GDALTileIndexOptions */
55 : /************************************************************************/
56 :
57 : struct GDALTileIndexOptions
58 : {
59 : bool bOverwrite = false;
60 : std::string osFormat{};
61 : std::string osIndexLayerName{};
62 : std::string osLocationField = "location";
63 : CPLStringList aosLCO{};
64 : std::string osTargetSRS{};
65 : bool bWriteAbsolutePath = false;
66 : bool bSkipDifferentProjection = false;
67 : std::string osSrcSRSFieldName{};
68 : SrcSRSFormat eSrcSRSFormat = FORMAT_AUTO;
69 : double xres = std::numeric_limits<double>::quiet_NaN();
70 : double yres = std::numeric_limits<double>::quiet_NaN();
71 : double xmin = std::numeric_limits<double>::quiet_NaN();
72 : double ymin = std::numeric_limits<double>::quiet_NaN();
73 : double xmax = std::numeric_limits<double>::quiet_NaN();
74 : double ymax = std::numeric_limits<double>::quiet_NaN();
75 : std::string osBandCount{};
76 : std::string osNodata{};
77 : std::string osColorInterp{};
78 : std::string osDataType{};
79 : bool bMaskBand = false;
80 : std::vector<std::string> aosMetadata{};
81 : std::string osGTIFilename{};
82 : bool bRecursive = false;
83 : double dfMinPixelSize = std::numeric_limits<double>::quiet_NaN();
84 : double dfMaxPixelSize = std::numeric_limits<double>::quiet_NaN();
85 : std::vector<GDALTileIndexRasterMetadata> aoFetchMD{};
86 : std::set<std::string> oSetFilenameFilters{};
87 : GDALProgressFunc pfnProgress = nullptr;
88 : void *pProgressData = nullptr;
89 : };
90 :
91 : /************************************************************************/
92 : /* GDALTileIndexAppOptionsGetParser() */
93 : /************************************************************************/
94 :
95 42 : static std::unique_ptr<GDALArgumentParser> GDALTileIndexAppOptionsGetParser(
96 : GDALTileIndexOptions *psOptions,
97 : GDALTileIndexOptionsForBinary *psOptionsForBinary)
98 : {
99 : auto argParser = std::make_unique<GDALArgumentParser>(
100 42 : "gdaltindex", /* bForBinary=*/psOptionsForBinary != nullptr);
101 :
102 42 : argParser->add_description(
103 42 : _("Build a tile index from a list of datasets."));
104 :
105 42 : argParser->add_epilog(
106 : _("For more details, see the full documentation for gdaltindex at\n"
107 42 : "https://gdal.org/programs/gdaltindex.html"));
108 :
109 42 : argParser->add_argument("-overwrite")
110 42 : .flag()
111 42 : .store_into(psOptions->bOverwrite)
112 42 : .help(_("Overwrite the output tile index file if it already exists."));
113 :
114 42 : argParser->add_argument("-recursive")
115 42 : .flag()
116 42 : .store_into(psOptions->bRecursive)
117 : .help(_("Whether directories specified in <file_or_dir> should be "
118 42 : "explored recursively."));
119 :
120 42 : argParser->add_argument("-filename_filter")
121 84 : .metavar("<val>")
122 42 : .append()
123 42 : .store_into(psOptions->oSetFilenameFilters)
124 : .help(_("Pattern that the filenames contained in directories pointed "
125 42 : "by <file_or_dir> should follow."));
126 :
127 42 : argParser->add_argument("-min_pixel_size")
128 84 : .metavar("<val>")
129 42 : .store_into(psOptions->dfMinPixelSize)
130 : .help(_("Minimum pixel size in term of geospatial extent per pixel "
131 42 : "(resolution) that a raster should have to be selected."));
132 :
133 42 : argParser->add_argument("-max_pixel_size")
134 84 : .metavar("<val>")
135 42 : .store_into(psOptions->dfMaxPixelSize)
136 : .help(_("Maximum pixel size in term of geospatial extent per pixel "
137 42 : "(resolution) that a raster should have to be selected."));
138 :
139 42 : argParser->add_output_format_argument(psOptions->osFormat);
140 :
141 42 : argParser->add_argument("-tileindex")
142 84 : .metavar("<field_name>")
143 42 : .store_into(psOptions->osLocationField)
144 42 : .help(_("Name of the layer in the tile index file."));
145 :
146 42 : argParser->add_argument("-write_absolute_path")
147 42 : .flag()
148 42 : .store_into(psOptions->bWriteAbsolutePath)
149 : .help(_("Write the absolute path of the raster files in the tile index "
150 42 : "file."));
151 :
152 42 : argParser->add_argument("-skip_different_projection")
153 42 : .flag()
154 42 : .store_into(psOptions->bSkipDifferentProjection)
155 : .help(_(
156 : "Only files with the same projection as files already inserted in "
157 42 : "the tile index will be inserted (unless -t_srs is specified)."));
158 :
159 42 : argParser->add_argument("-t_srs")
160 84 : .metavar("<srs_def>")
161 42 : .store_into(psOptions->osTargetSRS)
162 : .help(_("Geometries of input files will be transformed to the desired "
163 42 : "target coordinate reference system."));
164 :
165 42 : argParser->add_argument("-src_srs_name")
166 84 : .metavar("<field_name>")
167 42 : .store_into(psOptions->osSrcSRSFieldName)
168 : .help(_("Name of the field in the tile index file where the source SRS "
169 42 : "will be stored."));
170 :
171 42 : argParser->add_argument("-src_srs_format")
172 84 : .metavar("{AUTO|WKT|EPSG|PROJ}")
173 42 : .choices("AUTO", "WKT", "EPSG", "PROJ")
174 : .action(
175 10 : [psOptions](const auto &f)
176 : {
177 5 : if (f == "WKT")
178 1 : psOptions->eSrcSRSFormat = FORMAT_WKT;
179 4 : else if (f == "EPSG")
180 1 : psOptions->eSrcSRSFormat = FORMAT_EPSG;
181 3 : else if (f == "PROJ")
182 1 : psOptions->eSrcSRSFormat = FORMAT_PROJ;
183 : else
184 2 : psOptions->eSrcSRSFormat = FORMAT_AUTO;
185 42 : })
186 42 : .help(_("Format of the source SRS to store in the tile index file."));
187 :
188 42 : argParser->add_argument("-lyr_name")
189 84 : .metavar("<name>")
190 42 : .store_into(psOptions->osIndexLayerName)
191 42 : .help(_("Name of the layer in the tile index file."));
192 :
193 42 : argParser->add_layer_creation_options_argument(psOptions->aosLCO);
194 :
195 : // GTI driver options
196 :
197 42 : argParser->add_argument("-gti_filename")
198 84 : .metavar("<filename>")
199 42 : .store_into(psOptions->osGTIFilename)
200 42 : .help(_("Filename of the XML Virtual Tile Index file to generate."));
201 :
202 : // NOTE: no store_into
203 42 : argParser->add_argument("-tr")
204 84 : .metavar("<xres> <yres>")
205 42 : .nargs(2)
206 42 : .scan<'g', double>()
207 42 : .help(_("Set target resolution."));
208 :
209 : // NOTE: no store_into
210 42 : argParser->add_argument("-te")
211 84 : .metavar("<xmin> <ymin> <xmax> <ymax>")
212 42 : .nargs(4)
213 42 : .scan<'g', double>()
214 42 : .help(_("Set target extent in SRS unit."));
215 :
216 42 : argParser->add_argument("-ot")
217 84 : .metavar("<datatype>")
218 42 : .store_into(psOptions->osDataType)
219 42 : .help(_("Output data type."));
220 :
221 42 : argParser->add_argument("-bandcount")
222 84 : .metavar("<val>")
223 42 : .store_into(psOptions->osBandCount)
224 42 : .help(_("Number of bands of the tiles of the tile index."));
225 :
226 42 : argParser->add_argument("-nodata")
227 84 : .metavar("<val>")
228 42 : .append()
229 42 : .store_into(psOptions->osNodata)
230 42 : .help(_("Nodata value of the tiles of the tile index."));
231 :
232 : // Should we use choices here?
233 42 : argParser->add_argument("-colorinterp")
234 84 : .metavar("<val>")
235 42 : .append()
236 42 : .store_into(psOptions->osColorInterp)
237 : .help(_("Color interpretation of of the tiles of the tile index: red, "
238 42 : "green, blue, alpha, gray, undefined."));
239 :
240 42 : argParser->add_argument("-mask")
241 42 : .flag()
242 42 : .store_into(psOptions->bMaskBand)
243 42 : .help(_("Add a mask band to the tiles of the tile index."));
244 :
245 42 : argParser->add_argument("-mo")
246 84 : .metavar("<name>=<value>")
247 42 : .append()
248 42 : .store_into(psOptions->aosMetadata)
249 : .help(_("Write an arbitrary layer metadata item, for formats that "
250 42 : "support layer metadata."));
251 :
252 : // NOTE: no store_into
253 42 : argParser->add_argument("-fetch_md")
254 42 : .nargs(3)
255 84 : .metavar("<gdal_md_name> <fld_name> <fld_type>")
256 42 : .append()
257 : .help("Fetch a metadata item from the raster tile and write it as a "
258 42 : "field in the tile index.");
259 :
260 42 : if (psOptionsForBinary)
261 : {
262 6 : argParser->add_quiet_argument(&psOptionsForBinary->bQuiet);
263 :
264 6 : argParser->add_argument("index_file")
265 12 : .metavar("<index_file>")
266 6 : .store_into(psOptionsForBinary->osDest)
267 6 : .help(_("The name of the output file to create/append to."));
268 :
269 6 : argParser->add_argument("file_or_dir")
270 12 : .metavar("<file_or_dir>")
271 6 : .nargs(argparse::nargs_pattern::at_least_one)
272 14 : .action([psOptionsForBinary](const std::string &s)
273 20 : { psOptionsForBinary->aosSrcFiles.AddString(s.c_str()); })
274 : .help(_(
275 : "The input GDAL raster files or directory, can be multiple "
276 6 : "locations separated by spaces. Wildcards may also be used."));
277 : }
278 :
279 42 : return argParser;
280 : }
281 :
282 : /************************************************************************/
283 : /* GDALTileIndexAppGetParserUsage() */
284 : /************************************************************************/
285 :
286 0 : std::string GDALTileIndexAppGetParserUsage()
287 : {
288 : try
289 : {
290 0 : GDALTileIndexOptions sOptions;
291 0 : GDALTileIndexOptionsForBinary sOptionsForBinary;
292 : auto argParser =
293 0 : GDALTileIndexAppOptionsGetParser(&sOptions, &sOptionsForBinary);
294 0 : return argParser->usage();
295 : }
296 0 : catch (const std::exception &err)
297 : {
298 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unexpected exception: %s",
299 0 : err.what());
300 0 : return std::string();
301 : }
302 : }
303 :
304 : /************************************************************************/
305 : /* GDALTileIndexTileIterator */
306 : /************************************************************************/
307 :
308 : struct GDALTileIndexTileIterator
309 : {
310 : const GDALTileIndexOptions *psOptions = nullptr;
311 : int nSrcCount = 0;
312 : const char *const *papszSrcDSNames = nullptr;
313 : std::string osCurDir{};
314 : int iCurSrc = 0;
315 : VSIDIR *psDir = nullptr;
316 :
317 : CPL_DISALLOW_COPY_ASSIGN(GDALTileIndexTileIterator)
318 :
319 42 : GDALTileIndexTileIterator(const GDALTileIndexOptions *psOptionsIn,
320 : int nSrcCountIn,
321 : const char *const *papszSrcDSNamesIn)
322 42 : : psOptions(psOptionsIn), nSrcCount(nSrcCountIn),
323 42 : papszSrcDSNames(papszSrcDSNamesIn)
324 : {
325 42 : }
326 :
327 26 : void reset()
328 : {
329 26 : if (psDir)
330 4 : VSICloseDir(psDir);
331 26 : psDir = nullptr;
332 26 : iCurSrc = 0;
333 26 : }
334 :
335 1057 : std::string next()
336 : {
337 : while (true)
338 : {
339 1057 : if (!psDir)
340 : {
341 136 : if (iCurSrc == nSrcCount)
342 : {
343 42 : break;
344 : }
345 :
346 : VSIStatBufL sStatBuf;
347 94 : const char *pszCurName = papszSrcDSNames[iCurSrc++];
348 188 : if (VSIStatL(pszCurName, &sStatBuf) == 0 &&
349 94 : VSI_ISDIR(sStatBuf.st_mode))
350 : {
351 : auto poSrcDS = std::unique_ptr<GDALDataset>(
352 : GDALDataset::Open(pszCurName, GDAL_OF_RASTER, nullptr,
353 9 : nullptr, nullptr));
354 9 : if (poSrcDS)
355 0 : return pszCurName;
356 :
357 9 : osCurDir = pszCurName;
358 9 : psDir = VSIOpenDir(
359 : osCurDir.c_str(),
360 9 : /*nDepth=*/psOptions->bRecursive ? -1 : 0, nullptr);
361 9 : if (!psDir)
362 : {
363 0 : CPLError(CE_Failure, CPLE_AppDefined,
364 : "Cannot open directory %s", osCurDir.c_str());
365 0 : return std::string();
366 : }
367 : }
368 : else
369 : {
370 85 : return pszCurName;
371 : }
372 : }
373 :
374 930 : auto psEntry = VSIGetNextDirEntry(psDir);
375 930 : if (!psEntry)
376 : {
377 5 : VSICloseDir(psDir);
378 5 : psDir = nullptr;
379 5 : continue;
380 : }
381 :
382 925 : if (!psOptions->oSetFilenameFilters.empty())
383 : {
384 915 : bool bMatchFound = false;
385 : const std::string osFilenameOnly =
386 915 : CPLGetFilename(psEntry->pszName);
387 1823 : for (const auto &osFilter : psOptions->oSetFilenameFilters)
388 : {
389 915 : if (GDALPatternMatch(osFilenameOnly.c_str(),
390 : osFilter.c_str()))
391 : {
392 7 : bMatchFound = true;
393 7 : break;
394 : }
395 : }
396 915 : if (!bMatchFound)
397 908 : continue;
398 : }
399 :
400 : std::string osFilename = CPLFormFilenameSafe(
401 17 : osCurDir.c_str(), psEntry->pszName, nullptr);
402 17 : if (VSI_ISDIR(psEntry->nMode))
403 : {
404 : auto poSrcDS = std::unique_ptr<GDALDataset>(
405 : GDALDataset::Open(osFilename.c_str(), GDAL_OF_RASTER,
406 0 : nullptr, nullptr, nullptr));
407 0 : if (poSrcDS)
408 : {
409 0 : return osFilename;
410 : }
411 0 : continue;
412 : }
413 :
414 17 : return osFilename;
415 913 : }
416 42 : return std::string();
417 : }
418 : };
419 :
420 : /************************************************************************/
421 : /* GDALTileIndex() */
422 : /************************************************************************/
423 :
424 : /* clang-format off */
425 : /**
426 : * Build a tile index from a list of datasets.
427 : *
428 : * This is the equivalent of the
429 : * <a href="/programs/gdaltindex.html">gdaltindex</a> utility.
430 : *
431 : * GDALTileIndexOptions* must be allocated and freed with
432 : * GDALTileIndexOptionsNew() and GDALTileIndexOptionsFree() respectively.
433 : *
434 : * @param pszDest the destination dataset path.
435 : * @param nSrcCount the number of input datasets.
436 : * @param papszSrcDSNames the list of input dataset names
437 : * @param psOptionsIn the options struct returned by GDALTileIndexOptionsNew() or
438 : * NULL.
439 : * @param pbUsageError pointer to a integer output variable to store if any
440 : * usage error has occurred.
441 : * @return the output dataset (new dataset that must be closed using
442 : * GDALClose()) or NULL in case of error.
443 : *
444 : * @since GDAL3.9
445 : */
446 : /* clang-format on */
447 :
448 29 : GDALDatasetH GDALTileIndex(const char *pszDest, int nSrcCount,
449 : const char *const *papszSrcDSNames,
450 : const GDALTileIndexOptions *psOptionsIn,
451 : int *pbUsageError)
452 : {
453 29 : return GDALTileIndexInternal(pszDest, nullptr, nullptr, nSrcCount,
454 29 : papszSrcDSNames, psOptionsIn, pbUsageError);
455 : }
456 :
457 42 : GDALDatasetH GDALTileIndexInternal(const char *pszDest,
458 : GDALDatasetH hTileIndexDS, OGRLayerH hLayer,
459 : int nSrcCount,
460 : const char *const *papszSrcDSNames,
461 : const GDALTileIndexOptions *psOptionsIn,
462 : int *pbUsageError)
463 : {
464 42 : if (nSrcCount == 0)
465 : {
466 0 : CPLError(CE_Failure, CPLE_AppDefined, "No input dataset specified.");
467 :
468 0 : if (pbUsageError)
469 0 : *pbUsageError = TRUE;
470 0 : return nullptr;
471 : }
472 :
473 : auto psOptions = psOptionsIn
474 : ? std::make_unique<GDALTileIndexOptions>(*psOptionsIn)
475 84 : : std::make_unique<GDALTileIndexOptions>();
476 :
477 : GDALTileIndexTileIterator oGDALTileIndexTileIterator(
478 84 : psOptions.get(), nSrcCount, papszSrcDSNames);
479 :
480 : /* -------------------------------------------------------------------- */
481 : /* Create and validate target SRS if given. */
482 : /* -------------------------------------------------------------------- */
483 84 : OGRSpatialReference oTargetSRS;
484 42 : if (!psOptions->osTargetSRS.empty())
485 : {
486 7 : if (psOptions->bSkipDifferentProjection)
487 : {
488 0 : CPLError(CE_Warning, CPLE_AppDefined,
489 : "-skip_different_projections does not apply "
490 : "when -t_srs is requested.");
491 : }
492 7 : oTargetSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
493 : // coverity[tainted_data]
494 7 : oTargetSRS.SetFromUserInput(psOptions->osTargetSRS.c_str());
495 : }
496 :
497 : /* -------------------------------------------------------------------- */
498 : /* Open or create the target datasource */
499 : /* -------------------------------------------------------------------- */
500 :
501 42 : std::unique_ptr<GDALDataset> poTileIndexDSUnique;
502 42 : GDALDataset *poTileIndexDS = GDALDataset::FromHandle(hTileIndexDS);
503 42 : OGRLayer *poLayer = OGRLayer::FromHandle(hLayer);
504 42 : bool bExistingLayer = false;
505 84 : std::string osFormat;
506 :
507 42 : if (!hTileIndexDS)
508 : {
509 29 : if (psOptions->bOverwrite)
510 : {
511 5 : CPLPushErrorHandler(CPLQuietErrorHandler);
512 5 : auto hDriver = GDALIdentifyDriver(pszDest, nullptr);
513 5 : if (hDriver)
514 5 : GDALDeleteDataset(hDriver, pszDest);
515 : else
516 0 : VSIUnlink(pszDest);
517 5 : CPLPopErrorHandler();
518 : }
519 :
520 29 : poTileIndexDSUnique.reset(
521 : GDALDataset::Open(pszDest, GDAL_OF_VECTOR | GDAL_OF_UPDATE, nullptr,
522 : nullptr, nullptr));
523 :
524 29 : if (poTileIndexDSUnique != nullptr)
525 : {
526 7 : auto poDriver = poTileIndexDSUnique->GetDriver();
527 7 : if (poDriver)
528 7 : osFormat = poDriver->GetDescription();
529 :
530 7 : if (poTileIndexDSUnique->GetLayerCount() == 1)
531 : {
532 7 : poLayer = poTileIndexDSUnique->GetLayer(0);
533 : }
534 : else
535 : {
536 0 : if (psOptions->osIndexLayerName.empty())
537 : {
538 0 : CPLError(CE_Failure, CPLE_AppDefined,
539 : "Multiple layers detected: -lyr_name must be "
540 : "specified.");
541 0 : if (pbUsageError)
542 0 : *pbUsageError = true;
543 0 : return nullptr;
544 : }
545 0 : CPLPushErrorHandler(CPLQuietErrorHandler);
546 0 : poLayer = poTileIndexDSUnique->GetLayerByName(
547 0 : psOptions->osIndexLayerName.c_str());
548 0 : CPLPopErrorHandler();
549 : }
550 : }
551 : else
552 : {
553 22 : if (psOptions->osFormat.empty())
554 : {
555 : const auto aoDrivers =
556 21 : GetOutputDriversFor(pszDest, GDAL_OF_VECTOR);
557 21 : if (aoDrivers.empty())
558 : {
559 0 : CPLError(CE_Failure, CPLE_AppDefined,
560 : "Cannot guess driver for %s", pszDest);
561 0 : return nullptr;
562 : }
563 : else
564 : {
565 21 : if (aoDrivers.size() > 1)
566 : {
567 0 : CPLError(
568 : CE_Warning, CPLE_AppDefined,
569 : "Several drivers matching %s extension. Using %s",
570 0 : CPLGetExtensionSafe(pszDest).c_str(),
571 0 : aoDrivers[0].c_str());
572 : }
573 21 : osFormat = aoDrivers[0];
574 : }
575 : }
576 : else
577 : {
578 1 : osFormat = psOptions->osFormat;
579 : }
580 :
581 : auto poDriver =
582 22 : GetGDALDriverManager()->GetDriverByName(osFormat.c_str());
583 22 : if (poDriver == nullptr)
584 : {
585 0 : CPLError(CE_Warning, CPLE_AppDefined,
586 : "%s driver not available.", osFormat.c_str());
587 0 : return nullptr;
588 : }
589 :
590 22 : poTileIndexDSUnique.reset(
591 : poDriver->Create(pszDest, 0, 0, 0, GDT_Unknown, nullptr));
592 22 : if (!poTileIndexDSUnique)
593 0 : return nullptr;
594 : }
595 :
596 29 : poTileIndexDS = poTileIndexDSUnique.get();
597 : }
598 :
599 42 : auto poOutDrv = poTileIndexDS->GetDriver();
600 42 : if (osFormat.empty() && poOutDrv)
601 13 : osFormat = poOutDrv->GetDescription();
602 :
603 : const char *pszVal =
604 42 : poOutDrv ? poOutDrv->GetMetadataItem(GDAL_DMD_MAX_STRING_LENGTH)
605 42 : : nullptr;
606 42 : const int nMaxFieldSize = pszVal ? atoi(pszVal) : 0;
607 :
608 42 : if (poLayer)
609 : {
610 9 : bExistingLayer = true;
611 : }
612 : else
613 : {
614 33 : std::string osLayerName;
615 33 : if (psOptions->osIndexLayerName.empty())
616 : {
617 : VSIStatBuf sStat;
618 22 : if (EQUAL(osFormat.c_str(), "ESRI Shapefile") ||
619 3 : VSIStat(pszDest, &sStat) == 0)
620 : {
621 19 : osLayerName = CPLGetBasenameSafe(pszDest);
622 : }
623 : else
624 : {
625 0 : CPLError(CE_Failure, CPLE_AppDefined,
626 : "-lyr_name must be specified.");
627 0 : if (pbUsageError)
628 0 : *pbUsageError = true;
629 0 : return nullptr;
630 : }
631 : }
632 : else
633 : {
634 14 : if (psOptions->bOverwrite)
635 : {
636 0 : for (int i = 0; i < poTileIndexDS->GetLayerCount(); ++i)
637 : {
638 0 : auto poExistingLayer = poTileIndexDS->GetLayer(i);
639 0 : if (poExistingLayer && poExistingLayer->GetName() ==
640 0 : psOptions->osIndexLayerName)
641 : {
642 0 : if (poTileIndexDS->DeleteLayer(i) != OGRERR_NONE)
643 0 : return nullptr;
644 0 : break;
645 : }
646 : }
647 : }
648 :
649 14 : osLayerName = psOptions->osIndexLayerName;
650 : }
651 :
652 : /* get spatial reference for output file from target SRS (if set) */
653 : /* or from first input file */
654 33 : OGRSpatialReference oSRS;
655 33 : if (!oTargetSRS.IsEmpty())
656 : {
657 6 : oSRS = oTargetSRS;
658 : }
659 : else
660 : {
661 27 : std::string osFilename = oGDALTileIndexTileIterator.next();
662 27 : if (osFilename.empty())
663 : {
664 1 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find any tile");
665 1 : return nullptr;
666 : }
667 26 : oGDALTileIndexTileIterator.reset();
668 : auto poSrcDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
669 : osFilename.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
670 26 : nullptr, nullptr, nullptr));
671 26 : if (!poSrcDS)
672 0 : return nullptr;
673 :
674 26 : auto poSrcSRS = poSrcDS->GetSpatialRef();
675 26 : if (poSrcSRS)
676 26 : oSRS = *poSrcSRS;
677 : }
678 :
679 32 : poLayer = poTileIndexDS->CreateLayer(
680 32 : osLayerName.c_str(), oSRS.IsEmpty() ? nullptr : &oSRS, wkbPolygon,
681 32 : psOptions->aosLCO.List());
682 32 : if (!poLayer)
683 0 : return nullptr;
684 :
685 32 : OGRFieldDefn oLocationField(psOptions->osLocationField.c_str(),
686 32 : OFTString);
687 32 : oLocationField.SetWidth(nMaxFieldSize);
688 32 : if (poLayer->CreateField(&oLocationField) != OGRERR_NONE)
689 0 : return nullptr;
690 :
691 32 : if (!psOptions->osSrcSRSFieldName.empty())
692 : {
693 6 : OGRFieldDefn oSrcSRSField(psOptions->osSrcSRSFieldName.c_str(),
694 6 : OFTString);
695 6 : oSrcSRSField.SetWidth(nMaxFieldSize);
696 6 : if (poLayer->CreateField(&oSrcSRSField) != OGRERR_NONE)
697 0 : return nullptr;
698 : }
699 : }
700 :
701 41 : auto poLayerDefn = poLayer->GetLayerDefn();
702 :
703 46 : for (const auto &oFetchMD : psOptions->aoFetchMD)
704 : {
705 5 : if (poLayerDefn->GetFieldIndex(oFetchMD.osFieldName.c_str()) < 0)
706 : {
707 5 : OGRFieldDefn oField(oFetchMD.osFieldName.c_str(), oFetchMD.eType);
708 5 : if (poLayer->CreateField(&oField) != OGRERR_NONE)
709 0 : return nullptr;
710 : }
711 : }
712 :
713 41 : if (!psOptions->osGTIFilename.empty())
714 : {
715 2 : if (!psOptions->aosMetadata.empty())
716 : {
717 0 : CPLError(CE_Failure, CPLE_NotSupported,
718 : "-mo is not supported when -gti_filename is used");
719 0 : return nullptr;
720 : }
721 : CPLXMLNode *psRoot =
722 2 : CPLCreateXMLNode(nullptr, CXT_Element, "GDALTileIndexDataset");
723 2 : CPLCreateXMLElementAndValue(psRoot, "IndexDataset", pszDest);
724 2 : CPLCreateXMLElementAndValue(psRoot, "IndexLayer", poLayer->GetName());
725 2 : CPLCreateXMLElementAndValue(psRoot, "LocationField",
726 2 : psOptions->osLocationField.c_str());
727 2 : if (!std::isnan(psOptions->xres))
728 : {
729 1 : CPLCreateXMLElementAndValue(psRoot, "ResX",
730 1 : CPLSPrintf("%.18g", psOptions->xres));
731 1 : CPLCreateXMLElementAndValue(psRoot, "ResY",
732 1 : CPLSPrintf("%.18g", psOptions->yres));
733 : }
734 2 : if (!std::isnan(psOptions->xmin))
735 : {
736 1 : CPLCreateXMLElementAndValue(psRoot, "MinX",
737 1 : CPLSPrintf("%.18g", psOptions->xmin));
738 1 : CPLCreateXMLElementAndValue(psRoot, "MinY",
739 1 : CPLSPrintf("%.18g", psOptions->ymin));
740 1 : CPLCreateXMLElementAndValue(psRoot, "MaxX",
741 1 : CPLSPrintf("%.18g", psOptions->xmax));
742 1 : CPLCreateXMLElementAndValue(psRoot, "MaxY",
743 1 : CPLSPrintf("%.18g", psOptions->ymax));
744 : }
745 :
746 2 : int nBandCount = 0;
747 2 : if (!psOptions->osBandCount.empty())
748 : {
749 0 : nBandCount = atoi(psOptions->osBandCount.c_str());
750 : }
751 : else
752 : {
753 2 : if (!psOptions->osDataType.empty())
754 : {
755 0 : nBandCount = std::max(
756 : nBandCount,
757 0 : CPLStringList(CSLTokenizeString2(
758 0 : psOptions->osDataType.c_str(), ", ", 0))
759 0 : .size());
760 : }
761 2 : if (!psOptions->osNodata.empty())
762 : {
763 1 : nBandCount = std::max(
764 : nBandCount,
765 2 : CPLStringList(CSLTokenizeString2(
766 1 : psOptions->osNodata.c_str(), ", ", 0))
767 1 : .size());
768 : }
769 2 : if (!psOptions->osColorInterp.empty())
770 : {
771 1 : nBandCount =
772 1 : std::max(nBandCount,
773 2 : CPLStringList(
774 : CSLTokenizeString2(
775 1 : psOptions->osColorInterp.c_str(), ", ", 0))
776 1 : .size());
777 : }
778 : }
779 :
780 3 : for (int i = 0; i < nBandCount; ++i)
781 : {
782 1 : auto psBand = CPLCreateXMLNode(psRoot, CXT_Element, "Band");
783 1 : CPLAddXMLAttributeAndValue(psBand, "band", CPLSPrintf("%d", i + 1));
784 1 : if (!psOptions->osDataType.empty())
785 : {
786 : const CPLStringList aosTokens(
787 0 : CSLTokenizeString2(psOptions->osDataType.c_str(), ", ", 0));
788 0 : if (aosTokens.size() == 1)
789 0 : CPLAddXMLAttributeAndValue(psBand, "dataType",
790 : aosTokens[0]);
791 0 : else if (i < aosTokens.size())
792 0 : CPLAddXMLAttributeAndValue(psBand, "dataType",
793 : aosTokens[i]);
794 : }
795 1 : if (!psOptions->osNodata.empty())
796 : {
797 : const CPLStringList aosTokens(
798 2 : CSLTokenizeString2(psOptions->osNodata.c_str(), ", ", 0));
799 1 : if (aosTokens.size() == 1)
800 1 : CPLCreateXMLElementAndValue(psBand, "NoDataValue",
801 : aosTokens[0]);
802 0 : else if (i < aosTokens.size())
803 0 : CPLCreateXMLElementAndValue(psBand, "NoDataValue",
804 : aosTokens[i]);
805 : }
806 1 : if (!psOptions->osColorInterp.empty())
807 : {
808 : const CPLStringList aosTokens(CSLTokenizeString2(
809 2 : psOptions->osColorInterp.c_str(), ", ", 0));
810 1 : if (aosTokens.size() == 1)
811 1 : CPLCreateXMLElementAndValue(psBand, "ColorInterp",
812 : aosTokens[0]);
813 0 : else if (i < aosTokens.size())
814 0 : CPLCreateXMLElementAndValue(psBand, "ColorInterp",
815 : aosTokens[i]);
816 : }
817 : }
818 :
819 2 : if (psOptions->bMaskBand)
820 : {
821 1 : CPLCreateXMLElementAndValue(psRoot, "MaskBand", "true");
822 : }
823 : int res =
824 2 : CPLSerializeXMLTreeToFile(psRoot, psOptions->osGTIFilename.c_str());
825 2 : CPLDestroyXMLNode(psRoot);
826 2 : if (!res)
827 0 : return nullptr;
828 : }
829 : else
830 : {
831 39 : poLayer->SetMetadataItem("LOCATION_FIELD",
832 39 : psOptions->osLocationField.c_str());
833 39 : if (!std::isnan(psOptions->xres))
834 : {
835 2 : poLayer->SetMetadataItem("RESX",
836 2 : CPLSPrintf("%.18g", psOptions->xres));
837 2 : poLayer->SetMetadataItem("RESY",
838 2 : CPLSPrintf("%.18g", psOptions->yres));
839 : }
840 39 : if (!std::isnan(psOptions->xmin))
841 : {
842 2 : poLayer->SetMetadataItem("MINX",
843 2 : CPLSPrintf("%.18g", psOptions->xmin));
844 2 : poLayer->SetMetadataItem("MINY",
845 2 : CPLSPrintf("%.18g", psOptions->ymin));
846 2 : poLayer->SetMetadataItem("MAXX",
847 2 : CPLSPrintf("%.18g", psOptions->xmax));
848 2 : poLayer->SetMetadataItem("MAXY",
849 2 : CPLSPrintf("%.18g", psOptions->ymax));
850 : }
851 39 : if (!psOptions->osBandCount.empty())
852 : {
853 2 : poLayer->SetMetadataItem("BAND_COUNT",
854 2 : psOptions->osBandCount.c_str());
855 : }
856 39 : if (!psOptions->osDataType.empty())
857 : {
858 2 : poLayer->SetMetadataItem("DATA_TYPE",
859 2 : psOptions->osDataType.c_str());
860 : }
861 39 : if (!psOptions->osNodata.empty())
862 : {
863 2 : poLayer->SetMetadataItem("NODATA", psOptions->osNodata.c_str());
864 : }
865 39 : if (!psOptions->osColorInterp.empty())
866 : {
867 2 : poLayer->SetMetadataItem("COLOR_INTERPRETATION",
868 2 : psOptions->osColorInterp.c_str());
869 : }
870 39 : if (psOptions->bMaskBand)
871 : {
872 2 : poLayer->SetMetadataItem("MASK_BAND", "YES");
873 : }
874 78 : const CPLStringList aosMetadata(psOptions->aosMetadata);
875 4 : for (const auto &[pszKey, pszValue] :
876 43 : cpl::IterateNameValue(aosMetadata))
877 : {
878 2 : poLayer->SetMetadataItem(pszKey, pszValue);
879 : }
880 : }
881 :
882 : const int ti_field =
883 41 : poLayerDefn->GetFieldIndex(psOptions->osLocationField.c_str());
884 41 : if (ti_field < 0)
885 : {
886 0 : CPLError(CE_Failure, CPLE_AppDefined,
887 : "Unable to find field `%s' in file `%s'.",
888 0 : psOptions->osLocationField.c_str(), pszDest);
889 0 : return nullptr;
890 : }
891 :
892 41 : int i_SrcSRSName = -1;
893 41 : if (!psOptions->osSrcSRSFieldName.empty())
894 : {
895 : i_SrcSRSName =
896 6 : poLayerDefn->GetFieldIndex(psOptions->osSrcSRSFieldName.c_str());
897 6 : if (i_SrcSRSName < 0)
898 : {
899 0 : CPLError(CE_Failure, CPLE_AppDefined,
900 : "Unable to find field `%s' in file `%s'.",
901 0 : psOptions->osSrcSRSFieldName.c_str(), pszDest);
902 0 : return nullptr;
903 : }
904 : }
905 :
906 : // Load in memory existing file names in tile index.
907 82 : std::set<std::string> oSetExistingFiles;
908 82 : OGRSpatialReference oAlreadyExistingSRS;
909 41 : if (bExistingLayer)
910 : {
911 31 : for (auto &&poFeature : poLayer)
912 : {
913 22 : if (poFeature->IsFieldSetAndNotNull(ti_field))
914 : {
915 22 : if (oSetExistingFiles.empty())
916 : {
917 : auto poSrcDS =
918 : std::unique_ptr<GDALDataset>(GDALDataset::Open(
919 : poFeature->GetFieldAsString(ti_field),
920 18 : GDAL_OF_RASTER, nullptr, nullptr, nullptr));
921 9 : if (poSrcDS)
922 : {
923 9 : auto poSrcSRS = poSrcDS->GetSpatialRef();
924 9 : if (poSrcSRS)
925 9 : oAlreadyExistingSRS = *poSrcSRS;
926 : }
927 : }
928 22 : oSetExistingFiles.insert(poFeature->GetFieldAsString(ti_field));
929 : }
930 : }
931 : }
932 :
933 82 : std::string osCurrentPath;
934 41 : if (psOptions->bWriteAbsolutePath)
935 : {
936 2 : char *pszCurrentPath = CPLGetCurrentDir();
937 2 : if (pszCurrentPath)
938 : {
939 2 : osCurrentPath = pszCurrentPath;
940 : }
941 : else
942 : {
943 0 : CPLError(CE_Warning, CPLE_AppDefined,
944 : "This system does not support the CPLGetCurrentDir call. "
945 : "The option -bWriteAbsolutePath will have no effect.");
946 : }
947 2 : CPLFree(pszCurrentPath);
948 : }
949 :
950 : const bool bIsGTIContext =
951 79 : !std::isnan(psOptions->xres) || !std::isnan(psOptions->xmin) ||
952 38 : !psOptions->osBandCount.empty() || !psOptions->osNodata.empty() ||
953 38 : !psOptions->osColorInterp.empty() || !psOptions->osDataType.empty() ||
954 115 : psOptions->bMaskBand || !psOptions->aosMetadata.empty() ||
955 36 : !psOptions->osGTIFilename.empty();
956 :
957 : /* -------------------------------------------------------------------- */
958 : /* loop over GDAL files, processing. */
959 : /* -------------------------------------------------------------------- */
960 41 : int iCur = 0;
961 41 : int nTotal = nSrcCount + 1;
962 : while (true)
963 : {
964 117 : const std::string osSrcFilename = oGDALTileIndexTileIterator.next();
965 117 : if (osSrcFilename.empty())
966 41 : break;
967 :
968 76 : std::string osFileNameToWrite;
969 : VSIStatBuf sStatBuf;
970 :
971 : // Make sure it is a file before building absolute path name.
972 76 : if (!osCurrentPath.empty() &&
973 78 : CPLIsFilenameRelative(osSrcFilename.c_str()) &&
974 2 : VSIStat(osSrcFilename.c_str(), &sStatBuf) == 0)
975 : {
976 4 : osFileNameToWrite = CPLProjectRelativeFilenameSafe(
977 2 : osCurrentPath.c_str(), osSrcFilename.c_str());
978 : }
979 : else
980 : {
981 74 : osFileNameToWrite = osSrcFilename.c_str();
982 : }
983 :
984 : // Checks that file is not already in tileindex.
985 76 : if (oSetExistingFiles.find(osFileNameToWrite) !=
986 152 : oSetExistingFiles.end())
987 : {
988 4 : CPLError(CE_Warning, CPLE_AppDefined,
989 : "File %s is already in tileindex. Skipping it.",
990 : osFileNameToWrite.c_str());
991 4 : continue;
992 : }
993 :
994 : auto poSrcDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
995 : osSrcFilename.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
996 72 : nullptr, nullptr, nullptr));
997 72 : if (poSrcDS == nullptr)
998 : {
999 0 : CPLError(CE_Warning, CPLE_AppDefined,
1000 : "Unable to open %s, skipping.", osSrcFilename.c_str());
1001 0 : continue;
1002 : }
1003 :
1004 72 : GDALGeoTransform gt;
1005 72 : if (poSrcDS->GetGeoTransform(gt) != CE_None)
1006 : {
1007 0 : CPLError(CE_Warning, CPLE_AppDefined,
1008 : "It appears no georeferencing is available for\n"
1009 : "`%s', skipping.",
1010 : osSrcFilename.c_str());
1011 0 : continue;
1012 : }
1013 :
1014 72 : auto poSrcSRS = poSrcDS->GetSpatialRef();
1015 : // If not set target srs, test that the current file uses same
1016 : // projection as others.
1017 72 : if (oTargetSRS.IsEmpty())
1018 : {
1019 60 : if (!oAlreadyExistingSRS.IsEmpty())
1020 : {
1021 68 : if (poSrcSRS == nullptr ||
1022 34 : !poSrcSRS->IsSame(&oAlreadyExistingSRS))
1023 : {
1024 1 : CPLError(
1025 : CE_Warning, CPLE_AppDefined,
1026 : "%s is not using the same projection system "
1027 : "as other files in the tileindex.\n"
1028 : "This may cause problems when using it in MapServer "
1029 : "for example.\n"
1030 : "Use -t_srs option to set target projection system. %s",
1031 : osSrcFilename.c_str(),
1032 1 : psOptions->bSkipDifferentProjection
1033 : ? "Skipping this file."
1034 : : "");
1035 1 : if (psOptions->bSkipDifferentProjection)
1036 : {
1037 1 : continue;
1038 : }
1039 : }
1040 : }
1041 : else
1042 : {
1043 26 : if (poSrcSRS)
1044 26 : oAlreadyExistingSRS = *poSrcSRS;
1045 : }
1046 : }
1047 :
1048 71 : const int nXSize = poSrcDS->GetRasterXSize();
1049 71 : const int nYSize = poSrcDS->GetRasterYSize();
1050 71 : if (nXSize == 0 || nYSize == 0)
1051 : {
1052 0 : CPLError(CE_Warning, CPLE_AppDefined,
1053 : "%s has 0 width or height. Skipping",
1054 : osSrcFilename.c_str());
1055 0 : continue;
1056 : }
1057 :
1058 71 : double adfX[5] = {0.0, 0.0, 0.0, 0.0, 0.0};
1059 71 : double adfY[5] = {0.0, 0.0, 0.0, 0.0, 0.0};
1060 71 : adfX[0] = gt[0] + 0 * gt[1] + 0 * gt[2];
1061 71 : adfY[0] = gt[3] + 0 * gt[4] + 0 * gt[5];
1062 :
1063 71 : adfX[1] = gt[0] + nXSize * gt[1] + 0 * gt[2];
1064 71 : adfY[1] = gt[3] + nXSize * gt[4] + 0 * gt[5];
1065 :
1066 71 : adfX[2] = gt[0] + nXSize * gt[1] + nYSize * gt[2];
1067 71 : adfY[2] = gt[3] + nXSize * gt[4] + nYSize * gt[5];
1068 :
1069 71 : adfX[3] = gt[0] + 0 * gt[1] + nYSize * gt[2];
1070 71 : adfY[3] = gt[3] + 0 * gt[4] + nYSize * gt[5];
1071 :
1072 71 : adfX[4] = gt[0] + 0 * gt[1] + 0 * gt[2];
1073 71 : adfY[4] = gt[3] + 0 * gt[4] + 0 * gt[5];
1074 :
1075 : // If set target srs, do the forward transformation of all points.
1076 71 : if (!oTargetSRS.IsEmpty() && poSrcSRS)
1077 : {
1078 12 : if (!poSrcSRS->IsSame(&oTargetSRS))
1079 : {
1080 : auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
1081 7 : OGRCreateCoordinateTransformation(poSrcSRS, &oTargetSRS));
1082 7 : if (!poCT || !poCT->Transform(5, adfX, adfY, nullptr))
1083 : {
1084 0 : CPLError(CE_Warning, CPLE_AppDefined,
1085 : "unable to transform points from source "
1086 : "SRS `%s' to target SRS `%s' for file `%s' - file "
1087 : "skipped",
1088 : poSrcDS->GetProjectionRef(),
1089 0 : psOptions->osTargetSRS.c_str(),
1090 : osFileNameToWrite.c_str());
1091 0 : continue;
1092 : }
1093 : }
1094 : }
1095 71 : else if (bIsGTIContext && !oAlreadyExistingSRS.IsEmpty() &&
1096 12 : (poSrcSRS == nullptr ||
1097 12 : !poSrcSRS->IsSame(&oAlreadyExistingSRS)))
1098 : {
1099 0 : CPLError(
1100 : CE_Failure, CPLE_AppDefined,
1101 : "%s is not using the same projection system "
1102 : "as other files in the tileindex. This is not compatible of "
1103 : "GTI use. Use -t_srs option to reproject tile extents "
1104 : "to a common SRS.",
1105 : osSrcFilename.c_str());
1106 0 : return nullptr;
1107 : }
1108 :
1109 : const double dfMinX =
1110 71 : std::min(std::min(adfX[0], adfX[1]), std::min(adfX[2], adfX[3]));
1111 : const double dfMinY =
1112 71 : std::min(std::min(adfY[0], adfY[1]), std::min(adfY[2], adfY[3]));
1113 : const double dfMaxX =
1114 71 : std::max(std::max(adfX[0], adfX[1]), std::max(adfX[2], adfX[3]));
1115 : const double dfMaxY =
1116 71 : std::max(std::max(adfY[0], adfY[1]), std::max(adfY[2], adfY[3]));
1117 : const double dfRes =
1118 71 : sqrt((dfMaxX - dfMinX) * (dfMaxY - dfMinY) / nXSize / nYSize);
1119 81 : if (!std::isnan(psOptions->dfMinPixelSize) &&
1120 10 : dfRes < psOptions->dfMinPixelSize)
1121 : {
1122 5 : CPLError(CE_Warning, CPLE_AppDefined,
1123 : "%s has %f as pixel size (< %f). Skipping",
1124 5 : osSrcFilename.c_str(), dfRes, psOptions->dfMinPixelSize);
1125 5 : continue;
1126 : }
1127 74 : if (!std::isnan(psOptions->dfMaxPixelSize) &&
1128 8 : dfRes > psOptions->dfMaxPixelSize)
1129 : {
1130 4 : CPLError(CE_Warning, CPLE_AppDefined,
1131 : "%s has %f as pixel size (> %f). Skipping",
1132 4 : osSrcFilename.c_str(), dfRes, psOptions->dfMaxPixelSize);
1133 4 : continue;
1134 : }
1135 :
1136 62 : auto poFeature = std::make_unique<OGRFeature>(poLayerDefn);
1137 62 : poFeature->SetField(ti_field, osFileNameToWrite.c_str());
1138 :
1139 62 : if (i_SrcSRSName >= 0 && poSrcSRS)
1140 : {
1141 11 : const char *pszAuthorityCode = poSrcSRS->GetAuthorityCode(nullptr);
1142 11 : const char *pszAuthorityName = poSrcSRS->GetAuthorityName(nullptr);
1143 11 : if (psOptions->eSrcSRSFormat == FORMAT_AUTO)
1144 : {
1145 5 : if (pszAuthorityName != nullptr && pszAuthorityCode != nullptr)
1146 : {
1147 5 : poFeature->SetField(i_SrcSRSName,
1148 : CPLSPrintf("%s:%s", pszAuthorityName,
1149 : pszAuthorityCode));
1150 : }
1151 0 : else if (nMaxFieldSize == 0 ||
1152 0 : strlen(poSrcDS->GetProjectionRef()) <=
1153 0 : static_cast<size_t>(nMaxFieldSize))
1154 : {
1155 0 : poFeature->SetField(i_SrcSRSName,
1156 : poSrcDS->GetProjectionRef());
1157 : }
1158 : else
1159 : {
1160 0 : char *pszProj4 = nullptr;
1161 0 : if (poSrcSRS->exportToProj4(&pszProj4) == OGRERR_NONE)
1162 : {
1163 0 : poFeature->SetField(i_SrcSRSName, pszProj4);
1164 : }
1165 : else
1166 : {
1167 0 : poFeature->SetField(i_SrcSRSName,
1168 : poSrcDS->GetProjectionRef());
1169 : }
1170 0 : CPLFree(pszProj4);
1171 : }
1172 : }
1173 6 : else if (psOptions->eSrcSRSFormat == FORMAT_WKT)
1174 : {
1175 4 : if (nMaxFieldSize == 0 ||
1176 2 : strlen(poSrcDS->GetProjectionRef()) <=
1177 2 : static_cast<size_t>(nMaxFieldSize))
1178 : {
1179 0 : poFeature->SetField(i_SrcSRSName,
1180 : poSrcDS->GetProjectionRef());
1181 : }
1182 : else
1183 : {
1184 2 : CPLError(CE_Warning, CPLE_AppDefined,
1185 : "Cannot write WKT for file %s as it is too long!",
1186 : osFileNameToWrite.c_str());
1187 : }
1188 : }
1189 4 : else if (psOptions->eSrcSRSFormat == FORMAT_PROJ)
1190 : {
1191 2 : char *pszProj4 = nullptr;
1192 2 : if (poSrcSRS->exportToProj4(&pszProj4) == OGRERR_NONE)
1193 : {
1194 2 : poFeature->SetField(i_SrcSRSName, pszProj4);
1195 : }
1196 2 : CPLFree(pszProj4);
1197 : }
1198 2 : else if (psOptions->eSrcSRSFormat == FORMAT_EPSG)
1199 : {
1200 2 : if (pszAuthorityName != nullptr && pszAuthorityCode != nullptr)
1201 2 : poFeature->SetField(i_SrcSRSName,
1202 : CPLSPrintf("%s:%s", pszAuthorityName,
1203 : pszAuthorityCode));
1204 : }
1205 : }
1206 :
1207 67 : for (const auto &oFetchMD : psOptions->aoFetchMD)
1208 : {
1209 5 : if (EQUAL(oFetchMD.osRasterItemName.c_str(), "{PIXEL_SIZE}"))
1210 : {
1211 1 : poFeature->SetField(oFetchMD.osFieldName.c_str(), dfRes);
1212 1 : continue;
1213 : }
1214 :
1215 : const char *pszMD =
1216 4 : poSrcDS->GetMetadataItem(oFetchMD.osRasterItemName.c_str());
1217 4 : if (pszMD)
1218 : {
1219 4 : if (EQUAL(oFetchMD.osRasterItemName.c_str(),
1220 : "TIFFTAG_DATETIME"))
1221 : {
1222 : int nYear, nMonth, nDay, nHour, nMin, nSec;
1223 2 : if (sscanf(pszMD, "%04d:%02d:%02d %02d:%02d:%02d", &nYear,
1224 1 : &nMonth, &nDay, &nHour, &nMin, &nSec) == 6)
1225 : {
1226 1 : poFeature->SetField(
1227 : oFetchMD.osFieldName.c_str(),
1228 : CPLSPrintf("%04d/%02d/%02d %02d:%02d:%02d", nYear,
1229 : nMonth, nDay, nHour, nMin, nSec));
1230 1 : continue;
1231 : }
1232 : }
1233 3 : poFeature->SetField(oFetchMD.osFieldName.c_str(), pszMD);
1234 : }
1235 : }
1236 :
1237 62 : auto poPoly = std::make_unique<OGRPolygon>();
1238 62 : auto poRing = std::make_unique<OGRLinearRing>();
1239 372 : for (int k = 0; k < 5; k++)
1240 310 : poRing->addPoint(adfX[k], adfY[k]);
1241 62 : poPoly->addRing(std::move(poRing));
1242 62 : poFeature->SetGeometryDirectly(poPoly.release());
1243 :
1244 62 : if (poLayer->CreateFeature(poFeature.get()) != OGRERR_NONE)
1245 : {
1246 0 : CPLError(CE_Failure, CPLE_AppDefined,
1247 : "Failed to create feature in tile index.");
1248 0 : return nullptr;
1249 : }
1250 :
1251 62 : ++iCur;
1252 77 : if (psOptions->pfnProgress &&
1253 30 : !psOptions->pfnProgress(static_cast<double>(iCur) / nTotal, "",
1254 15 : psOptions->pProgressData))
1255 : {
1256 0 : return nullptr;
1257 : }
1258 62 : if (iCur >= nSrcCount)
1259 39 : ++nTotal;
1260 76 : }
1261 41 : if (psOptions->pfnProgress)
1262 7 : psOptions->pfnProgress(1.0, "", psOptions->pProgressData);
1263 :
1264 41 : if (poTileIndexDSUnique)
1265 28 : return GDALDataset::ToHandle(poTileIndexDSUnique.release());
1266 : else
1267 13 : return GDALDataset::ToHandle(poTileIndexDS);
1268 : }
1269 :
1270 : /************************************************************************/
1271 : /* SanitizeSRS */
1272 : /************************************************************************/
1273 :
1274 7 : static char *SanitizeSRS(const char *pszUserInput)
1275 :
1276 : {
1277 : OGRSpatialReferenceH hSRS;
1278 7 : char *pszResult = nullptr;
1279 :
1280 7 : CPLErrorReset();
1281 :
1282 7 : hSRS = OSRNewSpatialReference(nullptr);
1283 7 : if (OSRSetFromUserInput(hSRS, pszUserInput) == OGRERR_NONE)
1284 7 : OSRExportToWkt(hSRS, &pszResult);
1285 : else
1286 : {
1287 0 : CPLError(CE_Failure, CPLE_AppDefined, "Translating SRS failed:\n%s",
1288 : pszUserInput);
1289 : }
1290 :
1291 7 : OSRDestroySpatialReference(hSRS);
1292 :
1293 7 : return pszResult;
1294 : }
1295 :
1296 : /************************************************************************/
1297 : /* GDALTileIndexOptionsNew() */
1298 : /************************************************************************/
1299 :
1300 : /**
1301 : * Allocates a GDALTileIndexOptions struct.
1302 : *
1303 : * @param papszArgv NULL terminated list of options (potentially including
1304 : * filename and open options too), or NULL. The accepted options are the ones of
1305 : * the <a href="/programs/gdaltindex.html">gdaltindex</a> utility.
1306 : * @param psOptionsForBinary (output) may be NULL (and should generally be
1307 : * NULL), otherwise (gdaltindex_bin.cpp use case) must be allocated with
1308 : * GDALTileIndexOptionsForBinaryNew() prior to this function. Will be filled
1309 : * with potentially present filename, open options,...
1310 : * @return pointer to the allocated GDALTileIndexOptions struct. Must be freed
1311 : * with GDALTileIndexOptionsFree().
1312 : *
1313 : * @since GDAL 3.9
1314 : */
1315 :
1316 : GDALTileIndexOptions *
1317 42 : GDALTileIndexOptionsNew(char **papszArgv,
1318 : GDALTileIndexOptionsForBinary *psOptionsForBinary)
1319 : {
1320 84 : auto psOptions = std::make_unique<GDALTileIndexOptions>();
1321 :
1322 : /* -------------------------------------------------------------------- */
1323 : /* Parse arguments. */
1324 : /* -------------------------------------------------------------------- */
1325 :
1326 84 : CPLStringList aosArgv;
1327 :
1328 42 : if (papszArgv)
1329 : {
1330 42 : const int nArgc = CSLCount(papszArgv);
1331 321 : for (int i = 0; i < nArgc; i++)
1332 : {
1333 279 : aosArgv.AddString(papszArgv[i]);
1334 : }
1335 : }
1336 :
1337 : try
1338 : {
1339 : auto argParser = GDALTileIndexAppOptionsGetParser(psOptions.get(),
1340 42 : psOptionsForBinary);
1341 42 : argParser->parse_args_without_binary_name(aosArgv.List());
1342 :
1343 : // Check all no store_into args
1344 45 : if (auto oTr = argParser->present<std::vector<double>>("-tr"))
1345 : {
1346 3 : psOptions->xres = (*oTr)[0];
1347 3 : psOptions->yres = (*oTr)[1];
1348 : }
1349 :
1350 45 : if (auto oTargetExtent = argParser->present<std::vector<double>>("-te"))
1351 : {
1352 3 : psOptions->xmin = (*oTargetExtent)[0];
1353 3 : psOptions->ymin = (*oTargetExtent)[1];
1354 3 : psOptions->xmax = (*oTargetExtent)[2];
1355 3 : psOptions->ymax = (*oTargetExtent)[3];
1356 : }
1357 :
1358 42 : if (auto fetchMd =
1359 42 : argParser->present<std::vector<std::string>>("-fetch_md"))
1360 : {
1361 :
1362 3 : CPLAssert(fetchMd->size() % 3 == 0);
1363 :
1364 : // Loop
1365 8 : for (size_t i = 0; i < fetchMd->size(); i += 3)
1366 : {
1367 : OGRFieldType type;
1368 5 : const auto &typeName{fetchMd->at(i + 2)};
1369 5 : if (typeName == "String")
1370 : {
1371 3 : type = OFTString;
1372 : }
1373 2 : else if (typeName == "Integer")
1374 : {
1375 0 : type = OFTInteger;
1376 : }
1377 2 : else if (typeName == "Integer64")
1378 : {
1379 0 : type = OFTInteger64;
1380 : }
1381 2 : else if (typeName == "Real")
1382 : {
1383 1 : type = OFTReal;
1384 : }
1385 1 : else if (typeName == "Date")
1386 : {
1387 0 : type = OFTDate;
1388 : }
1389 1 : else if (typeName == "DateTime")
1390 : {
1391 1 : type = OFTDateTime;
1392 : }
1393 : else
1394 : {
1395 0 : CPLError(CE_Failure, CPLE_AppDefined,
1396 : "-fetch_md requires a valid type name as third "
1397 : "argument: %s was given.",
1398 0 : fetchMd->at(i).c_str());
1399 0 : return nullptr;
1400 : }
1401 :
1402 5 : const GDALTileIndexRasterMetadata oMD{type, fetchMd->at(i + 1),
1403 15 : fetchMd->at(i)};
1404 5 : psOptions->aoFetchMD.push_back(std::move(oMD));
1405 : }
1406 : }
1407 :
1408 : // Check -t_srs
1409 42 : if (!psOptions->osTargetSRS.empty())
1410 : {
1411 7 : auto sanitized{SanitizeSRS(psOptions->osTargetSRS.c_str())};
1412 7 : if (sanitized)
1413 : {
1414 7 : psOptions->osTargetSRS = sanitized;
1415 7 : CPLFree(sanitized);
1416 : }
1417 : else
1418 : {
1419 : // Error was already reported by SanitizeSRS, just return nullptr
1420 0 : psOptions->osTargetSRS.clear();
1421 0 : return nullptr;
1422 : }
1423 : }
1424 : }
1425 0 : catch (const std::exception &error)
1426 : {
1427 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s", error.what());
1428 0 : return nullptr;
1429 : }
1430 :
1431 42 : return psOptions.release();
1432 : }
1433 :
1434 : /************************************************************************/
1435 : /* GDALTileIndexOptionsFree() */
1436 : /************************************************************************/
1437 :
1438 : /**
1439 : * Frees the GDALTileIndexOptions struct.
1440 : *
1441 : * @param psOptions the options struct for GDALTileIndex().
1442 : *
1443 : * @since GDAL 3.9
1444 : */
1445 :
1446 42 : void GDALTileIndexOptionsFree(GDALTileIndexOptions *psOptions)
1447 : {
1448 42 : delete psOptions;
1449 42 : }
1450 :
1451 : /************************************************************************/
1452 : /* GDALTileIndexOptionsSetProgress() */
1453 : /************************************************************************/
1454 :
1455 : /**
1456 : * Set a progress function.
1457 : *
1458 : * @param psOptions the options struct for GDALTileIndex().
1459 : * @param pfnProgress the progress callback.
1460 : * @param pProgressData the user data for the progress callback.
1461 : *
1462 : * @since GDAL 3.11
1463 : */
1464 :
1465 19 : void GDALTileIndexOptionsSetProgress(GDALTileIndexOptions *psOptions,
1466 : GDALProgressFunc pfnProgress,
1467 : void *pProgressData)
1468 : {
1469 19 : psOptions->pfnProgress = pfnProgress;
1470 19 : psOptions->pProgressData = pProgressData;
1471 19 : }
1472 :
1473 : #undef CHECK_HAS_ENOUGH_ADDITIONAL_ARGS
|