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