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