LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_reproject.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 185 197 93.9 %
Date: 2025-12-21 22:14:19 Functions: 8 10 80.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  "reproject" step of "raster pipeline"
       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_reproject.h"
      14             : #include "gdalalg_raster_write.h"
      15             : 
      16             : #include "gdal_alg.h"
      17             : #include "gdal_priv.h"
      18             : #include "gdal_utils.h"
      19             : #include "gdalwarper.h"
      20             : 
      21             : #include <cmath>
      22             : 
      23             : //! @cond Doxygen_Suppress
      24             : 
      25             : #ifndef _
      26             : #define _(x) (x)
      27             : #endif
      28             : 
      29             : /************************************************************************/
      30             : /*      GDALRasterReprojectAlgorithm::GDALRasterReprojectAlgorithm()    */
      31             : /************************************************************************/
      32             : 
      33          99 : GDALRasterReprojectAlgorithm::GDALRasterReprojectAlgorithm(bool standaloneStep)
      34             :     : GDALRasterPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
      35          99 :                                       standaloneStep)
      36             : {
      37         198 :     AddArg("src-crs", 's', _("Source CRS"), &m_srsCrs)
      38         198 :         .SetIsCRSArg()
      39          99 :         .AddHiddenAlias("s_srs");
      40         198 :     AddArg("dst-crs", 'd', _("Destination CRS"), &m_dstCrs)
      41         198 :         .SetIsCRSArg()
      42          99 :         .AddHiddenAlias("t_srs");
      43             : 
      44          99 :     GDALRasterReprojectUtils::AddResamplingArg(this, m_resampling);
      45             : 
      46             :     AddArg("resolution", 0, _("Target resolution (in destination CRS units)"),
      47         198 :            &m_resolution)
      48          99 :         .SetMinCount(2)
      49          99 :         .SetMaxCount(2)
      50          99 :         .SetMinValueExcluded(0)
      51          99 :         .SetRepeatedArgAllowed(false)
      52          99 :         .SetDisplayHintAboutRepetition(false)
      53         198 :         .SetMetaVar("<xres>,<yres>")
      54          99 :         .SetMutualExclusionGroup("resolution-size");
      55             : 
      56         198 :     AddArg("size", 0, _("Target size in pixels"), &m_size)
      57          99 :         .SetMinCount(2)
      58          99 :         .SetMaxCount(2)
      59          99 :         .SetMinValueIncluded(0)
      60          99 :         .SetRepeatedArgAllowed(false)
      61          99 :         .SetDisplayHintAboutRepetition(false)
      62         198 :         .SetMetaVar("<width>,<height>")
      63          99 :         .SetMutualExclusionGroup("resolution-size");
      64             : 
      65             :     auto &arg = AddBBOXArg(&m_bbox,
      66          99 :                            _("Target bounding box (in destination CRS units)"));
      67             : 
      68             :     arg.AddValidationAction(
      69          10 :         [this, &arg]()
      70             :         {
      71             :             // Validate it's not empty
      72           8 :             const std::vector<double> &bbox = arg.Get<std::vector<double>>();
      73           8 :             if ((bbox[0] >= bbox[2]) || (bbox[1] >= bbox[3]))
      74             :             {
      75           2 :                 ReportError(CE_Failure, CPLE_AppDefined,
      76             :                             "Invalid bounding box specified");
      77           2 :                 return false;
      78             :             }
      79             :             else
      80             :             {
      81           6 :                 return true;
      82             :             }
      83          99 :         });
      84             : 
      85         198 :     AddArg("bbox-crs", 0, _("CRS of target bounding box"), &m_bboxCrs)
      86         198 :         .SetIsCRSArg()
      87          99 :         .AddHiddenAlias("bbox_srs");
      88             : 
      89             :     AddArg("target-aligned-pixels", 0,
      90             :            _("Round target extent to target resolution"),
      91         198 :            &m_targetAlignedPixels)
      92         198 :         .AddHiddenAlias("tap")
      93          99 :         .SetCategory(GAAC_ADVANCED);
      94             :     AddArg("src-nodata", 0,
      95             :            _("Set nodata values for input bands ('None' to unset)."),
      96         198 :            &m_srcNoData)
      97          99 :         .SetMinCount(1)
      98          99 :         .SetRepeatedArgAllowed(false)
      99          99 :         .SetCategory(GAAC_ADVANCED);
     100             :     AddArg("dst-nodata", 0,
     101             :            _("Set nodata values for output bands ('None' to unset)."),
     102         198 :            &m_dstNoData)
     103          99 :         .SetMinCount(1)
     104          99 :         .SetRepeatedArgAllowed(false)
     105          99 :         .SetCategory(GAAC_ADVANCED);
     106             :     AddArg("add-alpha", 0,
     107             :            _("Adds an alpha mask band to the destination when the source "
     108             :              "raster have none."),
     109         198 :            &m_addAlpha)
     110          99 :         .SetCategory(GAAC_ADVANCED);
     111             : 
     112          99 :     GDALRasterReprojectUtils::AddWarpOptTransformOptErrorThresholdArg(
     113          99 :         this, m_warpOptions, m_transformOptions, m_errorThreshold);
     114             : 
     115          99 :     AddNumThreadsArg(&m_numThreads, &m_numThreadsStr);
     116          99 : }
     117             : 
     118             : /************************************************************************/
     119             : /*           GDALRasterReprojectUtils::AddResamplingArg()               */
     120             : /************************************************************************/
     121             : 
     122             : /*static */ void
     123         153 : GDALRasterReprojectUtils::AddResamplingArg(GDALAlgorithm *alg,
     124             :                                            std::string &resampling)
     125             : {
     126         306 :     alg->AddArg("resampling", 'r', _("Resampling method"), &resampling)
     127             :         .SetChoices("nearest", "bilinear", "cubic", "cubicspline", "lanczos",
     128             :                     "average", "rms", "mode", "min", "max", "med", "q1", "q3",
     129         153 :                     "sum")
     130         153 :         .SetDefault("nearest")
     131         153 :         .SetHiddenChoices("near");
     132         153 : }
     133             : 
     134             : /************************************************************************/
     135             : /*            AddWarpOptTransformOptErrorThresholdArg()                 */
     136             : /************************************************************************/
     137             : 
     138             : /* static */
     139         153 : void GDALRasterReprojectUtils::AddWarpOptTransformOptErrorThresholdArg(
     140             :     GDALAlgorithm *alg, std::vector<std::string> &warpOptions,
     141             :     std::vector<std::string> &transformOptions, double &errorThreshold)
     142             : {
     143             :     {
     144             :         auto &arg =
     145         306 :             alg->AddArg("warp-option", 0, _("Warping option(s)"), &warpOptions)
     146         306 :                 .AddAlias("wo")
     147         306 :                 .SetMetaVar("<NAME>=<VALUE>")
     148         306 :                 .SetCategory(GAAC_ADVANCED)
     149         153 :                 .SetPackedValuesAllowed(false);
     150          10 :         arg.AddValidationAction([alg, &arg]()
     151         163 :                                 { return alg->ParseAndValidateKeyValue(arg); });
     152             :         arg.SetAutoCompleteFunction(
     153           0 :             [](const std::string &currentValue)
     154             :             {
     155           0 :                 std::vector<std::string> ret;
     156           0 :                 GDALAlgorithm::AddOptionsSuggestions(GDALWarpGetOptionList(), 0,
     157             :                                                      currentValue, ret);
     158           0 :                 return ret;
     159         153 :             });
     160             :     }
     161             :     {
     162             :         auto &arg = alg->AddArg("transform-option", 0, _("Transform option(s)"),
     163         306 :                                 &transformOptions)
     164         306 :                         .AddAlias("to")
     165         306 :                         .SetMetaVar("<NAME>=<VALUE>")
     166         306 :                         .SetCategory(GAAC_ADVANCED)
     167         153 :                         .SetPackedValuesAllowed(false);
     168           4 :         arg.AddValidationAction([alg, &arg]()
     169         157 :                                 { return alg->ParseAndValidateKeyValue(arg); });
     170             :         arg.SetAutoCompleteFunction(
     171           0 :             [](const std::string &currentValue)
     172             :             {
     173           0 :                 std::vector<std::string> ret;
     174           0 :                 GDALAlgorithm::AddOptionsSuggestions(
     175             :                     GDALGetGenImgProjTranformerOptionList(), 0, currentValue,
     176             :                     ret);
     177           0 :                 return ret;
     178         153 :             });
     179             :     }
     180         306 :     alg->AddArg("error-threshold", 0, _("Error threshold"), &errorThreshold)
     181         306 :         .AddAlias("et")
     182         153 :         .SetMinValueIncluded(0)
     183         153 :         .SetCategory(GAAC_ADVANCED);
     184         153 : }
     185             : 
     186             : /************************************************************************/
     187             : /*          GDALRasterReprojectAlgorithm::CanHandleNextStep()           */
     188             : /************************************************************************/
     189             : 
     190          35 : bool GDALRasterReprojectAlgorithm::CanHandleNextStep(
     191             :     GDALPipelineStepAlgorithm *poNextStep) const
     192             : {
     193          69 :     return poNextStep->GetName() == GDALRasterWriteAlgorithm::NAME &&
     194          69 :            poNextStep->GetOutputFormat() != "stream";
     195             : }
     196             : 
     197             : /************************************************************************/
     198             : /*            GDALRasterReprojectAlgorithm::RunStep()                   */
     199             : /************************************************************************/
     200             : 
     201          20 : bool GDALRasterReprojectAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
     202             : {
     203          20 :     auto poSrcDS = m_inputDataset[0].GetDatasetRef();
     204          20 :     CPLAssert(poSrcDS);
     205          20 :     CPLAssert(m_outputDataset.GetName().empty());
     206          20 :     CPLAssert(!m_outputDataset.GetDatasetRef());
     207             : 
     208          40 :     CPLStringList aosOptions;
     209          40 :     std::string outputFilename;
     210          20 :     if (ctxt.m_poNextUsableStep)
     211             :     {
     212          17 :         CPLAssert(CanHandleNextStep(ctxt.m_poNextUsableStep));
     213          17 :         outputFilename = ctxt.m_poNextUsableStep->GetOutputDataset().GetName();
     214          17 :         const auto &format = ctxt.m_poNextUsableStep->GetOutputFormat();
     215          17 :         if (!format.empty())
     216             :         {
     217           6 :             aosOptions.AddString("-of");
     218           6 :             aosOptions.AddString(format.c_str());
     219             :         }
     220             : 
     221          17 :         bool bFoundNumThreads = false;
     222           2 :         for (const std::string &co :
     223          21 :              ctxt.m_poNextUsableStep->GetCreationOptions())
     224             :         {
     225           2 :             aosOptions.AddString("-co");
     226           2 :             if (STARTS_WITH_CI(co.c_str(), "NUM_THREADS="))
     227           0 :                 bFoundNumThreads = true;
     228           2 :             aosOptions.AddString(co.c_str());
     229             :         }
     230             : 
     231             :         // Forward m_numThreads to GeoTIFF driver if --co NUM_THREADS not
     232             :         // specified
     233          34 :         if (!bFoundNumThreads && m_numThreads > 1 &&
     234          34 :             (EQUAL(format.c_str(), "GTIFF") || EQUAL(format.c_str(), "COG") ||
     235          17 :              (format.empty() &&
     236          28 :               EQUAL(CPLGetExtensionSafe(outputFilename.c_str()).c_str(),
     237             :                     "tif"))))
     238             :         {
     239          11 :             aosOptions.AddString("-co");
     240          11 :             aosOptions.AddString(CPLSPrintf("NUM_THREADS=%d", m_numThreads));
     241             :         }
     242             :     }
     243             :     else
     244             :     {
     245           3 :         aosOptions.AddString("-of");
     246           3 :         aosOptions.AddString("VRT");
     247             :     }
     248          20 :     if (!m_srsCrs.empty())
     249             :     {
     250           9 :         aosOptions.AddString("-s_srs");
     251           9 :         aosOptions.AddString(m_srsCrs.c_str());
     252             :     }
     253          20 :     if (!m_dstCrs.empty())
     254             :     {
     255          13 :         aosOptions.AddString("-t_srs");
     256          13 :         aosOptions.AddString(m_dstCrs.c_str());
     257             :     }
     258          20 :     if (!m_resampling.empty())
     259             :     {
     260          20 :         aosOptions.AddString("-r");
     261          20 :         aosOptions.AddString(m_resampling.c_str());
     262             :     }
     263          20 :     if (!m_resolution.empty())
     264             :     {
     265           1 :         aosOptions.AddString("-tr");
     266           1 :         aosOptions.AddString(CPLSPrintf("%.17g", m_resolution[0]));
     267           1 :         aosOptions.AddString(CPLSPrintf("%.17g", m_resolution[1]));
     268             :     }
     269          20 :     if (!m_size.empty())
     270             :     {
     271           1 :         aosOptions.AddString("-ts");
     272           1 :         aosOptions.AddString(CPLSPrintf("%d", m_size[0]));
     273           1 :         aosOptions.AddString(CPLSPrintf("%d", m_size[1]));
     274             :     }
     275          20 :     if (!m_bbox.empty())
     276             :     {
     277           2 :         aosOptions.AddString("-te");
     278           2 :         aosOptions.AddString(CPLSPrintf("%.17g", m_bbox[0]));
     279           2 :         aosOptions.AddString(CPLSPrintf("%.17g", m_bbox[1]));
     280           2 :         aosOptions.AddString(CPLSPrintf("%.17g", m_bbox[2]));
     281           2 :         aosOptions.AddString(CPLSPrintf("%.17g", m_bbox[3]));
     282             :     }
     283          20 :     if (!m_bboxCrs.empty())
     284             :     {
     285           1 :         aosOptions.AddString("-te_srs");
     286           1 :         aosOptions.AddString(m_bboxCrs.c_str());
     287             :     }
     288          20 :     if (m_targetAlignedPixels)
     289             :     {
     290           1 :         aosOptions.AddString("-tap");
     291             :     }
     292          20 :     if (!m_srcNoData.empty())
     293             :     {
     294           1 :         aosOptions.push_back("-srcnodata");
     295           2 :         std::string s;
     296           2 :         for (const std::string &v : m_srcNoData)
     297             :         {
     298           1 :             if (!s.empty())
     299           0 :                 s += " ";
     300           1 :             s += v;
     301             :         }
     302           1 :         aosOptions.push_back(s);
     303             :     }
     304          20 :     if (!m_dstNoData.empty())
     305             :     {
     306           2 :         aosOptions.push_back("-dstnodata");
     307           4 :         std::string s;
     308           5 :         for (const std::string &v : m_dstNoData)
     309             :         {
     310           3 :             if (!s.empty())
     311           1 :                 s += " ";
     312           3 :             s += v;
     313             :         }
     314           2 :         aosOptions.push_back(s);
     315             :     }
     316          20 :     if (m_addAlpha)
     317             :     {
     318           1 :         aosOptions.AddString("-dstalpha");
     319             :     }
     320             : 
     321          20 :     bool bFoundNumThreads = false;
     322          23 :     for (const std::string &opt : m_warpOptions)
     323             :     {
     324           3 :         aosOptions.AddString("-wo");
     325           3 :         if (STARTS_WITH_CI(opt.c_str(), "NUM_THREADS="))
     326           2 :             bFoundNumThreads = true;
     327           3 :         aosOptions.AddString(opt.c_str());
     328             :     }
     329          20 :     if (bFoundNumThreads)
     330             :     {
     331           2 :         if (GetArg(GDAL_ARG_NAME_NUM_THREADS)->IsExplicitlySet())
     332             :         {
     333           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     334             :                         "--num-threads argument and NUM_THREADS warp options "
     335             :                         "are mutually exclusive.");
     336           1 :             return false;
     337             :         }
     338             :     }
     339             :     else
     340             :     {
     341          18 :         aosOptions.AddString("-wo");
     342          18 :         aosOptions.AddString(CPLSPrintf("NUM_THREADS=%d", m_numThreads));
     343             :     }
     344             : 
     345          20 :     for (const std::string &opt : m_transformOptions)
     346             :     {
     347           1 :         aosOptions.AddString("-to");
     348           1 :         aosOptions.AddString(opt.c_str());
     349             :     }
     350          19 :     if (std::isfinite(m_errorThreshold))
     351             :     {
     352           0 :         aosOptions.AddString("-et");
     353           0 :         aosOptions.AddString(CPLSPrintf("%.17g", m_errorThreshold));
     354             :     }
     355             : 
     356          19 :     bool bOK = false;
     357             :     GDALWarpAppOptions *psOptions =
     358          19 :         GDALWarpAppOptionsNew(aosOptions.List(), nullptr);
     359          19 :     if (psOptions)
     360             :     {
     361          19 :         if (ctxt.m_poNextUsableStep)
     362             :         {
     363          16 :             GDALWarpAppOptionsSetProgress(psOptions, ctxt.m_pfnProgress,
     364             :                                           ctxt.m_pProgressData);
     365             :         }
     366          19 :         GDALDatasetH hSrcDS = GDALDataset::ToHandle(poSrcDS);
     367          19 :         auto poRetDS = GDALDataset::FromHandle(GDALWarp(
     368             :             outputFilename.c_str(), nullptr, 1, &hSrcDS, psOptions, nullptr));
     369          19 :         GDALWarpAppOptionsFree(psOptions);
     370          19 :         bOK = poRetDS != nullptr;
     371          19 :         if (bOK)
     372             :         {
     373          16 :             m_outputDataset.Set(std::unique_ptr<GDALDataset>(poRetDS));
     374             :         }
     375             :     }
     376          19 :     return bOK;
     377             : }
     378             : 
     379             : GDALRasterReprojectAlgorithmStandalone::
     380             :     ~GDALRasterReprojectAlgorithmStandalone() = default;
     381             : 
     382             : //! @endcond

Generated by: LCOV version 1.14