Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: gdal "dataset identify" subcommand
5 : * Author: Even Rouault <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : //! @cond Doxygen_Suppress
14 :
15 : #include "gdalalg_dataset_identify.h"
16 :
17 : #include "cpl_string.h"
18 : #include "gdal_dataset.h"
19 : #include "gdal_driver.h"
20 : #include "gdal_drivermanager.h"
21 : #include "gdal_rasterband.h"
22 : #include "ogrsf_frmts.h"
23 :
24 : #ifndef _
25 : #define _(x) (x)
26 : #endif
27 :
28 : /************************************************************************/
29 : /* GDALDatasetIdentifyAlgorithm() */
30 : /************************************************************************/
31 :
32 27 : GDALDatasetIdentifyAlgorithm::GDALDatasetIdentifyAlgorithm()
33 27 : : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL), m_oWriter(JSONPrint, this)
34 : {
35 27 : AddProgressArg();
36 :
37 54 : auto &arg = AddArg("filename", 0, _("File or directory name"), &m_filename)
38 54 : .AddAlias(GDAL_ARG_NAME_INPUT)
39 27 : .SetPositional()
40 27 : .SetRequired();
41 27 : SetAutoCompleteFunctionForFilename(arg, 0);
42 :
43 : AddOutputDatasetArg(&m_outputDataset, GDAL_OF_VECTOR,
44 27 : /* positionalAndRequired = */ false)
45 27 : .SetDatasetInputFlags(GADV_NAME);
46 :
47 27 : AddOutputFormatArg(&m_format)
48 : .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES,
49 108 : {GDAL_DCAP_VECTOR, GDAL_DCAP_CREATE})
50 81 : .AddMetadataItem(GAAMDI_EXTRA_FORMATS, {"json", "text"});
51 :
52 27 : AddCreationOptionsArg(&m_creationOptions);
53 27 : AddLayerCreationOptionsArg(&m_layerCreationOptions);
54 : AddArg(GDAL_ARG_NAME_OUTPUT_LAYER, 'l', _("Output layer name"),
55 27 : &m_outputLayerName);
56 27 : AddOverwriteArg(&m_overwrite);
57 :
58 : AddArg("recursive", 'r', _("Recursively scan files/folders for datasets"),
59 27 : &m_recursive);
60 :
61 : AddArg("force-recursive", 0,
62 : _("Recursively scan folders for datasets, forcing "
63 : "recursion in folders recognized as valid formats"),
64 27 : &m_forceRecursive);
65 :
66 : AddArg("detailed", 0,
67 : _("Most detailed output. Reports the presence of georeferencing, "
68 : "if a GeoTIFF file is cloud optimized, etc."),
69 27 : &m_detailed);
70 :
71 : AddArg("report-failures", 0,
72 : _("Report failures if file type is unidentified"),
73 27 : &m_reportFailures);
74 :
75 27 : AddOutputStringArg(&m_output);
76 27 : AddStdoutArg(&m_stdout);
77 27 : }
78 :
79 : /************************************************************************/
80 : /* ~GDALDatasetIdentifyAlgorithm() */
81 : /************************************************************************/
82 :
83 : GDALDatasetIdentifyAlgorithm::~GDALDatasetIdentifyAlgorithm() = default;
84 :
85 : /************************************************************************/
86 : /* GDALDatasetIdentifyAlgorithm::Print() */
87 : /************************************************************************/
88 :
89 1063 : void GDALDatasetIdentifyAlgorithm::Print(const char *str)
90 : {
91 1063 : if (m_fpOut)
92 25 : m_fpOut->Write(str, 1, strlen(str));
93 1038 : else if (m_stdout)
94 4 : fwrite(str, 1, strlen(str), stdout);
95 : else
96 1034 : m_output += str;
97 1063 : }
98 :
99 : /************************************************************************/
100 : /* GDALDatasetIdentifyAlgorithm::JSONPrint() */
101 : /************************************************************************/
102 :
103 1041 : /* static */ void GDALDatasetIdentifyAlgorithm::JSONPrint(const char *pszTxt,
104 : void *pUserData)
105 : {
106 1041 : static_cast<GDALDatasetIdentifyAlgorithm *>(pUserData)->Print(pszTxt);
107 1041 : }
108 :
109 : /************************************************************************/
110 : /* Process() */
111 : /************************************************************************/
112 :
113 81 : bool GDALDatasetIdentifyAlgorithm::Process(const char *pszTarget,
114 : CSLConstList papszSiblingList,
115 : GDALProgressFunc pfnProgress,
116 : void *pProgressData)
117 :
118 : {
119 81 : if (IsCalledFromCommandLine())
120 1 : pfnProgress = nullptr;
121 :
122 81 : if (m_format.empty())
123 0 : m_format = IsCalledFromCommandLine() ? "text" : "json";
124 :
125 81 : GDALDriverH hDriver = nullptr;
126 : {
127 81 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
128 81 : hDriver = GDALIdentifyDriver(pszTarget, papszSiblingList);
129 : }
130 :
131 81 : const char *pszDriverName = hDriver ? GDALGetDriverShortName(hDriver) : "";
132 :
133 162 : CPLStringList aosFileList;
134 162 : std::string osLayout;
135 81 : bool bHasCRS = false;
136 81 : bool bHasGeoTransform = false;
137 81 : bool bHasOverview = false;
138 81 : if (hDriver && m_detailed)
139 : {
140 6 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
141 3 : const char *const apszDriver[] = {pszDriverName, nullptr};
142 : auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
143 6 : pszTarget, 0, apszDriver, nullptr, papszSiblingList));
144 3 : if (poDS)
145 : {
146 3 : if (EQUAL(pszDriverName, "GTiff"))
147 : {
148 3 : if (const char *pszLayout =
149 3 : poDS->GetMetadataItem("LAYOUT", "IMAGE_STRUCTURE"))
150 : {
151 3 : osLayout = pszLayout;
152 : }
153 : }
154 :
155 3 : aosFileList.Assign(poDS->GetFileList(),
156 3 : /* bTakeOwnership = */ true);
157 3 : bHasCRS = poDS->GetSpatialRef() != nullptr;
158 3 : GDALGeoTransform gt;
159 3 : bHasGeoTransform = poDS->GetGeoTransform(gt) == CE_None;
160 6 : bHasOverview = (poDS->GetRasterCount() &&
161 3 : poDS->GetRasterBand(1)->GetOverviewCount() > 0);
162 : }
163 : }
164 :
165 81 : if (m_poLayer)
166 : {
167 4 : OGRFeature oFeature(m_poLayer->GetLayerDefn());
168 4 : oFeature.SetField("filename", pszTarget);
169 4 : if (hDriver)
170 : {
171 3 : oFeature.SetField("driver", pszDriverName);
172 :
173 3 : if (m_detailed)
174 : {
175 1 : if (!osLayout.empty())
176 1 : oFeature.SetField("layout", osLayout.c_str());
177 :
178 1 : if (!aosFileList.empty())
179 : {
180 1 : oFeature.SetField("file_list", aosFileList.List());
181 : }
182 :
183 1 : oFeature.SetField("has_crs", bHasCRS);
184 1 : oFeature.SetField("has_geotransform", bHasGeoTransform);
185 1 : oFeature.SetField("has_overview", bHasOverview);
186 : }
187 :
188 3 : if (m_poLayer->CreateFeature(&oFeature) != OGRERR_NONE)
189 0 : return false;
190 : }
191 1 : else if (m_reportFailures)
192 : {
193 1 : if (m_poLayer->CreateFeature(&oFeature) != OGRERR_NONE)
194 0 : return false;
195 : }
196 : }
197 77 : else if (m_format == "json")
198 : {
199 72 : if (hDriver)
200 : {
201 54 : m_oWriter.StartObj();
202 54 : m_oWriter.AddObjKey("name");
203 54 : m_oWriter.Add(pszTarget);
204 54 : m_oWriter.AddObjKey("driver");
205 54 : m_oWriter.Add(GDALGetDriverShortName(hDriver));
206 54 : if (m_detailed)
207 : {
208 1 : if (!osLayout.empty())
209 : {
210 1 : m_oWriter.AddObjKey("layout");
211 1 : m_oWriter.Add(osLayout);
212 : }
213 :
214 1 : if (!aosFileList.empty())
215 : {
216 1 : m_oWriter.AddObjKey("file_list");
217 1 : m_oWriter.StartArray();
218 2 : for (const char *pszFilename : aosFileList)
219 : {
220 1 : m_oWriter.Add(pszFilename);
221 : }
222 1 : m_oWriter.EndArray();
223 : }
224 :
225 1 : if (bHasCRS)
226 : {
227 1 : m_oWriter.AddObjKey("has_crs");
228 1 : m_oWriter.Add(true);
229 : }
230 :
231 1 : if (bHasGeoTransform)
232 : {
233 1 : m_oWriter.AddObjKey("has_geotransform");
234 1 : m_oWriter.Add(true);
235 : }
236 :
237 1 : if (bHasOverview)
238 : {
239 0 : m_oWriter.AddObjKey("has_overview");
240 0 : m_oWriter.Add(true);
241 : }
242 : }
243 54 : m_oWriter.EndObj();
244 : }
245 18 : else if (m_reportFailures)
246 : {
247 1 : m_oWriter.StartObj();
248 1 : m_oWriter.AddObjKey("name");
249 1 : m_oWriter.Add(pszTarget);
250 1 : m_oWriter.AddObjKey("driver");
251 1 : m_oWriter.AddNull();
252 1 : m_oWriter.EndObj();
253 : }
254 : }
255 : else
256 : {
257 5 : if (hDriver)
258 : {
259 4 : Print(pszTarget);
260 4 : Print(": ");
261 4 : Print(pszDriverName);
262 4 : if (m_detailed)
263 : {
264 1 : if (!osLayout.empty())
265 : {
266 1 : Print(", layout=");
267 1 : Print(osLayout.c_str());
268 : }
269 1 : if (aosFileList.size() > 1)
270 : {
271 0 : Print(", has side-car files");
272 : }
273 1 : if (bHasCRS)
274 : {
275 1 : Print(", has CRS");
276 : }
277 1 : if (bHasGeoTransform)
278 : {
279 1 : Print(", has geotransform");
280 : }
281 1 : if (bHasOverview)
282 : {
283 0 : Print(", has overview(s)");
284 : }
285 : }
286 4 : Print("\n");
287 : }
288 1 : else if (m_reportFailures)
289 : {
290 1 : Print(pszTarget);
291 1 : Print(": unrecognized\n");
292 : }
293 : }
294 :
295 81 : bool ret = true;
296 : VSIStatBufL sStatBuf;
297 16 : if ((m_forceRecursive || (m_recursive && hDriver == nullptr)) &&
298 97 : VSIStatL(pszTarget, &sStatBuf) == 0 && VSI_ISDIR(sStatBuf.st_mode))
299 : {
300 2 : const CPLStringList aosSiblingList(VSIReadDir(pszTarget));
301 1 : const int nListSize = aosSiblingList.size();
302 67 : for (int i = 0; i < nListSize; ++i)
303 : {
304 66 : const char *pszSubTarget = aosSiblingList[i];
305 66 : if (!(EQUAL(pszSubTarget, "..") || EQUAL(pszSubTarget, ".")))
306 : {
307 : const std::string osSubTarget =
308 128 : CPLFormFilenameSafe(pszTarget, pszSubTarget, nullptr);
309 :
310 : std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)>
311 : pScaledProgress(GDALCreateScaledProgress(
312 64 : static_cast<double>(i) / nListSize,
313 64 : static_cast<double>(i + 1) / nListSize,
314 : pfnProgress, pProgressData),
315 64 : GDALDestroyScaledProgress);
316 128 : ret = ret &&
317 128 : Process(osSubTarget.c_str(), aosSiblingList.List(),
318 64 : pScaledProgress ? GDALScaledProgress : nullptr,
319 : pScaledProgress.get());
320 : }
321 : }
322 : }
323 :
324 81 : return ret && (!pfnProgress || pfnProgress(1.0, "", pProgressData));
325 : }
326 :
327 : /************************************************************************/
328 : /* GDALDatasetIdentifyAlgorithm::RunImpl() */
329 : /************************************************************************/
330 :
331 20 : bool GDALDatasetIdentifyAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
332 : void *pProgressData)
333 : {
334 20 : if (m_format.empty() && m_outputDataset.GetName().empty())
335 6 : m_format = IsCalledFromCommandLine() ? "text" : "json";
336 :
337 20 : if (m_format == "text" || m_format == "json")
338 : {
339 13 : if (!m_outputDataset.GetName().empty())
340 : {
341 6 : m_fpOut = VSIFilesystemHandler::OpenStatic(
342 6 : m_outputDataset.GetName().c_str(), "wb");
343 3 : if (!m_fpOut)
344 : {
345 1 : ReportError(CE_Failure, CPLE_FileIO, "Cannot create '%s'",
346 1 : m_outputDataset.GetName().c_str());
347 1 : return false;
348 : }
349 : }
350 : }
351 : else
352 : {
353 7 : if (m_outputDataset.GetName().empty() && m_format != "MEM")
354 : {
355 1 : ReportError(CE_Failure, CPLE_AppDefined,
356 : "'output' argument must be specified for non-text or "
357 : "non-json output");
358 1 : return false;
359 : }
360 :
361 6 : if (m_format.empty())
362 : {
363 : const CPLStringList aosFormats(GDALGetOutputDriversForDatasetName(
364 2 : m_outputDataset.GetName().c_str(), GDAL_OF_VECTOR,
365 : /* bSingleMatch = */ true,
366 2 : /* bEmitWarning = */ true));
367 2 : if (aosFormats.size() != 1)
368 : {
369 1 : ReportError(CE_Failure, CPLE_AppDefined,
370 : "Cannot guess driver for %s",
371 1 : m_outputDataset.GetName().c_str());
372 1 : return false;
373 : }
374 1 : m_format = aosFormats[0];
375 : }
376 :
377 : auto poOutDrv =
378 5 : GetGDALDriverManager()->GetDriverByName(m_format.c_str());
379 5 : if (!poOutDrv)
380 : {
381 : // shouldn't happen given checks done in GDALAlgorithm unless
382 : // someone deregister the driver between ParseCommandLineArgs() and
383 : // Run()
384 0 : ReportError(CE_Failure, CPLE_AppDefined, "Driver %s does not exist",
385 : m_format.c_str());
386 0 : return false;
387 : }
388 :
389 5 : m_poOutDS.reset(poOutDrv->Create(
390 5 : m_outputDataset.GetName().c_str(), 0, 0, 0, GDT_Unknown,
391 10 : CPLStringList(m_creationOptions).List()));
392 5 : if (!m_poOutDS)
393 0 : return false;
394 :
395 5 : if (m_outputLayerName.empty())
396 : {
397 5 : if (EQUAL(poOutDrv->GetDescription(), "ESRI Shapefile"))
398 : m_outputLayerName =
399 0 : CPLGetBasenameSafe(m_outputDataset.GetName().c_str());
400 : else
401 5 : m_outputLayerName = "output";
402 : }
403 :
404 10 : m_poLayer = m_poOutDS->CreateLayer(
405 : m_outputLayerName.c_str(), nullptr,
406 10 : CPLStringList(m_layerCreationOptions).List());
407 5 : if (!m_poLayer)
408 1 : return false;
409 :
410 4 : bool ret = true;
411 : {
412 4 : OGRFieldDefn oFieldDefn("filename", OFTString);
413 4 : ret = m_poLayer->CreateField(&oFieldDefn) == OGRERR_NONE;
414 : }
415 :
416 : {
417 4 : OGRFieldDefn oFieldDefn("driver", OFTString);
418 4 : ret = ret && m_poLayer->CreateField(&oFieldDefn) == OGRERR_NONE;
419 : }
420 :
421 4 : if (m_detailed)
422 : {
423 : {
424 1 : OGRFieldDefn oFieldDefn("layout", OFTString);
425 1 : ret = ret && m_poLayer->CreateField(&oFieldDefn) == OGRERR_NONE;
426 : }
427 : {
428 : const char *pszSupportedFieldTypes =
429 1 : poOutDrv->GetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES);
430 : OGRFieldDefn oFieldDefn(
431 1 : "file_list", (pszSupportedFieldTypes &&
432 1 : strstr(pszSupportedFieldTypes, "StringList"))
433 : ? OFTStringList
434 2 : : OFTString);
435 1 : ret = ret && m_poLayer->CreateField(&oFieldDefn) == OGRERR_NONE;
436 : }
437 : {
438 1 : OGRFieldDefn oFieldDefn("has_crs", OFTInteger);
439 1 : oFieldDefn.SetSubType(OFSTBoolean);
440 1 : ret = ret && m_poLayer->CreateField(&oFieldDefn) == OGRERR_NONE;
441 : }
442 : {
443 1 : OGRFieldDefn oFieldDefn("has_geotransform", OFTInteger);
444 1 : oFieldDefn.SetSubType(OFSTBoolean);
445 1 : ret = ret && m_poLayer->CreateField(&oFieldDefn) == OGRERR_NONE;
446 : }
447 : {
448 1 : OGRFieldDefn oFieldDefn("has_overview", OFTInteger);
449 1 : oFieldDefn.SetSubType(OFSTBoolean);
450 1 : ret = ret && m_poLayer->CreateField(&oFieldDefn) == OGRERR_NONE;
451 : }
452 : }
453 :
454 4 : if (!ret)
455 0 : return false;
456 : }
457 :
458 16 : if (m_format == "json")
459 7 : m_oWriter.StartArray();
460 16 : int i = 0;
461 16 : bool ret = true;
462 33 : for (const std::string &osPath : m_filename)
463 : {
464 : std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)>
465 : pScaledProgress(GDALCreateScaledProgress(
466 17 : static_cast<double>(i) /
467 17 : static_cast<int>(m_filename.size()),
468 17 : static_cast<double>(i + 1) /
469 17 : static_cast<int>(m_filename.size()),
470 : pfnProgress, pProgressData),
471 17 : GDALDestroyScaledProgress);
472 34 : ret = ret && Process(osPath.c_str(), nullptr,
473 17 : pScaledProgress ? GDALScaledProgress : nullptr,
474 : pScaledProgress.get());
475 17 : ++i;
476 : }
477 16 : if (m_format == "json")
478 7 : m_oWriter.EndArray();
479 :
480 16 : if (!m_output.empty())
481 : {
482 9 : GetArg(GDAL_ARG_NAME_OUTPUT_STRING)->Set(m_output);
483 : }
484 : else
485 : {
486 7 : m_outputDataset.Set(std::move(m_poOutDS));
487 : }
488 :
489 16 : return ret;
490 : }
491 :
492 : //! @endcond
|