LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_reproject.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 218 231 94.4 %
Date: 2026-04-23 19:47:11 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         139 : GDALRasterReprojectAlgorithm::GDALRasterReprojectAlgorithm(bool standaloneStep)
      34             :     : GDALRasterPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
      35         139 :                                       standaloneStep)
      36             : {
      37             : 
      38         278 :     AddArg(GDAL_ARG_NAME_INPUT_CRS, 's', _("Input CRS"), &m_srcCrs)
      39         278 :         .SetIsCRSArg()
      40         278 :         .AddHiddenAlias("s_srs")
      41         139 :         .AddHiddenAlias("src-crs");
      42             : 
      43             :     AddArg("like", 0,
      44             :            _("Dataset to use as a template for target bounds, CRS, size and "
      45             :              "nodata"),
      46         278 :            &m_likeDataset, GDAL_OF_RASTER)
      47         139 :         .SetMetaVar("DATASET");
      48             : 
      49         278 :     AddArg(GDAL_ARG_NAME_OUTPUT_CRS, 'd', _("Output CRS"), &m_dstCrs)
      50         278 :         .SetIsCRSArg()
      51         278 :         .AddHiddenAlias("t_srs")
      52         139 :         .AddHiddenAlias("dst-crs");
      53             : 
      54         139 :     GDALRasterReprojectUtils::AddResamplingArg(this, m_resampling);
      55             : 
      56             :     AddArg("resolution", 0, _("Target resolution (in destination CRS units)"),
      57         278 :            &m_resolution)
      58         139 :         .SetMinCount(2)
      59         139 :         .SetMaxCount(2)
      60         139 :         .SetMinValueExcluded(0)
      61         139 :         .SetRepeatedArgAllowed(false)
      62         139 :         .SetDisplayHintAboutRepetition(false)
      63         278 :         .SetMetaVar("<xres>,<yres>")
      64         139 :         .SetMutualExclusionGroup("resolution-size");
      65             : 
      66         278 :     AddArg("size", 0, _("Target size in pixels"), &m_size)
      67         139 :         .SetMinCount(2)
      68         139 :         .SetMaxCount(2)
      69         139 :         .SetMinValueIncluded(0)
      70         139 :         .SetRepeatedArgAllowed(false)
      71         139 :         .SetDisplayHintAboutRepetition(false)
      72         278 :         .SetMetaVar("<width>,<height>")
      73         139 :         .SetMutualExclusionGroup("resolution-size");
      74             : 
      75             :     auto &arg = AddBBOXArg(&m_bbox,
      76         139 :                            _("Target bounding box (in destination CRS units)"));
      77             : 
      78             :     arg.AddValidationAction(
      79          10 :         [this, &arg]()
      80             :         {
      81             :             // Validate it's not empty
      82           8 :             const std::vector<double> &bbox = arg.Get<std::vector<double>>();
      83           8 :             if ((bbox[0] >= bbox[2]) || (bbox[1] >= bbox[3]))
      84             :             {
      85           2 :                 ReportError(CE_Failure, CPLE_AppDefined,
      86             :                             "Invalid bounding box specified");
      87           2 :                 return false;
      88             :             }
      89             :             else
      90             :             {
      91           6 :                 return true;
      92             :             }
      93         139 :         });
      94             : 
      95         278 :     AddArg("bbox-crs", 0, _("CRS of target bounding box"), &m_bboxCrs)
      96         278 :         .SetIsCRSArg()
      97         139 :         .AddHiddenAlias("bbox_srs");
      98             : 
      99             :     AddArg("target-aligned-pixels", 0,
     100             :            _("Round target extent to target resolution"),
     101         278 :            &m_targetAlignedPixels)
     102         278 :         .AddHiddenAlias("tap")
     103         139 :         .SetCategory(GAAC_ADVANCED);
     104             :     AddArg("input-nodata", 0,
     105             :            _("Set nodata values for input bands ('None' to unset)."),
     106         278 :            &m_srcNoData)
     107         139 :         .SetMinCount(1)
     108         278 :         .AddHiddenAlias("src-nodata")
     109         139 :         .SetRepeatedArgAllowed(false)
     110         139 :         .SetCategory(GAAC_ADVANCED);
     111             :     AddArg("output-nodata", 0,
     112             :            _("Set nodata values for output bands ('None' to unset)."),
     113         278 :            &m_dstNoData)
     114         139 :         .SetMinCount(1)
     115         278 :         .AddHiddenAlias("dst-nodata")
     116         139 :         .SetRepeatedArgAllowed(false)
     117         139 :         .SetCategory(GAAC_ADVANCED);
     118             :     AddArg("add-alpha", 0,
     119             :            _("Adds an alpha mask band to the destination when the source "
     120             :              "raster have none."),
     121         278 :            &m_addAlpha)
     122         139 :         .SetCategory(GAAC_ADVANCED);
     123             : 
     124         139 :     GDALRasterReprojectUtils::AddWarpOptTransformOptErrorThresholdArg(
     125         139 :         this, m_warpOptions, m_transformOptions, m_errorThreshold);
     126             : 
     127         139 :     AddNumThreadsArg(&m_numThreads, &m_numThreadsStr);
     128         139 : }
     129             : 
     130             : /************************************************************************/
     131             : /*             GDALRasterReprojectUtils::AddResamplingArg()             */
     132             : /************************************************************************/
     133             : 
     134             : /*static */ void
     135         230 : GDALRasterReprojectUtils::AddResamplingArg(GDALAlgorithm *alg,
     136             :                                            std::string &resampling)
     137             : {
     138         460 :     alg->AddArg("resampling", 'r', _("Resampling method"), &resampling)
     139             :         .SetChoices("nearest", "bilinear", "cubic", "cubicspline", "lanczos",
     140             :                     "average", "rms", "mode", "min", "max", "med", "q1", "q3",
     141         230 :                     "sum")
     142         230 :         .SetDefault("nearest")
     143         230 :         .SetHiddenChoices("near");
     144         230 : }
     145             : 
     146             : /************************************************************************/
     147             : /*              AddWarpOptTransformOptErrorThresholdArg()               */
     148             : /************************************************************************/
     149             : 
     150             : /* static */
     151         230 : void GDALRasterReprojectUtils::AddWarpOptTransformOptErrorThresholdArg(
     152             :     GDALAlgorithm *alg, std::vector<std::string> &warpOptions,
     153             :     std::vector<std::string> &transformOptions, double &errorThreshold)
     154             : {
     155             :     {
     156             :         auto &arg =
     157         460 :             alg->AddArg("warp-option", 0, _("Warping option(s)"), &warpOptions)
     158         460 :                 .AddAlias("wo")
     159         460 :                 .SetMetaVar("<NAME>=<VALUE>")
     160         460 :                 .SetCategory(GAAC_ADVANCED)
     161         230 :                 .SetPackedValuesAllowed(false);
     162          10 :         arg.AddValidationAction([alg, &arg]()
     163         240 :                                 { return alg->ParseAndValidateKeyValue(arg); });
     164             :         arg.SetAutoCompleteFunction(
     165           0 :             [](const std::string &currentValue)
     166             :             {
     167           0 :                 std::vector<std::string> ret;
     168           0 :                 GDALAlgorithm::AddOptionsSuggestions(GDALWarpGetOptionList(), 0,
     169             :                                                      currentValue, ret);
     170           0 :                 return ret;
     171         230 :             });
     172             :     }
     173             :     {
     174             :         auto &arg = alg->AddArg("transform-option", 0, _("Transform option(s)"),
     175         460 :                                 &transformOptions)
     176         460 :                         .AddAlias("to")
     177         460 :                         .SetMetaVar("<NAME>=<VALUE>")
     178         460 :                         .SetCategory(GAAC_ADVANCED)
     179         230 :                         .SetPackedValuesAllowed(false);
     180           4 :         arg.AddValidationAction([alg, &arg]()
     181         234 :                                 { return alg->ParseAndValidateKeyValue(arg); });
     182             :         arg.SetAutoCompleteFunction(
     183           0 :             [](const std::string &currentValue)
     184             :             {
     185           0 :                 std::vector<std::string> ret;
     186           0 :                 GDALAlgorithm::AddOptionsSuggestions(
     187             :                     GDALGetGenImgProjTranformerOptionList(), 0, currentValue,
     188             :                     ret);
     189           0 :                 return ret;
     190         230 :             });
     191             :     }
     192         460 :     alg->AddArg("error-threshold", 0, _("Error threshold"), &errorThreshold)
     193         460 :         .AddAlias("et")
     194         230 :         .SetMinValueIncluded(0)
     195         230 :         .SetCategory(GAAC_ADVANCED);
     196         230 : }
     197             : 
     198             : /************************************************************************/
     199             : /*          GDALRasterReprojectAlgorithm::CanHandleNextStep()           */
     200             : /************************************************************************/
     201             : 
     202          40 : bool GDALRasterReprojectAlgorithm::CanHandleNextStep(
     203             :     GDALPipelineStepAlgorithm *poNextStep) const
     204             : {
     205          78 :     return poNextStep->GetName() == GDALRasterWriteAlgorithm::NAME &&
     206          78 :            poNextStep->GetOutputFormat() != "stream";
     207             : }
     208             : 
     209             : /************************************************************************/
     210             : /*               GDALRasterReprojectAlgorithm::RunStep()                */
     211             : /************************************************************************/
     212             : 
     213          23 : bool GDALRasterReprojectAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
     214             : {
     215          23 :     auto poSrcDS = m_inputDataset[0].GetDatasetRef();
     216          23 :     CPLAssert(poSrcDS);
     217          23 :     CPLAssert(m_outputDataset.GetName().empty());
     218          23 :     CPLAssert(!m_outputDataset.GetDatasetRef());
     219             : 
     220          46 :     CPLStringList aosOptions;
     221          46 :     std::string outputFilename;
     222             : 
     223             :     // --like provide defaults: override if not explicitly set
     224          23 :     if (auto poLikeDS = m_likeDataset.GetDatasetRef())
     225             :     {
     226           2 :         const auto poSpatialRef = poLikeDS->GetSpatialRef();
     227           2 :         if (poSpatialRef)
     228             :         {
     229           2 :             char *pszWKT = nullptr;
     230           2 :             poSpatialRef->exportToWkt(&pszWKT);
     231           2 :             m_dstCrs = pszWKT;
     232           2 :             CPLFree(pszWKT);
     233           2 :             GDALGeoTransform gt;
     234           2 :             if (poLikeDS->GetGeoTransform(gt) == CE_None)
     235             :             {
     236           2 :                 if (gt.IsAxisAligned())
     237             :                 {
     238           1 :                     if (m_resolution.empty())
     239             :                     {
     240           1 :                         m_resolution = {std::abs(gt[1]), std::abs(gt[5])};
     241             :                     }
     242           1 :                     const int nXSize = poLikeDS->GetRasterXSize();
     243           1 :                     const int nYSize = poLikeDS->GetRasterYSize();
     244           1 :                     if (m_size.empty())
     245             :                     {
     246           1 :                         m_size = {nXSize, nYSize};
     247             :                     }
     248           1 :                     if (m_bbox.empty())
     249             :                     {
     250           1 :                         double minX = gt.xorig;
     251           1 :                         double maxY = gt.yorig;
     252           1 :                         double maxX =
     253           1 :                             gt.xorig + nXSize * gt.xscale + nYSize * gt.xrot;
     254           1 :                         double minY =
     255           1 :                             gt.yorig + nXSize * gt.yrot + nYSize * gt.yscale;
     256           1 :                         if (minY > maxY)
     257           0 :                             std::swap(minY, maxY);
     258           1 :                         m_bbox = {minX, minY, maxX, maxY};
     259           1 :                         m_bboxCrs = m_dstCrs;
     260             :                     }
     261             :                 }
     262             :                 else
     263             :                 {
     264           1 :                     ReportError(
     265             :                         CE_Warning, CPLE_AppDefined,
     266             :                         "Dataset provided with --like has a geotransform "
     267             :                         "with rotation. Ignoring it");
     268             :                 }
     269             :             }
     270             :         }
     271             :     }
     272             : 
     273          23 :     if (ctxt.m_poNextUsableStep)
     274             :     {
     275          19 :         CPLAssert(CanHandleNextStep(ctxt.m_poNextUsableStep));
     276          19 :         outputFilename = ctxt.m_poNextUsableStep->GetOutputDataset().GetName();
     277          19 :         const auto &format = ctxt.m_poNextUsableStep->GetOutputFormat();
     278          19 :         if (!format.empty())
     279             :         {
     280           7 :             aosOptions.AddString("-of");
     281           7 :             aosOptions.AddString(format.c_str());
     282             :         }
     283             : 
     284          19 :         bool bFoundNumThreads = false;
     285           2 :         for (const std::string &co :
     286          23 :              ctxt.m_poNextUsableStep->GetCreationOptions())
     287             :         {
     288           2 :             aosOptions.AddString("-co");
     289           2 :             if (STARTS_WITH_CI(co.c_str(), "NUM_THREADS="))
     290           0 :                 bFoundNumThreads = true;
     291           2 :             aosOptions.AddString(co.c_str());
     292             :         }
     293             : 
     294             :         // Forward m_numThreads to GeoTIFF driver if --co NUM_THREADS not
     295             :         // specified
     296          38 :         if (!bFoundNumThreads && m_numThreads > 1 &&
     297          37 :             (EQUAL(format.c_str(), "GTIFF") || EQUAL(format.c_str(), "COG") ||
     298          18 :              (format.empty() &&
     299          31 :               EQUAL(CPLGetExtensionSafe(outputFilename.c_str()).c_str(),
     300             :                     "tif"))))
     301             :         {
     302          13 :             aosOptions.AddString("-co");
     303          13 :             aosOptions.AddString(CPLSPrintf("NUM_THREADS=%d", m_numThreads));
     304             :         }
     305             :     }
     306             :     else
     307             :     {
     308           4 :         aosOptions.AddString("-of");
     309           4 :         aosOptions.AddString("VRT");
     310             :     }
     311          23 :     if (!m_srcCrs.empty())
     312             :     {
     313           9 :         aosOptions.AddString("-s_srs");
     314           9 :         aosOptions.AddString(m_srcCrs.c_str());
     315             :     }
     316          23 :     if (!m_dstCrs.empty())
     317             :     {
     318          16 :         aosOptions.AddString("-t_srs");
     319          16 :         aosOptions.AddString(m_dstCrs.c_str());
     320             :     }
     321          23 :     if (!m_resampling.empty())
     322             :     {
     323          23 :         aosOptions.AddString("-r");
     324          23 :         aosOptions.AddString(m_resampling.c_str());
     325             :     }
     326          23 :     if (!m_resolution.empty())
     327             :     {
     328           2 :         aosOptions.AddString("-tr");
     329           2 :         aosOptions.AddString(CPLSPrintf("%.17g", m_resolution[0]));
     330           2 :         aosOptions.AddString(CPLSPrintf("%.17g", m_resolution[1]));
     331             :     }
     332          23 :     if (!m_size.empty())
     333             :     {
     334           2 :         aosOptions.AddString("-ts");
     335           2 :         aosOptions.AddString(CPLSPrintf("%d", m_size[0]));
     336           2 :         aosOptions.AddString(CPLSPrintf("%d", m_size[1]));
     337             :     }
     338          23 :     if (!m_bbox.empty())
     339             :     {
     340           3 :         aosOptions.AddString("-te");
     341           3 :         aosOptions.AddString(CPLSPrintf("%.17g", m_bbox[0]));
     342           3 :         aosOptions.AddString(CPLSPrintf("%.17g", m_bbox[1]));
     343           3 :         aosOptions.AddString(CPLSPrintf("%.17g", m_bbox[2]));
     344           3 :         aosOptions.AddString(CPLSPrintf("%.17g", m_bbox[3]));
     345             :     }
     346          23 :     if (!m_bboxCrs.empty())
     347             :     {
     348           2 :         aosOptions.AddString("-te_srs");
     349           2 :         aosOptions.AddString(m_bboxCrs.c_str());
     350             :     }
     351          23 :     if (m_targetAlignedPixels)
     352             :     {
     353           1 :         aosOptions.AddString("-tap");
     354             :     }
     355          23 :     if (!m_srcNoData.empty())
     356             :     {
     357           1 :         aosOptions.push_back("-srcnodata");
     358           2 :         std::string s;
     359           2 :         for (const std::string &v : m_srcNoData)
     360             :         {
     361           1 :             if (!s.empty())
     362           0 :                 s += " ";
     363           1 :             s += v;
     364             :         }
     365           1 :         aosOptions.push_back(s);
     366             :     }
     367          23 :     if (!m_dstNoData.empty())
     368             :     {
     369           2 :         aosOptions.push_back("-dstnodata");
     370           4 :         std::string s;
     371           5 :         for (const std::string &v : m_dstNoData)
     372             :         {
     373           3 :             if (!s.empty())
     374           1 :                 s += " ";
     375           3 :             s += v;
     376             :         }
     377           2 :         aosOptions.push_back(s);
     378             :     }
     379          23 :     if (m_addAlpha)
     380             :     {
     381           1 :         aosOptions.AddString("-dstalpha");
     382             :     }
     383             : 
     384          23 :     bool bFoundNumThreads = false;
     385          26 :     for (const std::string &opt : m_warpOptions)
     386             :     {
     387           3 :         aosOptions.AddString("-wo");
     388           3 :         if (STARTS_WITH_CI(opt.c_str(), "NUM_THREADS="))
     389           2 :             bFoundNumThreads = true;
     390           3 :         aosOptions.AddString(opt.c_str());
     391             :     }
     392          23 :     if (bFoundNumThreads)
     393             :     {
     394           2 :         if (GetArg(GDAL_ARG_NAME_NUM_THREADS)->IsExplicitlySet())
     395             :         {
     396           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     397             :                         "--num-threads argument and NUM_THREADS warp options "
     398             :                         "are mutually exclusive.");
     399           1 :             return false;
     400             :         }
     401             :     }
     402             :     else
     403             :     {
     404          21 :         aosOptions.AddString("-wo");
     405          21 :         aosOptions.AddString(CPLSPrintf("NUM_THREADS=%d", m_numThreads));
     406             :     }
     407             : 
     408          23 :     for (const std::string &opt : m_transformOptions)
     409             :     {
     410           1 :         aosOptions.AddString("-to");
     411           1 :         aosOptions.AddString(opt.c_str());
     412             :     }
     413          22 :     if (std::isfinite(m_errorThreshold))
     414             :     {
     415           0 :         aosOptions.AddString("-et");
     416           0 :         aosOptions.AddString(CPLSPrintf("%.17g", m_errorThreshold));
     417             :     }
     418             : 
     419          22 :     bool bOK = false;
     420             :     GDALWarpAppOptions *psOptions =
     421          22 :         GDALWarpAppOptionsNew(aosOptions.List(), nullptr);
     422          22 :     if (psOptions)
     423             :     {
     424          22 :         if (ctxt.m_poNextUsableStep)
     425             :         {
     426          18 :             GDALWarpAppOptionsSetProgress(psOptions, ctxt.m_pfnProgress,
     427             :                                           ctxt.m_pProgressData);
     428             :         }
     429          22 :         GDALDatasetH hSrcDS = GDALDataset::ToHandle(poSrcDS);
     430          22 :         auto poRetDS = GDALDataset::FromHandle(GDALWarp(
     431             :             outputFilename.c_str(), nullptr, 1, &hSrcDS, psOptions, nullptr));
     432          22 :         GDALWarpAppOptionsFree(psOptions);
     433          22 :         bOK = poRetDS != nullptr;
     434          22 :         if (bOK)
     435             :         {
     436          19 :             m_outputDataset.Set(std::unique_ptr<GDALDataset>(poRetDS));
     437             :         }
     438             :     }
     439          22 :     return bOK;
     440             : }
     441             : 
     442             : GDALRasterReprojectAlgorithmStandalone::
     443             :     ~GDALRasterReprojectAlgorithmStandalone() = default;
     444             : 
     445             : //! @endcond

Generated by: LCOV version 1.14