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

Generated by: LCOV version 1.14