LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_stack.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 92 164 56.1 %
Date: 2025-03-28 11:40:40 Functions: 3 3 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  gdal "raster stack" subcommand
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "gdalalg_raster_stack.h"
      14             : 
      15             : #include "cpl_conv.h"
      16             : #include "cpl_vsi_virtual.h"
      17             : 
      18             : #include "gdal_priv.h"
      19             : #include "gdal_utils.h"
      20             : 
      21             : //! @cond Doxygen_Suppress
      22             : 
      23             : #ifndef _
      24             : #define _(x) (x)
      25             : #endif
      26             : 
      27             : /************************************************************************/
      28             : /*        GDALRasterStackAlgorithm::GDALRasterStackAlgorithm()    */
      29             : /************************************************************************/
      30             : 
      31           5 : GDALRasterStackAlgorithm::GDALRasterStackAlgorithm()
      32           5 :     : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
      33             : {
      34           5 :     m_supportsStreamedOutput = true;
      35             : 
      36           5 :     AddProgressArg();
      37             :     AddOutputFormatArg(&m_format, /* bStreamAllowed = */ true,
      38           5 :                        /* bGDALGAllowed = */ true);
      39             :     AddArg(GDAL_ARG_NAME_INPUT, 'i',
      40             :            _("Input raster datasets (or specify a @<filename> to point to a "
      41             :              "file containing filenames)"),
      42          10 :            &m_inputDatasets, GDAL_OF_RASTER)
      43           5 :         .SetPositional()
      44           5 :         .SetMinCount(1)
      45           5 :         .SetAutoOpenDataset(false)
      46           5 :         .SetMetaVar("INPUTS");
      47           5 :     AddOutputDatasetArg(&m_outputDataset, GDAL_OF_RASTER);
      48           5 :     AddCreationOptionsArg(&m_creationOptions);
      49           5 :     AddArg("band", 'b', _("Specify input band(s) number."), &m_bands);
      50           5 :     AddOverwriteArg(&m_overwrite);
      51             :     {
      52             :         auto &arg =
      53             :             AddArg("resolution", 0,
      54             :                    _("Target resolution (in destination CRS units)"),
      55          10 :                    &m_resolution)
      56           5 :                 .SetDefault("same")
      57           5 :                 .SetMetaVar("<xres>,<yres>|same|average|common|highest|lowest");
      58             :         arg.AddValidationAction(
      59           1 :             [this, &arg]()
      60             :             {
      61           2 :                 const std::string val = arg.Get<std::string>();
      62           2 :                 if (val != "average" && val != "highest" && val != "lowest" &&
      63           2 :                     val != "same" && val != "common")
      64             :                 {
      65             :                     const auto aosTokens =
      66           0 :                         CPLStringList(CSLTokenizeString2(val.c_str(), ",", 0));
      67           0 :                     if (aosTokens.size() != 2 ||
      68           0 :                         CPLGetValueType(aosTokens[0]) == CPL_VALUE_STRING ||
      69           0 :                         CPLGetValueType(aosTokens[1]) == CPL_VALUE_STRING ||
      70           0 :                         CPLAtof(aosTokens[0]) <= 0 ||
      71           0 :                         CPLAtof(aosTokens[1]) <= 0)
      72             :                     {
      73           0 :                         ReportError(
      74             :                             CE_Failure, CPLE_AppDefined,
      75             :                             "resolution: two comma-separated positive "
      76             :                             "values should be provided, or 'same', "
      77             :                             "'average', 'common', 'highest' or 'lowest'");
      78           0 :                         return false;
      79             :                     }
      80             :                 }
      81           1 :                 return true;
      82           5 :             });
      83             :     }
      84             :     AddBBOXArg(&m_bbox, _("Target bounding box as xmin,ymin,xmax,ymax (in "
      85           5 :                           "destination CRS units)"));
      86             :     AddArg("target-aligned-pixels", 0,
      87             :            _("Round target extent to target resolution"),
      88          10 :            &m_targetAlignedPixels)
      89           5 :         .AddHiddenAlias("tap");
      90             :     AddArg("srcnodata", 0, _("Set nodata values for input bands."),
      91          10 :            &m_srcNoData)
      92           5 :         .SetMinCount(1)
      93           5 :         .SetRepeatedArgAllowed(false);
      94             :     AddArg("dstnodata", 0,
      95          10 :            _("Set nodata values at the destination band level."), &m_dstNoData)
      96           5 :         .SetMinCount(1)
      97           5 :         .SetRepeatedArgAllowed(false);
      98             :     AddArg("hidenodata", 0,
      99             :            _("Makes the destination band not report the NoData."),
     100           5 :            &m_hideNoData);
     101           5 : }
     102             : 
     103             : /************************************************************************/
     104             : /*                   GDALRasterStackAlgorithm::RunImpl()               */
     105             : /************************************************************************/
     106             : 
     107           2 : bool GDALRasterStackAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
     108             :                                        void *pProgressData)
     109             : {
     110           2 :     if (m_outputDataset.GetDatasetRef())
     111             :     {
     112           0 :         ReportError(CE_Failure, CPLE_NotSupported,
     113             :                     "gdal raster stack does not support outputting to an "
     114             :                     "already opened output dataset");
     115           0 :         return false;
     116             :     }
     117             : 
     118           4 :     std::vector<GDALDatasetH> ahInputDatasets;
     119           4 :     CPLStringList aosInputDatasetNames;
     120           2 :     bool foundByRef = false;
     121           2 :     bool foundByName = false;
     122           5 :     for (auto &ds : m_inputDatasets)
     123             :     {
     124           3 :         if (ds.GetDatasetRef())
     125             :         {
     126           2 :             foundByRef = true;
     127           2 :             ahInputDatasets.push_back(
     128           2 :                 GDALDataset::ToHandle(ds.GetDatasetRef()));
     129             :         }
     130           1 :         else if (!ds.GetName().empty())
     131             :         {
     132           1 :             foundByName = true;
     133           1 :             if (ds.GetName()[0] == '@')
     134             :             {
     135             :                 auto f = VSIVirtualHandleUniquePtr(
     136           0 :                     VSIFOpenL(ds.GetName().c_str() + 1, "r"));
     137           0 :                 if (!f)
     138             :                 {
     139           0 :                     ReportError(CE_Failure, CPLE_FileIO, "Cannot open %s",
     140           0 :                                 ds.GetName().c_str() + 1);
     141           0 :                     return false;
     142             :                 }
     143           0 :                 while (const char *filename = CPLReadLineL(f.get()))
     144             :                 {
     145           0 :                     aosInputDatasetNames.push_back(filename);
     146           0 :                 }
     147             :             }
     148           1 :             else if (ds.GetName().find_first_of("*?[") != std::string::npos)
     149             :             {
     150           0 :                 CPLStringList aosMatches(VSIGlob(ds.GetName().c_str(), nullptr,
     151           0 :                                                  pfnProgress, pProgressData));
     152           0 :                 for (const char *pszStr : aosMatches)
     153             :                 {
     154           0 :                     aosInputDatasetNames.push_back(pszStr);
     155             :                 }
     156             :             }
     157             :             else
     158             :             {
     159           2 :                 std::string osDatasetName = ds.GetName();
     160           1 :                 if (!GetReferencePathForRelativePaths().empty())
     161             :                 {
     162           0 :                     osDatasetName = GDALDataset::BuildFilename(
     163             :                         osDatasetName.c_str(),
     164           0 :                         GetReferencePathForRelativePaths().c_str(), true);
     165             :                 }
     166           1 :                 aosInputDatasetNames.push_back(osDatasetName.c_str());
     167             :             }
     168             :         }
     169             :     }
     170           2 :     if (foundByName && foundByRef)
     171             :     {
     172           0 :         ReportError(CE_Failure, CPLE_NotSupported,
     173             :                     "Input datasets should be provided either all by reference "
     174             :                     "or all by name");
     175           0 :         return false;
     176             :     }
     177             : 
     178             :     VSIStatBufL sStat;
     179           3 :     if (!m_overwrite && !m_outputDataset.GetName().empty() &&
     180           2 :         (VSIStatL(m_outputDataset.GetName().c_str(), &sStat) == 0 ||
     181           3 :          std::unique_ptr<GDALDataset>(
     182           2 :              GDALDataset::Open(m_outputDataset.GetName().c_str()))))
     183             :     {
     184           0 :         ReportError(CE_Failure, CPLE_AppDefined,
     185             :                     "File '%s' already exists. Specify the --overwrite "
     186             :                     "option to overwrite it.",
     187           0 :                     m_outputDataset.GetName().c_str());
     188           0 :         return false;
     189             :     }
     190             : 
     191             :     const bool bVRTOutput =
     192           3 :         m_outputDataset.GetName().empty() || EQUAL(m_format.c_str(), "VRT") ||
     193           5 :         EQUAL(m_format.c_str(), "stream") ||
     194           2 :         EQUAL(CPLGetExtensionSafe(m_outputDataset.GetName().c_str()).c_str(),
     195             :               "VRT");
     196             : 
     197           4 :     CPLStringList aosOptions;
     198             : 
     199           2 :     aosOptions.push_back("-strict");
     200             : 
     201           2 :     aosOptions.push_back("-program_name");
     202           2 :     aosOptions.push_back("gdal raster stack");
     203             : 
     204           2 :     aosOptions.push_back("-separate");
     205             : 
     206             :     const auto aosTokens =
     207           4 :         CPLStringList(CSLTokenizeString2(m_resolution.c_str(), ",", 0));
     208           2 :     if (aosTokens.size() == 2)
     209             :     {
     210           0 :         aosOptions.push_back("-tr");
     211           0 :         aosOptions.push_back(aosTokens[0]);
     212           0 :         aosOptions.push_back(aosTokens[1]);
     213             :     }
     214             :     else
     215             :     {
     216           2 :         aosOptions.push_back("-resolution");
     217           2 :         aosOptions.push_back(m_resolution);
     218             :     }
     219             : 
     220           2 :     if (!m_bbox.empty())
     221             :     {
     222           0 :         aosOptions.push_back("-te");
     223           0 :         aosOptions.push_back(CPLSPrintf("%.17g", m_bbox[0]));
     224           0 :         aosOptions.push_back(CPLSPrintf("%.17g", m_bbox[1]));
     225           0 :         aosOptions.push_back(CPLSPrintf("%.17g", m_bbox[2]));
     226           0 :         aosOptions.push_back(CPLSPrintf("%.17g", m_bbox[3]));
     227             :     }
     228           2 :     if (m_targetAlignedPixels)
     229             :     {
     230           0 :         aosOptions.push_back("-tap");
     231             :     }
     232           2 :     if (!m_srcNoData.empty())
     233             :     {
     234           0 :         aosOptions.push_back("-srcnodata");
     235           0 :         std::string s;
     236           0 :         for (double v : m_srcNoData)
     237             :         {
     238           0 :             if (!s.empty())
     239           0 :                 s += " ";
     240           0 :             s += CPLSPrintf("%.17g", v);
     241             :         }
     242           0 :         aosOptions.push_back(s);
     243             :     }
     244           2 :     if (!m_dstNoData.empty())
     245             :     {
     246           0 :         aosOptions.push_back("-vrtnodata");
     247           0 :         std::string s;
     248           0 :         for (double v : m_dstNoData)
     249             :         {
     250           0 :             if (!s.empty())
     251           0 :                 s += " ";
     252           0 :             s += CPLSPrintf("%.17g", v);
     253             :         }
     254           0 :         aosOptions.push_back(s);
     255             :     }
     256           2 :     if (bVRTOutput)
     257             :     {
     258           2 :         for (const auto &co : m_creationOptions)
     259             :         {
     260           0 :             aosOptions.push_back("-co");
     261           0 :             aosOptions.push_back(co);
     262             :         }
     263             :     }
     264           2 :     for (const int b : m_bands)
     265             :     {
     266           0 :         aosOptions.push_back("-b");
     267           0 :         aosOptions.push_back(CPLSPrintf("%d", b));
     268             :     }
     269           2 :     if (m_hideNoData)
     270             :     {
     271           0 :         aosOptions.push_back("-hidenodata");
     272             :     }
     273             : 
     274             :     GDALBuildVRTOptions *psOptions =
     275           2 :         GDALBuildVRTOptionsNew(aosOptions.List(), nullptr);
     276           2 :     if (bVRTOutput)
     277             :     {
     278           2 :         GDALBuildVRTOptionsSetProgress(psOptions, pfnProgress, pProgressData);
     279             :     }
     280             : 
     281             :     auto poOutDS = std::unique_ptr<GDALDataset>(GDALDataset::FromHandle(
     282           2 :         GDALBuildVRT(EQUAL(m_format.c_str(), "stream") ? ""
     283           1 :                      : bVRTOutput ? m_outputDataset.GetName().c_str()
     284             :                                   : "",
     285           1 :                      foundByName ? aosInputDatasetNames.size()
     286           1 :                                  : static_cast<int>(m_inputDatasets.size()),
     287           3 :                      ahInputDatasets.empty() ? nullptr : ahInputDatasets.data(),
     288           9 :                      aosInputDatasetNames.List(), psOptions, nullptr)));
     289           2 :     GDALBuildVRTOptionsFree(psOptions);
     290           2 :     bool bOK = poOutDS != nullptr;
     291           2 :     if (bOK)
     292             :     {
     293           2 :         if (bVRTOutput)
     294             :         {
     295           2 :             m_outputDataset.Set(std::move(poOutDS));
     296             :         }
     297             :         else
     298             :         {
     299           0 :             CPLStringList aosTranslateOptions;
     300           0 :             if (!m_format.empty())
     301             :             {
     302           0 :                 aosTranslateOptions.AddString("-of");
     303           0 :                 aosTranslateOptions.AddString(m_format.c_str());
     304             :             }
     305           0 :             for (const auto &co : m_creationOptions)
     306             :             {
     307           0 :                 aosTranslateOptions.AddString("-co");
     308           0 :                 aosTranslateOptions.AddString(co.c_str());
     309             :             }
     310             : 
     311             :             GDALTranslateOptions *psTranslateOptions =
     312           0 :                 GDALTranslateOptionsNew(aosTranslateOptions.List(), nullptr);
     313           0 :             GDALTranslateOptionsSetProgress(psTranslateOptions, pfnProgress,
     314             :                                             pProgressData);
     315             : 
     316             :             auto poFinalDS =
     317             :                 std::unique_ptr<GDALDataset>(GDALDataset::FromHandle(
     318           0 :                     GDALTranslate(m_outputDataset.GetName().c_str(),
     319             :                                   GDALDataset::ToHandle(poOutDS.get()),
     320           0 :                                   psTranslateOptions, nullptr)));
     321           0 :             GDALTranslateOptionsFree(psTranslateOptions);
     322             : 
     323           0 :             bOK = poFinalDS != nullptr;
     324           0 :             if (bOK)
     325             :             {
     326           0 :                 m_outputDataset.Set(std::move(poFinalDS));
     327             :             }
     328             :         }
     329             :     }
     330             : 
     331           2 :     return bOK;
     332             : }
     333             : 
     334             : //! @endcond

Generated by: LCOV version 1.14