LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_pipeline.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 190 192 99.0 %
Date: 2025-01-18 12:42:00 Functions: 7 7 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  gdal "raster pipeline" 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_pipeline.h"
      14             : #include "gdalalg_raster_read.h"
      15             : #include "gdalalg_raster_clip.h"
      16             : #include "gdalalg_raster_edit.h"
      17             : #include "gdalalg_raster_reproject.h"
      18             : #include "gdalalg_raster_write.h"
      19             : 
      20             : #include "cpl_conv.h"
      21             : #include "cpl_string.h"
      22             : 
      23             : #include <algorithm>
      24             : 
      25             : //! @cond Doxygen_Suppress
      26             : 
      27             : #ifndef _
      28             : #define _(x) (x)
      29             : #endif
      30             : 
      31             : /************************************************************************/
      32             : /*  GDALRasterPipelineStepAlgorithm::GDALRasterPipelineStepAlgorithm()  */
      33             : /************************************************************************/
      34             : 
      35         222 : GDALRasterPipelineStepAlgorithm::GDALRasterPipelineStepAlgorithm(
      36             :     const std::string &name, const std::string &description,
      37         222 :     const std::string &helpURL, bool standaloneStep)
      38             :     : GDALAlgorithm(name, description, helpURL),
      39         222 :       m_standaloneStep(standaloneStep)
      40             : {
      41         222 :     if (m_standaloneStep)
      42             :     {
      43          13 :         AddInputArgs(false, false);
      44          13 :         AddProgressArg();
      45          13 :         AddOutputArgs(false);
      46             :     }
      47         222 : }
      48             : 
      49             : /************************************************************************/
      50             : /*             GDALRasterPipelineStepAlgorithm::AddInputArgs()          */
      51             : /************************************************************************/
      52             : 
      53         122 : void GDALRasterPipelineStepAlgorithm::AddInputArgs(
      54             :     bool openForMixedRasterVector, bool hiddenForCLI)
      55             : {
      56         122 :     AddInputFormatsArg(&m_inputFormats)
      57             :         .AddMetadataItem(
      58             :             GAAMDI_REQUIRED_CAPABILITIES,
      59             :             openForMixedRasterVector
      60         372 :                 ? std::vector<std::string>{GDAL_DCAP_RASTER, GDAL_DCAP_VECTOR}
      61         360 :                 : std::vector<std::string>{GDAL_DCAP_RASTER})
      62         122 :         .SetHiddenForCLI(hiddenForCLI);
      63         122 :     AddOpenOptionsArg(&m_openOptions).SetHiddenForCLI(hiddenForCLI);
      64             :     AddInputDatasetArg(&m_inputDataset,
      65             :                        openForMixedRasterVector
      66             :                            ? (GDAL_OF_RASTER | GDAL_OF_VECTOR)
      67             :                            : GDAL_OF_RASTER,
      68         122 :                        /* positionalAndRequired = */ !hiddenForCLI)
      69         122 :         .SetHiddenForCLI(hiddenForCLI);
      70         122 : }
      71             : 
      72             : /************************************************************************/
      73             : /*             GDALRasterPipelineStepAlgorithm::AddOutputArgs()         */
      74             : /************************************************************************/
      75             : 
      76         120 : void GDALRasterPipelineStepAlgorithm::AddOutputArgs(bool hiddenForCLI)
      77             : {
      78         120 :     AddOutputFormatArg(&m_format)
      79             :         .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES,
      80         480 :                          {GDAL_DCAP_RASTER, GDAL_DCAP_CREATECOPY})
      81         120 :         .SetHiddenForCLI(hiddenForCLI);
      82             :     AddOutputDatasetArg(&m_outputDataset, GDAL_OF_RASTER,
      83         120 :                         /* positionalAndRequired = */ !hiddenForCLI)
      84         120 :         .SetHiddenForCLI(hiddenForCLI);
      85         120 :     m_outputDataset.SetInputFlags(GADV_NAME | GADV_OBJECT);
      86         120 :     AddCreationOptionsArg(&m_creationOptions).SetHiddenForCLI(hiddenForCLI);
      87         120 :     AddOverwriteArg(&m_overwrite).SetHiddenForCLI(hiddenForCLI);
      88         120 : }
      89             : 
      90             : /************************************************************************/
      91             : /*            GDALRasterPipelineStepAlgorithm::RunImpl()                */
      92             : /************************************************************************/
      93             : 
      94          92 : bool GDALRasterPipelineStepAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
      95             :                                               void *pProgressData)
      96             : {
      97          92 :     if (m_standaloneStep)
      98             :     {
      99           4 :         GDALRasterReadAlgorithm readAlg;
     100          18 :         for (auto &arg : readAlg.GetArgs())
     101             :         {
     102          16 :             auto stepArg = GetArg(arg->GetName());
     103          16 :             if (stepArg && stepArg->IsExplicitlySet())
     104             :             {
     105           2 :                 arg->SetSkipIfAlreadySet(true);
     106           2 :                 arg->SetFrom(*stepArg);
     107             :             }
     108             :         }
     109             : 
     110           2 :         GDALRasterWriteAlgorithm writeAlg;
     111          20 :         for (auto &arg : writeAlg.GetArgs())
     112             :         {
     113          18 :             auto stepArg = GetArg(arg->GetName());
     114          18 :             if (stepArg && stepArg->IsExplicitlySet())
     115             :             {
     116           2 :                 arg->SetSkipIfAlreadySet(true);
     117           2 :                 arg->SetFrom(*stepArg);
     118             :             }
     119             :         }
     120             : 
     121           2 :         bool ret = false;
     122           2 :         if (readAlg.Run())
     123             :         {
     124           2 :             m_inputDataset.Set(readAlg.m_outputDataset.GetDatasetRef());
     125           2 :             m_outputDataset.Set(nullptr);
     126           2 :             if (RunStep(nullptr, nullptr))
     127             :             {
     128           1 :                 writeAlg.m_inputDataset.Set(m_outputDataset.GetDatasetRef());
     129           1 :                 if (writeAlg.Run(pfnProgress, pProgressData))
     130             :                 {
     131           1 :                     m_outputDataset.Set(
     132             :                         writeAlg.m_outputDataset.GetDatasetRef());
     133           1 :                     ret = true;
     134             :                 }
     135             :             }
     136             :         }
     137             : 
     138           2 :         return ret;
     139             :     }
     140             :     else
     141             :     {
     142          90 :         return RunStep(pfnProgress, pProgressData);
     143             :     }
     144             : }
     145             : 
     146             : /************************************************************************/
     147             : /*        GDALRasterPipelineAlgorithm::GDALRasterPipelineAlgorithm()    */
     148             : /************************************************************************/
     149             : 
     150          60 : GDALRasterPipelineAlgorithm::GDALRasterPipelineAlgorithm(
     151          60 :     bool openForMixedRasterVector)
     152             :     : GDALAbstractPipelineAlgorithm<GDALRasterPipelineStepAlgorithm>(
     153             :           NAME, DESCRIPTION, HELP_URL,
     154          60 :           /*standaloneStep=*/false)
     155             : {
     156          60 :     AddInputArgs(openForMixedRasterVector, /* hiddenForCLI = */ true);
     157          60 :     AddProgressArg();
     158         120 :     AddArg("pipeline", 0, _("Pipeline string"), &m_pipeline)
     159          60 :         .SetHiddenForCLI()
     160          60 :         .SetPositional();
     161          60 :     AddOutputArgs(/* hiddenForCLI = */ true);
     162             : 
     163          60 :     m_stepRegistry.Register<GDALRasterReadAlgorithm>();
     164          60 :     m_stepRegistry.Register<GDALRasterWriteAlgorithm>();
     165          60 :     m_stepRegistry.Register<GDALRasterClipAlgorithm>();
     166          60 :     m_stepRegistry.Register<GDALRasterEditAlgorithm>();
     167          60 :     m_stepRegistry.Register<GDALRasterReprojectAlgorithm>();
     168          60 : }
     169             : 
     170             : /************************************************************************/
     171             : /*       GDALRasterPipelineAlgorithm::ParseCommandLineArguments()       */
     172             : /************************************************************************/
     173             : 
     174          44 : bool GDALRasterPipelineAlgorithm::ParseCommandLineArguments(
     175             :     const std::vector<std::string> &args)
     176             : {
     177          50 :     if (args.size() == 1 && (args[0] == "-h" || args[0] == "--help" ||
     178           6 :                              args[0] == "help" || args[0] == "--json-usage"))
     179           1 :         return GDALAlgorithm::ParseCommandLineArguments(args);
     180             : 
     181         330 :     for (const auto &arg : args)
     182             :     {
     183         289 :         if (arg.find("--pipeline") == 0)
     184           2 :             return GDALAlgorithm::ParseCommandLineArguments(args);
     185             : 
     186             :         // gdal raster pipeline [--progress] "read in.tif ..."
     187         288 :         if (arg.find("read ") == 0)
     188           1 :             return GDALAlgorithm::ParseCommandLineArguments(args);
     189             :     }
     190             : 
     191          41 :     if (!m_steps.empty())
     192             :     {
     193           1 :         ReportError(CE_Failure, CPLE_AppDefined,
     194             :                     "ParseCommandLineArguments() can only be called once per "
     195             :                     "instance.");
     196           1 :         return false;
     197             :     }
     198             : 
     199             :     struct Step
     200             :     {
     201             :         std::unique_ptr<GDALRasterPipelineStepAlgorithm> alg{};
     202             :         std::vector<std::string> args{};
     203             :     };
     204             : 
     205          80 :     std::vector<Step> steps;
     206          40 :     steps.resize(1);
     207             : 
     208         318 :     for (const auto &arg : args)
     209             :     {
     210         279 :         if (arg == "--progress")
     211             :         {
     212           1 :             m_progressBarRequested = true;
     213           1 :             continue;
     214             :         }
     215             : 
     216         278 :         auto &curStep = steps.back();
     217             : 
     218         278 :         if (arg == "!" || arg == "|")
     219             :         {
     220          60 :             if (curStep.alg)
     221             :             {
     222          58 :                 steps.resize(steps.size() + 1);
     223             :             }
     224             :         }
     225             : #ifdef GDAL_PIPELINE_PROJ_NOSTALGIA
     226         218 :         else if (arg == "+step")
     227             :         {
     228           2 :             if (curStep.alg)
     229             :             {
     230           1 :                 steps.resize(steps.size() + 1);
     231             :             }
     232             :         }
     233         216 :         else if (arg.find("+gdal=") == 0)
     234             :         {
     235           1 :             const std::string stepName = arg.substr(strlen("+gdal="));
     236           1 :             curStep.alg = GetStepAlg(stepName);
     237           1 :             if (!curStep.alg)
     238             :             {
     239           0 :                 ReportError(CE_Failure, CPLE_AppDefined,
     240             :                             "unknown step name: %s", stepName.c_str());
     241           0 :                 return false;
     242             :             }
     243             :         }
     244             : #endif
     245         215 :         else if (!curStep.alg)
     246             :         {
     247          96 :             std::string algName = arg;
     248             : #ifdef GDAL_PIPELINE_PROJ_NOSTALGIA
     249          96 :             if (!algName.empty() && algName[0] == '+')
     250           1 :                 algName = algName.substr(1);
     251             : #endif
     252          96 :             curStep.alg = GetStepAlg(algName);
     253          96 :             if (!curStep.alg)
     254             :             {
     255           1 :                 ReportError(CE_Failure, CPLE_AppDefined,
     256             :                             "unknown step name: %s", algName.c_str());
     257           1 :                 return false;
     258             :             }
     259             :         }
     260             :         else
     261             :         {
     262             : #ifdef GDAL_PIPELINE_PROJ_NOSTALGIA
     263         119 :             if (!arg.empty() && arg[0] == '+')
     264             :             {
     265           2 :                 curStep.args.push_back("--" + arg.substr(1));
     266           2 :                 continue;
     267             :             }
     268             : #endif
     269         117 :             curStep.args.push_back(arg);
     270             :         }
     271             :     }
     272             : 
     273             :     // As we initially added a step without alg to bootstrap things, make
     274             :     // sure to remove it if it hasn't been filled, or the user has terminated
     275             :     // the pipeline with a '!' separator.
     276          39 :     if (!steps.back().alg)
     277           2 :         steps.pop_back();
     278             : 
     279          39 :     if (steps.size() < 2)
     280             :     {
     281           2 :         ReportError(CE_Failure, CPLE_AppDefined,
     282             :                     "At least 2 steps must be provided");
     283           2 :         return false;
     284             :     }
     285             : 
     286          37 :     if (steps.front().alg->GetName() != GDALRasterReadAlgorithm::NAME)
     287             :     {
     288           1 :         ReportError(CE_Failure, CPLE_AppDefined, "First step should be '%s'",
     289             :                     GDALRasterReadAlgorithm::NAME);
     290           1 :         return false;
     291             :     }
     292          55 :     for (size_t i = 1; i < steps.size() - 1; ++i)
     293             :     {
     294          20 :         if (steps[i].alg->GetName() == GDALRasterReadAlgorithm::NAME)
     295             :         {
     296           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     297             :                         "Only first step can be '%s'",
     298             :                         GDALRasterReadAlgorithm::NAME);
     299           1 :             return false;
     300             :         }
     301             :     }
     302          35 :     if (steps.back().alg->GetName() != GDALRasterWriteAlgorithm::NAME)
     303             :     {
     304           1 :         ReportError(CE_Failure, CPLE_AppDefined, "Last step should be '%s'",
     305             :                     GDALRasterWriteAlgorithm::NAME);
     306           1 :         return false;
     307             :     }
     308          86 :     for (size_t i = 0; i < steps.size() - 1; ++i)
     309             :     {
     310          53 :         if (steps[i].alg->GetName() == GDALRasterWriteAlgorithm::NAME)
     311             :         {
     312           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     313             :                         "Only last step can be '%s'",
     314             :                         GDALRasterWriteAlgorithm::NAME);
     315           1 :             return false;
     316             :         }
     317             :     }
     318             : 
     319          33 :     if (!m_pipeline.empty())
     320             :     {
     321             :         // Propagate input parameters set at the pipeline level to the
     322             :         // "read" step
     323             :         {
     324           6 :             auto &step = steps.front();
     325          54 :             for (auto &arg : step.alg->GetArgs())
     326             :             {
     327          48 :                 auto pipelineArg = GetArg(arg->GetName());
     328          48 :                 if (pipelineArg && pipelineArg->IsExplicitlySet())
     329             :                 {
     330           2 :                     arg->SetSkipIfAlreadySet(true);
     331           2 :                     arg->SetFrom(*pipelineArg);
     332             :                 }
     333             :             }
     334             :         }
     335             : 
     336             :         // Same with "write" step
     337             :         {
     338           6 :             auto &step = steps.back();
     339          60 :             for (auto &arg : step.alg->GetArgs())
     340             :             {
     341          54 :                 auto pipelineArg = GetArg(arg->GetName());
     342          54 :                 if (pipelineArg && pipelineArg->IsExplicitlySet())
     343             :                 {
     344           1 :                     arg->SetSkipIfAlreadySet(true);
     345           1 :                     arg->SetFrom(*pipelineArg);
     346             :                 }
     347             :             }
     348             :         }
     349             :     }
     350             : 
     351             :     // Parse each step, but without running the validation
     352         106 :     for (const auto &step : steps)
     353             :     {
     354          79 :         step.alg->m_skipValidationInParseCommandLine = true;
     355          79 :         if (!step.alg->ParseCommandLineArguments(step.args))
     356           6 :             return false;
     357             :     }
     358             : 
     359             :     // Evaluate "input" argument of "read" step, together with the "output"
     360             :     // argument of the "write" step, in case they point to the same dataset.
     361          27 :     auto inputArg = steps.front().alg->GetArg(GDAL_ARG_NAME_INPUT);
     362          54 :     if (inputArg && inputArg->IsExplicitlySet() &&
     363          27 :         inputArg->GetType() == GAAT_DATASET)
     364             :     {
     365          27 :         steps.front().alg->ProcessDatasetArg(inputArg, steps.back().alg.get());
     366             :     }
     367             : 
     368          93 :     for (const auto &step : steps)
     369             :     {
     370          67 :         if (!step.alg->ValidateArguments())
     371           1 :             return false;
     372             :     }
     373             : 
     374          92 :     for (auto &step : steps)
     375          66 :         m_steps.push_back(std::move(step.alg));
     376             : 
     377          26 :     return true;
     378             : }
     379             : 
     380             : /************************************************************************/
     381             : /*            GDALRasterPipelineAlgorithm::GetUsageForCLI()             */
     382             : /************************************************************************/
     383             : 
     384           2 : std::string GDALRasterPipelineAlgorithm::GetUsageForCLI(
     385             :     bool shortUsage, const UsageOptions &usageOptions) const
     386             : {
     387           2 :     std::string ret = GDALAlgorithm::GetUsageForCLI(shortUsage, usageOptions);
     388           2 :     if (shortUsage)
     389           1 :         return ret;
     390             : 
     391             :     ret += "\n<PIPELINE> is of the form: read [READ-OPTIONS] "
     392           1 :            "( ! <STEP-NAME> [STEP-OPTIONS] )* ! write [WRITE-OPTIONS]\n";
     393           1 :     ret += '\n';
     394           1 :     ret += "Example: 'gdal raster pipeline --progress ! read in.tif ! \\\n";
     395           1 :     ret += "               reproject --dst-crs=EPSG:32632 ! ";
     396           1 :     ret += "write out.tif --overwrite'\n";
     397           1 :     ret += '\n';
     398           1 :     ret += "Potential steps are:\n";
     399             : 
     400           1 :     UsageOptions stepUsageOptions;
     401           1 :     stepUsageOptions.isPipelineStep = true;
     402             : 
     403           6 :     for (const std::string &name : m_stepRegistry.GetNames())
     404             :     {
     405          10 :         auto alg = GetStepAlg(name);
     406           5 :         auto [options, maxOptLen] = alg->GetArgNamesForCLI();
     407           5 :         stepUsageOptions.maxOptLen =
     408           5 :             std::max(stepUsageOptions.maxOptLen, maxOptLen);
     409             :     }
     410             : 
     411             :     {
     412           1 :         const auto name = GDALRasterReadAlgorithm::NAME;
     413           1 :         ret += '\n';
     414           2 :         auto alg = GetStepAlg(name);
     415           2 :         alg->SetCallPath({name});
     416           1 :         ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
     417             :     }
     418           6 :     for (const std::string &name : m_stepRegistry.GetNames())
     419             :     {
     420           9 :         if (name != GDALRasterReadAlgorithm::NAME &&
     421           4 :             name != GDALRasterWriteAlgorithm::NAME)
     422             :         {
     423           3 :             ret += '\n';
     424           3 :             auto alg = GetStepAlg(name);
     425           6 :             alg->SetCallPath({name});
     426           3 :             ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
     427             :         }
     428             :     }
     429             :     {
     430           1 :         const auto name = GDALRasterWriteAlgorithm::NAME;
     431           1 :         ret += '\n';
     432           2 :         auto alg = GetStepAlg(name);
     433           2 :         alg->SetCallPath({name});
     434           1 :         ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
     435             :     }
     436             : 
     437           1 :     return ret;
     438             : }
     439             : 
     440             : //! @endcond

Generated by: LCOV version 1.14