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