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_md5.h"
17 : #include "cpl_minixml.h"
18 : #include "cpl_string.h"
19 : #include "cpl_vsi_virtual.h"
20 : #include "gdal_utils.h"
21 : #include "gdal_priv.h"
22 : #include "gdal_utils_priv.h"
23 : #include "ogr_api.h"
24 : #include "ograrrowarrayhelper.h"
25 : #include "ogrsf_frmts.h"
26 : #include "ogr_recordbatch.h"
27 : #include "ogr_spatialref.h"
28 : #include "commonutils.h"
29 : #include "gdalargumentparser.h"
30 :
31 : #include <ctype.h>
32 :
33 : #include <algorithm>
34 : #include <cmath>
35 : #include <limits>
36 : #include <set>
37 :
38 : constexpr const char ARROW_FORMAT_INT32[] = "i";
39 : constexpr const char ARROW_FORMAT_FLOAT32[] = "f";
40 : constexpr const char ARROW_FORMAT_FLOAT64[] = "g";
41 : constexpr const char ARROW_FORMAT_STRING[] = "u";
42 : constexpr const char ARROW_FORMAT_BINARY[] = "z";
43 : constexpr const char ARROW_FORMAT_LIST[] = "+l";
44 : constexpr const char ARROW_FORMAT_STRUCT[] = "+s";
45 : constexpr const char GEOPARQUET_GEOM_COL_NAME[] = "geometry";
46 :
47 : constexpr int NUM_ITEMS_PROJ_BBOX = 4;
48 : constexpr int NUM_ITEMS_PROJ_SHAPE = 2;
49 : constexpr int NUM_ITEMS_PROJ_TRANSFORM = 9;
50 :
51 : constexpr int ARROW_BUF_VALIDITY = 0;
52 : constexpr int ARROW_BUF_DATA = 1;
53 : constexpr int ARROW_BUF_BYTES = 2;
54 :
55 : constexpr int COUNT_STAC_EXTENSIONS = 2;
56 :
57 : typedef enum
58 : {
59 : FORMAT_AUTO,
60 : FORMAT_WKT,
61 : FORMAT_EPSG,
62 : FORMAT_PROJ
63 : } SrcSRSFormat;
64 :
65 : /************************************************************************/
66 : /* GDALTileIndexRasterMetadata */
67 : /************************************************************************/
68 :
69 : struct GDALTileIndexRasterMetadata
70 : {
71 : OGRFieldType eType = OFTString;
72 : std::string osFieldName{};
73 : std::string osRasterItemName{};
74 : };
75 :
76 : /************************************************************************/
77 : /* GDALTileIndexOptions */
78 : /************************************************************************/
79 :
80 : struct GDALTileIndexOptions
81 : {
82 : bool bInvokedFromGdalRasterIndex = false;
83 : bool bOverwrite = false;
84 : bool bSkipErrors = false;
85 : std::string osFormat{};
86 : std::string osIndexLayerName{};
87 : std::string osLocationField = "location";
88 : CPLStringList aosLCO{};
89 : std::string osTargetSRS{};
90 : bool bWriteAbsolutePath = false;
91 : bool bSkipDifferentProjection = false;
92 : std::string osSrcSRSFieldName{};
93 : SrcSRSFormat eSrcSRSFormat = FORMAT_AUTO;
94 : double xres = std::numeric_limits<double>::quiet_NaN();
95 : double yres = std::numeric_limits<double>::quiet_NaN();
96 : double xmin = std::numeric_limits<double>::quiet_NaN();
97 : double ymin = std::numeric_limits<double>::quiet_NaN();
98 : double xmax = std::numeric_limits<double>::quiet_NaN();
99 : double ymax = std::numeric_limits<double>::quiet_NaN();
100 : std::string osBandCount{};
101 : std::string osNodata{};
102 : std::string osColorInterp{};
103 : std::string osDataType{};
104 : bool bMaskBand = false;
105 : std::vector<std::string> aosMetadata{};
106 : std::string osGTIFilename{};
107 : bool bRecursive = false;
108 : double dfMinPixelSize = std::numeric_limits<double>::quiet_NaN();
109 : double dfMaxPixelSize = std::numeric_limits<double>::quiet_NaN();
110 : std::vector<GDALTileIndexRasterMetadata> aoFetchMD{};
111 : std::set<std::string> oSetFilenameFilters{};
112 : GDALProgressFunc pfnProgress = nullptr;
113 : void *pProgressData = nullptr;
114 : std::string osProfile{}; // Only "STAC-GeoParquet" handled
115 : std::string osBaseURL{}; // Used for "STAC-GeoParquet"
116 : std::string osIdMethod{}; // Used for "STAC-GeoParquet"
117 : std::string osIdMetadataItem{}; // Used for "STAC-GeoParquet"
118 : };
119 :
120 : /************************************************************************/
121 : /* ReleaseArray() */
122 : /************************************************************************/
123 :
124 288 : static void ReleaseArray(struct ArrowArray *array)
125 : {
126 288 : CPLAssert(array->release != nullptr);
127 288 : if (array->buffers)
128 : {
129 952 : for (int i = 0; i < static_cast<int>(array->n_buffers); ++i)
130 664 : VSIFree(const_cast<void *>(array->buffers[i]));
131 288 : CPLFree(array->buffers);
132 : }
133 288 : if (array->children)
134 : {
135 376 : for (int i = 0; i < static_cast<int>(array->n_children); ++i)
136 : {
137 280 : if (array->children[i] && array->children[i]->release)
138 : {
139 280 : array->children[i]->release(array->children[i]);
140 280 : CPLFree(array->children[i]);
141 : }
142 : }
143 96 : CPLFree(array->children);
144 : }
145 288 : array->release = nullptr;
146 288 : }
147 :
148 : /************************************************************************/
149 : /* GDALTileIndexAppOptionsGetParser() */
150 : /************************************************************************/
151 :
152 52 : static std::unique_ptr<GDALArgumentParser> GDALTileIndexAppOptionsGetParser(
153 : GDALTileIndexOptions *psOptions,
154 : GDALTileIndexOptionsForBinary *psOptionsForBinary)
155 : {
156 : auto argParser = std::make_unique<GDALArgumentParser>(
157 52 : "gdaltindex", /* bForBinary=*/psOptionsForBinary != nullptr);
158 :
159 52 : argParser->add_description(
160 52 : _("Build a tile index from a list of datasets."));
161 :
162 52 : argParser->add_epilog(
163 : _("For more details, see the full documentation for gdaltindex at\n"
164 52 : "https://gdal.org/programs/gdaltindex.html"));
165 :
166 : // Hidden as used by gdal raster index
167 52 : argParser->add_argument("--invoked-from-gdal-raster-index")
168 52 : .store_into(psOptions->bInvokedFromGdalRasterIndex)
169 52 : .hidden();
170 :
171 : // Hidden as used by gdal raster index
172 52 : argParser->add_argument("-skip_errors")
173 52 : .store_into(psOptions->bSkipErrors)
174 52 : .hidden();
175 :
176 : // Hidden as used by gdal raster index
177 52 : argParser->add_argument("-profile")
178 52 : .store_into(psOptions->osProfile)
179 52 : .hidden();
180 :
181 : // Hidden as used by gdal raster index
182 52 : argParser->add_argument("--base-url")
183 52 : .store_into(psOptions->osBaseURL)
184 52 : .hidden();
185 :
186 : // Hidden as used by gdal raster index
187 52 : argParser->add_argument("--id-method")
188 52 : .store_into(psOptions->osIdMethod)
189 52 : .hidden();
190 :
191 : // Hidden as used by gdal raster index
192 52 : argParser->add_argument("--id-metadata-item")
193 52 : .store_into(psOptions->osIdMetadataItem)
194 52 : .hidden();
195 :
196 52 : argParser->add_argument("-overwrite")
197 52 : .flag()
198 52 : .store_into(psOptions->bOverwrite)
199 52 : .help(_("Overwrite the output tile index file if it already exists."));
200 :
201 52 : argParser->add_argument("-recursive")
202 52 : .flag()
203 52 : .store_into(psOptions->bRecursive)
204 : .help(_("Whether directories specified in <file_or_dir> should be "
205 52 : "explored recursively."));
206 :
207 52 : argParser->add_argument("-filename_filter")
208 104 : .metavar("<val>")
209 52 : .append()
210 52 : .store_into(psOptions->oSetFilenameFilters)
211 : .help(_("Pattern that the filenames contained in directories pointed "
212 52 : "by <file_or_dir> should follow."));
213 :
214 52 : argParser->add_argument("-min_pixel_size")
215 104 : .metavar("<val>")
216 52 : .store_into(psOptions->dfMinPixelSize)
217 : .help(_("Minimum pixel size in term of geospatial extent per pixel "
218 52 : "(resolution) that a raster should have to be selected."));
219 :
220 52 : argParser->add_argument("-max_pixel_size")
221 104 : .metavar("<val>")
222 52 : .store_into(psOptions->dfMaxPixelSize)
223 : .help(_("Maximum pixel size in term of geospatial extent per pixel "
224 52 : "(resolution) that a raster should have to be selected."));
225 :
226 52 : argParser->add_output_format_argument(psOptions->osFormat);
227 :
228 52 : argParser->add_argument("-tileindex")
229 104 : .metavar("<field_name>")
230 52 : .store_into(psOptions->osLocationField)
231 52 : .help(_("Name of the layer in the tile index file."));
232 :
233 52 : argParser->add_argument("-write_absolute_path")
234 52 : .flag()
235 52 : .store_into(psOptions->bWriteAbsolutePath)
236 : .help(_("Write the absolute path of the raster files in the tile index "
237 52 : "file."));
238 :
239 52 : argParser->add_argument("-skip_different_projection")
240 52 : .flag()
241 52 : .store_into(psOptions->bSkipDifferentProjection)
242 : .help(_(
243 : "Only files with the same projection as files already inserted in "
244 52 : "the tile index will be inserted (unless -t_srs is specified)."));
245 :
246 52 : argParser->add_argument("-t_srs")
247 104 : .metavar("<srs_def>")
248 52 : .store_into(psOptions->osTargetSRS)
249 : .help(_("Geometries of input files will be transformed to the desired "
250 52 : "target coordinate reference system."));
251 :
252 52 : argParser->add_argument("-src_srs_name")
253 104 : .metavar("<field_name>")
254 52 : .store_into(psOptions->osSrcSRSFieldName)
255 : .help(_("Name of the field in the tile index file where the source SRS "
256 52 : "will be stored."));
257 :
258 52 : argParser->add_argument("-src_srs_format")
259 104 : .metavar("{AUTO|WKT|EPSG|PROJ}")
260 52 : .choices("AUTO", "WKT", "EPSG", "PROJ")
261 : .action(
262 10 : [psOptions](const auto &f)
263 : {
264 5 : if (f == "WKT")
265 1 : psOptions->eSrcSRSFormat = FORMAT_WKT;
266 4 : else if (f == "EPSG")
267 1 : psOptions->eSrcSRSFormat = FORMAT_EPSG;
268 3 : else if (f == "PROJ")
269 1 : psOptions->eSrcSRSFormat = FORMAT_PROJ;
270 : else
271 2 : psOptions->eSrcSRSFormat = FORMAT_AUTO;
272 52 : })
273 52 : .help(_("Format of the source SRS to store in the tile index file."));
274 :
275 52 : argParser->add_argument("-lyr_name")
276 104 : .metavar("<name>")
277 52 : .store_into(psOptions->osIndexLayerName)
278 52 : .help(_("Name of the layer in the tile index file."));
279 :
280 52 : argParser->add_layer_creation_options_argument(psOptions->aosLCO);
281 :
282 : // GTI driver options
283 :
284 52 : argParser->add_argument("-gti_filename")
285 104 : .metavar("<filename>")
286 52 : .store_into(psOptions->osGTIFilename)
287 52 : .help(_("Filename of the XML Virtual Tile Index file to generate."));
288 :
289 : // NOTE: no store_into
290 52 : argParser->add_argument("-tr")
291 104 : .metavar("<xres> <yres>")
292 52 : .nargs(2)
293 52 : .scan<'g', double>()
294 52 : .help(_("Set target resolution."));
295 :
296 : // NOTE: no store_into
297 52 : argParser->add_argument("-te")
298 104 : .metavar("<xmin> <ymin> <xmax> <ymax>")
299 52 : .nargs(4)
300 52 : .scan<'g', double>()
301 52 : .help(_("Set target extent in SRS unit."));
302 :
303 52 : argParser->add_argument("-ot")
304 104 : .metavar("<datatype>")
305 52 : .store_into(psOptions->osDataType)
306 52 : .help(_("Output data type."));
307 :
308 52 : argParser->add_argument("-bandcount")
309 104 : .metavar("<val>")
310 52 : .store_into(psOptions->osBandCount)
311 52 : .help(_("Number of bands of the tiles of the tile index."));
312 :
313 52 : argParser->add_argument("-nodata")
314 104 : .metavar("<val>")
315 52 : .append()
316 52 : .store_into(psOptions->osNodata)
317 52 : .help(_("Nodata value of the tiles of the tile index."));
318 :
319 : // Should we use choices here?
320 52 : argParser->add_argument("-colorinterp")
321 104 : .metavar("<val>")
322 52 : .append()
323 52 : .store_into(psOptions->osColorInterp)
324 : .help(_("Color interpretation of of the tiles of the tile index: red, "
325 52 : "green, blue, alpha, gray, undefined."));
326 :
327 52 : argParser->add_argument("-mask")
328 52 : .flag()
329 52 : .store_into(psOptions->bMaskBand)
330 52 : .help(_("Add a mask band to the tiles of the tile index."));
331 :
332 52 : argParser->add_argument("-mo")
333 104 : .metavar("<name>=<value>")
334 52 : .append()
335 52 : .store_into(psOptions->aosMetadata)
336 : .help(_("Write an arbitrary layer metadata item, for formats that "
337 52 : "support layer metadata."));
338 :
339 : // NOTE: no store_into
340 52 : argParser->add_argument("-fetch_md")
341 52 : .nargs(3)
342 104 : .metavar("<gdal_md_name> <fld_name> <fld_type>")
343 52 : .append()
344 : .help("Fetch a metadata item from the raster tile and write it as a "
345 52 : "field in the tile index.");
346 :
347 52 : if (psOptionsForBinary)
348 : {
349 6 : argParser->add_quiet_argument(&psOptionsForBinary->bQuiet);
350 :
351 6 : argParser->add_argument("index_file")
352 12 : .metavar("<index_file>")
353 6 : .store_into(psOptionsForBinary->osDest)
354 6 : .help(_("The name of the output file to create/append to."));
355 :
356 6 : argParser->add_argument("file_or_dir")
357 12 : .metavar("<file_or_dir>")
358 6 : .nargs(argparse::nargs_pattern::at_least_one)
359 14 : .action([psOptionsForBinary](const std::string &s)
360 20 : { psOptionsForBinary->aosSrcFiles.AddString(s.c_str()); })
361 : .help(_(
362 : "The input GDAL raster files or directory, can be multiple "
363 6 : "locations separated by spaces. Wildcards may also be used."));
364 : }
365 :
366 52 : return argParser;
367 : }
368 :
369 : /************************************************************************/
370 : /* GDALTileIndexAppGetParserUsage() */
371 : /************************************************************************/
372 :
373 0 : std::string GDALTileIndexAppGetParserUsage()
374 : {
375 : try
376 : {
377 0 : GDALTileIndexOptions sOptions;
378 0 : GDALTileIndexOptionsForBinary sOptionsForBinary;
379 : auto argParser =
380 0 : GDALTileIndexAppOptionsGetParser(&sOptions, &sOptionsForBinary);
381 0 : return argParser->usage();
382 : }
383 0 : catch (const std::exception &err)
384 : {
385 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unexpected exception: %s",
386 0 : err.what());
387 0 : return std::string();
388 : }
389 : }
390 :
391 : /************************************************************************/
392 : /* GDALTileIndexTileIterator */
393 : /************************************************************************/
394 :
395 : struct GDALTileIndexTileIterator
396 : {
397 : const GDALTileIndexOptions *psOptions = nullptr;
398 : int nSrcCount = 0;
399 : const char *const *papszSrcDSNames = nullptr;
400 : std::string osCurDir{};
401 : int iCurSrc = 0;
402 : VSIDIR *psDir = nullptr;
403 :
404 : CPL_DISALLOW_COPY_ASSIGN(GDALTileIndexTileIterator)
405 :
406 52 : GDALTileIndexTileIterator(const GDALTileIndexOptions *psOptionsIn,
407 : int nSrcCountIn,
408 : const char *const *papszSrcDSNamesIn)
409 52 : : psOptions(psOptionsIn), nSrcCount(nSrcCountIn),
410 52 : papszSrcDSNames(papszSrcDSNamesIn)
411 : {
412 52 : }
413 :
414 27 : void reset()
415 : {
416 27 : if (psDir)
417 4 : VSICloseDir(psDir);
418 27 : psDir = nullptr;
419 27 : iCurSrc = 0;
420 27 : }
421 :
422 1053 : std::string next()
423 : {
424 : while (true)
425 : {
426 1053 : if (!psDir)
427 : {
428 157 : if (iCurSrc == nSrcCount)
429 : {
430 51 : break;
431 : }
432 :
433 : VSIStatBufL sStatBuf;
434 106 : const char *pszCurName = papszSrcDSNames[iCurSrc++];
435 208 : if (VSIStatL(pszCurName, &sStatBuf) == 0 &&
436 102 : VSI_ISDIR(sStatBuf.st_mode))
437 : {
438 : auto poSrcDS = std::unique_ptr<GDALDataset>(
439 : GDALDataset::Open(pszCurName, GDAL_OF_RASTER, nullptr,
440 9 : nullptr, nullptr));
441 9 : if (poSrcDS)
442 0 : return pszCurName;
443 :
444 9 : osCurDir = pszCurName;
445 9 : psDir = VSIOpenDir(
446 : osCurDir.c_str(),
447 9 : /*nDepth=*/psOptions->bRecursive ? -1 : 0, nullptr);
448 9 : if (!psDir)
449 : {
450 0 : CPLError(CE_Failure, CPLE_AppDefined,
451 : "Cannot open directory %s", osCurDir.c_str());
452 0 : return std::string();
453 : }
454 : }
455 : else
456 : {
457 97 : return pszCurName;
458 : }
459 : }
460 :
461 905 : auto psEntry = VSIGetNextDirEntry(psDir);
462 905 : if (!psEntry)
463 : {
464 5 : VSICloseDir(psDir);
465 5 : psDir = nullptr;
466 5 : continue;
467 : }
468 :
469 900 : if (!psOptions->oSetFilenameFilters.empty())
470 : {
471 890 : bool bMatchFound = false;
472 : const std::string osFilenameOnly =
473 890 : CPLGetFilename(psEntry->pszName);
474 1773 : for (const auto &osFilter : psOptions->oSetFilenameFilters)
475 : {
476 890 : if (GDALPatternMatch(osFilenameOnly.c_str(),
477 : osFilter.c_str()))
478 : {
479 7 : bMatchFound = true;
480 7 : break;
481 : }
482 : }
483 890 : if (!bMatchFound)
484 883 : continue;
485 : }
486 :
487 : std::string osFilename = CPLFormFilenameSafe(
488 17 : osCurDir.c_str(), psEntry->pszName, nullptr);
489 17 : if (VSI_ISDIR(psEntry->nMode))
490 : {
491 : auto poSrcDS = std::unique_ptr<GDALDataset>(
492 : GDALDataset::Open(osFilename.c_str(), GDAL_OF_RASTER,
493 0 : nullptr, nullptr, nullptr));
494 0 : if (poSrcDS)
495 : {
496 0 : return osFilename;
497 : }
498 0 : continue;
499 : }
500 :
501 17 : return osFilename;
502 888 : }
503 51 : return std::string();
504 : }
505 : };
506 :
507 : /************************************************************************/
508 : /* GDALTileIndex() */
509 : /************************************************************************/
510 :
511 : /* clang-format off */
512 : /**
513 : * Build a tile index from a list of datasets.
514 : *
515 : * This is the equivalent of the
516 : * <a href="/programs/gdaltindex.html">gdaltindex</a> utility.
517 : *
518 : * GDALTileIndexOptions* must be allocated and freed with
519 : * GDALTileIndexOptionsNew() and GDALTileIndexOptionsFree() respectively.
520 : *
521 : * @param pszDest the destination dataset path.
522 : * @param nSrcCount the number of input datasets.
523 : * @param papszSrcDSNames the list of input dataset names
524 : * @param psOptionsIn the options struct returned by GDALTileIndexOptionsNew() or
525 : * NULL.
526 : * @param pbUsageError pointer to a integer output variable to store if any
527 : * usage error has occurred.
528 : * @return the output dataset (new dataset that must be closed using
529 : * GDALClose()) or NULL in case of error.
530 : *
531 : * @since GDAL3.9
532 : */
533 : /* clang-format on */
534 :
535 29 : GDALDatasetH GDALTileIndex(const char *pszDest, int nSrcCount,
536 : const char *const *papszSrcDSNames,
537 : const GDALTileIndexOptions *psOptionsIn,
538 : int *pbUsageError)
539 : {
540 29 : return GDALTileIndexInternal(pszDest, nullptr, nullptr, nSrcCount,
541 29 : papszSrcDSNames, psOptionsIn, pbUsageError);
542 : }
543 :
544 52 : GDALDatasetH GDALTileIndexInternal(const char *pszDest,
545 : GDALDatasetH hTileIndexDS, OGRLayerH hLayer,
546 : int nSrcCount,
547 : const char *const *papszSrcDSNames,
548 : const GDALTileIndexOptions *psOptionsIn,
549 : int *pbUsageError)
550 : {
551 52 : if (nSrcCount == 0)
552 : {
553 0 : CPLError(CE_Failure, CPLE_AppDefined, "No input dataset specified.");
554 :
555 0 : if (pbUsageError)
556 0 : *pbUsageError = TRUE;
557 0 : return nullptr;
558 : }
559 :
560 : auto psOptions = psOptionsIn
561 : ? std::make_unique<GDALTileIndexOptions>(*psOptionsIn)
562 104 : : std::make_unique<GDALTileIndexOptions>();
563 :
564 : GDALTileIndexTileIterator oGDALTileIndexTileIterator(
565 104 : psOptions.get(), nSrcCount, papszSrcDSNames);
566 :
567 : /* -------------------------------------------------------------------- */
568 : /* Create and validate target SRS if given. */
569 : /* -------------------------------------------------------------------- */
570 104 : OGRSpatialReference oTargetSRS;
571 52 : if (!psOptions->osTargetSRS.empty())
572 : {
573 16 : if (psOptions->bSkipDifferentProjection)
574 : {
575 0 : CPLError(CE_Warning, CPLE_AppDefined,
576 : "-skip_different_projections does not apply "
577 : "when -t_srs is requested.");
578 : }
579 16 : oTargetSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
580 : // coverity[tainted_data]
581 16 : oTargetSRS.SetFromUserInput(psOptions->osTargetSRS.c_str());
582 : }
583 :
584 : /* -------------------------------------------------------------------- */
585 : /* Open or create the target datasource */
586 : /* -------------------------------------------------------------------- */
587 :
588 52 : std::unique_ptr<GDALDataset> poTileIndexDSUnique;
589 52 : GDALDataset *poTileIndexDS = GDALDataset::FromHandle(hTileIndexDS);
590 52 : OGRLayer *poLayer = OGRLayer::FromHandle(hLayer);
591 52 : bool bExistingLayer = false;
592 104 : std::string osFormat;
593 :
594 52 : if (!hTileIndexDS)
595 : {
596 29 : if (psOptions->bOverwrite)
597 : {
598 5 : CPLPushErrorHandler(CPLQuietErrorHandler);
599 5 : auto hDriver = GDALIdentifyDriver(pszDest, nullptr);
600 5 : if (hDriver)
601 5 : GDALDeleteDataset(hDriver, pszDest);
602 : else
603 0 : VSIUnlink(pszDest);
604 5 : CPLPopErrorHandler();
605 : }
606 :
607 29 : poTileIndexDSUnique.reset(
608 : GDALDataset::Open(pszDest, GDAL_OF_VECTOR | GDAL_OF_UPDATE, nullptr,
609 : nullptr, nullptr));
610 :
611 29 : if (poTileIndexDSUnique != nullptr)
612 : {
613 7 : auto poDriver = poTileIndexDSUnique->GetDriver();
614 7 : if (poDriver)
615 7 : osFormat = poDriver->GetDescription();
616 :
617 7 : if (poTileIndexDSUnique->GetLayerCount() == 1)
618 : {
619 7 : poLayer = poTileIndexDSUnique->GetLayer(0);
620 : }
621 : else
622 : {
623 0 : if (psOptions->osIndexLayerName.empty())
624 : {
625 0 : CPLError(CE_Failure, CPLE_AppDefined,
626 : "Multiple layers detected: -lyr_name must be "
627 : "specified.");
628 0 : if (pbUsageError)
629 0 : *pbUsageError = true;
630 0 : return nullptr;
631 : }
632 0 : CPLPushErrorHandler(CPLQuietErrorHandler);
633 0 : poLayer = poTileIndexDSUnique->GetLayerByName(
634 0 : psOptions->osIndexLayerName.c_str());
635 0 : CPLPopErrorHandler();
636 : }
637 : }
638 : else
639 : {
640 22 : if (psOptions->osFormat.empty())
641 : {
642 : const auto aoDrivers =
643 21 : GetOutputDriversFor(pszDest, GDAL_OF_VECTOR);
644 21 : if (aoDrivers.empty())
645 : {
646 0 : CPLError(CE_Failure, CPLE_AppDefined,
647 : "Cannot guess driver for %s", pszDest);
648 0 : return nullptr;
649 : }
650 : else
651 : {
652 21 : if (aoDrivers.size() > 1)
653 : {
654 0 : CPLError(
655 : CE_Warning, CPLE_AppDefined,
656 : "Several drivers matching %s extension. Using %s",
657 0 : CPLGetExtensionSafe(pszDest).c_str(),
658 0 : aoDrivers[0].c_str());
659 : }
660 21 : osFormat = aoDrivers[0];
661 : }
662 : }
663 : else
664 : {
665 1 : osFormat = psOptions->osFormat;
666 : }
667 :
668 : auto poDriver =
669 22 : GetGDALDriverManager()->GetDriverByName(osFormat.c_str());
670 22 : if (poDriver == nullptr)
671 : {
672 0 : CPLError(CE_Warning, CPLE_AppDefined,
673 : "%s driver not available.", osFormat.c_str());
674 0 : return nullptr;
675 : }
676 :
677 22 : poTileIndexDSUnique.reset(
678 : poDriver->Create(pszDest, 0, 0, 0, GDT_Unknown, nullptr));
679 22 : if (!poTileIndexDSUnique)
680 0 : return nullptr;
681 : }
682 :
683 29 : poTileIndexDS = poTileIndexDSUnique.get();
684 : }
685 :
686 : const bool bIsSTACGeoParquet =
687 52 : EQUAL(psOptions->osProfile.c_str(), "STAC-GeoParquet");
688 :
689 52 : auto poOutDrv = poTileIndexDS->GetDriver();
690 52 : if (osFormat.empty() && poOutDrv)
691 23 : osFormat = poOutDrv->GetDescription();
692 :
693 : const char *pszVal =
694 52 : poOutDrv ? poOutDrv->GetMetadataItem(GDAL_DMD_MAX_STRING_LENGTH)
695 52 : : nullptr;
696 52 : const int nMaxFieldSize = pszVal ? atoi(pszVal) : 0;
697 :
698 : const bool bFailOnErrors =
699 52 : psOptions->bInvokedFromGdalRasterIndex && !psOptions->bSkipErrors;
700 52 : bool bSkipFirstTile = false;
701 :
702 : // Configurable mostly/only for autotest purposes.
703 : const int nMaxBatchSize = std::max(
704 52 : 1, atoi(CPLGetConfigOption("GDAL_RASTER_INDEX_BATCH_SIZE", "65536")));
705 :
706 104 : std::vector<ArrowSchema> topSchemas;
707 104 : std::vector<ArrowSchema *> topSchemasPointers;
708 104 : std::vector<std::unique_ptr<ArrowSchema>> auxSchemas;
709 104 : std::vector<ArrowSchema *> stacExtensionsSchemaChildren,
710 104 : linksSchemaChildren, linksItemSchemaChildren, assetsSchemaChildren,
711 104 : imageAssetSchemaChildren, imageAssetRolesSchemaChildren,
712 104 : bandsSchemaChildren, bandsItemSchemaChildren, projBboxSchemaChildren,
713 104 : projShapeSchemaChildren, projTransformSchemaChildren;
714 52 : ArrowSchema topSchema{};
715 0 : const auto noop_release = [](struct ArrowSchema *) {};
716 336 : const auto AddTopSchema = [&topSchemas, &noop_release]() -> ArrowSchema &
717 : {
718 84 : topSchemas.push_back(ArrowSchema{});
719 84 : topSchemas.back().release = noop_release;
720 84 : return topSchemas.back();
721 52 : };
722 :
723 52 : if (poLayer)
724 : {
725 9 : bExistingLayer = true;
726 : }
727 : else
728 : {
729 43 : std::string osLayerName;
730 43 : if (psOptions->osIndexLayerName.empty())
731 : {
732 : VSIStatBuf sStat;
733 22 : if (EQUAL(osFormat.c_str(), "ESRI Shapefile") ||
734 3 : VSIStat(pszDest, &sStat) == 0)
735 : {
736 19 : osLayerName = CPLGetBasenameSafe(pszDest);
737 : }
738 : else
739 : {
740 0 : CPLError(CE_Failure, CPLE_AppDefined,
741 : "-lyr_name must be specified.");
742 0 : if (pbUsageError)
743 0 : *pbUsageError = true;
744 0 : return nullptr;
745 : }
746 : }
747 : else
748 : {
749 24 : if (psOptions->bOverwrite)
750 : {
751 0 : for (int i = 0; i < poTileIndexDS->GetLayerCount(); ++i)
752 : {
753 0 : auto poExistingLayer = poTileIndexDS->GetLayer(i);
754 0 : if (poExistingLayer && poExistingLayer->GetName() ==
755 0 : psOptions->osIndexLayerName)
756 : {
757 0 : if (poTileIndexDS->DeleteLayer(i) != OGRERR_NONE)
758 0 : return nullptr;
759 0 : break;
760 : }
761 : }
762 : }
763 :
764 24 : osLayerName = psOptions->osIndexLayerName;
765 : }
766 :
767 : /* get spatial reference for output file from target SRS (if set) */
768 : /* or from first input file */
769 43 : OGRSpatialReference oSRS;
770 43 : if (!oTargetSRS.IsEmpty())
771 : {
772 15 : oSRS = oTargetSRS;
773 : }
774 : else
775 : {
776 28 : std::string osFilename = oGDALTileIndexTileIterator.next();
777 28 : if (osFilename.empty())
778 : {
779 1 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find any tile");
780 1 : return nullptr;
781 : }
782 27 : oGDALTileIndexTileIterator.reset();
783 : std::unique_ptr<CPLTurnFailureIntoWarningBackuper>
784 0 : poFailureIntoWarning;
785 27 : if (!bFailOnErrors)
786 : poFailureIntoWarning =
787 17 : std::make_unique<CPLTurnFailureIntoWarningBackuper>();
788 27 : CPL_IGNORE_RET_VAL(poFailureIntoWarning);
789 : auto poSrcDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
790 : osFilename.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
791 27 : nullptr, nullptr, nullptr));
792 27 : if (!poSrcDS)
793 : {
794 1 : CPLError(bFailOnErrors ? CE_Failure : CE_Warning,
795 : CPLE_AppDefined, "Unable to open %s%s.",
796 : osFilename.c_str(), bFailOnErrors ? "" : ", skipping");
797 1 : if (bFailOnErrors)
798 0 : return nullptr;
799 1 : bSkipFirstTile = true;
800 : }
801 : else
802 : {
803 26 : auto poSrcSRS = poSrcDS->GetSpatialRef();
804 26 : if (poSrcSRS)
805 26 : oSRS = *poSrcSRS;
806 : }
807 : }
808 :
809 42 : if (bIsSTACGeoParquet)
810 : {
811 7 : psOptions->aosLCO.SetNameValue("ROW_GROUP_SIZE",
812 7 : CPLSPrintf("%d", nMaxBatchSize));
813 7 : psOptions->aosLCO.SetNameValue("GEOMETRY_ENCODING", "WKB");
814 7 : psOptions->aosLCO.SetNameValue("GEOMETRY_NAME",
815 7 : GEOPARQUET_GEOM_COL_NAME);
816 7 : psOptions->aosLCO.SetNameValue("FID", "");
817 7 : psOptions->aosLCO.SetNameValue("WRITE_COVERING_BBOX", "YES");
818 7 : psOptions->aosLCO.SetNameValue("COVERING_BBOX_NAME", "bbox");
819 7 : if (CPLTestBool(
820 7 : psOptions->aosLCO.FetchNameValueDef("SORT_BY_BBOX", "NO")))
821 : {
822 0 : CPLError(CE_Failure, CPLE_AppDefined,
823 : "SORT_BY_BBOX=YES is not compatible with "
824 : "STAC-GeoParquet profile");
825 0 : return nullptr;
826 : }
827 : }
828 :
829 42 : poLayer = poTileIndexDS->CreateLayer(
830 42 : osLayerName.c_str(), oSRS.IsEmpty() ? nullptr : &oSRS, wkbPolygon,
831 42 : psOptions->aosLCO.List());
832 42 : if (!poLayer)
833 0 : return nullptr;
834 :
835 42 : if (bIsSTACGeoParquet)
836 : {
837 644 : const auto AddAuxSchema = [&auxSchemas, &noop_release]()
838 : {
839 322 : auto newSchema = std::make_unique<ArrowSchema>(ArrowSchema{});
840 161 : newSchema->release = noop_release;
841 161 : auxSchemas.push_back(std::move(newSchema));
842 322 : return auxSchemas.back().get();
843 7 : };
844 :
845 : // "id" field
846 : {
847 7 : auto &schema = AddTopSchema();
848 7 : schema.format = ARROW_FORMAT_STRING;
849 7 : schema.name = "id";
850 7 : if (!poLayer->CreateFieldFromArrowSchema(&schema))
851 0 : return nullptr;
852 : }
853 : // "stac_extensions" field
854 : {
855 7 : auto &schema = AddTopSchema();
856 :
857 7 : auto &sub_schema = *AddAuxSchema();
858 7 : stacExtensionsSchemaChildren.push_back(&sub_schema);
859 :
860 7 : schema.format = ARROW_FORMAT_LIST;
861 7 : schema.name = "stac_extensions";
862 7 : schema.n_children = 1;
863 7 : schema.children = stacExtensionsSchemaChildren.data();
864 7 : sub_schema.format = ARROW_FORMAT_STRING;
865 7 : sub_schema.name = "item";
866 7 : if (!poLayer->CreateFieldFromArrowSchema(&schema))
867 0 : return nullptr;
868 : }
869 : // "links" field
870 : {
871 7 : auto &links = AddTopSchema();
872 :
873 7 : auto &item = *AddAuxSchema();
874 7 : linksSchemaChildren.push_back(&item);
875 :
876 7 : auto &href = *AddAuxSchema();
877 7 : linksItemSchemaChildren.push_back(&href);
878 :
879 7 : auto &rel = *AddAuxSchema();
880 7 : linksItemSchemaChildren.push_back(&rel);
881 :
882 7 : auto &type = *AddAuxSchema();
883 7 : linksItemSchemaChildren.push_back(&type);
884 :
885 7 : auto &title = *AddAuxSchema();
886 7 : linksItemSchemaChildren.push_back(&title);
887 :
888 7 : links.format = ARROW_FORMAT_LIST;
889 7 : links.name = "links";
890 7 : links.n_children = linksSchemaChildren.size();
891 7 : links.children = linksSchemaChildren.data();
892 :
893 7 : item.format = ARROW_FORMAT_STRUCT;
894 7 : item.name = "item";
895 7 : item.n_children = linksItemSchemaChildren.size();
896 7 : item.children = linksItemSchemaChildren.data();
897 :
898 7 : href.format = ARROW_FORMAT_STRING;
899 7 : href.name = "href";
900 :
901 7 : rel.format = ARROW_FORMAT_STRING;
902 7 : rel.name = "rel";
903 :
904 7 : type.format = ARROW_FORMAT_STRING;
905 7 : type.name = "type";
906 7 : type.flags = ARROW_FLAG_NULLABLE;
907 :
908 7 : title.format = ARROW_FORMAT_STRING;
909 7 : title.name = "title";
910 7 : title.flags = ARROW_FLAG_NULLABLE;
911 :
912 7 : if (!poLayer->CreateFieldFromArrowSchema(&links))
913 0 : return nullptr;
914 : }
915 :
916 : // "assets" field
917 : {
918 7 : auto &assets = AddTopSchema();
919 :
920 7 : auto &image = *AddAuxSchema();
921 7 : assetsSchemaChildren.push_back(&image);
922 :
923 7 : auto &href = *AddAuxSchema();
924 7 : imageAssetSchemaChildren.push_back(&href);
925 :
926 7 : auto &roles = *AddAuxSchema();
927 7 : imageAssetSchemaChildren.push_back(&roles);
928 :
929 7 : auto &title = *AddAuxSchema();
930 7 : imageAssetSchemaChildren.push_back(&title);
931 :
932 7 : auto &type = *AddAuxSchema();
933 7 : imageAssetSchemaChildren.push_back(&type);
934 :
935 7 : auto &roles_item = *AddAuxSchema();
936 7 : imageAssetRolesSchemaChildren.push_back(&roles_item);
937 :
938 7 : assets.format = ARROW_FORMAT_STRUCT;
939 7 : assets.name = "assets";
940 7 : assets.n_children = assetsSchemaChildren.size();
941 7 : assets.children = assetsSchemaChildren.data();
942 :
943 7 : image.format = ARROW_FORMAT_STRUCT;
944 7 : image.name = "image";
945 7 : image.n_children = imageAssetSchemaChildren.size();
946 7 : image.children = imageAssetSchemaChildren.data();
947 :
948 7 : href.format = ARROW_FORMAT_STRING;
949 7 : href.name = "href";
950 :
951 7 : roles.format = ARROW_FORMAT_LIST;
952 7 : roles.name = "roles";
953 7 : roles.flags = ARROW_FLAG_NULLABLE;
954 7 : roles.n_children = imageAssetRolesSchemaChildren.size();
955 7 : roles.children = imageAssetRolesSchemaChildren.data();
956 :
957 7 : roles_item.format = ARROW_FORMAT_STRING;
958 7 : roles_item.name = "item";
959 7 : roles_item.flags = ARROW_FLAG_NULLABLE;
960 :
961 7 : title.format = ARROW_FORMAT_STRING;
962 7 : title.name = "title";
963 7 : title.flags = ARROW_FLAG_NULLABLE;
964 :
965 7 : type.format = ARROW_FORMAT_STRING;
966 7 : type.name = "type";
967 7 : type.flags = ARROW_FLAG_NULLABLE;
968 :
969 7 : if (!poLayer->CreateFieldFromArrowSchema(&assets))
970 0 : return nullptr;
971 : }
972 :
973 : // "bands" field
974 : {
975 7 : auto &bands = AddTopSchema();
976 :
977 7 : auto &bandsItem = *AddAuxSchema();
978 7 : bandsSchemaChildren.push_back(&bandsItem);
979 :
980 7 : bands.format = ARROW_FORMAT_LIST;
981 7 : bands.name = "bands";
982 7 : bands.n_children = bandsSchemaChildren.size();
983 7 : bands.children = bandsSchemaChildren.data();
984 :
985 7 : auto &name = *AddAuxSchema();
986 7 : bandsItemSchemaChildren.push_back(&name);
987 :
988 7 : auto &commonName = *AddAuxSchema();
989 7 : bandsItemSchemaChildren.push_back(&commonName);
990 :
991 7 : auto ¢erWavelength = *AddAuxSchema();
992 7 : bandsItemSchemaChildren.push_back(¢erWavelength);
993 :
994 7 : auto &fullWidthHalfMax = *AddAuxSchema();
995 7 : bandsItemSchemaChildren.push_back(&fullWidthHalfMax);
996 :
997 7 : auto &nodata = *AddAuxSchema();
998 7 : bandsItemSchemaChildren.push_back(&nodata);
999 :
1000 7 : auto &data_type = *AddAuxSchema();
1001 7 : bandsItemSchemaChildren.push_back(&data_type);
1002 :
1003 7 : auto &unit = *AddAuxSchema();
1004 7 : bandsItemSchemaChildren.push_back(&unit);
1005 :
1006 7 : bandsItem.format = ARROW_FORMAT_STRUCT;
1007 7 : bandsItem.name = "item";
1008 7 : bandsItem.n_children = bandsItemSchemaChildren.size();
1009 7 : bandsItem.children = bandsItemSchemaChildren.data();
1010 :
1011 7 : name.format = ARROW_FORMAT_STRING;
1012 7 : name.name = "name";
1013 :
1014 7 : commonName.format = ARROW_FORMAT_STRING;
1015 7 : commonName.name = "eo:common_name";
1016 7 : commonName.flags = ARROW_FLAG_NULLABLE;
1017 :
1018 7 : centerWavelength.format = ARROW_FORMAT_FLOAT32;
1019 7 : centerWavelength.name = "eo:center_wavelength";
1020 7 : centerWavelength.flags = ARROW_FLAG_NULLABLE;
1021 :
1022 7 : fullWidthHalfMax.format = ARROW_FORMAT_FLOAT32;
1023 7 : fullWidthHalfMax.name = "eo:full_width_half_max";
1024 7 : fullWidthHalfMax.flags = ARROW_FLAG_NULLABLE;
1025 :
1026 7 : nodata.format = ARROW_FORMAT_STRING;
1027 7 : nodata.name = "nodata";
1028 7 : nodata.flags = ARROW_FLAG_NULLABLE;
1029 :
1030 7 : data_type.format = ARROW_FORMAT_STRING;
1031 7 : data_type.name = "data_type";
1032 :
1033 7 : unit.format = ARROW_FORMAT_STRING;
1034 7 : unit.name = "unit";
1035 7 : unit.flags = ARROW_FLAG_NULLABLE;
1036 :
1037 7 : if (!poLayer->CreateFieldFromArrowSchema(&bands))
1038 0 : return nullptr;
1039 : }
1040 :
1041 : // "proj:code" field
1042 : {
1043 7 : auto &schema = AddTopSchema();
1044 7 : schema.format = ARROW_FORMAT_STRING;
1045 7 : schema.name = "proj:code";
1046 7 : schema.flags = ARROW_FLAG_NULLABLE;
1047 7 : if (!poLayer->CreateFieldFromArrowSchema(&schema))
1048 0 : return nullptr;
1049 : }
1050 : // "proj:wkt2" field
1051 : {
1052 7 : auto &schema = AddTopSchema();
1053 7 : schema.format = ARROW_FORMAT_STRING;
1054 7 : schema.name = "proj:wkt2";
1055 7 : schema.flags = ARROW_FLAG_NULLABLE;
1056 7 : if (!poLayer->CreateFieldFromArrowSchema(&schema))
1057 0 : return nullptr;
1058 : }
1059 : // "proj:projjson" field
1060 : {
1061 7 : auto &schema = AddTopSchema();
1062 7 : schema.format = ARROW_FORMAT_STRING;
1063 7 : schema.name = "proj:projjson";
1064 : // clang-format off
1065 : static const char jsonMetadata[] = {
1066 : // Number of key/value pairs (uint32)
1067 : 1, 0, 0, 0,
1068 : // Length of key (uint32)
1069 : 20, 0, 0, 0,
1070 : 'A', 'R', 'R', 'O', 'W', ':',
1071 : 'e', 'x', 't', 'e', 'n', 's', 'i', 'o', 'n', ':',
1072 : 'n', 'a', 'm', 'e',
1073 : // Length of value (uint32)
1074 : 10, 0, 0, 0,
1075 : 'a', 'r', 'r', 'o', 'w', '.', 'j', 's', 'o', 'n',
1076 : };
1077 : // clang-format on
1078 7 : schema.metadata = jsonMetadata;
1079 7 : schema.flags = ARROW_FLAG_NULLABLE;
1080 7 : if (!poLayer->CreateFieldFromArrowSchema(&schema))
1081 0 : return nullptr;
1082 : }
1083 : // "proj:bbox" field
1084 : {
1085 7 : auto &schema = AddTopSchema();
1086 : static const char FORMAT_PROJ_BBOX[] = {
1087 : '+', 'w', ':', '0' + NUM_ITEMS_PROJ_BBOX, 0};
1088 7 : schema.format = FORMAT_PROJ_BBOX;
1089 7 : schema.name = "proj:bbox";
1090 :
1091 7 : auto &sub_schema = *AddAuxSchema();
1092 7 : projBboxSchemaChildren.push_back(&sub_schema);
1093 :
1094 7 : schema.n_children = projBboxSchemaChildren.size();
1095 7 : schema.children = projBboxSchemaChildren.data();
1096 7 : sub_schema.format = ARROW_FORMAT_FLOAT64;
1097 7 : sub_schema.name = "item";
1098 :
1099 7 : if (!poLayer->CreateFieldFromArrowSchema(&schema))
1100 0 : return nullptr;
1101 : }
1102 : // "proj:shape" field
1103 : {
1104 7 : auto &schema = AddTopSchema();
1105 : static const char FORMAT_PROJ_SHAPE[] = {
1106 : '+', 'w', ':', '0' + NUM_ITEMS_PROJ_SHAPE, 0};
1107 7 : schema.format = FORMAT_PROJ_SHAPE;
1108 7 : schema.name = "proj:shape";
1109 :
1110 7 : auto &sub_schema = *AddAuxSchema();
1111 7 : projShapeSchemaChildren.push_back(&sub_schema);
1112 :
1113 7 : schema.n_children = projShapeSchemaChildren.size();
1114 7 : schema.children = projShapeSchemaChildren.data();
1115 7 : sub_schema.format = ARROW_FORMAT_INT32;
1116 7 : sub_schema.name = "item";
1117 :
1118 7 : if (!poLayer->CreateFieldFromArrowSchema(&schema))
1119 0 : return nullptr;
1120 : }
1121 : // "proj:transform" field
1122 : {
1123 7 : auto &schema = AddTopSchema();
1124 : static const char FORMAT_PROJ_TRANSFORM[] = {
1125 : '+', 'w', ':', '0' + NUM_ITEMS_PROJ_TRANSFORM, 0};
1126 7 : schema.format = FORMAT_PROJ_TRANSFORM;
1127 7 : schema.name = "proj:transform";
1128 :
1129 7 : auto &sub_schema = *AddAuxSchema();
1130 7 : projTransformSchemaChildren.push_back(&sub_schema);
1131 :
1132 7 : schema.n_children = projTransformSchemaChildren.size();
1133 7 : schema.children = projTransformSchemaChildren.data();
1134 7 : sub_schema.format = ARROW_FORMAT_FLOAT64;
1135 7 : sub_schema.name = "item";
1136 :
1137 7 : if (!poLayer->CreateFieldFromArrowSchema(&schema))
1138 0 : return nullptr;
1139 : }
1140 : }
1141 : else
1142 : {
1143 35 : OGRFieldDefn oLocationField(psOptions->osLocationField.c_str(),
1144 35 : OFTString);
1145 35 : oLocationField.SetWidth(nMaxFieldSize);
1146 35 : if (poLayer->CreateField(&oLocationField) != OGRERR_NONE)
1147 0 : return nullptr;
1148 : }
1149 :
1150 42 : if (!psOptions->osSrcSRSFieldName.empty())
1151 : {
1152 6 : OGRFieldDefn oSrcSRSField(psOptions->osSrcSRSFieldName.c_str(),
1153 6 : OFTString);
1154 6 : oSrcSRSField.SetWidth(nMaxFieldSize);
1155 6 : if (poLayer->CreateField(&oSrcSRSField) != OGRERR_NONE)
1156 0 : return nullptr;
1157 : }
1158 : }
1159 :
1160 51 : auto poLayerDefn = poLayer->GetLayerDefn();
1161 :
1162 51 : if (!bIsSTACGeoParquet)
1163 : {
1164 49 : for (const auto &oFetchMD : psOptions->aoFetchMD)
1165 : {
1166 5 : if (poLayerDefn->GetFieldIndex(oFetchMD.osFieldName.c_str()) < 0)
1167 : {
1168 : OGRFieldDefn oField(oFetchMD.osFieldName.c_str(),
1169 5 : oFetchMD.eType);
1170 5 : if (poLayer->CreateField(&oField) != OGRERR_NONE)
1171 0 : return nullptr;
1172 : }
1173 : }
1174 : }
1175 :
1176 51 : if (bIsSTACGeoParquet)
1177 : {
1178 : {
1179 7 : auto &geometry = AddTopSchema();
1180 7 : geometry.format = ARROW_FORMAT_BINARY;
1181 7 : geometry.name = GEOPARQUET_GEOM_COL_NAME;
1182 : }
1183 :
1184 91 : for (auto &schema : topSchemas)
1185 84 : topSchemasPointers.push_back(&schema);
1186 :
1187 7 : topSchema.format = ARROW_FORMAT_STRUCT;
1188 7 : topSchema.name = "main";
1189 7 : topSchema.release = noop_release;
1190 7 : topSchema.n_children = topSchemasPointers.size();
1191 7 : topSchema.children = topSchemasPointers.data();
1192 : }
1193 :
1194 51 : if (!psOptions->osGTIFilename.empty())
1195 : {
1196 2 : if (!psOptions->aosMetadata.empty())
1197 : {
1198 0 : CPLError(CE_Failure, CPLE_NotSupported,
1199 : "-mo is not supported when -gti_filename is used");
1200 0 : return nullptr;
1201 : }
1202 : CPLXMLNode *psRoot =
1203 2 : CPLCreateXMLNode(nullptr, CXT_Element, "GDALTileIndexDataset");
1204 2 : CPLCreateXMLElementAndValue(psRoot, "IndexDataset", pszDest);
1205 2 : CPLCreateXMLElementAndValue(psRoot, "IndexLayer", poLayer->GetName());
1206 2 : CPLCreateXMLElementAndValue(psRoot, "LocationField",
1207 2 : psOptions->osLocationField.c_str());
1208 2 : if (!std::isnan(psOptions->xres))
1209 : {
1210 1 : CPLCreateXMLElementAndValue(psRoot, "ResX",
1211 1 : CPLSPrintf("%.18g", psOptions->xres));
1212 1 : CPLCreateXMLElementAndValue(psRoot, "ResY",
1213 1 : CPLSPrintf("%.18g", psOptions->yres));
1214 : }
1215 2 : if (!std::isnan(psOptions->xmin))
1216 : {
1217 1 : CPLCreateXMLElementAndValue(psRoot, "MinX",
1218 1 : CPLSPrintf("%.18g", psOptions->xmin));
1219 1 : CPLCreateXMLElementAndValue(psRoot, "MinY",
1220 1 : CPLSPrintf("%.18g", psOptions->ymin));
1221 1 : CPLCreateXMLElementAndValue(psRoot, "MaxX",
1222 1 : CPLSPrintf("%.18g", psOptions->xmax));
1223 1 : CPLCreateXMLElementAndValue(psRoot, "MaxY",
1224 1 : CPLSPrintf("%.18g", psOptions->ymax));
1225 : }
1226 :
1227 2 : int nBandCount = 0;
1228 2 : if (!psOptions->osBandCount.empty())
1229 : {
1230 0 : nBandCount = atoi(psOptions->osBandCount.c_str());
1231 : }
1232 : else
1233 : {
1234 2 : if (!psOptions->osDataType.empty())
1235 : {
1236 0 : nBandCount = std::max(
1237 : nBandCount,
1238 0 : CPLStringList(CSLTokenizeString2(
1239 0 : psOptions->osDataType.c_str(), ", ", 0))
1240 0 : .size());
1241 : }
1242 2 : if (!psOptions->osNodata.empty())
1243 : {
1244 1 : nBandCount = std::max(
1245 : nBandCount,
1246 2 : CPLStringList(CSLTokenizeString2(
1247 1 : psOptions->osNodata.c_str(), ", ", 0))
1248 1 : .size());
1249 : }
1250 2 : if (!psOptions->osColorInterp.empty())
1251 : {
1252 1 : nBandCount =
1253 1 : std::max(nBandCount,
1254 2 : CPLStringList(
1255 : CSLTokenizeString2(
1256 1 : psOptions->osColorInterp.c_str(), ", ", 0))
1257 1 : .size());
1258 : }
1259 : }
1260 :
1261 3 : for (int i = 0; i < nBandCount; ++i)
1262 : {
1263 1 : auto psBand = CPLCreateXMLNode(psRoot, CXT_Element, "Band");
1264 1 : CPLAddXMLAttributeAndValue(psBand, "band", CPLSPrintf("%d", i + 1));
1265 1 : if (!psOptions->osDataType.empty())
1266 : {
1267 : const CPLStringList aosTokens(
1268 0 : CSLTokenizeString2(psOptions->osDataType.c_str(), ", ", 0));
1269 0 : if (aosTokens.size() == 1)
1270 0 : CPLAddXMLAttributeAndValue(psBand, "dataType",
1271 : aosTokens[0]);
1272 0 : else if (i < aosTokens.size())
1273 0 : CPLAddXMLAttributeAndValue(psBand, "dataType",
1274 : aosTokens[i]);
1275 : }
1276 1 : if (!psOptions->osNodata.empty())
1277 : {
1278 : const CPLStringList aosTokens(
1279 2 : CSLTokenizeString2(psOptions->osNodata.c_str(), ", ", 0));
1280 1 : if (aosTokens.size() == 1)
1281 1 : CPLCreateXMLElementAndValue(psBand, "NoDataValue",
1282 : aosTokens[0]);
1283 0 : else if (i < aosTokens.size())
1284 0 : CPLCreateXMLElementAndValue(psBand, "NoDataValue",
1285 : aosTokens[i]);
1286 : }
1287 1 : if (!psOptions->osColorInterp.empty())
1288 : {
1289 : const CPLStringList aosTokens(CSLTokenizeString2(
1290 2 : psOptions->osColorInterp.c_str(), ", ", 0));
1291 1 : if (aosTokens.size() == 1)
1292 1 : CPLCreateXMLElementAndValue(psBand, "ColorInterp",
1293 : aosTokens[0]);
1294 0 : else if (i < aosTokens.size())
1295 0 : CPLCreateXMLElementAndValue(psBand, "ColorInterp",
1296 : aosTokens[i]);
1297 : }
1298 : }
1299 :
1300 2 : if (psOptions->bMaskBand)
1301 : {
1302 1 : CPLCreateXMLElementAndValue(psRoot, "MaskBand", "true");
1303 : }
1304 : int res =
1305 2 : CPLSerializeXMLTreeToFile(psRoot, psOptions->osGTIFilename.c_str());
1306 2 : CPLDestroyXMLNode(psRoot);
1307 2 : if (!res)
1308 0 : return nullptr;
1309 : }
1310 : else
1311 : {
1312 49 : if (!bIsSTACGeoParquet)
1313 : {
1314 42 : poLayer->SetMetadataItem("LOCATION_FIELD",
1315 42 : psOptions->osLocationField.c_str());
1316 : }
1317 49 : if (!std::isnan(psOptions->xres))
1318 : {
1319 2 : poLayer->SetMetadataItem("RESX",
1320 2 : CPLSPrintf("%.18g", psOptions->xres));
1321 2 : poLayer->SetMetadataItem("RESY",
1322 2 : CPLSPrintf("%.18g", psOptions->yres));
1323 : }
1324 49 : if (!std::isnan(psOptions->xmin))
1325 : {
1326 2 : poLayer->SetMetadataItem("MINX",
1327 2 : CPLSPrintf("%.18g", psOptions->xmin));
1328 2 : poLayer->SetMetadataItem("MINY",
1329 2 : CPLSPrintf("%.18g", psOptions->ymin));
1330 2 : poLayer->SetMetadataItem("MAXX",
1331 2 : CPLSPrintf("%.18g", psOptions->xmax));
1332 2 : poLayer->SetMetadataItem("MAXY",
1333 2 : CPLSPrintf("%.18g", psOptions->ymax));
1334 : }
1335 49 : if (!psOptions->osBandCount.empty())
1336 : {
1337 2 : poLayer->SetMetadataItem("BAND_COUNT",
1338 2 : psOptions->osBandCount.c_str());
1339 : }
1340 49 : if (!psOptions->osDataType.empty())
1341 : {
1342 2 : poLayer->SetMetadataItem("DATA_TYPE",
1343 2 : psOptions->osDataType.c_str());
1344 : }
1345 49 : if (!psOptions->osNodata.empty())
1346 : {
1347 2 : poLayer->SetMetadataItem("NODATA", psOptions->osNodata.c_str());
1348 : }
1349 49 : if (!psOptions->osColorInterp.empty())
1350 : {
1351 2 : poLayer->SetMetadataItem("COLOR_INTERPRETATION",
1352 2 : psOptions->osColorInterp.c_str());
1353 : }
1354 49 : if (psOptions->bMaskBand)
1355 : {
1356 2 : poLayer->SetMetadataItem("MASK_BAND", "YES");
1357 : }
1358 98 : const CPLStringList aosMetadata(psOptions->aosMetadata);
1359 4 : for (const auto &[pszKey, pszValue] :
1360 53 : cpl::IterateNameValue(aosMetadata))
1361 : {
1362 2 : poLayer->SetMetadataItem(pszKey, pszValue);
1363 : }
1364 : }
1365 :
1366 : const int ti_field =
1367 51 : poLayerDefn->GetFieldIndex(psOptions->osLocationField.c_str());
1368 51 : if (!bIsSTACGeoParquet && ti_field < 0)
1369 : {
1370 0 : CPLError(CE_Failure, CPLE_AppDefined,
1371 : "Unable to find field `%s' in file `%s'.",
1372 0 : psOptions->osLocationField.c_str(), pszDest);
1373 0 : return nullptr;
1374 : }
1375 :
1376 51 : int i_SrcSRSName = -1;
1377 51 : if (!bIsSTACGeoParquet && !psOptions->osSrcSRSFieldName.empty())
1378 : {
1379 : i_SrcSRSName =
1380 6 : poLayerDefn->GetFieldIndex(psOptions->osSrcSRSFieldName.c_str());
1381 6 : if (i_SrcSRSName < 0)
1382 : {
1383 0 : CPLError(CE_Failure, CPLE_AppDefined,
1384 : "Unable to find field `%s' in file `%s'.",
1385 0 : psOptions->osSrcSRSFieldName.c_str(), pszDest);
1386 0 : return nullptr;
1387 : }
1388 : }
1389 :
1390 : // Load in memory existing file names in tile index.
1391 102 : std::set<std::string> oSetExistingFiles;
1392 102 : OGRSpatialReference oAlreadyExistingSRS;
1393 51 : if (bExistingLayer)
1394 : {
1395 31 : for (auto &&poFeature : poLayer)
1396 : {
1397 22 : if (poFeature->IsFieldSetAndNotNull(ti_field))
1398 : {
1399 22 : if (oSetExistingFiles.empty())
1400 : {
1401 : auto poSrcDS =
1402 : std::unique_ptr<GDALDataset>(GDALDataset::Open(
1403 : poFeature->GetFieldAsString(ti_field),
1404 18 : GDAL_OF_RASTER, nullptr, nullptr, nullptr));
1405 9 : if (poSrcDS)
1406 : {
1407 9 : auto poSrcSRS = poSrcDS->GetSpatialRef();
1408 9 : if (poSrcSRS)
1409 9 : oAlreadyExistingSRS = *poSrcSRS;
1410 : }
1411 : }
1412 22 : oSetExistingFiles.insert(poFeature->GetFieldAsString(ti_field));
1413 : }
1414 : }
1415 : }
1416 :
1417 102 : std::string osCurrentPath;
1418 51 : if (psOptions->bWriteAbsolutePath)
1419 : {
1420 3 : char *pszCurrentPath = CPLGetCurrentDir();
1421 3 : if (pszCurrentPath)
1422 : {
1423 3 : osCurrentPath = pszCurrentPath;
1424 : }
1425 : else
1426 : {
1427 0 : CPLError(CE_Warning, CPLE_AppDefined,
1428 : "This system does not support the CPLGetCurrentDir call. "
1429 : "The option -bWriteAbsolutePath will have no effect.");
1430 : }
1431 3 : CPLFree(pszCurrentPath);
1432 : }
1433 :
1434 : const bool bIsGTIContext =
1435 99 : !std::isnan(psOptions->xres) || !std::isnan(psOptions->xmin) ||
1436 48 : !psOptions->osBandCount.empty() || !psOptions->osNodata.empty() ||
1437 48 : !psOptions->osColorInterp.empty() || !psOptions->osDataType.empty() ||
1438 145 : psOptions->bMaskBand || !psOptions->aosMetadata.empty() ||
1439 46 : !psOptions->osGTIFilename.empty();
1440 :
1441 : /* -------------------------------------------------------------------- */
1442 : /* loop over GDAL files, processing. */
1443 : /* -------------------------------------------------------------------- */
1444 51 : ArrowArray topArray{};
1445 :
1446 : struct TopArrayReleaser
1447 : {
1448 : ArrowArray *m_array = nullptr;
1449 :
1450 51 : explicit TopArrayReleaser(ArrowArray *array) : m_array(array)
1451 : {
1452 51 : }
1453 :
1454 51 : ~TopArrayReleaser()
1455 51 : {
1456 51 : if (m_array && m_array->release)
1457 0 : m_array->release(m_array);
1458 51 : }
1459 :
1460 : TopArrayReleaser(const TopArrayReleaser &) = delete;
1461 : TopArrayReleaser &operator=(const TopArrayReleaser &) = delete;
1462 : };
1463 :
1464 102 : TopArrayReleaser arrayReleaser(&topArray);
1465 :
1466 51 : ArrowArray **topArrays = nullptr;
1467 :
1468 51 : int iArray = 0;
1469 51 : const int iIdArray = iArray++;
1470 :
1471 51 : const int iStacExtensionsArray = iArray++;
1472 51 : ArrowArray *stacExtensionSubArray = nullptr;
1473 51 : uint32_t nStacExtensionSubArrayMaxAlloc = 0;
1474 :
1475 51 : const int iLinksArray = iArray++;
1476 51 : ArrowArray *linksItemArray = nullptr;
1477 :
1478 51 : const int iAssetsArray = iArray++;
1479 51 : ArrowArray *imageArray = nullptr;
1480 51 : uint32_t nImageHrefArrayMaxAlloc = 0;
1481 51 : ArrowArray *imageHrefArray = nullptr;
1482 51 : ArrowArray *imageRoleArray = nullptr;
1483 51 : ArrowArray *imageRoleItemArray = nullptr;
1484 51 : uint32_t nImageRoleItemArrayMaxAlloc = 0;
1485 51 : ArrowArray *imageTitleArray = nullptr;
1486 51 : uint32_t nImageTitleArrayMaxAlloc = 0;
1487 51 : ArrowArray *imageTypeArray = nullptr;
1488 51 : uint32_t nImageTypeArrayMaxAlloc = 0;
1489 :
1490 51 : const int iBandsArray = iArray++;
1491 51 : uint32_t nBandsItemCount = 0;
1492 51 : uint32_t nBandsItemAlloc = 0;
1493 51 : ArrowArray *bandsItemArray = nullptr;
1494 51 : ArrowArray *bandsNameArray = nullptr;
1495 51 : uint32_t nBandsNameArrayMaxAlloc = 0;
1496 51 : ArrowArray *bandsCommonNameArray = nullptr;
1497 51 : uint32_t nBandsCommonNameArrayMaxAlloc = 0;
1498 51 : ArrowArray *bandsCenterWavelengthArray = nullptr;
1499 51 : uint32_t nBandsCenterWavelengthArrayMaxAlloc = 0;
1500 51 : ArrowArray *bandsFWHMArray = nullptr;
1501 51 : uint32_t nBandsFWHMArrayMaxAlloc = 0;
1502 51 : ArrowArray *bandsNodataArray = nullptr;
1503 51 : uint32_t nBandsNodataArrayMaxAlloc = 0;
1504 51 : ArrowArray *bandsDataTypeArray = nullptr;
1505 51 : uint32_t nBandsDataTypeArrayMaxAlloc = 0;
1506 51 : ArrowArray *bandsUnitArray = nullptr;
1507 51 : uint32_t nBandsUnitArrayMaxAlloc = 0;
1508 :
1509 51 : const int iProjCode = iArray++;
1510 51 : uint32_t nProjCodeArrayMaxAlloc = 0;
1511 51 : const int iProjWKT2 = iArray++;
1512 51 : uint32_t nProjWKT2ArrayMaxAlloc = 0;
1513 51 : const int iProjPROJJSON = iArray++;
1514 51 : uint32_t nProjPROJJSONArrayMaxAlloc = 0;
1515 51 : const int iProjBBOX = iArray++;
1516 51 : ArrowArray *projBBOXItems = nullptr;
1517 51 : const int iProjShape = iArray++;
1518 51 : ArrowArray *projShapeItems = nullptr;
1519 51 : const int iProjTransform = iArray++;
1520 51 : ArrowArray *projTransformItems = nullptr;
1521 :
1522 51 : const int iWkbArray = iArray++;
1523 :
1524 51 : std::unique_ptr<OGRArrowArrayHelper> arrayHelper;
1525 :
1526 : const auto InitTopArray =
1527 8 : [iIdArray, iStacExtensionsArray, iLinksArray, iAssetsArray, iBandsArray,
1528 : iProjCode, iProjWKT2, iProjPROJJSON, iProjBBOX, iProjShape,
1529 : iProjTransform, iWkbArray, nMaxBatchSize, &arrayHelper, &topArray,
1530 : &topArrays, &topSchema, &nStacExtensionSubArrayMaxAlloc,
1531 : &stacExtensionSubArray, &linksItemArray, &imageArray,
1532 : &nImageHrefArrayMaxAlloc, &imageHrefArray, &imageRoleArray,
1533 : &imageRoleItemArray, &nImageRoleItemArrayMaxAlloc, &imageTitleArray,
1534 : &imageTypeArray, &nImageTitleArrayMaxAlloc, &nImageTypeArrayMaxAlloc,
1535 : &nBandsItemCount, &nBandsItemAlloc, &bandsItemArray, &bandsNameArray,
1536 : &nBandsNameArrayMaxAlloc, &bandsCommonNameArray,
1537 : &nBandsCommonNameArrayMaxAlloc, &nBandsCenterWavelengthArrayMaxAlloc,
1538 : &bandsCenterWavelengthArray, &nBandsFWHMArrayMaxAlloc, &bandsFWHMArray,
1539 : &bandsNodataArray, &nBandsNodataArrayMaxAlloc, &bandsDataTypeArray,
1540 : &nBandsDataTypeArrayMaxAlloc, &bandsUnitArray,
1541 : &nBandsUnitArrayMaxAlloc, &nProjCodeArrayMaxAlloc,
1542 : &nProjWKT2ArrayMaxAlloc, &nProjPROJJSONArrayMaxAlloc, &projBBOXItems,
1543 664 : &projShapeItems, &projTransformItems]()
1544 : {
1545 280 : const auto AllocArray = []()
1546 : {
1547 : auto array =
1548 280 : static_cast<ArrowArray *>(CPLCalloc(1, sizeof(ArrowArray)));
1549 280 : array->release = ReleaseArray;
1550 280 : return array;
1551 : };
1552 :
1553 288 : const auto AllocNBuffers = [](ArrowArray &array, int n_buffers)
1554 : {
1555 288 : array.n_buffers = n_buffers;
1556 288 : array.buffers = static_cast<const void **>(
1557 288 : CPLCalloc(n_buffers, sizeof(const void *)));
1558 288 : };
1559 :
1560 88 : const auto AllocNArrays = [](ArrowArray &array, int n_children)
1561 : {
1562 88 : array.n_children = n_children;
1563 88 : array.children = static_cast<ArrowArray **>(
1564 88 : CPLCalloc(n_children, sizeof(ArrowArray *)));
1565 88 : };
1566 :
1567 : const auto InitializePrimitiveArray =
1568 16 : [&AllocNBuffers](ArrowArray &sArray, size_t nEltSize,
1569 16 : size_t nLength)
1570 : {
1571 16 : AllocNBuffers(sArray, 2);
1572 32 : sArray.buffers[ARROW_BUF_DATA] =
1573 16 : static_cast<const void *>(CPLCalloc(nLength, nEltSize));
1574 24 : };
1575 :
1576 : const auto InitializeStringOrBinaryArray =
1577 152 : [&AllocNBuffers](ArrowArray &sArray, size_t nLength)
1578 : {
1579 152 : AllocNBuffers(sArray, 3);
1580 : // +1 since the length of string of idx i is given by
1581 : // offset[i+1] - offset[i]
1582 304 : sArray.buffers[ARROW_BUF_DATA] = static_cast<const void *>(
1583 152 : CPLCalloc(nLength + 1, sizeof(uint32_t)));
1584 : // Allocate a minimum amount to not get a null pointer
1585 304 : sArray.buffers[ARROW_BUF_BYTES] =
1586 152 : static_cast<const void *>(CPLCalloc(1, 1));
1587 160 : };
1588 :
1589 : const auto InitializeListArray =
1590 32 : [&AllocNBuffers, &AllocNArrays](
1591 64 : ArrowArray &sArray, ArrowArray *subArray, size_t nLength)
1592 : {
1593 32 : AllocNBuffers(sArray, 2);
1594 64 : sArray.buffers[ARROW_BUF_DATA] = static_cast<const void *>(
1595 32 : CPLCalloc(nLength + 1, sizeof(uint32_t)));
1596 32 : AllocNArrays(sArray, 1);
1597 32 : sArray.children[0] = subArray;
1598 40 : };
1599 :
1600 : const auto InitializeFixedSizeListArray =
1601 24 : [&AllocNBuffers,
1602 : &AllocNArrays](ArrowArray &sArray, ArrowArray *subArray,
1603 72 : size_t nItemSize, size_t nItemCount, size_t nLength)
1604 : {
1605 24 : AllocNArrays(sArray, 1);
1606 24 : AllocNBuffers(sArray, 1);
1607 24 : sArray.children[0] = subArray;
1608 24 : AllocNBuffers(*subArray, 2);
1609 48 : subArray->buffers[ARROW_BUF_DATA] = static_cast<const void *>(
1610 24 : CPLCalloc(nItemCount * nItemSize, nLength));
1611 32 : };
1612 :
1613 40 : const auto InitializeStructArray = [&AllocNBuffers](ArrowArray &sArray)
1614 40 : { AllocNBuffers(sArray, 1); };
1615 :
1616 16 : topArrays = static_cast<ArrowArray **>(CPLCalloc(
1617 8 : static_cast<int>(topSchema.n_children), sizeof(ArrowArray *)));
1618 104 : for (int i = 0; i < static_cast<int>(topSchema.n_children); ++i)
1619 96 : topArrays[i] = AllocArray();
1620 :
1621 8 : topArray = ArrowArray{};
1622 8 : topArray.release = ReleaseArray;
1623 8 : topArray.n_children = topSchema.n_children;
1624 8 : topArray.children = topArrays;
1625 8 : InitializeStructArray(topArray);
1626 :
1627 8 : InitializeStringOrBinaryArray(*topArrays[iIdArray], nMaxBatchSize);
1628 :
1629 8 : stacExtensionSubArray = AllocArray();
1630 8 : nStacExtensionSubArrayMaxAlloc = 0;
1631 : {
1632 8 : auto *array = topArrays[iStacExtensionsArray];
1633 8 : InitializeListArray(*array, stacExtensionSubArray, nMaxBatchSize);
1634 8 : InitializeStringOrBinaryArray(
1635 8 : *stacExtensionSubArray, COUNT_STAC_EXTENSIONS * nMaxBatchSize);
1636 : }
1637 :
1638 8 : linksItemArray = AllocArray();
1639 : {
1640 8 : auto *array = topArrays[iLinksArray];
1641 8 : InitializeListArray(*array, linksItemArray, nMaxBatchSize);
1642 8 : InitializeStructArray(*linksItemArray);
1643 :
1644 8 : AllocNArrays(*linksItemArray, 4);
1645 8 : ArrowArray *linksHrefArray = AllocArray();
1646 8 : ArrowArray *linksRelArray = AllocArray();
1647 8 : ArrowArray *linksTypeArray = AllocArray();
1648 8 : ArrowArray *linksTitleArray = AllocArray();
1649 8 : linksItemArray->children[0] = linksHrefArray;
1650 8 : linksItemArray->children[1] = linksRelArray;
1651 8 : linksItemArray->children[2] = linksTypeArray;
1652 8 : linksItemArray->children[3] = linksTitleArray;
1653 8 : InitializeStringOrBinaryArray(*linksHrefArray, nMaxBatchSize);
1654 8 : InitializeStringOrBinaryArray(*linksRelArray, nMaxBatchSize);
1655 8 : InitializeStringOrBinaryArray(*linksTypeArray, nMaxBatchSize);
1656 8 : InitializeStringOrBinaryArray(*linksTitleArray, nMaxBatchSize);
1657 : }
1658 :
1659 8 : imageArray = AllocArray();
1660 8 : nImageHrefArrayMaxAlloc = 0;
1661 8 : imageHrefArray = AllocArray();
1662 8 : imageRoleArray = AllocArray();
1663 8 : imageRoleItemArray = AllocArray();
1664 8 : nImageHrefArrayMaxAlloc = 0;
1665 8 : nImageRoleItemArrayMaxAlloc = 0;
1666 8 : imageTitleArray = AllocArray();
1667 8 : nImageTitleArrayMaxAlloc = 0;
1668 8 : imageTypeArray = AllocArray();
1669 8 : nImageTypeArrayMaxAlloc = 0;
1670 : {
1671 8 : auto *assets = topArrays[iAssetsArray];
1672 8 : InitializeStructArray(*assets);
1673 8 : AllocNArrays(*assets, 1);
1674 8 : assets->children[0] = imageArray;
1675 :
1676 8 : InitializeStructArray(*imageArray);
1677 8 : AllocNArrays(*imageArray, 4);
1678 8 : imageArray->children[0] = imageHrefArray;
1679 8 : imageArray->children[1] = imageRoleArray;
1680 8 : imageArray->children[2] = imageTitleArray;
1681 8 : imageArray->children[3] = imageTypeArray;
1682 :
1683 8 : InitializeStringOrBinaryArray(*imageHrefArray, nMaxBatchSize);
1684 8 : InitializeStringOrBinaryArray(*imageTitleArray, nMaxBatchSize);
1685 8 : InitializeStringOrBinaryArray(*imageTypeArray, nMaxBatchSize);
1686 8 : InitializeListArray(*imageRoleArray, imageRoleItemArray,
1687 : nMaxBatchSize);
1688 8 : InitializeStringOrBinaryArray(*imageRoleItemArray, nMaxBatchSize);
1689 : }
1690 :
1691 : // "bands" related initialization
1692 : {
1693 8 : nBandsItemCount = 0;
1694 8 : nBandsItemAlloc = 0;
1695 8 : bandsItemArray = AllocArray();
1696 8 : InitializeListArray(*(topArrays[iBandsArray]), bandsItemArray,
1697 : nMaxBatchSize);
1698 8 : InitializeStructArray(*bandsItemArray);
1699 :
1700 8 : bandsNameArray = AllocArray();
1701 8 : InitializeStringOrBinaryArray(*bandsNameArray, 0);
1702 8 : nBandsNameArrayMaxAlloc = 0;
1703 :
1704 8 : bandsCommonNameArray = AllocArray();
1705 8 : InitializeStringOrBinaryArray(*bandsCommonNameArray, 0);
1706 8 : nBandsCommonNameArrayMaxAlloc = 0;
1707 :
1708 8 : bandsCenterWavelengthArray = AllocArray();
1709 8 : InitializePrimitiveArray(*bandsCenterWavelengthArray, sizeof(float),
1710 : 1);
1711 8 : nBandsCenterWavelengthArrayMaxAlloc = 0;
1712 :
1713 8 : bandsFWHMArray = AllocArray();
1714 8 : InitializePrimitiveArray(*bandsFWHMArray, sizeof(float), 1);
1715 8 : nBandsFWHMArrayMaxAlloc = 0;
1716 :
1717 8 : bandsNodataArray = AllocArray();
1718 8 : InitializeStringOrBinaryArray(*bandsNodataArray, 0);
1719 8 : nBandsNodataArrayMaxAlloc = 0;
1720 :
1721 8 : bandsDataTypeArray = AllocArray();
1722 8 : InitializeStringOrBinaryArray(*bandsDataTypeArray, 0);
1723 8 : nBandsDataTypeArrayMaxAlloc = 0;
1724 :
1725 8 : bandsUnitArray = AllocArray();
1726 8 : InitializeStringOrBinaryArray(*bandsUnitArray, 0);
1727 8 : nBandsUnitArrayMaxAlloc = 0;
1728 :
1729 8 : AllocNArrays(*bandsItemArray, 7);
1730 8 : bandsItemArray->children[0] = bandsNameArray;
1731 8 : bandsItemArray->children[1] = bandsCommonNameArray;
1732 8 : bandsItemArray->children[2] = bandsCenterWavelengthArray;
1733 8 : bandsItemArray->children[3] = bandsFWHMArray;
1734 8 : bandsItemArray->children[4] = bandsNodataArray;
1735 8 : bandsItemArray->children[5] = bandsDataTypeArray;
1736 8 : bandsItemArray->children[6] = bandsUnitArray;
1737 : }
1738 :
1739 : // proj:xxxx related initializations
1740 : {
1741 8 : InitializeStringOrBinaryArray(*topArrays[iProjCode], nMaxBatchSize);
1742 8 : nProjCodeArrayMaxAlloc = 0;
1743 8 : InitializeStringOrBinaryArray(*topArrays[iProjWKT2], nMaxBatchSize);
1744 8 : nProjWKT2ArrayMaxAlloc = 0;
1745 8 : InitializeStringOrBinaryArray(*topArrays[iProjPROJJSON],
1746 : nMaxBatchSize);
1747 8 : nProjPROJJSONArrayMaxAlloc = 0;
1748 :
1749 8 : projBBOXItems = AllocArray();
1750 8 : InitializeFixedSizeListArray(*(topArrays[iProjBBOX]), projBBOXItems,
1751 : sizeof(double), NUM_ITEMS_PROJ_BBOX,
1752 : nMaxBatchSize);
1753 :
1754 8 : projShapeItems = AllocArray();
1755 8 : InitializeFixedSizeListArray(*(topArrays[iProjShape]),
1756 : projShapeItems, sizeof(int32_t),
1757 : NUM_ITEMS_PROJ_SHAPE, nMaxBatchSize);
1758 :
1759 8 : projTransformItems = AllocArray();
1760 8 : InitializeFixedSizeListArray(
1761 8 : *(topArrays[iProjTransform]), projTransformItems,
1762 : sizeof(double), NUM_ITEMS_PROJ_TRANSFORM, nMaxBatchSize);
1763 : }
1764 :
1765 8 : InitializeStringOrBinaryArray(*topArrays[iWkbArray], nMaxBatchSize);
1766 :
1767 : arrayHelper =
1768 8 : std::make_unique<OGRArrowArrayHelper>(&topArray, nMaxBatchSize);
1769 8 : };
1770 :
1771 51 : int nBatchSize = 0;
1772 :
1773 8 : const auto FlushArrays = [poLayer, &topArray, &linksItemArray, &imageArray,
1774 232 : &topSchema, &nBatchSize, &arrayHelper]()
1775 : {
1776 8 : topArray.length = nBatchSize;
1777 8 : linksItemArray->length = nBatchSize;
1778 8 : imageArray->length = nBatchSize;
1779 104 : for (int i = 0; i < static_cast<int>(topArray.n_children); ++i)
1780 96 : topArray.children[i]->length = nBatchSize;
1781 8 : const bool ret = poLayer->WriteArrowBatch(&topSchema, &topArray);
1782 8 : if (topArray.release)
1783 : {
1784 0 : topArray.release(&topArray);
1785 : }
1786 8 : memset(&topArray, 0, sizeof(topArray));
1787 8 : nBatchSize = 0;
1788 8 : arrayHelper.reset();
1789 8 : return ret;
1790 51 : };
1791 :
1792 51 : int iCur = 0;
1793 51 : int nTotal = nSrcCount + 1;
1794 : while (true)
1795 : {
1796 137 : const std::string osSrcFilename = oGDALTileIndexTileIterator.next();
1797 137 : if (osSrcFilename.empty())
1798 50 : break;
1799 87 : if (bSkipFirstTile)
1800 : {
1801 1 : bSkipFirstTile = false;
1802 1 : continue;
1803 : }
1804 :
1805 86 : std::string osFileNameToWrite;
1806 : VSIStatBuf sStatBuf;
1807 :
1808 : // Make sure it is a file before building absolute path name.
1809 86 : if (!osCurrentPath.empty() &&
1810 90 : CPLIsFilenameRelative(osSrcFilename.c_str()) &&
1811 4 : VSIStat(osSrcFilename.c_str(), &sStatBuf) == 0)
1812 : {
1813 8 : osFileNameToWrite = CPLProjectRelativeFilenameSafe(
1814 4 : osCurrentPath.c_str(), osSrcFilename.c_str());
1815 : }
1816 : else
1817 : {
1818 82 : osFileNameToWrite = osSrcFilename.c_str();
1819 : }
1820 :
1821 : // Checks that file is not already in tileindex.
1822 86 : if (oSetExistingFiles.find(osFileNameToWrite) !=
1823 172 : oSetExistingFiles.end())
1824 : {
1825 4 : CPLError(CE_Warning, CPLE_AppDefined,
1826 : "File %s is already in tileindex. Skipping it.",
1827 : osFileNameToWrite.c_str());
1828 4 : continue;
1829 : }
1830 :
1831 0 : std::unique_ptr<GDALDataset> poSrcDS;
1832 : {
1833 : std::unique_ptr<CPLTurnFailureIntoWarningBackuper>
1834 0 : poFailureIntoWarning;
1835 82 : if (!bFailOnErrors)
1836 : poFailureIntoWarning =
1837 60 : std::make_unique<CPLTurnFailureIntoWarningBackuper>();
1838 82 : CPL_IGNORE_RET_VAL(poFailureIntoWarning);
1839 :
1840 82 : poSrcDS.reset(GDALDataset::Open(
1841 : osSrcFilename.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
1842 : nullptr, nullptr, nullptr));
1843 82 : if (poSrcDS == nullptr)
1844 : {
1845 2 : CPLError(bFailOnErrors ? CE_Failure : CE_Warning,
1846 : CPLE_AppDefined, "Unable to open %s%s.",
1847 : osSrcFilename.c_str(),
1848 : bFailOnErrors ? "" : ", skipping");
1849 2 : if (bFailOnErrors)
1850 1 : return nullptr;
1851 1 : continue;
1852 : }
1853 : }
1854 :
1855 80 : GDALGeoTransform gt;
1856 80 : if (poSrcDS->GetGeoTransform(gt) != CE_None)
1857 : {
1858 0 : CPLError(bFailOnErrors ? CE_Failure : CE_Warning, CPLE_AppDefined,
1859 : "It appears no georeferencing is available for\n"
1860 : "`%s'%s.",
1861 : osSrcFilename.c_str(), bFailOnErrors ? "" : ", skipping");
1862 0 : if (bFailOnErrors)
1863 0 : return nullptr;
1864 0 : continue;
1865 : }
1866 :
1867 80 : auto poSrcSRS = poSrcDS->GetSpatialRef();
1868 : // If not set target srs, test that the current file uses same
1869 : // projection as others.
1870 80 : if (oTargetSRS.IsEmpty())
1871 : {
1872 60 : if (!oAlreadyExistingSRS.IsEmpty())
1873 : {
1874 68 : if (poSrcSRS == nullptr ||
1875 34 : !poSrcSRS->IsSame(&oAlreadyExistingSRS))
1876 : {
1877 1 : CPLError(
1878 : CE_Warning, CPLE_AppDefined,
1879 : "%s is not using the same projection system "
1880 : "as other files in the tileindex.\n"
1881 : "This may cause problems when using it in MapServer "
1882 : "for example.\n"
1883 : "Use -t_srs option to set target projection system. %s",
1884 : osSrcFilename.c_str(),
1885 1 : psOptions->bSkipDifferentProjection
1886 : ? "Skipping this file."
1887 : : "");
1888 1 : if (psOptions->bSkipDifferentProjection)
1889 : {
1890 1 : continue;
1891 : }
1892 : }
1893 : }
1894 : else
1895 : {
1896 26 : if (poSrcSRS)
1897 26 : oAlreadyExistingSRS = *poSrcSRS;
1898 : }
1899 : }
1900 :
1901 79 : const int nXSize = poSrcDS->GetRasterXSize();
1902 79 : const int nYSize = poSrcDS->GetRasterYSize();
1903 79 : if (nXSize == 0 || nYSize == 0)
1904 : {
1905 0 : CPLError(bFailOnErrors ? CE_Failure : CE_Warning, CPLE_AppDefined,
1906 : "%s has 0 width or height%s", osSrcFilename.c_str(),
1907 : bFailOnErrors ? "" : ", skipping");
1908 0 : if (bFailOnErrors)
1909 0 : return nullptr;
1910 0 : continue;
1911 : }
1912 :
1913 79 : double adfX[5] = {0.0, 0.0, 0.0, 0.0, 0.0};
1914 79 : double adfY[5] = {0.0, 0.0, 0.0, 0.0, 0.0};
1915 79 : adfX[0] = gt[0] + 0 * gt[1] + 0 * gt[2];
1916 79 : adfY[0] = gt[3] + 0 * gt[4] + 0 * gt[5];
1917 :
1918 79 : adfX[1] = gt[0] + nXSize * gt[1] + 0 * gt[2];
1919 79 : adfY[1] = gt[3] + nXSize * gt[4] + 0 * gt[5];
1920 :
1921 79 : adfX[2] = gt[0] + nXSize * gt[1] + nYSize * gt[2];
1922 79 : adfY[2] = gt[3] + nXSize * gt[4] + nYSize * gt[5];
1923 :
1924 79 : adfX[3] = gt[0] + 0 * gt[1] + nYSize * gt[2];
1925 79 : adfY[3] = gt[3] + 0 * gt[4] + nYSize * gt[5];
1926 :
1927 79 : adfX[4] = gt[0] + 0 * gt[1] + 0 * gt[2];
1928 79 : adfY[4] = gt[3] + 0 * gt[4] + 0 * gt[5];
1929 :
1930 : const double dfMinXBeforeReproj =
1931 79 : std::min(std::min(adfX[0], adfX[1]), std::min(adfX[2], adfX[3]));
1932 : const double dfMinYBeforeReproj =
1933 79 : std::min(std::min(adfY[0], adfY[1]), std::min(adfY[2], adfY[3]));
1934 : const double dfMaxXBeforeReproj =
1935 79 : std::max(std::max(adfX[0], adfX[1]), std::max(adfX[2], adfX[3]));
1936 : const double dfMaxYBeforeReproj =
1937 79 : std::max(std::max(adfY[0], adfY[1]), std::max(adfY[2], adfY[3]));
1938 :
1939 : // If set target srs, do the forward transformation of all points.
1940 79 : if (!oTargetSRS.IsEmpty() && poSrcSRS)
1941 : {
1942 19 : if (!poSrcSRS->IsSame(&oTargetSRS))
1943 : {
1944 : auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
1945 13 : OGRCreateCoordinateTransformation(poSrcSRS, &oTargetSRS));
1946 13 : if (!poCT || !poCT->Transform(5, adfX, adfY, nullptr))
1947 : {
1948 0 : CPLError(bFailOnErrors ? CE_Failure : CE_Warning,
1949 : CPLE_AppDefined,
1950 : "unable to transform points from source "
1951 : "SRS `%s' to target SRS `%s' for file `%s'%s",
1952 : poSrcDS->GetProjectionRef(),
1953 0 : psOptions->osTargetSRS.c_str(),
1954 : osFileNameToWrite.c_str(),
1955 : bFailOnErrors ? "" : ", skipping");
1956 0 : if (bFailOnErrors)
1957 0 : return nullptr;
1958 0 : continue;
1959 : }
1960 : }
1961 : }
1962 72 : else if (bIsGTIContext && !oAlreadyExistingSRS.IsEmpty() &&
1963 12 : (poSrcSRS == nullptr ||
1964 12 : !poSrcSRS->IsSame(&oAlreadyExistingSRS)))
1965 : {
1966 0 : CPLError(
1967 : CE_Failure, CPLE_AppDefined,
1968 : "%s is not using the same projection system "
1969 : "as other files in the tileindex. This is not compatible of "
1970 : "GTI use. Use -t_srs option to reproject tile extents "
1971 : "to a common SRS.",
1972 : osSrcFilename.c_str());
1973 0 : return nullptr;
1974 : }
1975 :
1976 : const double dfMinX =
1977 79 : std::min(std::min(adfX[0], adfX[1]), std::min(adfX[2], adfX[3]));
1978 : const double dfMinY =
1979 79 : std::min(std::min(adfY[0], adfY[1]), std::min(adfY[2], adfY[3]));
1980 : const double dfMaxX =
1981 79 : std::max(std::max(adfX[0], adfX[1]), std::max(adfX[2], adfX[3]));
1982 : const double dfMaxY =
1983 79 : std::max(std::max(adfY[0], adfY[1]), std::max(adfY[2], adfY[3]));
1984 : const double dfRes =
1985 79 : sqrt((dfMaxX - dfMinX) * (dfMaxY - dfMinY) / nXSize / nYSize);
1986 89 : if (!std::isnan(psOptions->dfMinPixelSize) &&
1987 10 : dfRes < psOptions->dfMinPixelSize)
1988 : {
1989 5 : CPLError(CE_Warning, CPLE_AppDefined,
1990 : "%s has %f as pixel size (< %f). Skipping",
1991 5 : osSrcFilename.c_str(), dfRes, psOptions->dfMinPixelSize);
1992 5 : continue;
1993 : }
1994 82 : if (!std::isnan(psOptions->dfMaxPixelSize) &&
1995 8 : dfRes > psOptions->dfMaxPixelSize)
1996 : {
1997 4 : CPLError(CE_Warning, CPLE_AppDefined,
1998 : "%s has %f as pixel size (> %f). Skipping",
1999 4 : osSrcFilename.c_str(), dfRes, psOptions->dfMaxPixelSize);
2000 4 : continue;
2001 : }
2002 :
2003 70 : auto poPoly = std::make_unique<OGRPolygon>();
2004 70 : auto poRing = std::make_unique<OGRLinearRing>();
2005 420 : for (int k = 0; k < 5; k++)
2006 350 : poRing->addPoint(adfX[k], adfY[k]);
2007 70 : poPoly->addRing(std::move(poRing));
2008 :
2009 70 : if (bIsSTACGeoParquet)
2010 : {
2011 8 : const char *pszDriverName = poSrcDS->GetDriverName();
2012 8 : if (pszDriverName && EQUAL(pszDriverName, "MEM"))
2013 : {
2014 0 : CPLError(CE_Failure, CPLE_AppDefined,
2015 : "Memory datasets cannot be referenced in a "
2016 : "STAC-GeoParquet catalog");
2017 0 : return nullptr;
2018 : }
2019 :
2020 8 : if (!arrayHelper)
2021 : {
2022 8 : InitTopArray();
2023 : }
2024 :
2025 : // Write "id"
2026 : {
2027 8 : std::string osId(CPLGetFilename(osFileNameToWrite.c_str()));
2028 :
2029 8 : if (psOptions->osIdMethod == "md5")
2030 : {
2031 : const std::string osFilename =
2032 1 : VSIFileManager::GetHandler(osFileNameToWrite.c_str())
2033 1 : ->GetStreamingFilename(osFileNameToWrite);
2034 : auto fp = VSIFilesystemHandler::OpenStatic(
2035 1 : osFilename.c_str(), "rb");
2036 1 : if (!fp)
2037 : {
2038 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s",
2039 : osFileNameToWrite.c_str());
2040 0 : return nullptr;
2041 : }
2042 : CPLMD5Context md5Context;
2043 1 : CPLMD5Init(&md5Context);
2044 1 : constexpr size_t CHUNK_SIZE = 1024 * 1024;
2045 1 : std::vector<GByte> buffer(CHUNK_SIZE, 0);
2046 : while (true)
2047 : {
2048 : const size_t nRead =
2049 1 : fp->Read(buffer.data(), 1, buffer.size());
2050 1 : CPLMD5Update(&md5Context, buffer.data(), nRead);
2051 1 : if (nRead < buffer.size())
2052 : {
2053 1 : if (fp->Error())
2054 : {
2055 0 : CPLError(CE_Failure, CPLE_FileIO,
2056 : "Error while reading %s",
2057 : osFileNameToWrite.c_str());
2058 0 : return nullptr;
2059 : }
2060 1 : break;
2061 : }
2062 0 : }
2063 1 : unsigned char digest[16] = {0};
2064 1 : CPLMD5Final(digest, &md5Context);
2065 1 : char *pszMD5 = CPLBinaryToHex(16, digest);
2066 1 : osId = pszMD5;
2067 1 : CPLFree(pszMD5);
2068 1 : osId += '-';
2069 1 : osId += CPLGetFilename(osFileNameToWrite.c_str());
2070 : }
2071 7 : else if (psOptions->osIdMethod == "metadata-item")
2072 : {
2073 2 : const char *pszId = poSrcDS->GetMetadataItem(
2074 1 : psOptions->osIdMetadataItem.c_str());
2075 1 : if (!pszId)
2076 : {
2077 0 : CPLError(CE_Failure, CPLE_AppDefined,
2078 : "No metadata item '%s' in dataset %s",
2079 0 : psOptions->osIdMetadataItem.c_str(),
2080 : osFileNameToWrite.c_str());
2081 0 : return nullptr;
2082 : }
2083 1 : osId = pszId;
2084 : }
2085 6 : else if (psOptions->osIdMethod != "filename")
2086 : {
2087 : // shouldn't happen
2088 0 : CPLError(CE_Failure, CPLE_NotSupported,
2089 : "Unhandled id method '%s'",
2090 0 : psOptions->osIdMethod.c_str());
2091 0 : return nullptr;
2092 : }
2093 :
2094 8 : void *ptr = arrayHelper->GetPtrForStringOrBinary(
2095 : iIdArray, nBatchSize, osId.size(), false);
2096 8 : if (!ptr)
2097 0 : return nullptr;
2098 8 : memcpy(ptr, osId.data(), osId.size());
2099 : }
2100 :
2101 : // Write "stac_extensions"
2102 : {
2103 8 : uint32_t *panOffsets = static_cast<uint32_t *>(
2104 8 : const_cast<void *>(topArrays[iStacExtensionsArray]
2105 8 : ->buffers[ARROW_BUF_DATA]));
2106 8 : panOffsets[nBatchSize + 1] =
2107 8 : panOffsets[nBatchSize] + COUNT_STAC_EXTENSIONS;
2108 :
2109 : {
2110 8 : constexpr const char extension[] =
2111 : "https://stac-extensions.github.io/projection/v2.0.0/"
2112 : "schema.json";
2113 8 : constexpr size_t nStrLen = sizeof(extension) - 1;
2114 8 : void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2115 : stacExtensionSubArray,
2116 : COUNT_STAC_EXTENSIONS * nBatchSize + 0, nStrLen,
2117 : nStacExtensionSubArrayMaxAlloc, false);
2118 8 : if (!ptr)
2119 0 : return nullptr;
2120 8 : memcpy(ptr, extension, nStrLen);
2121 8 : stacExtensionSubArray->length++;
2122 : }
2123 :
2124 : {
2125 8 : constexpr const char extension[] =
2126 : "https://stac-extensions.github.io/eo/v2.0.0/"
2127 : "schema.json";
2128 8 : constexpr size_t nStrLen = sizeof(extension) - 1;
2129 16 : void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2130 : stacExtensionSubArray,
2131 8 : COUNT_STAC_EXTENSIONS * nBatchSize + 1, nStrLen,
2132 : nStacExtensionSubArrayMaxAlloc, false);
2133 8 : if (!ptr)
2134 0 : return nullptr;
2135 8 : memcpy(ptr, extension, nStrLen);
2136 8 : stacExtensionSubArray->length++;
2137 : }
2138 : }
2139 :
2140 : // Write "assets.image.href"
2141 : {
2142 8 : std::string osHref = osFileNameToWrite;
2143 8 : CPL_IGNORE_RET_VAL(osFileNameToWrite);
2144 8 : if (!psOptions->osBaseURL.empty())
2145 : {
2146 2 : osHref = CPLFormFilenameSafe(psOptions->osBaseURL.c_str(),
2147 : CPLGetFilename(osHref.c_str()),
2148 1 : nullptr);
2149 : }
2150 7 : else if (VSIIsLocal(osHref.c_str()))
2151 : {
2152 7 : if (!CPLIsFilenameRelative(osHref.c_str()))
2153 : {
2154 4 : osHref = "file://" + osHref;
2155 : }
2156 : }
2157 0 : else if (STARTS_WITH(osHref.c_str(), "/vsicurl/"))
2158 : {
2159 0 : osHref = osHref.substr(strlen("/vsicurl/"));
2160 : }
2161 0 : else if (STARTS_WITH(osHref.c_str(), "/vsis3/"))
2162 : {
2163 0 : osHref = "s3://" + osHref.substr(strlen("/vsis3/"));
2164 : }
2165 0 : else if (STARTS_WITH(osHref.c_str(), "/vsigs/"))
2166 : {
2167 0 : osHref = "gcs://" + osHref.substr(strlen("/vsigs/"));
2168 : }
2169 0 : else if (STARTS_WITH(osHref.c_str(), "/vsiaz/"))
2170 : {
2171 0 : osHref = "azure://" + osHref.substr(strlen("/vsiaz/"));
2172 : }
2173 8 : const size_t nHrefLen = osHref.size();
2174 8 : void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2175 : imageHrefArray, nBatchSize, nHrefLen,
2176 : nImageHrefArrayMaxAlloc, false);
2177 8 : if (!ptr)
2178 0 : return nullptr;
2179 8 : memcpy(ptr, osHref.data(), nHrefLen);
2180 8 : imageHrefArray->length++;
2181 : }
2182 :
2183 : // Write "assets.image.roles"
2184 : {
2185 8 : uint32_t *panRolesOffsets =
2186 : static_cast<uint32_t *>(const_cast<void *>(
2187 8 : imageRoleArray->buffers[ARROW_BUF_DATA]));
2188 8 : panRolesOffsets[nBatchSize + 1] =
2189 8 : panRolesOffsets[nBatchSize] + 1;
2190 :
2191 8 : constexpr const char ROLE_DATA[] = "data";
2192 8 : constexpr size_t nStrLen = sizeof(ROLE_DATA) - 1;
2193 8 : void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2194 : imageRoleItemArray, nBatchSize, nStrLen,
2195 : nImageRoleItemArrayMaxAlloc, false);
2196 8 : if (!ptr)
2197 0 : return nullptr;
2198 8 : memcpy(ptr, ROLE_DATA, nStrLen);
2199 8 : imageRoleItemArray->length++;
2200 : }
2201 :
2202 : // Write "assets.image.type"
2203 8 : if (pszDriverName && EQUAL(pszDriverName, "GTiff"))
2204 : {
2205 : const char *pszLayout =
2206 8 : poSrcDS->GetMetadataItem("LAYOUT", "IMAGE_STRUCTURE");
2207 8 : if (pszLayout && EQUAL(pszLayout, "COG"))
2208 : {
2209 0 : constexpr const char TYPE[] =
2210 : "image/tiff; application=geotiff; "
2211 : "profile=cloud-optimized";
2212 0 : constexpr size_t TYPE_SIZE = sizeof(TYPE) - 1;
2213 0 : void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2214 : imageTypeArray, nBatchSize, TYPE_SIZE,
2215 : nImageTypeArrayMaxAlloc, false);
2216 0 : if (!ptr)
2217 0 : return nullptr;
2218 0 : memcpy(ptr, TYPE, TYPE_SIZE);
2219 : }
2220 : else
2221 : {
2222 8 : constexpr const char TYPE[] =
2223 : "image/tiff; application=geotiff";
2224 8 : constexpr size_t TYPE_SIZE = sizeof(TYPE) - 1;
2225 8 : void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2226 : imageTypeArray, nBatchSize, TYPE_SIZE,
2227 : nImageTypeArrayMaxAlloc, false);
2228 8 : if (!ptr)
2229 0 : return nullptr;
2230 8 : memcpy(ptr, TYPE, TYPE_SIZE);
2231 8 : }
2232 : }
2233 0 : else if (pszDriverName && EQUAL(pszDriverName, "PNG"))
2234 : {
2235 0 : constexpr const char TYPE[] = "image/png";
2236 0 : constexpr size_t TYPE_SIZE = sizeof(TYPE) - 1;
2237 0 : void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2238 : imageTypeArray, nBatchSize, TYPE_SIZE,
2239 : nImageTypeArrayMaxAlloc, false);
2240 0 : if (!ptr)
2241 0 : return nullptr;
2242 0 : memcpy(ptr, TYPE, TYPE_SIZE);
2243 : }
2244 0 : else if (pszDriverName && EQUAL(pszDriverName, "JPEG"))
2245 : {
2246 0 : constexpr const char TYPE[] = "image/jpeg";
2247 0 : constexpr size_t TYPE_SIZE = sizeof(TYPE) - 1;
2248 0 : void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2249 : imageTypeArray, nBatchSize, TYPE_SIZE,
2250 : nImageTypeArrayMaxAlloc, false);
2251 0 : if (!ptr)
2252 0 : return nullptr;
2253 0 : memcpy(ptr, TYPE, TYPE_SIZE);
2254 : }
2255 0 : else if (pszDriverName && (EQUAL(pszDriverName, "JP2KAK") ||
2256 0 : EQUAL(pszDriverName, "JP2OpenJPEG") ||
2257 0 : EQUAL(pszDriverName, "JP2ECW") ||
2258 0 : EQUAL(pszDriverName, "JP2MrSID")))
2259 : {
2260 0 : constexpr const char TYPE[] = "image/jp2";
2261 0 : constexpr size_t TYPE_SIZE = sizeof(TYPE) - 1;
2262 0 : void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2263 : imageTypeArray, nBatchSize, TYPE_SIZE,
2264 : nImageTypeArrayMaxAlloc, false);
2265 0 : if (!ptr)
2266 0 : return nullptr;
2267 0 : memcpy(ptr, TYPE, TYPE_SIZE);
2268 : }
2269 : else
2270 : {
2271 0 : OGRArrowArrayHelper::SetNull(imageTypeArray, nBatchSize,
2272 : nMaxBatchSize, false);
2273 0 : OGRArrowArrayHelper::SetEmptyStringOrBinary(imageTypeArray,
2274 : nBatchSize);
2275 : }
2276 8 : imageTypeArray->length++;
2277 :
2278 : // Write "assets.image.title"
2279 : {
2280 8 : OGRArrowArrayHelper::SetNull(imageTitleArray, nBatchSize,
2281 : nMaxBatchSize, false);
2282 8 : OGRArrowArrayHelper::SetEmptyStringOrBinary(imageTitleArray,
2283 : nBatchSize);
2284 8 : imageTitleArray->length++;
2285 : }
2286 :
2287 : // Write "bands"
2288 : {
2289 8 : const int nThisBands = poSrcDS->GetRasterCount();
2290 8 : if (nThisBands + nBandsItemCount > nBandsItemAlloc)
2291 : {
2292 8 : const auto nOldAlloc = nBandsItemAlloc;
2293 8 : if (nBandsItemAlloc >
2294 8 : std::numeric_limits<uint32_t>::max() / 2)
2295 : {
2296 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too many bands");
2297 0 : return nullptr;
2298 : }
2299 16 : nBandsItemAlloc = std::max(2 * nBandsItemAlloc,
2300 8 : nThisBands + nBandsItemCount);
2301 :
2302 56 : auto ReallocArray = [nOldAlloc, nBandsItemAlloc](
2303 112 : ArrowArray *array, size_t nItemSize)
2304 : {
2305 56 : if (array->buffers[ARROW_BUF_VALIDITY])
2306 : {
2307 : // Bitmap
2308 0 : const uint32_t nNewSizeBytes =
2309 0 : (nBandsItemAlloc + 7) / 8;
2310 : char *newPtr =
2311 0 : static_cast<char *>(VSI_REALLOC_VERBOSE(
2312 : const_cast<void *>(
2313 : array->buffers[ARROW_BUF_VALIDITY]),
2314 : nNewSizeBytes));
2315 0 : if (!newPtr)
2316 0 : return false;
2317 0 : array->buffers[ARROW_BUF_VALIDITY] =
2318 : static_cast<const void *>(
2319 : const_cast<const char *>(newPtr));
2320 0 : const uint32_t nOldSizeBytes = (nOldAlloc + 7) / 8;
2321 0 : if (nNewSizeBytes > nOldSizeBytes)
2322 : {
2323 : // Initialize new allocated bytes as valid
2324 : // They are set invalid explicitly with SetNull()
2325 0 : memset(newPtr + nOldSizeBytes, 0xFF,
2326 0 : nNewSizeBytes - nOldSizeBytes);
2327 : }
2328 : }
2329 56 : char *newPtr = static_cast<char *>(VSI_REALLOC_VERBOSE(
2330 : const_cast<void *>(array->buffers[ARROW_BUF_DATA]),
2331 : (nBandsItemAlloc + 1) * nItemSize));
2332 56 : if (!newPtr)
2333 0 : return false;
2334 56 : array->buffers[ARROW_BUF_DATA] =
2335 : static_cast<const void *>(
2336 : const_cast<const char *>(newPtr));
2337 56 : memset(newPtr + (nOldAlloc + 1) * nItemSize, 0,
2338 56 : (nBandsItemAlloc - nOldAlloc) * nItemSize);
2339 56 : return true;
2340 8 : };
2341 :
2342 8 : if (!ReallocArray(bandsNameArray, sizeof(uint32_t)) ||
2343 8 : !ReallocArray(bandsCommonNameArray, sizeof(uint32_t)) ||
2344 8 : !ReallocArray(bandsCenterWavelengthArray,
2345 8 : sizeof(float)) ||
2346 8 : !ReallocArray(bandsFWHMArray, sizeof(float)) ||
2347 8 : !ReallocArray(bandsNodataArray, sizeof(uint32_t)) ||
2348 24 : !ReallocArray(bandsDataTypeArray, sizeof(uint32_t)) ||
2349 8 : !ReallocArray(bandsUnitArray, sizeof(uint32_t)))
2350 : {
2351 0 : return nullptr;
2352 : }
2353 : }
2354 :
2355 8 : uint32_t *panBandsOffsets =
2356 : static_cast<uint32_t *>(const_cast<void *>(
2357 8 : topArrays[iBandsArray]->buffers[ARROW_BUF_DATA]));
2358 8 : panBandsOffsets[nBatchSize + 1] =
2359 8 : panBandsOffsets[nBatchSize] + nThisBands;
2360 :
2361 18 : for (int i = 0; i < nThisBands; ++i, ++nBandsItemCount)
2362 : {
2363 10 : bandsItemArray->length++;
2364 :
2365 10 : const auto poBand = poSrcDS->GetRasterBand(i + 1);
2366 : {
2367 10 : std::string osBandName = poBand->GetDescription();
2368 10 : if (osBandName.empty())
2369 9 : osBandName = "Band " + std::to_string(i + 1);
2370 : void *ptr =
2371 10 : OGRArrowArrayHelper::GetPtrForStringOrBinary(
2372 : bandsNameArray, nBandsItemCount,
2373 : osBandName.size(), nBandsNameArrayMaxAlloc,
2374 : false);
2375 10 : if (!ptr)
2376 0 : return nullptr;
2377 10 : memcpy(ptr, osBandName.data(), osBandName.size());
2378 10 : bandsNameArray->length++;
2379 : }
2380 :
2381 : const char *pszCommonName =
2382 10 : GDALGetSTACCommonNameFromColorInterp(
2383 10 : poBand->GetColorInterpretation());
2384 10 : if (pszCommonName)
2385 : {
2386 3 : const size_t nLen = strlen(pszCommonName);
2387 : void *ptr =
2388 3 : OGRArrowArrayHelper::GetPtrForStringOrBinary(
2389 : bandsCommonNameArray, nBandsItemCount, nLen,
2390 : nBandsCommonNameArrayMaxAlloc, false);
2391 3 : if (!ptr)
2392 0 : return nullptr;
2393 3 : memcpy(ptr, pszCommonName, nLen);
2394 : }
2395 : else
2396 : {
2397 7 : OGRArrowArrayHelper::SetNull(bandsCommonNameArray,
2398 : nBandsItemCount,
2399 : nBandsItemAlloc, false);
2400 7 : OGRArrowArrayHelper::SetEmptyStringOrBinary(
2401 : bandsCommonNameArray, nBandsItemCount);
2402 : }
2403 10 : bandsCommonNameArray->length++;
2404 :
2405 10 : if (const char *pszCenterWavelength =
2406 10 : poBand->GetMetadataItem("CENTRAL_WAVELENGTH_UM",
2407 10 : "IMAGERY"))
2408 : {
2409 1 : float *values = static_cast<float *>(
2410 : const_cast<void *>(bandsCenterWavelengthArray
2411 1 : ->buffers[ARROW_BUF_DATA]));
2412 1 : values[nBandsItemCount] =
2413 1 : static_cast<float>(CPLAtof(pszCenterWavelength));
2414 : }
2415 : else
2416 : {
2417 9 : OGRArrowArrayHelper::SetNull(bandsCenterWavelengthArray,
2418 : nBandsItemCount,
2419 : nBandsItemAlloc, false);
2420 : }
2421 10 : bandsCenterWavelengthArray->length++;
2422 :
2423 10 : if (const char *pszFWHM =
2424 10 : poBand->GetMetadataItem("FWHM_UM", "IMAGERY"))
2425 : {
2426 1 : float *values = static_cast<float *>(const_cast<void *>(
2427 1 : bandsFWHMArray->buffers[ARROW_BUF_DATA]));
2428 1 : values[nBandsItemCount] =
2429 1 : static_cast<float>(CPLAtof(pszFWHM));
2430 : }
2431 : else
2432 : {
2433 9 : OGRArrowArrayHelper::SetNull(bandsFWHMArray,
2434 : nBandsItemCount,
2435 : nBandsItemAlloc, false);
2436 : }
2437 10 : bandsFWHMArray->length++;
2438 :
2439 10 : int bHasNoData = false;
2440 : const double dfNoDataValue =
2441 10 : poBand->GetNoDataValue(&bHasNoData);
2442 10 : if (bHasNoData)
2443 : {
2444 : const std::string osNodata =
2445 1 : std::isnan(dfNoDataValue) ? "nan"
2446 1 : : std::isinf(dfNoDataValue)
2447 1 : ? (dfNoDataValue > 0 ? "inf" : "-inf")
2448 3 : : CPLSPrintf("%.17g", dfNoDataValue);
2449 : void *ptr =
2450 1 : OGRArrowArrayHelper::GetPtrForStringOrBinary(
2451 : bandsNodataArray, nBandsItemCount,
2452 : osNodata.size(), nBandsNodataArrayMaxAlloc,
2453 : false);
2454 1 : if (!ptr)
2455 0 : return nullptr;
2456 1 : memcpy(ptr, osNodata.data(), osNodata.size());
2457 : }
2458 : else
2459 : {
2460 9 : OGRArrowArrayHelper::SetNull(bandsNodataArray,
2461 : nBandsItemCount,
2462 : nBandsItemAlloc, false);
2463 : }
2464 10 : bandsNodataArray->length++;
2465 :
2466 : {
2467 10 : const char *pszDT = "other";
2468 : // clang-format off
2469 10 : switch (poBand->GetRasterDataType())
2470 : {
2471 0 : case GDT_Int8: pszDT = "int8"; break;
2472 9 : case GDT_UInt8: pszDT = "uint8"; break;
2473 0 : case GDT_Int16: pszDT = "int16"; break;
2474 1 : case GDT_UInt16: pszDT = "uint16"; break;
2475 0 : case GDT_Int32: pszDT = "int32"; break;
2476 0 : case GDT_UInt32: pszDT = "uint32"; break;
2477 0 : case GDT_Int64: pszDT = "int64"; break;
2478 0 : case GDT_UInt64: pszDT = "uint64"; break;
2479 0 : case GDT_Float16: pszDT = "float16"; break;
2480 0 : case GDT_Float32: pszDT = "float32"; break;
2481 0 : case GDT_Float64: pszDT = "float64"; break;
2482 0 : case GDT_CInt16: pszDT = "cint16"; break;
2483 0 : case GDT_CInt32: pszDT = "cint32"; break;
2484 0 : case GDT_CFloat16: pszDT = "cfloat16"; break;
2485 0 : case GDT_CFloat32: pszDT = "cfloat32"; break;
2486 0 : case GDT_CFloat64: pszDT = "cfloat64"; break;
2487 0 : case GDT_Unknown: break;
2488 0 : case GDT_TypeCount: break;
2489 : }
2490 : // clang-format on
2491 10 : const size_t nLen = strlen(pszDT);
2492 : void *ptr =
2493 10 : OGRArrowArrayHelper::GetPtrForStringOrBinary(
2494 : bandsDataTypeArray, nBandsItemCount, nLen,
2495 : nBandsDataTypeArrayMaxAlloc, false);
2496 10 : if (!ptr)
2497 0 : return nullptr;
2498 10 : memcpy(ptr, pszDT, nLen);
2499 :
2500 10 : bandsDataTypeArray->length++;
2501 : }
2502 :
2503 10 : const char *pszUnits = poBand->GetUnitType();
2504 10 : if (pszUnits && pszUnits[0])
2505 : {
2506 1 : const size_t nLen = strlen(pszUnits);
2507 : void *ptr =
2508 1 : OGRArrowArrayHelper::GetPtrForStringOrBinary(
2509 : bandsUnitArray, nBandsItemCount, nLen,
2510 : nBandsUnitArrayMaxAlloc, false);
2511 1 : if (!ptr)
2512 0 : return nullptr;
2513 1 : memcpy(ptr, pszUnits, nLen);
2514 : }
2515 : else
2516 : {
2517 9 : OGRArrowArrayHelper::SetNull(bandsUnitArray,
2518 : nBandsItemCount,
2519 : nBandsItemAlloc, false);
2520 : }
2521 10 : bandsUnitArray->length++;
2522 : }
2523 : }
2524 :
2525 : // Write "proj:code"
2526 8 : bool bHasProjCode = false;
2527 : {
2528 8 : auto psArray = topArrays[iProjCode];
2529 : const char *pszSRSAuthName =
2530 8 : poSrcSRS ? poSrcSRS->GetAuthorityName(nullptr) : nullptr;
2531 : const char *pszSRSAuthCode =
2532 8 : poSrcSRS ? poSrcSRS->GetAuthorityCode(nullptr) : nullptr;
2533 8 : if (pszSRSAuthName && pszSRSAuthCode)
2534 : {
2535 6 : std::string osCode(pszSRSAuthName);
2536 6 : osCode += ':';
2537 6 : osCode += pszSRSAuthCode;
2538 6 : void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2539 : psArray, nBatchSize, osCode.size(),
2540 : nProjCodeArrayMaxAlloc, false);
2541 6 : if (!ptr)
2542 0 : return nullptr;
2543 6 : memcpy(ptr, osCode.data(), osCode.size());
2544 12 : bHasProjCode = true;
2545 : }
2546 : else
2547 : {
2548 2 : OGRArrowArrayHelper::SetNull(psArray, nBatchSize,
2549 : nMaxBatchSize, false);
2550 2 : OGRArrowArrayHelper::SetEmptyStringOrBinary(psArray,
2551 : nBatchSize);
2552 : }
2553 : }
2554 :
2555 : // Write "proj:wkt2"
2556 : {
2557 8 : auto psArray = topArrays[iProjWKT2];
2558 8 : std::string osWKT2;
2559 8 : if (poSrcSRS && !bHasProjCode)
2560 : {
2561 1 : const char *const apszOptions[] = {"FORMAT=WKT2_2019",
2562 : nullptr};
2563 1 : osWKT2 = poSrcSRS->exportToWkt(apszOptions);
2564 : }
2565 8 : if (!osWKT2.empty())
2566 : {
2567 1 : void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2568 : psArray, nBatchSize, osWKT2.size(),
2569 : nProjWKT2ArrayMaxAlloc, false);
2570 1 : if (!ptr)
2571 0 : return nullptr;
2572 1 : memcpy(ptr, osWKT2.data(), osWKT2.size());
2573 : }
2574 : else
2575 : {
2576 7 : OGRArrowArrayHelper::SetNull(psArray, nBatchSize,
2577 : nMaxBatchSize, false);
2578 7 : OGRArrowArrayHelper::SetEmptyStringOrBinary(psArray,
2579 : nBatchSize);
2580 : }
2581 : }
2582 :
2583 : // Write "proj:projjson"
2584 : {
2585 8 : auto psArray = topArrays[iProjPROJJSON];
2586 8 : std::string osPROJJSON;
2587 8 : if (poSrcSRS && !bHasProjCode)
2588 : {
2589 1 : char *pszPROJJSON = nullptr;
2590 1 : poSrcSRS->exportToPROJJSON(&pszPROJJSON, nullptr);
2591 1 : if (pszPROJJSON)
2592 1 : osPROJJSON = pszPROJJSON;
2593 1 : CPLFree(pszPROJJSON);
2594 : }
2595 8 : if (!osPROJJSON.empty())
2596 : {
2597 1 : void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2598 : psArray, nBatchSize, osPROJJSON.size(),
2599 : nProjPROJJSONArrayMaxAlloc, false);
2600 1 : if (!ptr)
2601 0 : return nullptr;
2602 1 : memcpy(ptr, osPROJJSON.data(), osPROJJSON.size());
2603 : }
2604 : else
2605 : {
2606 7 : OGRArrowArrayHelper::SetNull(psArray, nBatchSize,
2607 : nMaxBatchSize, false);
2608 7 : OGRArrowArrayHelper::SetEmptyStringOrBinary(psArray,
2609 : nBatchSize);
2610 : }
2611 : }
2612 :
2613 : // Write proj:bbox
2614 : {
2615 8 : double *values = static_cast<double *>(
2616 8 : const_cast<void *>(projBBOXItems->buffers[ARROW_BUF_DATA]));
2617 8 : auto ptr = values + nBatchSize * NUM_ITEMS_PROJ_BBOX;
2618 8 : ptr[0] = dfMinXBeforeReproj;
2619 8 : ptr[1] = dfMinYBeforeReproj;
2620 8 : ptr[2] = dfMaxXBeforeReproj;
2621 8 : ptr[3] = dfMaxYBeforeReproj;
2622 : }
2623 :
2624 : // Write proj:shape
2625 : {
2626 8 : int32_t *values = static_cast<int32_t *>(const_cast<void *>(
2627 8 : projShapeItems->buffers[ARROW_BUF_DATA]));
2628 8 : auto ptr = values + nBatchSize * NUM_ITEMS_PROJ_SHAPE;
2629 8 : ptr[0] = poSrcDS->GetRasterYSize();
2630 8 : ptr[1] = poSrcDS->GetRasterXSize();
2631 : }
2632 :
2633 : // Write proj:transform
2634 : {
2635 8 : double *values = static_cast<double *>(const_cast<void *>(
2636 8 : projTransformItems->buffers[ARROW_BUF_DATA]));
2637 8 : auto ptr = values + nBatchSize * NUM_ITEMS_PROJ_TRANSFORM;
2638 8 : ptr[0] = gt[1];
2639 8 : ptr[1] = gt[2];
2640 8 : ptr[2] = gt[0];
2641 8 : ptr[3] = gt[4];
2642 8 : ptr[4] = gt[5];
2643 8 : ptr[5] = gt[3];
2644 8 : ptr[6] = 0;
2645 8 : ptr[7] = 0;
2646 8 : ptr[8] = 1;
2647 : }
2648 :
2649 : // Write geometry
2650 : {
2651 8 : const size_t nWKBSize = poPoly->WkbSize();
2652 8 : void *ptr = arrayHelper->GetPtrForStringOrBinary(
2653 : iWkbArray, nBatchSize, nWKBSize, false);
2654 8 : if (!ptr)
2655 0 : return nullptr;
2656 8 : OGRwkbExportOptions sExportOptions;
2657 8 : sExportOptions.eWkbVariant = wkbVariantIso;
2658 8 : if (poPoly->exportToWkb(static_cast<unsigned char *>(ptr),
2659 8 : &sExportOptions) != OGRERR_NONE)
2660 0 : return nullptr;
2661 : }
2662 :
2663 8 : nBatchSize++;
2664 8 : if (nBatchSize == nMaxBatchSize && !FlushArrays())
2665 : {
2666 0 : return nullptr;
2667 : }
2668 : }
2669 : else
2670 : {
2671 62 : auto poFeature = std::make_unique<OGRFeature>(poLayerDefn);
2672 62 : poFeature->SetField(ti_field, osFileNameToWrite.c_str());
2673 :
2674 62 : if (i_SrcSRSName >= 0 && poSrcSRS)
2675 : {
2676 : const char *pszAuthorityCode =
2677 11 : poSrcSRS->GetAuthorityCode(nullptr);
2678 : const char *pszAuthorityName =
2679 11 : poSrcSRS->GetAuthorityName(nullptr);
2680 11 : if (psOptions->eSrcSRSFormat == FORMAT_AUTO)
2681 : {
2682 5 : if (pszAuthorityName != nullptr &&
2683 : pszAuthorityCode != nullptr)
2684 : {
2685 5 : poFeature->SetField(
2686 : i_SrcSRSName, CPLSPrintf("%s:%s", pszAuthorityName,
2687 : pszAuthorityCode));
2688 : }
2689 0 : else if (nMaxFieldSize == 0 ||
2690 0 : strlen(poSrcDS->GetProjectionRef()) <=
2691 0 : static_cast<size_t>(nMaxFieldSize))
2692 : {
2693 0 : poFeature->SetField(i_SrcSRSName,
2694 : poSrcDS->GetProjectionRef());
2695 : }
2696 : else
2697 : {
2698 0 : char *pszProj4 = nullptr;
2699 0 : if (poSrcSRS->exportToProj4(&pszProj4) == OGRERR_NONE)
2700 : {
2701 0 : poFeature->SetField(i_SrcSRSName, pszProj4);
2702 : }
2703 : else
2704 : {
2705 0 : poFeature->SetField(i_SrcSRSName,
2706 : poSrcDS->GetProjectionRef());
2707 : }
2708 0 : CPLFree(pszProj4);
2709 : }
2710 : }
2711 6 : else if (psOptions->eSrcSRSFormat == FORMAT_WKT)
2712 : {
2713 4 : if (nMaxFieldSize == 0 ||
2714 2 : strlen(poSrcDS->GetProjectionRef()) <=
2715 2 : static_cast<size_t>(nMaxFieldSize))
2716 : {
2717 0 : poFeature->SetField(i_SrcSRSName,
2718 : poSrcDS->GetProjectionRef());
2719 : }
2720 : else
2721 : {
2722 2 : CPLError(
2723 : CE_Warning, CPLE_AppDefined,
2724 : "Cannot write WKT for file %s as it is too long!",
2725 : osFileNameToWrite.c_str());
2726 : }
2727 : }
2728 4 : else if (psOptions->eSrcSRSFormat == FORMAT_PROJ)
2729 : {
2730 2 : char *pszProj4 = nullptr;
2731 2 : if (poSrcSRS->exportToProj4(&pszProj4) == OGRERR_NONE)
2732 : {
2733 2 : poFeature->SetField(i_SrcSRSName, pszProj4);
2734 : }
2735 2 : CPLFree(pszProj4);
2736 : }
2737 2 : else if (psOptions->eSrcSRSFormat == FORMAT_EPSG)
2738 : {
2739 2 : if (pszAuthorityName != nullptr &&
2740 : pszAuthorityCode != nullptr)
2741 2 : poFeature->SetField(
2742 : i_SrcSRSName, CPLSPrintf("%s:%s", pszAuthorityName,
2743 : pszAuthorityCode));
2744 : }
2745 : }
2746 :
2747 67 : for (const auto &oFetchMD : psOptions->aoFetchMD)
2748 : {
2749 5 : if (EQUAL(oFetchMD.osRasterItemName.c_str(), "{PIXEL_SIZE}"))
2750 : {
2751 1 : poFeature->SetField(oFetchMD.osFieldName.c_str(), dfRes);
2752 1 : continue;
2753 : }
2754 :
2755 : const char *pszMD =
2756 4 : poSrcDS->GetMetadataItem(oFetchMD.osRasterItemName.c_str());
2757 4 : if (pszMD)
2758 : {
2759 4 : if (EQUAL(oFetchMD.osRasterItemName.c_str(),
2760 : "TIFFTAG_DATETIME"))
2761 : {
2762 : int nYear, nMonth, nDay, nHour, nMin, nSec;
2763 2 : if (sscanf(pszMD, "%04d:%02d:%02d %02d:%02d:%02d",
2764 : &nYear, &nMonth, &nDay, &nHour, &nMin,
2765 1 : &nSec) == 6)
2766 : {
2767 1 : poFeature->SetField(
2768 : oFetchMD.osFieldName.c_str(),
2769 : CPLSPrintf("%04d/%02d/%02d %02d:%02d:%02d",
2770 : nYear, nMonth, nDay, nHour, nMin,
2771 : nSec));
2772 1 : continue;
2773 : }
2774 : }
2775 3 : poFeature->SetField(oFetchMD.osFieldName.c_str(), pszMD);
2776 : }
2777 : }
2778 :
2779 62 : poFeature->SetGeometryDirectly(poPoly.release());
2780 :
2781 62 : if (poLayer->CreateFeature(poFeature.get()) != OGRERR_NONE)
2782 : {
2783 0 : CPLError(CE_Failure, CPLE_AppDefined,
2784 : "Failed to create feature in tile index.");
2785 0 : return nullptr;
2786 : }
2787 : }
2788 :
2789 70 : ++iCur;
2790 85 : if (psOptions->pfnProgress &&
2791 30 : !psOptions->pfnProgress(static_cast<double>(iCur) / nTotal, "",
2792 15 : psOptions->pProgressData))
2793 : {
2794 0 : return nullptr;
2795 : }
2796 70 : if (iCur >= nSrcCount)
2797 46 : ++nTotal;
2798 86 : }
2799 50 : if (psOptions->pfnProgress)
2800 7 : psOptions->pfnProgress(1.0, "", psOptions->pProgressData);
2801 :
2802 50 : if (bIsSTACGeoParquet && nBatchSize != 0 && !FlushArrays())
2803 : {
2804 0 : return nullptr;
2805 : }
2806 :
2807 50 : if (poTileIndexDSUnique)
2808 28 : return GDALDataset::ToHandle(poTileIndexDSUnique.release());
2809 : else
2810 22 : return GDALDataset::ToHandle(poTileIndexDS);
2811 : }
2812 :
2813 : /************************************************************************/
2814 : /* SanitizeSRS */
2815 : /************************************************************************/
2816 :
2817 16 : static char *SanitizeSRS(const char *pszUserInput)
2818 :
2819 : {
2820 : OGRSpatialReferenceH hSRS;
2821 16 : char *pszResult = nullptr;
2822 :
2823 16 : CPLErrorReset();
2824 :
2825 16 : hSRS = OSRNewSpatialReference(nullptr);
2826 16 : if (OSRSetFromUserInput(hSRS, pszUserInput) == OGRERR_NONE)
2827 16 : OSRExportToWkt(hSRS, &pszResult);
2828 : else
2829 : {
2830 0 : CPLError(CE_Failure, CPLE_AppDefined, "Translating SRS failed:\n%s",
2831 : pszUserInput);
2832 : }
2833 :
2834 16 : OSRDestroySpatialReference(hSRS);
2835 :
2836 16 : return pszResult;
2837 : }
2838 :
2839 : /************************************************************************/
2840 : /* GDALTileIndexOptionsNew() */
2841 : /************************************************************************/
2842 :
2843 : /**
2844 : * Allocates a GDALTileIndexOptions struct.
2845 : *
2846 : * @param papszArgv NULL terminated list of options (potentially including
2847 : * filename and open options too), or NULL. The accepted options are the ones of
2848 : * the <a href="/programs/gdaltindex.html">gdaltindex</a> utility.
2849 : * @param psOptionsForBinary (output) may be NULL (and should generally be
2850 : * NULL), otherwise (gdaltindex_bin.cpp use case) must be allocated with
2851 : * GDALTileIndexOptionsForBinaryNew() prior to this function. Will be filled
2852 : * with potentially present filename, open options,...
2853 : * @return pointer to the allocated GDALTileIndexOptions struct. Must be freed
2854 : * with GDALTileIndexOptionsFree().
2855 : *
2856 : * @since GDAL 3.9
2857 : */
2858 :
2859 : GDALTileIndexOptions *
2860 52 : GDALTileIndexOptionsNew(char **papszArgv,
2861 : GDALTileIndexOptionsForBinary *psOptionsForBinary)
2862 : {
2863 104 : auto psOptions = std::make_unique<GDALTileIndexOptions>();
2864 :
2865 : /* -------------------------------------------------------------------- */
2866 : /* Parse arguments. */
2867 : /* -------------------------------------------------------------------- */
2868 :
2869 104 : CPLStringList aosArgv;
2870 :
2871 52 : if (papszArgv)
2872 : {
2873 52 : const int nArgc = CSLCount(papszArgv);
2874 460 : for (int i = 0; i < nArgc; i++)
2875 : {
2876 408 : aosArgv.AddString(papszArgv[i]);
2877 : }
2878 : }
2879 :
2880 : try
2881 : {
2882 : auto argParser = GDALTileIndexAppOptionsGetParser(psOptions.get(),
2883 52 : psOptionsForBinary);
2884 52 : argParser->parse_args_without_binary_name(aosArgv.List());
2885 :
2886 : // Check all no store_into args
2887 55 : if (auto oTr = argParser->present<std::vector<double>>("-tr"))
2888 : {
2889 3 : psOptions->xres = (*oTr)[0];
2890 3 : psOptions->yres = (*oTr)[1];
2891 : }
2892 :
2893 55 : if (auto oTargetExtent = argParser->present<std::vector<double>>("-te"))
2894 : {
2895 3 : psOptions->xmin = (*oTargetExtent)[0];
2896 3 : psOptions->ymin = (*oTargetExtent)[1];
2897 3 : psOptions->xmax = (*oTargetExtent)[2];
2898 3 : psOptions->ymax = (*oTargetExtent)[3];
2899 : }
2900 :
2901 52 : if (auto fetchMd =
2902 52 : argParser->present<std::vector<std::string>>("-fetch_md"))
2903 : {
2904 :
2905 3 : CPLAssert(fetchMd->size() % 3 == 0);
2906 :
2907 : // Loop
2908 8 : for (size_t i = 0; i < fetchMd->size(); i += 3)
2909 : {
2910 : OGRFieldType type;
2911 5 : const auto &typeName{fetchMd->at(i + 2)};
2912 5 : if (typeName == "String")
2913 : {
2914 3 : type = OFTString;
2915 : }
2916 2 : else if (typeName == "Integer")
2917 : {
2918 0 : type = OFTInteger;
2919 : }
2920 2 : else if (typeName == "Integer64")
2921 : {
2922 0 : type = OFTInteger64;
2923 : }
2924 2 : else if (typeName == "Real")
2925 : {
2926 1 : type = OFTReal;
2927 : }
2928 1 : else if (typeName == "Date")
2929 : {
2930 0 : type = OFTDate;
2931 : }
2932 1 : else if (typeName == "DateTime")
2933 : {
2934 1 : type = OFTDateTime;
2935 : }
2936 : else
2937 : {
2938 0 : CPLError(CE_Failure, CPLE_AppDefined,
2939 : "-fetch_md requires a valid type name as third "
2940 : "argument: %s was given.",
2941 0 : fetchMd->at(i).c_str());
2942 0 : return nullptr;
2943 : }
2944 :
2945 5 : const GDALTileIndexRasterMetadata oMD{type, fetchMd->at(i + 1),
2946 15 : fetchMd->at(i)};
2947 5 : psOptions->aoFetchMD.push_back(std::move(oMD));
2948 : }
2949 : }
2950 :
2951 : // Check -t_srs
2952 52 : if (!psOptions->osTargetSRS.empty())
2953 : {
2954 16 : auto sanitized{SanitizeSRS(psOptions->osTargetSRS.c_str())};
2955 16 : if (sanitized)
2956 : {
2957 16 : psOptions->osTargetSRS = sanitized;
2958 16 : CPLFree(sanitized);
2959 : }
2960 : else
2961 : {
2962 : // Error was already reported by SanitizeSRS, just return nullptr
2963 0 : psOptions->osTargetSRS.clear();
2964 0 : return nullptr;
2965 : }
2966 : }
2967 : }
2968 0 : catch (const std::exception &error)
2969 : {
2970 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s", error.what());
2971 0 : return nullptr;
2972 : }
2973 :
2974 52 : return psOptions.release();
2975 : }
2976 :
2977 : /************************************************************************/
2978 : /* GDALTileIndexOptionsFree() */
2979 : /************************************************************************/
2980 :
2981 : /**
2982 : * Frees the GDALTileIndexOptions struct.
2983 : *
2984 : * @param psOptions the options struct for GDALTileIndex().
2985 : *
2986 : * @since GDAL 3.9
2987 : */
2988 :
2989 52 : void GDALTileIndexOptionsFree(GDALTileIndexOptions *psOptions)
2990 : {
2991 52 : delete psOptions;
2992 52 : }
2993 :
2994 : /************************************************************************/
2995 : /* GDALTileIndexOptionsSetProgress() */
2996 : /************************************************************************/
2997 :
2998 : /**
2999 : * Set a progress function.
3000 : *
3001 : * @param psOptions the options struct for GDALTileIndex().
3002 : * @param pfnProgress the progress callback.
3003 : * @param pProgressData the user data for the progress callback.
3004 : *
3005 : * @since GDAL 3.11
3006 : */
3007 :
3008 29 : void GDALTileIndexOptionsSetProgress(GDALTileIndexOptions *psOptions,
3009 : GDALProgressFunc pfnProgress,
3010 : void *pProgressData)
3011 : {
3012 29 : psOptions->pfnProgress = pfnProgress;
3013 29 : psOptions->pProgressData = pProgressData;
3014 29 : }
3015 :
3016 : #undef CHECK_HAS_ENOUGH_ADDITIONAL_ARGS
|