LCOV - code coverage report
Current view: top level - apps - gdalalg_pipeline.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 409 428 95.6 %
Date: 2025-06-19 12:30:01 Functions: 13 15 86.7 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  gdal "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 "cpl_error.h"
      14             : #include "cpl_error_internal.h"
      15             : #include "gdalalg_abstract_pipeline.h"
      16             : #include "gdal_priv.h"
      17             : 
      18             : #include "gdalalg_raster_read.h"
      19             : #include "gdalalg_raster_mosaic.h"
      20             : #include "gdalalg_raster_stack.h"
      21             : #include "gdalalg_raster_write.h"
      22             : 
      23             : #include "gdalalg_vector_read.h"
      24             : #include "gdalalg_vector_concat.h"
      25             : #include "gdalalg_vector_sql.h"
      26             : #include "gdalalg_vector_write.h"
      27             : 
      28             : #include "gdalalg_raster_contour.h"
      29             : #include "gdalalg_raster_footprint.h"
      30             : #include "gdalalg_raster_polygonize.h"
      31             : #include "gdalalg_vector_grid.h"
      32             : #include "gdalalg_vector_rasterize.h"
      33             : 
      34             : #include <algorithm>
      35             : #include <cassert>
      36             : 
      37             : //! @cond Doxygen_Suppress
      38             : 
      39             : #ifndef _
      40             : #define _(x) (x)
      41             : #endif
      42             : 
      43             : /************************************************************************/
      44             : /*                     GDALPipelineStepAlgorithm()                      */
      45             : /************************************************************************/
      46             : 
      47        3465 : GDALPipelineStepAlgorithm::GDALPipelineStepAlgorithm(
      48             :     const std::string &name, const std::string &description,
      49        3465 :     const std::string &helpURL, const ConstructorOptions &options)
      50             :     : GDALAlgorithm(name, description, helpURL),
      51        3465 :       m_standaloneStep(options.standaloneStep), m_constructorOptions(options)
      52             : {
      53        3465 : }
      54             : 
      55             : /************************************************************************/
      56             : /*       GDALPipelineStepAlgorithm::AddRasterHiddenInputDatasetArg()    */
      57             : /************************************************************************/
      58             : 
      59         900 : void GDALPipelineStepAlgorithm::AddRasterHiddenInputDatasetArg()
      60             : {
      61             :     // Added so that "band" argument validation works, because
      62             :     // GDALAlgorithm must be able to retrieve the input dataset
      63             :     AddInputDatasetArg(&m_inputDataset, GDAL_OF_RASTER,
      64         900 :                        /* positionalAndRequired = */ false)
      65         900 :         .SetHidden();
      66         900 : }
      67             : 
      68             : /************************************************************************/
      69             : /*             GDALPipelineStepAlgorithm::AddRasterInputArgs()          */
      70             : /************************************************************************/
      71             : 
      72        1030 : void GDALPipelineStepAlgorithm::AddRasterInputArgs(
      73             :     bool openForMixedRasterVector, bool hiddenForCLI)
      74             : {
      75        1030 :     AddInputFormatsArg(&m_inputFormats)
      76             :         .AddMetadataItem(
      77             :             GAAMDI_REQUIRED_CAPABILITIES,
      78             :             openForMixedRasterVector
      79        3117 :                 ? std::vector<std::string>{GDAL_DCAP_RASTER, GDAL_DCAP_VECTOR}
      80        3063 :                 : std::vector<std::string>{GDAL_DCAP_RASTER})
      81        1030 :         .SetHiddenForCLI(hiddenForCLI);
      82        1030 :     AddOpenOptionsArg(&m_openOptions).SetHiddenForCLI(hiddenForCLI);
      83             :     auto &arg =
      84             :         AddInputDatasetArg(&m_inputDataset,
      85             :                            openForMixedRasterVector
      86             :                                ? (GDAL_OF_RASTER | GDAL_OF_VECTOR)
      87             :                                : GDAL_OF_RASTER,
      88        1030 :                            /* positionalAndRequired = */ !hiddenForCLI,
      89        1030 :                            m_constructorOptions.inputDatasetHelpMsg.c_str())
      90        1030 :             .SetMinCount(1)
      91        1030 :             .SetMaxCount(m_constructorOptions.inputDatasetMaxCount)
      92        1030 :             .SetAutoOpenDataset(m_constructorOptions.autoOpenInputDatasets)
      93        1030 :             .SetMetaVar(m_constructorOptions.inputDatasetMetaVar)
      94        1030 :             .SetHiddenForCLI(hiddenForCLI);
      95        1030 :     if (!m_constructorOptions.inputDatasetAlias.empty())
      96          33 :         arg.AddAlias(m_constructorOptions.inputDatasetAlias);
      97        1030 : }
      98             : 
      99             : /************************************************************************/
     100             : /*             GDALPipelineStepAlgorithm::AddRasterOutputArgs()         */
     101             : /************************************************************************/
     102             : 
     103        1005 : void GDALPipelineStepAlgorithm::AddRasterOutputArgs(bool hiddenForCLI)
     104             : {
     105        1005 :     m_outputFormatArg =
     106             :         &(AddOutputFormatArg(&m_format, /* bStreamAllowed = */ true,
     107        1005 :                              /* bGDALGAllowed = */ true)
     108             :               .AddMetadataItem(
     109             :                   GAAMDI_REQUIRED_CAPABILITIES,
     110             :                   {GDAL_DCAP_RASTER,
     111        4020 :                    m_constructorOptions.outputFormatCreateCapability.c_str()})
     112        1005 :               .SetHiddenForCLI(hiddenForCLI));
     113             :     AddOutputDatasetArg(&m_outputDataset, GDAL_OF_RASTER,
     114        1005 :                         /* positionalAndRequired = */ !hiddenForCLI,
     115        1005 :                         m_constructorOptions.outputDatasetHelpMsg.c_str())
     116        1005 :         .SetHiddenForCLI(hiddenForCLI)
     117        1005 :         .SetDatasetInputFlags(GADV_NAME | GADV_OBJECT);
     118        1005 :     AddCreationOptionsArg(&m_creationOptions).SetHiddenForCLI(hiddenForCLI);
     119        1005 :     AddOverwriteArg(&m_overwrite).SetHiddenForCLI(hiddenForCLI);
     120        1005 : }
     121             : 
     122             : /************************************************************************/
     123             : /*             GDALPipelineStepAlgorithm::AddVectorInputArgs()         */
     124             : /************************************************************************/
     125             : 
     126         502 : void GDALPipelineStepAlgorithm::AddVectorInputArgs(bool hiddenForCLI)
     127             : {
     128         502 :     AddInputFormatsArg(&m_inputFormats)
     129        1506 :         .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_VECTOR})
     130         502 :         .SetHiddenForCLI(hiddenForCLI);
     131         502 :     AddOpenOptionsArg(&m_openOptions).SetHiddenForCLI(hiddenForCLI);
     132             :     auto &datasetArg =
     133             :         AddInputDatasetArg(&m_inputDataset, GDAL_OF_VECTOR,
     134         502 :                            /* positionalAndRequired = */ !hiddenForCLI)
     135         502 :             .SetMinCount(1)
     136         502 :             .SetMaxCount(m_constructorOptions.inputDatasetMaxCount)
     137         502 :             .SetHiddenForCLI(hiddenForCLI);
     138         502 :     if (m_constructorOptions.addInputLayerNameArgument)
     139             :     {
     140             :         auto &layerArg = AddArg("input-layer", 'l', _("Input layer name(s)"),
     141         976 :                                 &m_inputLayerNames)
     142         976 :                              .AddAlias("layer")
     143         488 :                              .SetHiddenForCLI(hiddenForCLI);
     144         488 :         SetAutoCompleteFunctionForLayerName(layerArg, datasetArg);
     145             :     }
     146         502 : }
     147             : 
     148             : /************************************************************************/
     149             : /*             GDALPipelineStepAlgorithm::AddVectorOutputArgs()         */
     150             : /************************************************************************/
     151             : 
     152         509 : void GDALPipelineStepAlgorithm::AddVectorOutputArgs(
     153             :     bool hiddenForCLI, bool shortNameOutputLayerAllowed)
     154             : {
     155             :     AddOutputFormatArg(&m_format, /* bStreamAllowed = */ true,
     156         509 :                        /* bGDALGAllowed = */ true)
     157             :         .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES,
     158        2036 :                          {GDAL_DCAP_VECTOR, GDAL_DCAP_CREATE})
     159         509 :         .SetHiddenForCLI(hiddenForCLI);
     160             :     auto &outputDatasetArg =
     161             :         AddOutputDatasetArg(&m_outputDataset, GDAL_OF_VECTOR,
     162         509 :                             /* positionalAndRequired = */ false)
     163         509 :             .SetHiddenForCLI(hiddenForCLI)
     164         509 :             .SetDatasetInputFlags(GADV_NAME | GADV_OBJECT);
     165         509 :     if (!hiddenForCLI)
     166         431 :         outputDatasetArg.SetPositional();
     167         509 :     if (!hiddenForCLI && m_constructorOptions.outputDatasetRequired)
     168         417 :         outputDatasetArg.SetRequired();
     169         509 :     if (!m_constructorOptions.outputDatasetMutualExclusionGroup.empty())
     170             :     {
     171             :         outputDatasetArg.SetMutualExclusionGroup(
     172          14 :             m_constructorOptions.outputDatasetMutualExclusionGroup);
     173             :     }
     174         509 :     AddCreationOptionsArg(&m_creationOptions).SetHiddenForCLI(hiddenForCLI);
     175         509 :     AddLayerCreationOptionsArg(&m_layerCreationOptions)
     176         509 :         .SetHiddenForCLI(hiddenForCLI);
     177         509 :     AddOverwriteArg(&m_overwrite).SetHiddenForCLI(hiddenForCLI);
     178         509 :     auto &updateArg = AddUpdateArg(&m_update).SetHiddenForCLI(hiddenForCLI);
     179         509 :     if (!m_constructorOptions.updateMutualExclusionGroup.empty())
     180             :     {
     181             :         updateArg.SetMutualExclusionGroup(
     182          14 :             m_constructorOptions.updateMutualExclusionGroup);
     183             :     }
     184         509 :     AddOverwriteLayerArg(&m_overwriteLayer).SetHiddenForCLI(hiddenForCLI);
     185         509 :     AddAppendLayerArg(&m_appendLayer).SetHiddenForCLI(hiddenForCLI);
     186        1004 :     if (GetName() != GDALVectorSQLAlgorithm::NAME &&
     187         495 :         GetName() != GDALVectorConcatAlgorithm::NAME)
     188             :     {
     189             :         AddArg("output-layer", shortNameOutputLayerAllowed ? 'l' : 0,
     190         936 :                _("Output layer name"), &m_outputLayerName)
     191         936 :             .AddHiddenAlias("nln")  // For ogr2ogr nostalgic people
     192         468 :             .SetHiddenForCLI(hiddenForCLI);
     193             :     }
     194         509 : }
     195             : 
     196             : /************************************************************************/
     197             : /*                 GDALPipelineStepAlgorithm::RunImpl()                 */
     198             : /************************************************************************/
     199             : 
     200         963 : bool GDALPipelineStepAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
     201             :                                         void *pProgressData)
     202             : {
     203         963 :     if (m_standaloneStep)
     204             :     {
     205         388 :         std::unique_ptr<GDALPipelineStepAlgorithm> readAlg;
     206         388 :         if (GetInputType() == GDAL_OF_RASTER)
     207         256 :             readAlg = std::make_unique<GDALRasterReadAlgorithm>();
     208             :         else
     209         132 :             readAlg = std::make_unique<GDALVectorReadAlgorithm>();
     210        3236 :         for (auto &arg : readAlg->GetArgs())
     211             :         {
     212        2848 :             auto stepArg = GetArg(arg->GetName());
     213        2848 :             if (stepArg && stepArg->IsExplicitlySet())
     214             :             {
     215         388 :                 arg->SetSkipIfAlreadySet(true);
     216         388 :                 arg->SetFrom(*stepArg);
     217             :             }
     218             :         }
     219             : 
     220           0 :         std::unique_ptr<GDALPipelineStepAlgorithm> writeAlg;
     221         388 :         if (GetOutputType() == GDAL_OF_RASTER)
     222         256 :             writeAlg = std::make_unique<GDALRasterWriteAlgorithm>();
     223             :         else
     224         132 :             writeAlg = std::make_unique<GDALVectorWriteAlgorithm>();
     225        4408 :         for (auto &arg : writeAlg->GetArgs())
     226             :         {
     227        4020 :             auto stepArg = GetArg(arg->GetName());
     228        4020 :             if (stepArg && stepArg->IsExplicitlySet())
     229             :             {
     230         960 :                 arg->SetSkipIfAlreadySet(true);
     231         960 :                 arg->SetFrom(*stepArg);
     232             :             }
     233             :         }
     234             : 
     235         388 :         const bool bIsStreaming = m_format == "stream";
     236             : 
     237             :         // Already checked by GDALAlgorithm::Run()
     238         388 :         CPLAssert(!m_executionForStreamOutput || bIsStreaming);
     239             : 
     240         388 :         bool ret = false;
     241         443 :         if (!m_outputVRTCompatible &&
     242         110 :             (EQUAL(m_format.c_str(), "VRT") ||
     243          55 :              (m_format.empty() &&
     244         388 :               EQUAL(CPLGetExtensionSafe(m_outputDataset.GetName().c_str())
     245             :                         .c_str(),
     246             :                     "VRT"))))
     247             :         {
     248           0 :             ReportError(CE_Failure, CPLE_NotSupported,
     249             :                         "VRT output is not supported. Consider using the "
     250             :                         "GDALG driver instead (files with .gdalg.json "
     251             :                         "extension)");
     252             :         }
     253         388 :         else if (readAlg->Run())
     254             :         {
     255             :             const bool bOutputSpecified =
     256         388 :                 GetArg(GDAL_ARG_NAME_OUTPUT)->IsExplicitlySet();
     257             : 
     258         388 :             m_inputDataset.clear();
     259         388 :             m_inputDataset.resize(1);
     260         388 :             m_inputDataset[0].Set(readAlg->m_outputDataset.GetDatasetRef());
     261         388 :             if (bOutputSpecified)
     262         385 :                 m_outputDataset.Set(nullptr);
     263             : 
     264             :             std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)>
     265         776 :                 pScaledData(nullptr, GDALDestroyScaledProgress);
     266             : 
     267             :             const bool bCanHandleNextStep =
     268         388 :                 !bIsStreaming && CanHandleNextStep(writeAlg.get());
     269             : 
     270         407 :             if (pfnProgress &&
     271          19 :                 (bCanHandleNextStep || !IsNativelyStreamingCompatible()))
     272             :             {
     273          36 :                 pScaledData.reset(GDALCreateScaledProgress(
     274          18 :                     0.0, bIsStreaming || bCanHandleNextStep ? 1.0 : 0.5,
     275             :                     pfnProgress, pProgressData));
     276             :             }
     277             : 
     278         388 :             GDALPipelineStepRunContext stepCtxt;
     279         388 :             stepCtxt.m_pfnProgress = pScaledData ? GDALScaledProgress : nullptr;
     280         388 :             stepCtxt.m_pProgressData = pScaledData.get();
     281         388 :             if (bCanHandleNextStep)
     282          42 :                 stepCtxt.m_poNextUsableStep = writeAlg.get();
     283         388 :             if (RunPreStepPipelineValidations() && RunStep(stepCtxt))
     284             :             {
     285         320 :                 if (bIsStreaming || bCanHandleNextStep || !bOutputSpecified)
     286             :                 {
     287         123 :                     ret = true;
     288             :                 }
     289             :                 else
     290             :                 {
     291         197 :                     writeAlg->m_outputVRTCompatible = m_outputVRTCompatible;
     292         197 :                     writeAlg->m_inputDataset.clear();
     293         197 :                     writeAlg->m_inputDataset.resize(1);
     294         197 :                     writeAlg->m_inputDataset[0].Set(
     295             :                         m_outputDataset.GetDatasetRef());
     296         197 :                     if (pfnProgress)
     297             :                     {
     298          19 :                         pScaledData.reset(GDALCreateScaledProgress(
     299          19 :                             IsNativelyStreamingCompatible() ? 0.0 : 0.5, 1.0,
     300             :                             pfnProgress, pProgressData));
     301             :                     }
     302         197 :                     stepCtxt.m_pfnProgress =
     303         197 :                         pScaledData ? GDALScaledProgress : nullptr;
     304         197 :                     stepCtxt.m_pProgressData = pScaledData.get();
     305         394 :                     if (writeAlg->ValidateArguments() &&
     306         197 :                         writeAlg->RunStep(stepCtxt))
     307             :                     {
     308         195 :                         if (pfnProgress)
     309          19 :                             pfnProgress(1.0, "", pProgressData);
     310             : 
     311         195 :                         m_outputDataset.Set(
     312         195 :                             writeAlg->m_outputDataset.GetDatasetRef());
     313         195 :                         ret = true;
     314             :                     }
     315             :                 }
     316             :             }
     317             :         }
     318             : 
     319         388 :         return ret;
     320             :     }
     321             :     else
     322             :     {
     323         575 :         GDALPipelineStepRunContext stepCtxt;
     324         575 :         stepCtxt.m_pfnProgress = pfnProgress;
     325         575 :         stepCtxt.m_pProgressData = pProgressData;
     326         575 :         return RunPreStepPipelineValidations() && RunStep(stepCtxt);
     327             :     }
     328             : }
     329             : 
     330             : /************************************************************************/
     331             : /*                          ProcessGDALGOutput()                        */
     332             : /************************************************************************/
     333             : 
     334             : GDALAlgorithm::ProcessGDALGOutputRet
     335        1323 : GDALPipelineStepAlgorithm::ProcessGDALGOutput()
     336             : {
     337        1323 :     if (m_standaloneStep)
     338             :     {
     339         691 :         return GDALAlgorithm::ProcessGDALGOutput();
     340             :     }
     341             :     else
     342             :     {
     343             :         // GDALAbstractPipelineAlgorithm<StepAlgorithm>::RunStep() might
     344             :         // actually detect a GDALG output request and process it.
     345         632 :         return GDALAlgorithm::ProcessGDALGOutputRet::NOT_GDALG;
     346             :     }
     347             : }
     348             : 
     349             : /************************************************************************/
     350             : /*          GDALPipelineStepAlgorithm::CheckSafeForStreamOutput()       */
     351             : /************************************************************************/
     352             : 
     353          47 : bool GDALPipelineStepAlgorithm::CheckSafeForStreamOutput()
     354             : {
     355          47 :     if (m_standaloneStep)
     356             :     {
     357          27 :         return GDALAlgorithm::CheckSafeForStreamOutput();
     358             :     }
     359             :     else
     360             :     {
     361             :         // The check is actually done in
     362             :         // GDALAbstractPipelineAlgorithm<StepAlgorithm>::RunStep()
     363             :         // so return true for now.
     364          20 :         return true;
     365             :     }
     366             : }
     367             : 
     368             : /************************************************************************/
     369             : /*                       GDALPipelineAlgorithm                          */
     370             : /************************************************************************/
     371             : 
     372             : class GDALPipelineAlgorithm final
     373             :     : public GDALAbstractPipelineAlgorithm<GDALPipelineStepAlgorithm>
     374             : 
     375             : {
     376             :   public:
     377             :     static constexpr const char *NAME = "pipeline";
     378             :     static constexpr const char *DESCRIPTION = "Execute a pipeline.";
     379             :     static constexpr const char *HELP_URL = "/programs/gdal_pipeline.html";
     380             : 
     381          49 :     GDALPipelineAlgorithm()
     382          49 :         : GDALAbstractPipelineAlgorithm(
     383             :               NAME, DESCRIPTION, HELP_URL,
     384          49 :               ConstructorOptions().SetStandaloneStep(false))
     385             :     {
     386          49 :         m_supportsStreamedOutput = true;
     387             : 
     388          49 :         AddProgressArg();
     389             :         AddInputDatasetArg(&m_inputDataset, GDAL_OF_RASTER | GDAL_OF_VECTOR,
     390          49 :                            /* positionalAndRequired = */ false)
     391          49 :             .SetMinCount(1)
     392          49 :             .SetMaxCount(INT_MAX)
     393          49 :             .SetHiddenForCLI();
     394             :         AddOutputDatasetArg(&m_outputDataset, GDAL_OF_RASTER | GDAL_OF_VECTOR,
     395          49 :                             /* positionalAndRequired = */ false)
     396          49 :             .SetHiddenForCLI()
     397          49 :             .SetDatasetInputFlags(GADV_NAME | GADV_OBJECT);
     398             :         AddOutputFormatArg(&m_format, /* bStreamAllowed = */ true,
     399          49 :                            /* bGDALGAllowed = */ true)
     400          49 :             .SetHiddenForCLI();
     401          98 :         AddArg("pipeline", 0, _("Pipeline string"), &m_pipeline)
     402          49 :             .SetHiddenForCLI()
     403          49 :             .SetPositional();
     404             : 
     405          49 :         GDALRasterPipelineAlgorithm::RegisterAlgorithms(m_stepRegistry, true);
     406          49 :         GDALVectorPipelineAlgorithm::RegisterAlgorithms(m_stepRegistry, true);
     407          49 :         m_stepRegistry.Register<GDALRasterContourAlgorithm>();
     408          49 :         m_stepRegistry.Register<GDALRasterFootprintAlgorithm>();
     409          49 :         m_stepRegistry.Register<GDALRasterPolygonizeAlgorithm>();
     410          49 :         m_stepRegistry.Register<GDALVectorGridAlgorithm>();
     411          49 :         m_stepRegistry.Register<GDALVectorRasterizeAlgorithm>();
     412          49 :     }
     413             : 
     414             :     // Declared to satisfy GDALPipelineStepAlgorithm, but not called as this
     415             :     // class is not an actual step, hence return value is "random"
     416           0 :     int GetInputType() const override
     417             :     {
     418           0 :         CPLAssert(false);
     419             :         return 0;
     420             :     }
     421             : 
     422             :     // Declared to satisfy GDALPipelineStepAlgorithm, but not called as this
     423             :     // class is not an actual step, hence return value is "random"
     424           0 :     int GetOutputType() const override
     425             :     {
     426           0 :         CPLAssert(false);
     427             :         return 0;
     428             :     }
     429             : 
     430             :   protected:
     431             :     bool
     432             :     ParseCommandLineArguments(const std::vector<std::string> &args) override;
     433             : 
     434             :     std::string GetUsageForCLI(bool shortUsage,
     435             :                                const UsageOptions &usageOptions) const override;
     436             : };
     437             : 
     438             : /************************************************************************/
     439             : /*           GDALPipelineAlgorithm::ParseCommandLineArguments()         */
     440             : /************************************************************************/
     441             : 
     442          35 : bool GDALPipelineAlgorithm::ParseCommandLineArguments(
     443             :     const std::vector<std::string> &args)
     444             : {
     445          43 :     if (args.size() == 1 && (args[0] == "-h" || args[0] == "--help" ||
     446           8 :                              args[0] == "help" || args[0] == "--json-usage"))
     447             :     {
     448           1 :         return GDALAlgorithm::ParseCommandLineArguments(args);
     449             :     }
     450          34 :     else if (args.size() == 1 && STARTS_WITH(args[0].c_str(), "--help-doc="))
     451             :     {
     452           3 :         m_helpDocCategory = args[0].substr(strlen("--help-doc="));
     453           6 :         return GDALAlgorithm::ParseCommandLineArguments({"--help-doc"});
     454             :     }
     455             : 
     456         200 :     for (const auto &arg : args)
     457             :     {
     458         170 :         if (arg.find("--pipeline") == 0)
     459           1 :             return GDALAlgorithm::ParseCommandLineArguments(args);
     460             : 
     461             :         // gdal pipeline [--progress] "read poly.gpkg ..."
     462         170 :         if (arg.find("read ") == 0)
     463           1 :             return GDALAlgorithm::ParseCommandLineArguments(args);
     464             :     }
     465             : 
     466          30 :     if (!m_steps.empty())
     467             :     {
     468           1 :         ReportError(CE_Failure, CPLE_AppDefined,
     469             :                     "ParseCommandLineArguments() can only be called once per "
     470             :                     "instance.");
     471           1 :         return false;
     472             :     }
     473             : 
     474             :     struct Step
     475             :     {
     476             :         std::unique_ptr<GDALPipelineStepAlgorithm> alg{};
     477             :         std::vector<std::string> args{};
     478             :         bool alreadyChangedType = false;
     479             :         bool isSubAlgorithm = false;
     480             :     };
     481             : 
     482          58 :     std::vector<Step> steps;
     483          29 :     steps.resize(1);
     484             : 
     485         190 :     for (const auto &arg : args)
     486             :     {
     487         162 :         if (arg == "--progress")
     488             :         {
     489           1 :             m_progressBarRequested = true;
     490           1 :             continue;
     491             :         }
     492             : 
     493         161 :         if (IsCalledFromCommandLine() && (arg == "-h" || arg == "--help"))
     494             :         {
     495           0 :             if (!steps.back().alg)
     496           0 :                 steps.pop_back();
     497           0 :             if (steps.empty())
     498             :             {
     499           1 :                 return GDALAlgorithm::ParseCommandLineArguments(args);
     500             :             }
     501             :             else
     502             :             {
     503           0 :                 m_stepOnWhichHelpIsRequested = std::move(steps.back().alg);
     504           0 :                 return true;
     505             :             }
     506             :         }
     507             : 
     508         161 :         auto &curStep = steps.back();
     509             : 
     510         161 :         if (arg == "!" || arg == "|")
     511             :         {
     512          38 :             if (curStep.alg)
     513             :             {
     514          38 :                 steps.resize(steps.size() + 1);
     515             :             }
     516             :         }
     517         123 :         else if (!curStep.alg)
     518             :         {
     519          67 :             std::string algName = arg;
     520          67 :             if (algName == "read")
     521             :             {
     522          27 :                 curStep.alg = std::make_unique<GDALRasterReadAlgorithm>(true);
     523             :             }
     524             :             else
     525             :             {
     526          40 :                 curStep.alg = GetStepAlg(algName);
     527          40 :                 if (!curStep.alg)
     528          31 :                     curStep.alg = GetStepAlg(algName + RASTER_SUFFIX);
     529             :             }
     530          67 :             if (!curStep.alg)
     531             :             {
     532           1 :                 ReportError(CE_Failure, CPLE_AppDefined,
     533             :                             "unknown step name: %s", algName.c_str());
     534           1 :                 return false;
     535             :             }
     536         132 :             curStep.alg->SetCallPath({std::move(algName)});
     537          66 :             curStep.alg->SetReferencePathForRelativePaths(
     538             :                 GetReferencePathForRelativePaths());
     539             :         }
     540             :         else
     541             :         {
     542          56 :             if (curStep.alg->HasSubAlgorithms())
     543             :             {
     544             :                 auto subAlg = std::unique_ptr<GDALPipelineStepAlgorithm>(
     545             :                     cpl::down_cast<GDALPipelineStepAlgorithm *>(
     546           1 :                         curStep.alg->InstantiateSubAlgorithm(arg).release()));
     547           1 :                 if (!subAlg)
     548             :                 {
     549           0 :                     ReportError(CE_Failure, CPLE_AppDefined,
     550             :                                 "'%s' is a unknown sub-algorithm of '%s'",
     551           0 :                                 arg.c_str(), curStep.alg->GetName().c_str());
     552           0 :                     return false;
     553             :                 }
     554           1 :                 curStep.isSubAlgorithm = true;
     555           1 :                 curStep.alg = std::move(subAlg);
     556           1 :                 continue;
     557             :             }
     558             : 
     559          55 :             curStep.args.push_back(arg);
     560             :         }
     561             :     }
     562             : 
     563             :     // As we initially added a step without alg to bootstrap things, make
     564             :     // sure to remove it if it hasn't been filled, or the user has terminated
     565             :     // the pipeline with a '!' separator.
     566          28 :     if (!steps.back().alg)
     567           0 :         steps.pop_back();
     568             : 
     569             :     // Automatically add a final write step if none in m_executionForStreamOutput
     570             :     // mode
     571          30 :     if (m_executionForStreamOutput && !steps.empty() &&
     572           2 :         steps.back().alg->GetName() !=
     573          30 :             std::string(GDALRasterWriteAlgorithm::NAME).append(RASTER_SUFFIX))
     574             :     {
     575           2 :         steps.resize(steps.size() + 1);
     576           4 :         steps.back().alg = GetStepAlg(
     577           6 :             std::string(GDALRasterWriteAlgorithm::NAME).append(RASTER_SUFFIX));
     578           2 :         steps.back().args.push_back("--output-format");
     579           2 :         steps.back().args.push_back("stream");
     580           2 :         steps.back().args.push_back("streamed_dataset");
     581             :     }
     582             : 
     583          28 :     bool helpRequested = false;
     584          28 :     if (IsCalledFromCommandLine())
     585             :     {
     586          15 :         for (auto &step : steps)
     587          10 :             step.alg->SetCalledFromCommandLine();
     588             : 
     589          31 :         for (const std::string &v : args)
     590             :         {
     591          26 :             if (cpl::ends_with(v, "=?"))
     592           1 :                 helpRequested = true;
     593             :         }
     594             :     }
     595             : 
     596          28 :     if (steps.size() < 2)
     597             :     {
     598           1 :         if (!steps.empty() && helpRequested)
     599             :         {
     600           0 :             steps.back().alg->ParseCommandLineArguments(steps.back().args);
     601           0 :             return false;
     602             :         }
     603             : 
     604           1 :         ReportError(CE_Failure, CPLE_AppDefined,
     605             :                     "At least 2 steps must be provided");
     606           1 :         return false;
     607             :     }
     608             : 
     609          54 :     std::vector<GDALPipelineStepAlgorithm *> stepAlgs;
     610          94 :     for (const auto &step : steps)
     611          67 :         stepAlgs.push_back(step.alg.get());
     612          27 :     if (!CheckFirstStep(stepAlgs))
     613           2 :         return false;  // CheckFirstStep emits an error
     614             : 
     615          25 :     if (steps.back().alg->GetName() != GDALRasterWriteAlgorithm::NAME)
     616             :     {
     617           2 :         if (helpRequested)
     618             :         {
     619           1 :             steps.back().alg->ParseCommandLineArguments(steps.back().args);
     620           1 :             return false;
     621             :         }
     622           1 :         ReportError(CE_Failure, CPLE_AppDefined, "Last step should be '%s'",
     623             :                     GDALRasterWriteAlgorithm::NAME);
     624           1 :         return false;
     625             :     }
     626          57 :     for (size_t i = 0; i < steps.size() - 1; ++i)
     627             :     {
     628          35 :         if (steps[i].alg->GetName() == GDALRasterWriteAlgorithm::NAME)
     629             :         {
     630           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     631             :                         "Only last step can be '%s'",
     632             :                         GDALRasterWriteAlgorithm::NAME);
     633           1 :             return false;
     634             :         }
     635             :     }
     636             : 
     637             :     // Propagate input parameters set at the pipeline level to the
     638             :     // "read" step
     639             :     {
     640          22 :         auto &step = steps.front();
     641         183 :         for (auto &arg : step.alg->GetArgs())
     642             :         {
     643         161 :             if (!arg->IsHidden())
     644             :             {
     645         138 :                 auto pipelineArg = GetArg(arg->GetName());
     646         138 :                 if (pipelineArg && pipelineArg->IsExplicitlySet())
     647             :                 {
     648           6 :                     arg->SetSkipIfAlreadySet(true);
     649           6 :                     arg->SetFrom(*pipelineArg);
     650             :                 }
     651             :             }
     652             :         }
     653             :     }
     654             : 
     655             :     // Same with "write" step
     656         352 :     const auto SetWriteArgFromPipeline = [this, &steps]()
     657             :     {
     658          39 :         auto &step = steps.back();
     659         422 :         for (auto &arg : step.alg->GetArgs())
     660             :         {
     661         383 :             if (!arg->IsHidden())
     662             :             {
     663         313 :                 auto pipelineArg = GetArg(arg->GetName());
     664         313 :                 if (pipelineArg && pipelineArg->IsExplicitlySet())
     665             :                 {
     666          28 :                     arg->SetSkipIfAlreadySet(true);
     667          28 :                     arg->SetFrom(*pipelineArg);
     668             :                 }
     669             :             }
     670             :         }
     671          39 :     };
     672             : 
     673          22 :     SetWriteArgFromPipeline();
     674             : 
     675             :     // Parse each step, but without running the validation
     676          73 :     for (auto &step : steps)
     677             :     {
     678             :         bool ret;
     679          53 :         CPLErrorAccumulator oAccumulator;
     680             :         {
     681             :             [[maybe_unused]] auto context =
     682          53 :                 oAccumulator.InstallForCurrentScope();
     683          53 :             step.alg->m_skipValidationInParseCommandLine = true;
     684          53 :             ret = step.alg->ParseCommandLineArguments(step.args);
     685             :         }
     686          53 :         if (!ret)
     687             :         {
     688           3 :             auto alg = GetStepAlg(step.alg->GetName() + VECTOR_SUFFIX);
     689           3 :             if (alg)
     690             :             {
     691           2 :                 step.alg = std::move(alg);
     692           2 :                 step.alg->m_skipValidationInParseCommandLine = true;
     693           2 :                 ret = step.alg->ParseCommandLineArguments(step.args);
     694           2 :                 if (!ret)
     695           1 :                     return false;
     696           2 :                 step.alg->SetCallPath({step.alg->GetName()});
     697           1 :                 step.alg->SetReferencePathForRelativePaths(
     698             :                     GetReferencePathForRelativePaths());
     699           1 :                 step.alreadyChangedType = true;
     700             :             }
     701             :             else
     702             :             {
     703           2 :                 for (const auto &sError : oAccumulator.GetErrors())
     704             :                 {
     705           1 :                     CPLError(sError.type, sError.no, "%s", sError.msg.c_str());
     706             :                 }
     707           1 :                 return false;
     708             :             }
     709             :         }
     710             :     }
     711             : 
     712             :     // Evaluate "input" argument of "read" step, together with the "output"
     713             :     // argument of the "write" step, in case they point to the same dataset.
     714          20 :     auto inputArg = steps.front().alg->GetArg(GDAL_ARG_NAME_INPUT);
     715          40 :     if (inputArg && inputArg->IsExplicitlySet() &&
     716          60 :         inputArg->GetType() == GAAT_DATASET_LIST &&
     717          20 :         inputArg->Get<std::vector<GDALArgDatasetValue>>().size() == 1)
     718             :     {
     719          20 :         steps.front().alg->ProcessDatasetArg(inputArg, steps.back().alg.get());
     720             :     }
     721             : 
     722          20 :     int nLastStepOutputType = GDAL_OF_VECTOR;
     723          20 :     if (steps.front().alg->GetName() !=
     724          41 :             std::string(GDALRasterReadAlgorithm::NAME) &&
     725           1 :         steps.front().alg->GetOutputType() == GDAL_OF_RASTER)
     726             :     {
     727           1 :         nLastStepOutputType = GDAL_OF_RASTER;
     728             :     }
     729             :     else
     730             :     {
     731          19 :         auto poSrcDS = steps.front().alg->GetInputDatasets()[0].GetDatasetRef();
     732          19 :         if (poSrcDS)
     733             :         {
     734          19 :             if (poSrcDS->GetRasterCount() != 0)
     735          10 :                 nLastStepOutputType = GDAL_OF_RASTER;
     736             :         }
     737             :     }
     738             : 
     739          44 :     for (size_t i = 1; i < steps.size(); ++i)
     740             :     {
     741          52 :         if (!steps[i].alreadyChangedType && !steps[i].isSubAlgorithm &&
     742          52 :             GetStepAlg(steps[i].alg->GetName()) == nullptr)
     743             :         {
     744          38 :             steps[i].alg = GetStepAlg(steps[i].alg->GetName() +
     745             :                                       (nLastStepOutputType == GDAL_OF_RASTER
     746             :                                            ? RASTER_SUFFIX
     747          19 :                                            : VECTOR_SUFFIX));
     748          19 :             CPLAssert(steps[i].alg);
     749             : 
     750          19 :             if (i == steps.size() - 1)
     751             :             {
     752          17 :                 SetWriteArgFromPipeline();
     753             :             }
     754             : 
     755          19 :             steps[i].alg->m_skipValidationInParseCommandLine = true;
     756          19 :             if (!steps[i].alg->ParseCommandLineArguments(steps[i].args))
     757           1 :                 return false;
     758          36 :             steps[i].alg->SetCallPath({steps[i].alg->GetName()});
     759          18 :             steps[i].alg->SetReferencePathForRelativePaths(
     760             :                 GetReferencePathForRelativePaths());
     761          18 :             steps[i].alreadyChangedType = true;
     762             :         }
     763           8 :         else if (steps[i].alg->GetInputType() != nLastStepOutputType)
     764             :         {
     765           8 :             ReportError(
     766             :                 CE_Failure, CPLE_AppDefined,
     767             :                 "Step '%s' expects a %s input dataset, but previous step '%s' "
     768             :                 "generates a %s output dataset",
     769           2 :                 steps[i].alg->GetName().c_str(),
     770           2 :                 steps[i].alg->GetInputType() == GDAL_OF_RASTER ? "raster"
     771             :                                                                : "vector",
     772           2 :                 steps[i - 1].alg->GetName().c_str(),
     773             :                 nLastStepOutputType == GDAL_OF_RASTER ? "raster" : "vector");
     774           2 :             return false;
     775             :         }
     776          24 :         nLastStepOutputType = steps[i].alg->GetOutputType();
     777             :     }
     778             : 
     779          58 :     for (const auto &step : steps)
     780             :     {
     781          41 :         if (!step.alg->ValidateArguments())
     782           0 :             return false;
     783             :     }
     784             : 
     785          58 :     for (auto &step : steps)
     786          41 :         m_steps.push_back(std::move(step.alg));
     787             : 
     788          17 :     return true;
     789             : }
     790             : 
     791             : /************************************************************************/
     792             : /*               GDALPipelineAlgorithm::GetUsageForCLI()                */
     793             : /************************************************************************/
     794             : 
     795             : std::string
     796           4 : GDALPipelineAlgorithm::GetUsageForCLI(bool shortUsage,
     797             :                                       const UsageOptions &usageOptions) const
     798             : {
     799           4 :     UsageOptions stepUsageOptions;
     800           4 :     stepUsageOptions.isPipelineStep = true;
     801             : 
     802           4 :     if (!m_helpDocCategory.empty() && m_helpDocCategory != "main")
     803             :     {
     804           4 :         auto alg = GetStepAlg(m_helpDocCategory);
     805           4 :         std::string ret;
     806           2 :         if (alg)
     807             :         {
     808           3 :             alg->SetCallPath({CPLString(m_helpDocCategory)
     809           2 :                                   .replaceAll(RASTER_SUFFIX, "")
     810           3 :                                   .replaceAll(VECTOR_SUFFIX, "")});
     811           1 :             alg->GetArg("help-doc")->Set(true);
     812           1 :             return alg->GetUsageForCLI(shortUsage, stepUsageOptions);
     813             :         }
     814             :         else
     815             :         {
     816           1 :             fprintf(stderr, "ERROR: unknown pipeline step '%s'\n",
     817             :                     m_helpDocCategory.c_str());
     818             :             return CPLSPrintf("ERROR: unknown pipeline step '%s'\n",
     819           1 :                               m_helpDocCategory.c_str());
     820             :         }
     821             :     }
     822             : 
     823           2 :     UsageOptions usageOptionsMain(usageOptions);
     824           2 :     usageOptionsMain.isPipelineMain = true;
     825             :     std::string ret =
     826           4 :         GDALAlgorithm::GetUsageForCLI(shortUsage, usageOptionsMain);
     827           2 :     if (shortUsage)
     828           0 :         return ret;
     829             : 
     830             :     ret += "\n<PIPELINE> is of the form: read|calc|concat|mosaic|stack "
     831             :            "[READ-OPTIONS] "
     832           2 :            "( ! <STEP-NAME> [STEP-OPTIONS] )* ! write [WRITE-OPTIONS]\n";
     833             : 
     834           2 :     if (m_helpDocCategory == "main")
     835             :     {
     836           1 :         return ret;
     837             :     }
     838             : 
     839           1 :     ret += '\n';
     840           1 :     ret += "Example: 'gdal pipeline --progress ! read in.tif ! \\\n";
     841           1 :     ret += "               rasterize --size 256 256 ! buffer 20 ! ";
     842           1 :     ret += "write out.gpkg --overwrite'\n";
     843           1 :     ret += '\n';
     844           1 :     ret += "Potential steps are:\n";
     845             : 
     846          53 :     for (const std::string &name : m_stepRegistry.GetNames())
     847             :     {
     848         104 :         auto alg = GetStepAlg(name);
     849          52 :         assert(alg);
     850          52 :         auto [options, maxOptLen] = alg->GetArgNamesForCLI();
     851          52 :         stepUsageOptions.maxOptLen =
     852          52 :             std::max(stepUsageOptions.maxOptLen, maxOptLen);
     853             :     }
     854             : 
     855             :     {
     856           1 :         ret += '\n';
     857           1 :         auto alg = std::make_unique<GDALRasterReadAlgorithm>();
     858           2 :         alg->SetCallPath({alg->GetName()});
     859           1 :         ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
     860             :     }
     861             :     {
     862           1 :         ret += '\n';
     863           1 :         auto alg = std::make_unique<GDALVectorReadAlgorithm>();
     864           2 :         alg->SetCallPath({alg->GetName()});
     865           1 :         ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
     866             :     }
     867          53 :     for (const std::string &name : m_stepRegistry.GetNames())
     868             :     {
     869         104 :         auto alg = GetStepAlg(name);
     870          52 :         assert(alg);
     871          58 :         if (alg->CanBeFirstStep() && !alg->IsHidden() &&
     872           6 :             !STARTS_WITH(name.c_str(), GDALRasterReadAlgorithm::NAME))
     873             :         {
     874           4 :             ret += '\n';
     875           8 :             alg->SetCallPath({name});
     876           4 :             ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
     877             :         }
     878             :     }
     879          53 :     for (const std::string &name : m_stepRegistry.GetNames())
     880             :     {
     881         104 :         auto alg = GetStepAlg(name);
     882          52 :         assert(alg);
     883          97 :         if (!alg->CanBeFirstStep() && !alg->IsHidden() &&
     884          45 :             !STARTS_WITH(name.c_str(), GDALRasterWriteAlgorithm::NAME))
     885             :         {
     886          43 :             ret += '\n';
     887         129 :             alg->SetCallPath({CPLString(alg->GetName())
     888          86 :                                   .replaceAll(RASTER_SUFFIX, "")
     889         129 :                                   .replaceAll(VECTOR_SUFFIX, "")});
     890          43 :             ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
     891             :         }
     892             :     }
     893             :     {
     894           1 :         ret += '\n';
     895           1 :         auto alg = std::make_unique<GDALRasterWriteAlgorithm>();
     896           2 :         alg->SetCallPath({alg->GetName()});
     897           1 :         ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
     898             :     }
     899             :     {
     900           1 :         ret += '\n';
     901           1 :         auto alg = std::make_unique<GDALVectorWriteAlgorithm>();
     902           1 :         assert(alg);
     903           2 :         alg->SetCallPath({alg->GetName()});
     904           1 :         ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
     905             :     }
     906             : 
     907           1 :     ret += GetUsageForCLIEnd();
     908             : 
     909           1 :     return ret;
     910             : }
     911             : 
     912             : GDAL_STATIC_REGISTER_ALG(GDALPipelineAlgorithm);
     913             : 
     914             : //! @endcond

Generated by: LCOV version 1.14