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