LCOV - code coverage report
Current view: top level - apps - gdalalg_dataset_identify.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 222 234 94.9 %
Date: 2025-12-13 23:48:27 Functions: 5 5 100.0 %

          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

Generated by: LCOV version 1.14