LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_footprint.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 176 179 98.3 %
Date: 2026-09-01 21:14:11 Functions: 5 5 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  gdal "raster footprint" 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             : #include "gdalalg_raster_footprint.h"
      14             : 
      15             : #include "cpl_conv.h"
      16             : 
      17             : #include "gdal_priv.h"
      18             : #include "gdal_utils.h"
      19             : 
      20             : //! @cond Doxygen_Suppress
      21             : 
      22             : #ifndef _
      23             : #define _(x) (x)
      24             : #endif
      25             : 
      26             : /************************************************************************/
      27             : /*     GDALRasterFootprintAlgorithm::GDALRasterFootprintAlgorithm()     */
      28             : /************************************************************************/
      29             : 
      30          96 : GDALRasterFootprintAlgorithm::GDALRasterFootprintAlgorithm(bool standaloneStep)
      31             :     : GDALPipelineStepAlgorithm(
      32             :           NAME, DESCRIPTION, HELP_URL,
      33           0 :           ConstructorOptions()
      34          96 :               .SetStandaloneStep(standaloneStep)
      35         192 :               .SetOutputFormatCreateCapability(GDAL_DCAP_CREATE))
      36             : {
      37          96 :     if (standaloneStep)
      38             :     {
      39          80 :         AddProgressArg();
      40          80 :         AddOpenOptionsArg(&m_openOptions).SetAvailableInPipelineStep(false);
      41          80 :         AddInputFormatsArg(&m_inputFormats)
      42         240 :             .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_RASTER})
      43          80 :             .SetAvailableInPipelineStep(false);
      44             :         AddInputDatasetArg(&m_inputDataset, GDAL_OF_RASTER, true,
      45          80 :                            _("Input raster dataset"))
      46          80 :             .SetMaxCount(1)
      47          80 :             .SetAvailableInPipelineStep(false);
      48             : 
      49          80 :         AddOutputDatasetArg(&m_outputDataset, GDAL_OF_VECTOR)
      50          80 :             .SetDatasetInputFlags(GADV_NAME | GADV_OBJECT)
      51          80 :             .SetAvailableInPipelineStep(false);
      52             :         AddOutputFormatArg(&m_format, /* bStreamAllowed = */ false,
      53          80 :                            /* bGDALGAllowed = */ false)
      54             :             .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES,
      55         320 :                              {GDAL_DCAP_VECTOR, GDAL_DCAP_CREATE})
      56          80 :             .SetAvailableInPipelineStep(false);
      57          80 :         AddCreationOptionsArg(&m_creationOptions)
      58          80 :             .SetAvailableInPipelineStep(false);
      59          80 :         AddLayerCreationOptionsArg(&m_layerCreationOptions)
      60          80 :             .SetAvailableInPipelineStep(false);
      61          80 :         AddUpdateArg(&m_update)
      62          80 :             .SetAvailableInPipelineStep(false)
      63          80 :             .SetHidden();  // needed for correct append execution
      64          80 :         AddAppendLayerArg(&m_appendLayer).SetAvailableInPipelineStep(false);
      65          80 :         AddOverwriteArg(&m_overwrite).SetAvailableInPipelineStep(false);
      66             :     }
      67             :     else
      68             :     {
      69          16 :         AddRasterHiddenInputDatasetArg();
      70             :     }
      71             : 
      72          96 :     m_outputLayerName = "footprint";
      73             :     AddArg(GDAL_ARG_NAME_OUTPUT_LAYER, 0, _("Output layer name"),
      74         192 :            &m_outputLayerName)
      75          96 :         .SetDefault(m_outputLayerName);
      76             : 
      77          96 :     AddBandArg(&m_bands);
      78             :     AddArg("combine-bands", 0,
      79             :            _("Defines how the mask bands of the selected bands are combined to "
      80             :              "generate a single mask band, before being vectorized."),
      81         192 :            &m_combineBands)
      82          96 :         .SetChoices("union", "intersection")
      83          96 :         .SetDefault(m_combineBands);
      84             :     AddArg("overview", 0, _("Which overview level of source file must be used"),
      85         192 :            &m_overview)
      86         192 :         .SetMutualExclusionGroup("overview-srcnodata")
      87          96 :         .SetMinValueIncluded(0);
      88             :     AddArg("input-nodata", 0, _("Set nodata values for input bands."),
      89         192 :            &m_srcNoData)
      90          96 :         .SetMinCount(1)
      91          96 :         .SetRepeatedArgAllowed(false)
      92         192 :         .AddHiddenAlias("src-nodata")
      93          96 :         .SetMutualExclusionGroup("overview-srcnodata");
      94             :     AddArg("coordinate-system", 0, _("Target coordinate system"),
      95         192 :            &m_coordinateSystem)
      96          96 :         .SetChoices("georeferenced", "pixel");
      97         192 :     AddArg(GDAL_ARG_NAME_OUTPUT_CRS, 0, _("Output CRS"), &m_dstCrs)
      98         192 :         .SetIsCRSArg()
      99         192 :         .AddHiddenAlias("dst-crs")
     100          96 :         .AddHiddenAlias("t_srs");
     101             :     AddArg("split-multipolygons", 0,
     102             :            _("Whether to split multipolygons as several features each with one "
     103             :              "single polygon"),
     104          96 :            &m_splitMultiPolygons);
     105             :     AddArg("convex-hull", 0,
     106             :            _("Whether to compute the convex hull of the footprint"),
     107          96 :            &m_convexHull);
     108             :     AddArg("densify-distance", 0,
     109             :            _("Maximum distance between 2 consecutive points of the output "
     110             :              "geometry."),
     111         192 :            &m_densifyVal)
     112          96 :         .SetMinValueExcluded(0);
     113             :     AddArg(
     114             :         "simplify-tolerance", 0,
     115             :         _("Tolerance used to merge consecutive points of the output geometry."),
     116         192 :         &m_simplifyVal)
     117          96 :         .SetMinValueExcluded(0);
     118             :     AddArg("min-ring-area", 0, _("Minimum value for the area of a ring"),
     119         192 :            &m_minRingArea)
     120          96 :         .SetMinValueIncluded(0);
     121             :     AddArg("max-points", 0,
     122         192 :            _("Maximum number of points of each output geometry"), &m_maxPoints)
     123          96 :         .SetDefault(m_maxPoints)
     124             :         .AddValidationAction(
     125          11 :             [this]()
     126             :             {
     127           3 :                 if (m_maxPoints != "unlimited")
     128             :                 {
     129           3 :                     char *endptr = nullptr;
     130             :                     const auto nVal =
     131           3 :                         std::strtoll(m_maxPoints.c_str(), &endptr, 10);
     132           5 :                     if (nVal < 4 ||
     133           2 :                         endptr != m_maxPoints.c_str() + m_maxPoints.size())
     134             :                     {
     135           1 :                         ReportError(
     136             :                             CE_Failure, CPLE_IllegalArg,
     137             :                             "Value of 'max-points' should be a positive "
     138             :                             "integer greater or equal to 4, or 'unlimited'");
     139           1 :                         return false;
     140             :                     }
     141             :                 }
     142           2 :                 return true;
     143          96 :             });
     144             :     AddArg("location-field", 0,
     145             :            _("Name of the field where the path of the input dataset will be "
     146             :              "stored."),
     147         192 :            &m_locationField)
     148          96 :         .SetDefault(m_locationField)
     149          96 :         .SetMutualExclusionGroup("location");
     150             :     AddArg("no-location-field", 0,
     151             :            _("Disable creating a field with the path of the input dataset"),
     152         192 :            &m_noLocation)
     153          96 :         .SetMutualExclusionGroup("location");
     154          96 :     AddAbsolutePathArg(&m_writeAbsolutePaths);
     155             : 
     156          96 :     AddValidationAction(
     157         108 :         [this]
     158             :         {
     159          35 :             if (m_inputDataset.size() == 1)
     160             :             {
     161          34 :                 if (auto poSrcDS = m_inputDataset[0].GetDatasetRef())
     162             :                 {
     163             :                     const int nOvrCount =
     164          34 :                         poSrcDS->GetRasterBand(1)->GetOverviewCount();
     165          37 :                     if (m_overview >= 0 && poSrcDS->GetRasterCount() > 0 &&
     166           3 :                         m_overview >= nOvrCount)
     167             :                     {
     168           2 :                         if (nOvrCount == 0)
     169             :                         {
     170           1 :                             ReportError(
     171             :                                 CE_Failure, CPLE_IllegalArg,
     172             :                                 "Source dataset has no overviews. "
     173             :                                 "Argument 'overview' should not be specified.");
     174             :                         }
     175             :                         else
     176             :                         {
     177           1 :                             ReportError(
     178             :                                 CE_Failure, CPLE_IllegalArg,
     179             :                                 "Source dataset has only %d overview levels. "
     180             :                                 "'overview' "
     181             :                                 "value should be strictly lower than this "
     182             :                                 "number.",
     183             :                                 nOvrCount);
     184             :                         }
     185           2 :                         return false;
     186             :                     }
     187             :                 }
     188             :             }
     189          33 :             return true;
     190             :         });
     191          96 : }
     192             : 
     193             : /************************************************************************/
     194             : /*               GDALRasterFootprintAlgorithm::RunImpl()                */
     195             : /************************************************************************/
     196             : 
     197          30 : bool GDALRasterFootprintAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
     198             :                                            void *pProgressData)
     199             : {
     200          30 :     GDALPipelineStepRunContext stepCtxt;
     201          30 :     stepCtxt.m_pfnProgress = pfnProgress;
     202          30 :     stepCtxt.m_pProgressData = pProgressData;
     203          30 :     return RunPreStepPipelineValidations() && RunStep(stepCtxt);
     204             : }
     205             : 
     206             : /************************************************************************/
     207             : /*               GDALRasterFootprintAlgorithm::RunStep()                */
     208             : /************************************************************************/
     209             : 
     210          31 : bool GDALRasterFootprintAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
     211             : {
     212          31 :     auto poSrcDS = m_inputDataset[0].GetDatasetRef();
     213          31 :     CPLAssert(poSrcDS);
     214             : 
     215          62 :     CPLStringList aosOptions;
     216             : 
     217          62 :     std::string outputFilename;
     218          31 :     if (m_standaloneStep)
     219             :     {
     220          30 :         outputFilename = m_outputDataset.GetName();
     221          30 :         if (!m_format.empty())
     222             :         {
     223          25 :             aosOptions.AddString("-of");
     224          25 :             aosOptions.AddString(m_format.c_str());
     225             :         }
     226             : 
     227          31 :         for (const auto &co : m_creationOptions)
     228             :         {
     229           1 :             aosOptions.push_back("-dsco");
     230           1 :             aosOptions.push_back(co.c_str());
     231             :         }
     232             : 
     233          31 :         for (const auto &co : m_layerCreationOptions)
     234             :         {
     235           1 :             aosOptions.push_back("-lco");
     236           1 :             aosOptions.push_back(co.c_str());
     237             :         }
     238             :     }
     239             :     else
     240             :     {
     241           1 :         if (GetGDALDriverManager()->GetDriverByName("GPKG"))
     242             :         {
     243           1 :             aosOptions.AddString("-of");
     244           1 :             aosOptions.AddString("GPKG");
     245             : 
     246             :             outputFilename =
     247           1 :                 CPLGenerateTempFilenameSafe("_footprint") + ".gpkg";
     248             :         }
     249             :         else
     250             :         {
     251           0 :             aosOptions.AddString("-of");
     252           0 :             aosOptions.AddString("MEM");
     253             :         }
     254             :     }
     255             : 
     256          37 :     for (int band : m_bands)
     257             :     {
     258           6 :         aosOptions.push_back("-b");
     259           6 :         aosOptions.push_back(CPLSPrintf("%d", band));
     260             :     }
     261             : 
     262          31 :     aosOptions.push_back("-combine_bands");
     263          31 :     aosOptions.push_back(m_combineBands);
     264             : 
     265          31 :     if (m_overview >= 0)
     266             :     {
     267           1 :         aosOptions.push_back("-ovr");
     268           1 :         aosOptions.push_back(CPLSPrintf("%d", m_overview));
     269             :     }
     270             : 
     271          31 :     if (!m_srcNoData.empty())
     272             :     {
     273           2 :         aosOptions.push_back("-srcnodata");
     274           4 :         std::string s;
     275           5 :         for (double v : m_srcNoData)
     276             :         {
     277           3 :             if (!s.empty())
     278           1 :                 s += " ";
     279           3 :             s += CPLSPrintf("%.17g", v);
     280             :         }
     281           2 :         aosOptions.push_back(s);
     282             :     }
     283             : 
     284          31 :     if (m_coordinateSystem == "pixel")
     285             :     {
     286           1 :         aosOptions.push_back("-t_cs");
     287           1 :         aosOptions.push_back("pixel");
     288             :     }
     289          30 :     else if (m_coordinateSystem == "georeferenced")
     290             :     {
     291           1 :         aosOptions.push_back("-t_cs");
     292           1 :         aosOptions.push_back("georef");
     293             :     }
     294             : 
     295          31 :     if (!m_dstCrs.empty())
     296             :     {
     297           1 :         aosOptions.push_back("-t_srs");
     298           1 :         aosOptions.push_back(m_dstCrs);
     299             :     }
     300             : 
     301          31 :     if (GetArg(GDAL_ARG_NAME_OUTPUT_LAYER)->IsExplicitlySet())
     302             :     {
     303           5 :         aosOptions.push_back("-lyr_name");
     304           5 :         aosOptions.push_back(m_outputLayerName.c_str());
     305             :     }
     306             : 
     307          31 :     if (m_splitMultiPolygons)
     308           1 :         aosOptions.push_back("-split_polys");
     309             : 
     310          31 :     if (m_convexHull)
     311           1 :         aosOptions.push_back("-convex_hull");
     312             : 
     313          31 :     if (m_densifyVal > 0)
     314             :     {
     315           1 :         aosOptions.push_back("-densify");
     316           1 :         aosOptions.push_back(CPLSPrintf("%.17g", m_densifyVal));
     317             :     }
     318             : 
     319          31 :     if (m_simplifyVal > 0)
     320             :     {
     321           1 :         aosOptions.push_back("-simplify");
     322           1 :         aosOptions.push_back(CPLSPrintf("%.17g", m_simplifyVal));
     323             :     }
     324             : 
     325          31 :     aosOptions.push_back("-min_ring_area");
     326          31 :     aosOptions.push_back(CPLSPrintf("%.17g", m_minRingArea));
     327             : 
     328          31 :     aosOptions.push_back("-max_points");
     329          31 :     aosOptions.push_back(m_maxPoints);
     330             : 
     331          31 :     if (m_noLocation)
     332             :     {
     333           1 :         aosOptions.push_back("-no_location");
     334             :     }
     335             :     else
     336             :     {
     337          30 :         aosOptions.push_back("-location_field_name");
     338          30 :         aosOptions.push_back(m_locationField);
     339             : 
     340          30 :         if (m_writeAbsolutePaths)
     341           1 :             aosOptions.push_back("-write_absolute_path");
     342             :     }
     343             : 
     344          31 :     bool bOK = false;
     345             :     std::unique_ptr<GDALFootprintOptions, decltype(&GDALFootprintOptionsFree)>
     346             :         psOptions{GDALFootprintOptionsNew(aosOptions.List(), nullptr),
     347          31 :                   GDALFootprintOptionsFree};
     348          31 :     if (psOptions)
     349             :     {
     350          31 :         GDALFootprintOptionsSetProgress(psOptions.get(), ctxt.m_pfnProgress,
     351             :                                         ctxt.m_pProgressData);
     352             : 
     353          31 :         GDALDatasetH hSrcDS = GDALDataset::ToHandle(poSrcDS);
     354             :         GDALDatasetH hDstDS =
     355          31 :             GDALDataset::ToHandle(m_outputDataset.GetDatasetRef());
     356          31 :         auto poRetDS = GDALDataset::FromHandle(GDALFootprint(
     357          31 :             outputFilename.c_str(), hDstDS, hSrcDS, psOptions.get(), nullptr));
     358          31 :         if ((bOK = (poRetDS != nullptr)) && !hDstDS)
     359             :         {
     360          28 :             if (!m_standaloneStep && !outputFilename.empty())
     361             :             {
     362           1 :                 bOK = poRetDS->FlushCache() == CE_None;
     363             : #if !defined(__APPLE__)
     364             :                 // For some unknown reason, unlinking the file on MacOSX
     365             :                 // leads to later "disk I/O error". See https://github.com/OSGeo/gdal/issues/13794
     366           1 :                 VSIUnlink(outputFilename.c_str());
     367             : #endif
     368           1 :                 poRetDS->MarkSuppressOnClose();
     369             :             }
     370          28 :             m_outputDataset.Set(std::unique_ptr<GDALDataset>(poRetDS));
     371             :         }
     372             :     }
     373             : 
     374          62 :     return bOK;
     375             : }
     376             : 
     377             : GDALRasterFootprintAlgorithmStandalone::
     378             :     ~GDALRasterFootprintAlgorithmStandalone() = default;
     379             : 
     380             : //! @endcond

Generated by: LCOV version 1.14