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