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 30 : static std::unique_ptr<GDALArgumentParser> GDALTileIndexAppOptionsGetParser(
158 : GDALTileIndexOptions *psOptions,
159 : GDALTileIndexOptionsForBinary *psOptionsForBinary)
160 : {
161 : auto argParser = std::make_unique<GDALArgumentParser>(
162 30 : "gdaltindex", /* bForBinary=*/psOptionsForBinary != nullptr);
163 :
164 30 : argParser->add_description(
165 30 : _("Build a tile index from a list of datasets."));
166 :
167 30 : argParser->add_epilog(
168 : _("For more details, see the full documentation for gdaltindex at\n"
169 30 : "https://gdal.org/programs/gdaltindex.html"));
170 :
171 30 : argParser->add_argument("-overwrite")
172 30 : .flag()
173 30 : .store_into(psOptions->bOverwrite)
174 30 : .help(_("Overwrite the output tile index file if it already exists."));
175 :
176 30 : argParser->add_argument("-recursive")
177 30 : .flag()
178 30 : .store_into(psOptions->bRecursive)
179 : .help(_("Whether directories specified in <file_or_dir> should be "
180 30 : "explored recursively."));
181 :
182 30 : argParser->add_argument("-filename_filter")
183 60 : .metavar("<val>")
184 30 : .append()
185 30 : .store_into(psOptions->oSetFilenameFilters)
186 : .help(_("Pattern that the filenames contained in directories pointed "
187 30 : "by <file_or_dir> should follow."));
188 :
189 30 : argParser->add_argument("-min_pixel_size")
190 60 : .metavar("<val>")
191 30 : .store_into(psOptions->dfMinPixelSize)
192 : .help(_("Minimum pixel size in term of geospatial extent per pixel "
193 30 : "(resolution) that a raster should have to be selected."));
194 :
195 30 : argParser->add_argument("-max_pixel_size")
196 60 : .metavar("<val>")
197 30 : .store_into(psOptions->dfMaxPixelSize)
198 : .help(_("Maximum pixel size in term of geospatial extent per pixel "
199 30 : "(resolution) that a raster should have to be selected."));
200 :
201 30 : argParser->add_output_format_argument(psOptions->osFormat);
202 :
203 30 : argParser->add_argument("-tileindex")
204 60 : .metavar("<field_name>")
205 30 : .store_into(psOptions->osLocationField)
206 30 : .help(_("Name of the layer in the tile index file."));
207 :
208 30 : argParser->add_argument("-write_absolute_path")
209 30 : .flag()
210 30 : .store_into(psOptions->bWriteAbsolutePath)
211 : .help(_("Write the absolute path of the raster files in the tile index "
212 30 : "file."));
213 :
214 30 : argParser->add_argument("-skip_different_projection")
215 30 : .flag()
216 30 : .store_into(psOptions->bSkipDifferentProjection)
217 : .help(_(
218 : "Only files with the same projection as files already inserted in "
219 30 : "the tile index will be inserted (unless -t_srs is specified)."));
220 :
221 30 : argParser->add_argument("-t_srs")
222 60 : .metavar("<srs_def>")
223 30 : .store_into(psOptions->osTargetSRS)
224 : .help(_("Geometries of input files will be transformed to the desired "
225 30 : "target coordinate reference system."));
226 :
227 30 : argParser->add_argument("-src_srs_name")
228 60 : .metavar("<field_name>")
229 30 : .store_into(psOptions->osSrcSRSFieldName)
230 : .help(_("Name of the field in the tile index file where the source SRS "
231 30 : "will be stored."));
232 :
233 30 : argParser->add_argument("-src_srs_format")
234 60 : .metavar("{AUTO|WKT|EPSG|PROJ}")
235 30 : .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 30 : })
248 30 : .help(_("Format of the source SRS to store in the tile index file."));
249 :
250 30 : argParser->add_argument("-lyr_name")
251 60 : .metavar("<name>")
252 30 : .store_into(psOptions->osIndexLayerName)
253 30 : .help(_("Name of the layer in the tile index file."));
254 :
255 30 : argParser->add_layer_creation_options_argument(psOptions->aosLCO);
256 :
257 : // GTI driver options
258 :
259 30 : argParser->add_argument("-gti_filename")
260 60 : .metavar("<filename>")
261 30 : .store_into(psOptions->osGTIFilename)
262 30 : .help(_("Filename of the XML Virtual Tile Index file to generate."));
263 :
264 : // NOTE: no store_into
265 30 : argParser->add_argument("-tr")
266 60 : .metavar("<xres> <yres>")
267 30 : .nargs(2)
268 30 : .scan<'g', double>()
269 30 : .help(_("Set target resolution."));
270 :
271 : // NOTE: no store_into
272 30 : argParser->add_argument("-te")
273 60 : .metavar("<xmin> <ymin> <xmax> <ymax>")
274 30 : .nargs(4)
275 30 : .scan<'g', double>()
276 30 : .help(_("Set target extent in SRS unit."));
277 :
278 30 : argParser->add_argument("-ot")
279 60 : .metavar("<datatype>")
280 30 : .store_into(psOptions->osDataType)
281 30 : .help(_("Output data type."));
282 :
283 30 : argParser->add_argument("-bandcount")
284 60 : .metavar("<val>")
285 30 : .store_into(psOptions->osBandCount)
286 30 : .help(_("Number of bands of the tiles of the tile index."));
287 :
288 30 : argParser->add_argument("-nodata")
289 60 : .metavar("<val>")
290 30 : .append()
291 30 : .store_into(psOptions->osNodata)
292 30 : .help(_("Nodata value of the tiles of the tile index."));
293 :
294 : // Should we use choices here?
295 30 : argParser->add_argument("-colorinterp")
296 60 : .metavar("<val>")
297 30 : .append()
298 30 : .store_into(psOptions->osColorInterp)
299 : .help(_("Color interpretation of of the tiles of the tile index: red, "
300 30 : "green, blue, alpha, gray, undefined."));
301 :
302 30 : argParser->add_argument("-mask")
303 30 : .flag()
304 30 : .store_into(psOptions->bMaskBand)
305 30 : .help(_("Add a mask band to the tiles of the tile index."));
306 :
307 30 : argParser->add_argument("-mo")
308 60 : .metavar("<name>=<value>")
309 30 : .append()
310 30 : .store_into(psOptions->aosMetadata)
311 : .help(_("Write an arbitrary layer metadata item, for formats that "
312 30 : "support layer metadata."));
313 :
314 : // NOTE: no store_into
315 30 : argParser->add_argument("-fetch_md")
316 30 : .nargs(3)
317 60 : .metavar("<gdal_md_name> <fld_name> <fld_type>")
318 30 : .append()
319 : .help("Fetch a metadata item from the raster tile and write it as a "
320 30 : "field in the tile index.");
321 :
322 30 : if (psOptionsForBinary)
323 : {
324 7 : argParser->add_quiet_argument(&psOptionsForBinary->bQuiet);
325 :
326 7 : argParser->add_argument("index_file")
327 14 : .metavar("<index_file>")
328 7 : .store_into(psOptionsForBinary->osDest)
329 7 : .help(_("The name of the output file to create/append to."));
330 :
331 7 : argParser->add_argument("file_or_dir")
332 14 : .metavar("<file_or_dir>")
333 7 : .nargs(argparse::nargs_pattern::at_least_one)
334 14 : .action([psOptionsForBinary](const std::string &s)
335 21 : { psOptionsForBinary->aosSrcFiles.AddString(s.c_str()); })
336 : .help(_(
337 : "The input GDAL raster files or directory, can be multiple "
338 7 : "locations separated by spaces. Wildcards may also be used."));
339 : }
340 :
341 30 : 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 =
462 15 : CPLFormFilename(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 : CPLGetExtension(pszDest), aoDrivers[0].c_str());
611 : }
612 21 : osFormat = aoDrivers[0];
613 : }
614 : }
615 : else
616 : {
617 1 : osFormat = psOptions->osFormat;
618 : }
619 22 : if (!EQUAL(osFormat.c_str(), "ESRI Shapefile"))
620 6 : nMaxFieldSize = 0;
621 :
622 : auto poDriver =
623 22 : GetGDALDriverManager()->GetDriverByName(osFormat.c_str());
624 22 : if (poDriver == nullptr)
625 : {
626 0 : CPLError(CE_Warning, CPLE_AppDefined, "%s driver not available.",
627 : osFormat.c_str());
628 0 : return nullptr;
629 : }
630 :
631 22 : poTileIndexDS.reset(
632 : poDriver->Create(pszDest, 0, 0, 0, GDT_Unknown, nullptr));
633 22 : if (!poTileIndexDS)
634 0 : return nullptr;
635 : }
636 :
637 29 : if (poLayer)
638 : {
639 7 : bExistingLayer = true;
640 : }
641 : else
642 : {
643 22 : std::string osLayerName;
644 22 : if (psOptions->osIndexLayerName.empty())
645 : {
646 : VSIStatBuf sStat;
647 22 : if (EQUAL(osFormat.c_str(), "ESRI Shapefile") ||
648 3 : VSIStat(pszDest, &sStat) == 0)
649 : {
650 19 : osLayerName = CPLGetBasename(pszDest);
651 : }
652 : else
653 : {
654 0 : CPLError(CE_Failure, CPLE_AppDefined,
655 : "-lyr_name must be specified.");
656 0 : if (pbUsageError)
657 0 : *pbUsageError = true;
658 0 : return nullptr;
659 : }
660 : }
661 : else
662 : {
663 3 : if (psOptions->bOverwrite)
664 : {
665 0 : for (int i = 0; i < poTileIndexDS->GetLayerCount(); ++i)
666 : {
667 0 : auto poExistingLayer = poTileIndexDS->GetLayer(i);
668 0 : if (poExistingLayer && poExistingLayer->GetName() ==
669 0 : psOptions->osIndexLayerName)
670 : {
671 0 : poTileIndexDS->DeleteLayer(i);
672 0 : break;
673 : }
674 : }
675 : }
676 :
677 3 : osLayerName = psOptions->osIndexLayerName;
678 : }
679 :
680 : /* get spatial reference for output file from target SRS (if set) */
681 : /* or from first input file */
682 22 : OGRSpatialReference oSRS;
683 22 : if (!oTargetSRS.IsEmpty())
684 : {
685 5 : oSRS = oTargetSRS;
686 : }
687 : else
688 : {
689 17 : std::string osFilename = oGDALTileIndexTileIterator.next();
690 17 : if (osFilename.empty())
691 : {
692 1 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find any tile");
693 1 : return nullptr;
694 : }
695 16 : oGDALTileIndexTileIterator.reset();
696 : auto poSrcDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
697 : osFilename.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
698 16 : nullptr, nullptr, nullptr));
699 16 : if (!poSrcDS)
700 0 : return nullptr;
701 :
702 16 : auto poSrcSRS = poSrcDS->GetSpatialRef();
703 16 : if (poSrcSRS)
704 16 : oSRS = *poSrcSRS;
705 : }
706 :
707 42 : poLayer = poTileIndexDS->CreateLayer(
708 21 : osLayerName.c_str(), oSRS.IsEmpty() ? nullptr : &oSRS, wkbPolygon,
709 21 : psOptions->aosLCO.List());
710 21 : if (!poLayer)
711 0 : return nullptr;
712 :
713 21 : OGRFieldDefn oLocationField(psOptions->osLocationField.c_str(),
714 21 : OFTString);
715 21 : oLocationField.SetWidth(nMaxFieldSize);
716 21 : if (poLayer->CreateField(&oLocationField) != OGRERR_NONE)
717 0 : return nullptr;
718 :
719 21 : if (!psOptions->osSrcSRSFieldName.empty())
720 : {
721 5 : OGRFieldDefn oSrcSRSField(psOptions->osSrcSRSFieldName.c_str(),
722 5 : OFTString);
723 5 : oSrcSRSField.SetWidth(nMaxFieldSize);
724 5 : if (poLayer->CreateField(&oSrcSRSField) != OGRERR_NONE)
725 0 : return nullptr;
726 : }
727 : }
728 :
729 28 : auto poLayerDefn = poLayer->GetLayerDefn();
730 :
731 32 : for (const auto &oFetchMD : psOptions->aoFetchMD)
732 : {
733 4 : if (poLayerDefn->GetFieldIndex(oFetchMD.osFieldName.c_str()) < 0)
734 : {
735 4 : OGRFieldDefn oField(oFetchMD.osFieldName.c_str(), oFetchMD.eType);
736 4 : if (poLayer->CreateField(&oField) != OGRERR_NONE)
737 0 : return nullptr;
738 : }
739 : }
740 :
741 28 : if (!psOptions->osGTIFilename.empty())
742 : {
743 1 : if (!psOptions->aosMetadata.empty())
744 : {
745 0 : CPLError(CE_Failure, CPLE_NotSupported,
746 : "-mo is not supported when -gti_filename is used");
747 0 : return nullptr;
748 : }
749 : CPLXMLNode *psRoot =
750 1 : CPLCreateXMLNode(nullptr, CXT_Element, "GDALTileIndexDataset");
751 1 : CPLCreateXMLElementAndValue(psRoot, "IndexDataset", pszDest);
752 1 : CPLCreateXMLElementAndValue(psRoot, "IndexLayer", poLayer->GetName());
753 1 : CPLCreateXMLElementAndValue(psRoot, "LocationField",
754 1 : psOptions->osLocationField.c_str());
755 1 : if (!std::isnan(psOptions->xres))
756 : {
757 1 : CPLCreateXMLElementAndValue(psRoot, "ResX",
758 1 : CPLSPrintf("%.18g", psOptions->xres));
759 1 : CPLCreateXMLElementAndValue(psRoot, "ResY",
760 1 : CPLSPrintf("%.18g", psOptions->yres));
761 : }
762 1 : if (!std::isnan(psOptions->xmin))
763 : {
764 1 : CPLCreateXMLElementAndValue(psRoot, "MinX",
765 1 : CPLSPrintf("%.18g", psOptions->xmin));
766 1 : CPLCreateXMLElementAndValue(psRoot, "MinY",
767 1 : CPLSPrintf("%.18g", psOptions->ymin));
768 1 : CPLCreateXMLElementAndValue(psRoot, "MaxX",
769 1 : CPLSPrintf("%.18g", psOptions->xmax));
770 1 : CPLCreateXMLElementAndValue(psRoot, "MaxY",
771 1 : CPLSPrintf("%.18g", psOptions->ymax));
772 : }
773 :
774 1 : int nBandCount = 0;
775 1 : if (!psOptions->osBandCount.empty())
776 : {
777 0 : nBandCount = atoi(psOptions->osBandCount.c_str());
778 : }
779 : else
780 : {
781 1 : if (!psOptions->osDataType.empty())
782 : {
783 0 : nBandCount = std::max(
784 : nBandCount,
785 0 : CPLStringList(CSLTokenizeString2(
786 0 : psOptions->osDataType.c_str(), ", ", 0))
787 0 : .size());
788 : }
789 1 : if (!psOptions->osNodata.empty())
790 : {
791 1 : nBandCount = std::max(
792 : nBandCount,
793 2 : CPLStringList(CSLTokenizeString2(
794 1 : psOptions->osNodata.c_str(), ", ", 0))
795 1 : .size());
796 : }
797 1 : if (!psOptions->osColorInterp.empty())
798 : {
799 1 : nBandCount =
800 1 : std::max(nBandCount,
801 2 : CPLStringList(
802 : CSLTokenizeString2(
803 1 : psOptions->osColorInterp.c_str(), ", ", 0))
804 1 : .size());
805 : }
806 : }
807 :
808 2 : for (int i = 0; i < nBandCount; ++i)
809 : {
810 1 : auto psBand = CPLCreateXMLNode(psRoot, CXT_Element, "Band");
811 1 : CPLAddXMLAttributeAndValue(psBand, "band", CPLSPrintf("%d", i + 1));
812 1 : if (!psOptions->osDataType.empty())
813 : {
814 : const CPLStringList aosTokens(
815 0 : CSLTokenizeString2(psOptions->osDataType.c_str(), ", ", 0));
816 0 : if (aosTokens.size() == 1)
817 0 : CPLAddXMLAttributeAndValue(psBand, "dataType",
818 : aosTokens[0]);
819 0 : else if (i < aosTokens.size())
820 0 : CPLAddXMLAttributeAndValue(psBand, "dataType",
821 : aosTokens[i]);
822 : }
823 1 : if (!psOptions->osNodata.empty())
824 : {
825 : const CPLStringList aosTokens(
826 2 : CSLTokenizeString2(psOptions->osNodata.c_str(), ", ", 0));
827 1 : if (aosTokens.size() == 1)
828 1 : CPLCreateXMLElementAndValue(psBand, "NoDataValue",
829 : aosTokens[0]);
830 0 : else if (i < aosTokens.size())
831 0 : CPLCreateXMLElementAndValue(psBand, "NoDataValue",
832 : aosTokens[i]);
833 : }
834 1 : if (!psOptions->osColorInterp.empty())
835 : {
836 : const CPLStringList aosTokens(CSLTokenizeString2(
837 2 : psOptions->osColorInterp.c_str(), ", ", 0));
838 1 : if (aosTokens.size() == 1)
839 1 : CPLCreateXMLElementAndValue(psBand, "ColorInterp",
840 : aosTokens[0]);
841 0 : else if (i < aosTokens.size())
842 0 : CPLCreateXMLElementAndValue(psBand, "ColorInterp",
843 : aosTokens[i]);
844 : }
845 : }
846 :
847 1 : if (psOptions->bMaskBand)
848 : {
849 1 : CPLCreateXMLElementAndValue(psRoot, "MaskBand", "true");
850 : }
851 : int res =
852 1 : CPLSerializeXMLTreeToFile(psRoot, psOptions->osGTIFilename.c_str());
853 1 : CPLDestroyXMLNode(psRoot);
854 1 : if (!res)
855 0 : return nullptr;
856 : }
857 : else
858 : {
859 27 : poLayer->SetMetadataItem("LOCATION_FIELD",
860 27 : psOptions->osLocationField.c_str());
861 27 : if (!std::isnan(psOptions->xres))
862 : {
863 1 : poLayer->SetMetadataItem("RESX",
864 1 : CPLSPrintf("%.18g", psOptions->xres));
865 1 : poLayer->SetMetadataItem("RESY",
866 1 : CPLSPrintf("%.18g", psOptions->yres));
867 : }
868 27 : if (!std::isnan(psOptions->xmin))
869 : {
870 1 : poLayer->SetMetadataItem("MINX",
871 1 : CPLSPrintf("%.18g", psOptions->xmin));
872 1 : poLayer->SetMetadataItem("MINY",
873 1 : CPLSPrintf("%.18g", psOptions->ymin));
874 1 : poLayer->SetMetadataItem("MAXX",
875 1 : CPLSPrintf("%.18g", psOptions->xmax));
876 1 : poLayer->SetMetadataItem("MAXY",
877 1 : CPLSPrintf("%.18g", psOptions->ymax));
878 : }
879 27 : if (!psOptions->osBandCount.empty())
880 : {
881 1 : poLayer->SetMetadataItem("BAND_COUNT",
882 1 : psOptions->osBandCount.c_str());
883 : }
884 27 : if (!psOptions->osDataType.empty())
885 : {
886 1 : poLayer->SetMetadataItem("DATA_TYPE",
887 1 : psOptions->osDataType.c_str());
888 : }
889 27 : if (!psOptions->osNodata.empty())
890 : {
891 1 : poLayer->SetMetadataItem("NODATA", psOptions->osNodata.c_str());
892 : }
893 27 : if (!psOptions->osColorInterp.empty())
894 : {
895 1 : poLayer->SetMetadataItem("COLOR_INTERPRETATION",
896 1 : psOptions->osColorInterp.c_str());
897 : }
898 27 : if (psOptions->bMaskBand)
899 : {
900 1 : poLayer->SetMetadataItem("MASK_BAND", "YES");
901 : }
902 54 : const CPLStringList aosMetadata(psOptions->aosMetadata);
903 2 : for (const auto &[pszKey, pszValue] :
904 29 : cpl::IterateNameValue(aosMetadata))
905 : {
906 1 : poLayer->SetMetadataItem(pszKey, pszValue);
907 : }
908 : }
909 :
910 : const int ti_field =
911 28 : poLayerDefn->GetFieldIndex(psOptions->osLocationField.c_str());
912 28 : if (ti_field < 0)
913 : {
914 0 : CPLError(CE_Failure, CPLE_AppDefined,
915 : "Unable to find field `%s' in file `%s'.",
916 0 : psOptions->osLocationField.c_str(), pszDest);
917 0 : return nullptr;
918 : }
919 :
920 28 : int i_SrcSRSName = -1;
921 28 : if (!psOptions->osSrcSRSFieldName.empty())
922 : {
923 : i_SrcSRSName =
924 5 : poLayerDefn->GetFieldIndex(psOptions->osSrcSRSFieldName.c_str());
925 5 : if (i_SrcSRSName < 0)
926 : {
927 0 : CPLError(CE_Failure, CPLE_AppDefined,
928 : "Unable to find field `%s' in file `%s'.",
929 0 : psOptions->osSrcSRSFieldName.c_str(), pszDest);
930 0 : return nullptr;
931 : }
932 : }
933 :
934 : // Load in memory existing file names in tile index.
935 56 : std::set<std::string> oSetExistingFiles;
936 56 : OGRSpatialReference oAlreadyExistingSRS;
937 28 : if (bExistingLayer)
938 : {
939 27 : for (auto &&poFeature : poLayer)
940 : {
941 20 : if (poFeature->IsFieldSetAndNotNull(ti_field))
942 : {
943 20 : if (oSetExistingFiles.empty())
944 : {
945 : auto poSrcDS =
946 : std::unique_ptr<GDALDataset>(GDALDataset::Open(
947 : poFeature->GetFieldAsString(ti_field),
948 14 : GDAL_OF_RASTER, nullptr, nullptr, nullptr));
949 7 : if (poSrcDS)
950 : {
951 7 : auto poSrcSRS = poSrcDS->GetSpatialRef();
952 7 : if (poSrcSRS)
953 7 : oAlreadyExistingSRS = *poSrcSRS;
954 : }
955 : }
956 20 : oSetExistingFiles.insert(poFeature->GetFieldAsString(ti_field));
957 : }
958 : }
959 : }
960 :
961 56 : std::string osCurrentPath;
962 28 : if (psOptions->bWriteAbsolutePath)
963 : {
964 1 : char *pszCurrentPath = CPLGetCurrentDir();
965 1 : if (pszCurrentPath)
966 : {
967 1 : osCurrentPath = pszCurrentPath;
968 : }
969 : else
970 : {
971 0 : CPLError(CE_Warning, CPLE_AppDefined,
972 : "This system does not support the CPLGetCurrentDir call. "
973 : "The option -bWriteAbsolutePath will have no effect.");
974 : }
975 1 : CPLFree(pszCurrentPath);
976 : }
977 :
978 : const bool bIsGTIContext =
979 54 : !std::isnan(psOptions->xres) || !std::isnan(psOptions->xmin) ||
980 26 : !psOptions->osBandCount.empty() || !psOptions->osNodata.empty() ||
981 26 : !psOptions->osColorInterp.empty() || !psOptions->osDataType.empty() ||
982 79 : psOptions->bMaskBand || !psOptions->aosMetadata.empty() ||
983 53 : !psOptions->osGTIFilename.empty();
984 :
985 : /* -------------------------------------------------------------------- */
986 : /* loop over GDAL files, processing. */
987 : /* -------------------------------------------------------------------- */
988 : while (true)
989 : {
990 91 : const std::string osSrcFilename = oGDALTileIndexTileIterator.next();
991 91 : if (osSrcFilename.empty())
992 28 : break;
993 :
994 63 : std::string osFileNameToWrite;
995 : VSIStatBuf sStatBuf;
996 :
997 : // Make sure it is a file before building absolute path name.
998 63 : if (!osCurrentPath.empty() &&
999 64 : CPLIsFilenameRelative(osSrcFilename.c_str()) &&
1000 1 : VSIStat(osSrcFilename.c_str(), &sStatBuf) == 0)
1001 : {
1002 : osFileNameToWrite = CPLProjectRelativeFilename(
1003 1 : osCurrentPath.c_str(), osSrcFilename.c_str());
1004 : }
1005 : else
1006 : {
1007 62 : osFileNameToWrite = osSrcFilename.c_str();
1008 : }
1009 :
1010 : // Checks that file is not already in tileindex.
1011 63 : if (oSetExistingFiles.find(osFileNameToWrite) !=
1012 126 : oSetExistingFiles.end())
1013 : {
1014 4 : CPLError(CE_Warning, CPLE_AppDefined,
1015 : "File %s is already in tileindex. Skipping it.",
1016 : osFileNameToWrite.c_str());
1017 4 : continue;
1018 : }
1019 :
1020 : auto poSrcDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
1021 : osSrcFilename.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
1022 59 : nullptr, nullptr, nullptr));
1023 59 : if (poSrcDS == nullptr)
1024 : {
1025 0 : CPLError(CE_Warning, CPLE_AppDefined,
1026 : "Unable to open %s, skipping.", osSrcFilename.c_str());
1027 0 : continue;
1028 : }
1029 :
1030 59 : double adfGeoTransform[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
1031 59 : if (poSrcDS->GetGeoTransform(adfGeoTransform) != CE_None)
1032 : {
1033 0 : CPLError(CE_Warning, CPLE_AppDefined,
1034 : "It appears no georeferencing is available for\n"
1035 : "`%s', skipping.",
1036 : osSrcFilename.c_str());
1037 0 : continue;
1038 : }
1039 :
1040 59 : auto poSrcSRS = poSrcDS->GetSpatialRef();
1041 : // If not set target srs, test that the current file uses same
1042 : // projection as others.
1043 59 : if (oTargetSRS.IsEmpty())
1044 : {
1045 48 : if (!oAlreadyExistingSRS.IsEmpty())
1046 : {
1047 64 : if (poSrcSRS == nullptr ||
1048 32 : !poSrcSRS->IsSame(&oAlreadyExistingSRS))
1049 : {
1050 1 : CPLError(
1051 : CE_Warning, CPLE_AppDefined,
1052 : "%s is not using the same projection system "
1053 : "as other files in the tileindex.\n"
1054 : "This may cause problems when using it in MapServer "
1055 : "for example.\n"
1056 : "Use -t_srs option to set target projection system. %s",
1057 : osSrcFilename.c_str(),
1058 1 : psOptions->bSkipDifferentProjection
1059 : ? "Skipping this file."
1060 : : "");
1061 1 : if (psOptions->bSkipDifferentProjection)
1062 : {
1063 1 : continue;
1064 : }
1065 : }
1066 : }
1067 : else
1068 : {
1069 16 : if (poSrcSRS)
1070 16 : oAlreadyExistingSRS = *poSrcSRS;
1071 : }
1072 : }
1073 :
1074 58 : const int nXSize = poSrcDS->GetRasterXSize();
1075 58 : const int nYSize = poSrcDS->GetRasterYSize();
1076 58 : if (nXSize == 0 || nYSize == 0)
1077 : {
1078 0 : CPLError(CE_Warning, CPLE_AppDefined,
1079 : "%s has 0 width or height. Skipping",
1080 : osSrcFilename.c_str());
1081 0 : continue;
1082 : }
1083 :
1084 58 : double adfX[5] = {0.0, 0.0, 0.0, 0.0, 0.0};
1085 58 : double adfY[5] = {0.0, 0.0, 0.0, 0.0, 0.0};
1086 58 : adfX[0] = adfGeoTransform[0] + 0 * adfGeoTransform[1] +
1087 58 : 0 * adfGeoTransform[2];
1088 58 : adfY[0] = adfGeoTransform[3] + 0 * adfGeoTransform[4] +
1089 58 : 0 * adfGeoTransform[5];
1090 :
1091 58 : adfX[1] = adfGeoTransform[0] + nXSize * adfGeoTransform[1] +
1092 58 : 0 * adfGeoTransform[2];
1093 58 : adfY[1] = adfGeoTransform[3] + nXSize * adfGeoTransform[4] +
1094 58 : 0 * adfGeoTransform[5];
1095 :
1096 58 : adfX[2] = adfGeoTransform[0] + nXSize * adfGeoTransform[1] +
1097 58 : nYSize * adfGeoTransform[2];
1098 58 : adfY[2] = adfGeoTransform[3] + nXSize * adfGeoTransform[4] +
1099 58 : nYSize * adfGeoTransform[5];
1100 :
1101 58 : adfX[3] = adfGeoTransform[0] + 0 * adfGeoTransform[1] +
1102 58 : nYSize * adfGeoTransform[2];
1103 58 : adfY[3] = adfGeoTransform[3] + 0 * adfGeoTransform[4] +
1104 58 : nYSize * adfGeoTransform[5];
1105 :
1106 58 : adfX[4] = adfGeoTransform[0] + 0 * adfGeoTransform[1] +
1107 58 : 0 * adfGeoTransform[2];
1108 58 : adfY[4] = adfGeoTransform[3] + 0 * adfGeoTransform[4] +
1109 58 : 0 * adfGeoTransform[5];
1110 :
1111 : // If set target srs, do the forward transformation of all points.
1112 58 : if (!oTargetSRS.IsEmpty() && poSrcSRS)
1113 : {
1114 11 : if (!poSrcSRS->IsSame(&oTargetSRS))
1115 : {
1116 : auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
1117 6 : OGRCreateCoordinateTransformation(poSrcSRS, &oTargetSRS));
1118 6 : if (!poCT || !poCT->Transform(5, adfX, adfY, nullptr))
1119 : {
1120 0 : CPLError(CE_Warning, CPLE_AppDefined,
1121 : "unable to transform points from source "
1122 : "SRS `%s' to target SRS `%s' for file `%s' - file "
1123 : "skipped",
1124 : poSrcDS->GetProjectionRef(),
1125 0 : psOptions->osTargetSRS.c_str(),
1126 : osFileNameToWrite.c_str());
1127 0 : continue;
1128 : }
1129 : }
1130 : }
1131 56 : else if (bIsGTIContext && !oAlreadyExistingSRS.IsEmpty() &&
1132 9 : (poSrcSRS == nullptr ||
1133 9 : !poSrcSRS->IsSame(&oAlreadyExistingSRS)))
1134 : {
1135 0 : CPLError(
1136 : CE_Failure, CPLE_AppDefined,
1137 : "%s is not using the same projection system "
1138 : "as other files in the tileindex. This is not compatible of "
1139 : "GTI use. Use -t_srs option to reproject tile extents "
1140 : "to a common SRS.",
1141 : osSrcFilename.c_str());
1142 0 : return nullptr;
1143 : }
1144 :
1145 : const double dfMinX =
1146 58 : std::min(std::min(adfX[0], adfX[1]), std::min(adfX[2], adfX[3]));
1147 : const double dfMinY =
1148 58 : std::min(std::min(adfY[0], adfY[1]), std::min(adfY[2], adfY[3]));
1149 : const double dfMaxX =
1150 58 : std::max(std::max(adfX[0], adfX[1]), std::max(adfX[2], adfX[3]));
1151 : const double dfMaxY =
1152 58 : std::max(std::max(adfY[0], adfY[1]), std::max(adfY[2], adfY[3]));
1153 58 : const double dfRes =
1154 58 : (dfMaxX - dfMinX) * (dfMaxY - dfMinY) / nXSize / nYSize;
1155 66 : if (!std::isnan(psOptions->dfMinPixelSize) &&
1156 8 : dfRes < psOptions->dfMinPixelSize)
1157 : {
1158 4 : CPLError(CE_Warning, CPLE_AppDefined,
1159 : "%s has %f as pixel size (< %f). Skipping",
1160 4 : osSrcFilename.c_str(), dfRes, psOptions->dfMinPixelSize);
1161 4 : continue;
1162 : }
1163 62 : if (!std::isnan(psOptions->dfMaxPixelSize) &&
1164 8 : dfRes > psOptions->dfMaxPixelSize)
1165 : {
1166 4 : CPLError(CE_Warning, CPLE_AppDefined,
1167 : "%s has %f as pixel size (> %f). Skipping",
1168 4 : osSrcFilename.c_str(), dfRes, psOptions->dfMaxPixelSize);
1169 4 : continue;
1170 : }
1171 :
1172 50 : auto poFeature = std::make_unique<OGRFeature>(poLayerDefn);
1173 50 : poFeature->SetField(ti_field, osFileNameToWrite.c_str());
1174 :
1175 50 : if (i_SrcSRSName >= 0 && poSrcSRS)
1176 : {
1177 10 : const char *pszAuthorityCode = poSrcSRS->GetAuthorityCode(nullptr);
1178 10 : const char *pszAuthorityName = poSrcSRS->GetAuthorityName(nullptr);
1179 10 : if (psOptions->eSrcSRSFormat == FORMAT_AUTO)
1180 : {
1181 4 : if (pszAuthorityName != nullptr && pszAuthorityCode != nullptr)
1182 : {
1183 4 : poFeature->SetField(i_SrcSRSName,
1184 : CPLSPrintf("%s:%s", pszAuthorityName,
1185 : pszAuthorityCode));
1186 : }
1187 0 : else if (nMaxFieldSize == 0 ||
1188 0 : strlen(poSrcDS->GetProjectionRef()) <=
1189 0 : static_cast<size_t>(nMaxFieldSize))
1190 : {
1191 0 : poFeature->SetField(i_SrcSRSName,
1192 : poSrcDS->GetProjectionRef());
1193 : }
1194 : else
1195 : {
1196 0 : char *pszProj4 = nullptr;
1197 0 : if (poSrcSRS->exportToProj4(&pszProj4) == OGRERR_NONE)
1198 : {
1199 0 : poFeature->SetField(i_SrcSRSName, pszProj4);
1200 : }
1201 : else
1202 : {
1203 0 : poFeature->SetField(i_SrcSRSName,
1204 : poSrcDS->GetProjectionRef());
1205 : }
1206 0 : CPLFree(pszProj4);
1207 : }
1208 : }
1209 6 : else if (psOptions->eSrcSRSFormat == FORMAT_WKT)
1210 : {
1211 4 : if (nMaxFieldSize == 0 ||
1212 2 : strlen(poSrcDS->GetProjectionRef()) <=
1213 2 : static_cast<size_t>(nMaxFieldSize))
1214 : {
1215 0 : poFeature->SetField(i_SrcSRSName,
1216 : poSrcDS->GetProjectionRef());
1217 : }
1218 : else
1219 : {
1220 2 : CPLError(CE_Warning, CPLE_AppDefined,
1221 : "Cannot write WKT for file %s as it is too long!",
1222 : osFileNameToWrite.c_str());
1223 : }
1224 : }
1225 4 : else if (psOptions->eSrcSRSFormat == FORMAT_PROJ)
1226 : {
1227 2 : char *pszProj4 = nullptr;
1228 2 : if (poSrcSRS->exportToProj4(&pszProj4) == OGRERR_NONE)
1229 : {
1230 2 : poFeature->SetField(i_SrcSRSName, pszProj4);
1231 : }
1232 2 : CPLFree(pszProj4);
1233 : }
1234 2 : else if (psOptions->eSrcSRSFormat == FORMAT_EPSG)
1235 : {
1236 2 : if (pszAuthorityName != nullptr && pszAuthorityCode != nullptr)
1237 2 : poFeature->SetField(i_SrcSRSName,
1238 : CPLSPrintf("%s:%s", pszAuthorityName,
1239 : pszAuthorityCode));
1240 : }
1241 : }
1242 :
1243 54 : for (const auto &oFetchMD : psOptions->aoFetchMD)
1244 : {
1245 4 : if (EQUAL(oFetchMD.osRasterItemName.c_str(), "{PIXEL_SIZE}"))
1246 : {
1247 1 : poFeature->SetField(oFetchMD.osFieldName.c_str(), dfRes);
1248 1 : continue;
1249 : }
1250 :
1251 : const char *pszMD =
1252 3 : poSrcDS->GetMetadataItem(oFetchMD.osRasterItemName.c_str());
1253 3 : if (pszMD)
1254 : {
1255 3 : if (EQUAL(oFetchMD.osRasterItemName.c_str(),
1256 : "TIFFTAG_DATETIME"))
1257 : {
1258 : int nYear, nMonth, nDay, nHour, nMin, nSec;
1259 2 : if (sscanf(pszMD, "%04d:%02d:%02d %02d:%02d:%02d", &nYear,
1260 1 : &nMonth, &nDay, &nHour, &nMin, &nSec) == 6)
1261 : {
1262 1 : poFeature->SetField(
1263 : oFetchMD.osFieldName.c_str(),
1264 : CPLSPrintf("%04d/%02d/%02d %02d:%02d:%02d", nYear,
1265 : nMonth, nDay, nHour, nMin, nSec));
1266 1 : continue;
1267 : }
1268 : }
1269 2 : poFeature->SetField(oFetchMD.osFieldName.c_str(), pszMD);
1270 : }
1271 : }
1272 :
1273 50 : auto poPoly = std::make_unique<OGRPolygon>();
1274 50 : auto poRing = std::make_unique<OGRLinearRing>();
1275 300 : for (int k = 0; k < 5; k++)
1276 250 : poRing->addPoint(adfX[k], adfY[k]);
1277 50 : poPoly->addRing(std::move(poRing));
1278 50 : poFeature->SetGeometryDirectly(poPoly.release());
1279 :
1280 50 : if (poLayer->CreateFeature(poFeature.get()) != OGRERR_NONE)
1281 : {
1282 0 : CPLError(CE_Failure, CPLE_AppDefined,
1283 : "Failed to create feature in tile index.");
1284 0 : return nullptr;
1285 : }
1286 63 : }
1287 :
1288 28 : return GDALDataset::ToHandle(poTileIndexDS.release());
1289 : }
1290 :
1291 : /************************************************************************/
1292 : /* SanitizeSRS */
1293 : /************************************************************************/
1294 :
1295 6 : static char *SanitizeSRS(const char *pszUserInput)
1296 :
1297 : {
1298 : OGRSpatialReferenceH hSRS;
1299 6 : char *pszResult = nullptr;
1300 :
1301 6 : CPLErrorReset();
1302 :
1303 6 : hSRS = OSRNewSpatialReference(nullptr);
1304 6 : if (OSRSetFromUserInput(hSRS, pszUserInput) == OGRERR_NONE)
1305 6 : OSRExportToWkt(hSRS, &pszResult);
1306 : else
1307 : {
1308 0 : CPLError(CE_Failure, CPLE_AppDefined, "Translating SRS failed:\n%s",
1309 : pszUserInput);
1310 : }
1311 :
1312 6 : OSRDestroySpatialReference(hSRS);
1313 :
1314 6 : return pszResult;
1315 : }
1316 :
1317 : /************************************************************************/
1318 : /* GDALTileIndexOptionsNew() */
1319 : /************************************************************************/
1320 :
1321 : /**
1322 : * Allocates a GDALTileIndexOptions struct.
1323 : *
1324 : * @param papszArgv NULL terminated list of options (potentially including
1325 : * filename and open options too), or NULL. The accepted options are the ones of
1326 : * the <a href="/programs/gdaltindex.html">gdaltindex</a> utility.
1327 : * @param psOptionsForBinary (output) may be NULL (and should generally be
1328 : * NULL), otherwise (gdaltindex_bin.cpp use case) must be allocated with
1329 : * GDALTileIndexOptionsForBinaryNew() prior to this function. Will be filled
1330 : * with potentially present filename, open options,...
1331 : * @return pointer to the allocated GDALTileIndexOptions struct. Must be freed
1332 : * with GDALTileIndexOptionsFree().
1333 : *
1334 : * @since GDAL 3.9
1335 : */
1336 :
1337 : GDALTileIndexOptions *
1338 30 : GDALTileIndexOptionsNew(char **papszArgv,
1339 : GDALTileIndexOptionsForBinary *psOptionsForBinary)
1340 : {
1341 59 : auto psOptions = std::make_unique<GDALTileIndexOptions>();
1342 :
1343 : /* -------------------------------------------------------------------- */
1344 : /* Parse arguments. */
1345 : /* -------------------------------------------------------------------- */
1346 :
1347 59 : CPLStringList aosArgv;
1348 :
1349 30 : if (papszArgv)
1350 : {
1351 30 : const int nArgc = CSLCount(papszArgv);
1352 205 : for (int i = 0; i < nArgc; i++)
1353 : {
1354 175 : aosArgv.AddString(papszArgv[i]);
1355 : }
1356 : }
1357 :
1358 : try
1359 : {
1360 : auto argParser = GDALTileIndexAppOptionsGetParser(psOptions.get(),
1361 30 : psOptionsForBinary);
1362 30 : argParser->parse_args_without_binary_name(aosArgv.List());
1363 :
1364 : // Check all no store_into args
1365 31 : if (auto oTr = argParser->present<std::vector<double>>("-tr"))
1366 : {
1367 2 : psOptions->xres = (*oTr)[0];
1368 2 : psOptions->yres = (*oTr)[1];
1369 : }
1370 :
1371 31 : if (auto oTargetExtent = argParser->present<std::vector<double>>("-te"))
1372 : {
1373 2 : psOptions->xmin = (*oTargetExtent)[0];
1374 2 : psOptions->ymin = (*oTargetExtent)[1];
1375 2 : psOptions->xmax = (*oTargetExtent)[2];
1376 2 : psOptions->ymax = (*oTargetExtent)[3];
1377 : }
1378 :
1379 29 : if (auto fetchMd =
1380 29 : argParser->present<std::vector<std::string>>("-fetch_md"))
1381 : {
1382 :
1383 2 : CPLAssert(fetchMd->size() % 3 == 0);
1384 :
1385 : // Loop
1386 6 : for (size_t i = 0; i < fetchMd->size(); i += 3)
1387 : {
1388 : OGRFieldType type;
1389 4 : const auto &typeName{fetchMd->at(i + 2)};
1390 4 : if (typeName == "String")
1391 : {
1392 2 : type = OFTString;
1393 : }
1394 2 : else if (typeName == "Integer")
1395 : {
1396 0 : type = OFTInteger;
1397 : }
1398 2 : else if (typeName == "Integer64")
1399 : {
1400 0 : type = OFTInteger64;
1401 : }
1402 2 : else if (typeName == "Real")
1403 : {
1404 1 : type = OFTReal;
1405 : }
1406 1 : else if (typeName == "Date")
1407 : {
1408 0 : type = OFTDate;
1409 : }
1410 1 : else if (typeName == "DateTime")
1411 : {
1412 1 : type = OFTDateTime;
1413 : }
1414 : else
1415 : {
1416 0 : CPLError(CE_Failure, CPLE_AppDefined,
1417 : "-fetch_md requires a valid type name as third "
1418 : "argument: %s was given.",
1419 0 : fetchMd->at(i).c_str());
1420 0 : return nullptr;
1421 : }
1422 :
1423 4 : const GDALTileIndexRasterMetadata oMD{type, fetchMd->at(i + 1),
1424 12 : fetchMd->at(i)};
1425 4 : psOptions->aoFetchMD.push_back(oMD);
1426 : }
1427 : }
1428 :
1429 : // Check -t_srs
1430 29 : if (!psOptions->osTargetSRS.empty())
1431 : {
1432 6 : auto sanitized{SanitizeSRS(psOptions->osTargetSRS.c_str())};
1433 6 : if (sanitized)
1434 : {
1435 6 : psOptions->osTargetSRS = sanitized;
1436 6 : CPLFree(sanitized);
1437 : }
1438 : else
1439 : {
1440 : // Error was already reported by SanitizeSRS, just return nullptr
1441 0 : psOptions->osTargetSRS.clear();
1442 0 : return nullptr;
1443 : }
1444 : }
1445 : }
1446 0 : catch (const std::exception &error)
1447 : {
1448 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s", error.what());
1449 0 : return nullptr;
1450 : }
1451 :
1452 29 : return psOptions.release();
1453 : }
1454 :
1455 : /************************************************************************/
1456 : /* GDALTileIndexOptionsFree() */
1457 : /************************************************************************/
1458 :
1459 : /**
1460 : * Frees the GDALTileIndexOptions struct.
1461 : *
1462 : * @param psOptions the options struct for GDALTileIndex().
1463 : *
1464 : * @since GDAL 3.9
1465 : */
1466 :
1467 29 : void GDALTileIndexOptionsFree(GDALTileIndexOptions *psOptions)
1468 : {
1469 29 : delete psOptions;
1470 29 : }
1471 :
1472 : #undef CHECK_HAS_ENOUGH_ADDITIONAL_ARGS
|