LCOV - code coverage report
Current view: top level - apps - gdalalg_vector_layer_algebra.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 159 160 99.4 %
Date: 2026-06-23 16:35:19 Functions: 6 6 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  gdal "vector layer-algebra" subcommand
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "gdalalg_vector_layer_algebra.h"
      14             : #include "gdalalg_vector_write.h"
      15             : 
      16             : #include "cpl_conv.h"
      17             : #include "gdal_priv.h"
      18             : #include "gdal_utils.h"
      19             : #include "ogr_api.h"
      20             : #include "ogrsf_frmts.h"
      21             : 
      22             : #include <algorithm>
      23             : 
      24             : //! @cond Doxygen_Suppress
      25             : 
      26             : #ifndef _
      27             : #define _(x) (x)
      28             : #endif
      29             : 
      30             : /************************************************************************/
      31             : /*                  GDALVectorLayerAlgebraAlgorithm()                   */
      32             : /************************************************************************/
      33             : 
      34         121 : GDALVectorLayerAlgebraAlgorithm::GDALVectorLayerAlgebraAlgorithm(
      35         121 :     bool standaloneStep)
      36             :     : GDALVectorPipelineStepAlgorithm(
      37             :           NAME, DESCRIPTION, HELP_URL,
      38           0 :           ConstructorOptions()
      39         121 :               .SetStandaloneStep(standaloneStep)
      40         121 :               .SetInputDatasetMaxCount(1)
      41         121 :               .SetAddInputLayerNameArgument(false)
      42         121 :               .SetAddDefaultArguments(false)
      43         121 :               .SetAddUpsertArgument(false)
      44         121 :               .SetAddSkipErrorsArgument(false)
      45         121 :               .SetOutputLayerNameAvailableInPipelineStep(true)
      46         242 :               .SetOutputFormatCreateCapability(GDAL_DCAP_CREATE))
      47             : {
      48         121 :     if (standaloneStep)
      49             :     {
      50          78 :         AddProgressArg();
      51             :     }
      52             : 
      53             :     auto &opArg =
      54         242 :         AddArg("operation", 0, _("Operation to perform"), &m_operation)
      55             :             .SetChoices("union", "intersection", "sym-difference", "identity",
      56         121 :                         "update", "clip", "erase")
      57         121 :             .SetRequired();
      58         121 :     if (standaloneStep)
      59          78 :         opArg.SetPositional();
      60             : 
      61         121 :     if (standaloneStep)
      62             :     {
      63          78 :         AddVectorInputArgs(false);
      64             :     }
      65             :     else
      66             :     {
      67          43 :         AddVectorHiddenInputDatasetArg();
      68             :     }
      69             : 
      70             :     {
      71             :         auto &arg = AddArg("method", 0, _("Method vector dataset"),
      72         242 :                            &m_methodDataset, GDAL_OF_VECTOR)
      73         121 :                         .SetRequired();
      74         121 :         if (standaloneStep)
      75          78 :             arg.SetPositional();
      76             : 
      77         121 :         SetAutoCompleteFunctionForFilename(arg, GDAL_OF_VECTOR);
      78             :     }
      79             : 
      80         121 :     if (standaloneStep)
      81             :     {
      82          78 :         AddVectorOutputArgs(false, false);
      83             :     }
      84             :     else
      85             :     {
      86          43 :         AddOutputLayerNameArg(/* hiddenForCLI = */ false,
      87             :                               /* shortNameOutputLayerAllowed = */ false);
      88             :     }
      89             : 
      90             :     AddArg(GDAL_ARG_NAME_INPUT_LAYER, 0, _("Input layer name"),
      91         121 :            &m_inputLayerName);
      92             : 
      93         121 :     AddArg("method-layer", 0, _("Method layer name"), &m_methodLayerName);
      94             : 
      95         121 :     AddGeometryTypeArg(&m_geometryType);
      96             : 
      97             :     AddArg("input-prefix", 0,
      98         242 :            _("Prefix for fields corresponding to input layer"), &m_inputPrefix)
      99         121 :         .SetCategory(GAAC_ADVANCED);
     100             :     AddArg("input-field", 0, _("Input field(s) to add to output layer"),
     101         242 :            &m_inputFields)
     102         242 :         .SetCategory(GAAC_ADVANCED)
     103         121 :         .SetMutualExclusionGroup("input-field");
     104             :     AddArg("no-input-field", 0, _("Do not add any input field to output layer"),
     105         242 :            &m_noInputFields)
     106         242 :         .SetCategory(GAAC_ADVANCED)
     107         121 :         .SetMutualExclusionGroup("input-field");
     108             :     AddArg("all-input-field", 0, _("Add all input fields to output layer"),
     109         242 :            &m_allInputFields)
     110         242 :         .SetCategory(GAAC_ADVANCED)
     111         121 :         .SetMutualExclusionGroup("input-field");
     112             : 
     113             :     AddArg("method-prefix", 0,
     114             :            _("Prefix for fields corresponding to method layer"),
     115         242 :            &m_methodPrefix)
     116         121 :         .SetCategory(GAAC_ADVANCED);
     117             :     AddArg("method-field", 0, _("Method field(s) to add to output layer"),
     118         242 :            &m_methodFields)
     119         242 :         .SetCategory(GAAC_ADVANCED)
     120         121 :         .SetMutualExclusionGroup("method-field");
     121             :     AddArg("no-method-field", 0,
     122         242 :            _("Do not add any method field to output layer"), &m_noMethodFields)
     123         242 :         .SetCategory(GAAC_ADVANCED)
     124         121 :         .SetMutualExclusionGroup("method-field");
     125             :     AddArg("all-method-field", 0, _("Add all method fields to output layer"),
     126         242 :            &m_allMethodFields)
     127         242 :         .SetCategory(GAAC_ADVANCED)
     128         121 :         .SetMutualExclusionGroup("method-field");
     129         121 : }
     130             : 
     131             : /************************************************************************/
     132             : /*         GDALVectorLayerAlgebraAlgorithm::CanHandleNextStep()         */
     133             : /************************************************************************/
     134             : 
     135           2 : bool GDALVectorLayerAlgebraAlgorithm::CanHandleNextStep(
     136             :     GDALPipelineStepAlgorithm *poNextStep) const
     137             : {
     138           3 :     return poNextStep->GetName() == GDALVectorWriteAlgorithm::NAME &&
     139           3 :            poNextStep->GetOutputFormat() != "stream";
     140             : }
     141             : 
     142             : /************************************************************************/
     143             : /*               GDALRasterPolygonizeAlgorithm::RunImpl()               */
     144             : /************************************************************************/
     145             : 
     146          34 : bool GDALVectorLayerAlgebraAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
     147             :                                               void *pProgressData)
     148             : {
     149          34 :     GDALPipelineStepRunContext stepCtxt;
     150          34 :     stepCtxt.m_pfnProgress = pfnProgress;
     151          34 :     stepCtxt.m_pProgressData = pProgressData;
     152          34 :     return RunPreStepPipelineValidations() && RunStep(stepCtxt);
     153             : }
     154             : 
     155             : /************************************************************************/
     156             : /*              GDALVectorLayerAlgebraAlgorithm::RunImpl()              */
     157             : /************************************************************************/
     158             : 
     159          37 : bool GDALVectorLayerAlgebraAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
     160             : {
     161             : #ifdef HAVE_GEOS
     162          37 :     auto poSrcDS = m_inputDataset[0].GetDatasetRef();
     163          37 :     CPLAssert(poSrcDS);
     164          37 :     auto poMethodDS = m_methodDataset.GetDatasetRef();
     165          37 :     CPLAssert(poMethodDS);
     166             : 
     167          37 :     if (poSrcDS == poMethodDS)
     168             :     {
     169           1 :         ReportError(CE_Failure, CPLE_NotSupported,
     170             :                     "Input and method datasets must be different");
     171           1 :         return false;
     172             :     }
     173             : 
     174          36 :     auto poWriteStep = ctxt.m_poNextUsableStep ? ctxt.m_poNextUsableStep : this;
     175             : 
     176          36 :     GDALDataset *poDstDS = nullptr;
     177          36 :     bool bTemporaryFile = false;
     178          36 :     std::unique_ptr<GDALDataset> poNewRetDS;
     179          72 :     std::string outputLayerName;
     180          36 :     OGRLayer *poDstLayer = nullptr;
     181          36 :     if (!CreateDatasetSingleOutputLayerIfNeeded(ctxt, "output", poDstDS,
     182             :                                                 bTemporaryFile, poNewRetDS,
     183             :                                                 outputLayerName, poDstLayer))
     184             :     {
     185          11 :         return false;
     186             :     }
     187             : 
     188             :     OGRLayer *poInputLayer;
     189          25 :     if (m_inputLayerName.empty() && poSrcDS->GetLayerCount() == 1)
     190          24 :         poInputLayer = poSrcDS->GetLayer(0);
     191             :     else
     192           1 :         poInputLayer = poSrcDS->GetLayerByName(m_inputLayerName.c_str());
     193          25 :     if (!poInputLayer)
     194             :     {
     195           1 :         ReportError(CE_Failure, CPLE_AppDefined, "Cannot get input layer '%s'",
     196             :                     m_inputLayerName.c_str());
     197           1 :         return false;
     198             :     }
     199             : 
     200             :     OGRLayer *poMethodLayer;
     201          24 :     if (m_methodLayerName.empty() && poMethodDS->GetLayerCount() == 1)
     202          23 :         poMethodLayer = poMethodDS->GetLayer(0);
     203             :     else
     204           1 :         poMethodLayer = poMethodDS->GetLayerByName(m_methodLayerName.c_str());
     205          24 :     if (!poMethodLayer)
     206             :     {
     207           1 :         ReportError(CE_Failure, CPLE_AppDefined, "Cannot get method layer '%s'",
     208             :                     m_methodLayerName.c_str());
     209           1 :         return false;
     210             :     }
     211             : 
     212          23 :     if (!poDstLayer)
     213             :     {
     214             :         const CPLStringList aosLayerCreationOptions(
     215          20 :             poWriteStep->GetLayerCreationOptions());
     216             : 
     217             :         const OGRwkbGeometryType eType =
     218          20 :             !m_geometryType.empty() ? OGRFromOGCGeomType(m_geometryType.c_str())
     219          19 :                                     : poInputLayer->GetGeomType();
     220          40 :         poDstLayer = poDstDS->CreateLayer(outputLayerName.c_str(),
     221          20 :                                           poInputLayer->GetSpatialRef(), eType,
     222             :                                           aosLayerCreationOptions.List());
     223          20 :         if (!poDstLayer)
     224           1 :             return false;
     225             :     }
     226             : 
     227          44 :     CPLStringList aosOptions;
     228             : 
     229          22 :     if (m_inputFields.empty() && !m_noInputFields)
     230          15 :         m_allInputFields = true;
     231             : 
     232          22 :     if (m_methodFields.empty() && !m_noMethodFields && !m_allMethodFields)
     233             :     {
     234          28 :         if (m_operation == "update" || m_operation == "clip" ||
     235          13 :             m_operation == "erase")
     236           3 :             m_noMethodFields = true;
     237             :         else
     238          12 :             m_allMethodFields = true;
     239             :     }
     240             : 
     241          22 :     if (m_noInputFields && m_noMethodFields)
     242             :     {
     243           6 :         aosOptions.SetNameValue("ADD_INPUT_FIELDS", "NO");
     244           6 :         aosOptions.SetNameValue("ADD_METHOD_FIELDS", "NO");
     245             :     }
     246             :     else
     247             :     {
     248             :         // Copy fields from input or method layer to output layer
     249             :         const auto CopyFields =
     250          28 :             [poDstLayer](OGRLayer *poSrcLayer, const std::string &prefix,
     251          65 :                          const std::vector<std::string> &srcFields)
     252             :         {
     253             :             const auto contains =
     254           4 :                 [](const std::vector<std::string> &v, const std::string &s)
     255           4 :             { return std::find(v.begin(), v.end(), s) != v.end(); };
     256             : 
     257          28 :             const auto poOutFDefn = poDstLayer->GetLayerDefn();
     258          28 :             const auto poFDefn = poSrcLayer->GetLayerDefn();
     259          28 :             const int nCount = poFDefn->GetFieldCount();
     260          71 :             for (int i = 0; i < nCount; ++i)
     261             :             {
     262          45 :                 const auto poSrcFieldDefn = poFDefn->GetFieldDefn(i);
     263          45 :                 const char *pszName = poSrcFieldDefn->GetNameRef();
     264          45 :                 if (srcFields.empty() || contains(srcFields, pszName))
     265             :                 {
     266          43 :                     OGRFieldDefn oField(*poSrcFieldDefn);
     267          43 :                     const std::string outName = prefix + pszName;
     268          43 :                     whileUnsealing(&oField)->SetName(outName.c_str());
     269          80 :                     if (poOutFDefn->GetFieldIndex(outName.c_str()) < 0 &&
     270          37 :                         poDstLayer->CreateField(&oField) != OGRERR_NONE)
     271             :                     {
     272           2 :                         return false;
     273             :                     }
     274             :                 }
     275             :             }
     276          26 :             return true;
     277          16 :         };
     278             : 
     279          16 :         if (!m_noInputFields)
     280             :         {
     281          63 :             if (!GetArg("input-prefix")->IsExplicitlySet() &&
     282          63 :                 m_inputPrefix.empty() && !m_noMethodFields)
     283             :             {
     284          12 :                 m_inputPrefix = "input_";
     285             :             }
     286          16 :             if (!m_inputPrefix.empty())
     287             :             {
     288          12 :                 aosOptions.SetNameValue("INPUT_PREFIX", m_inputPrefix.c_str());
     289             :             }
     290          16 :             if (!CopyFields(poInputLayer, m_inputPrefix, m_inputFields))
     291           2 :                 return false;
     292             :         }
     293             : 
     294          15 :         if (!m_noMethodFields)
     295             :         {
     296          47 :             if (!GetArg("method-prefix")->IsExplicitlySet() &&
     297          47 :                 m_methodPrefix.empty() && !m_noInputFields)
     298             :             {
     299          11 :                 m_methodPrefix = "method_";
     300             :             }
     301          12 :             if (!m_methodPrefix.empty())
     302             :             {
     303             :                 aosOptions.SetNameValue("METHOD_PREFIX",
     304          11 :                                         m_methodPrefix.c_str());
     305             :             }
     306          12 :             if (!CopyFields(poMethodLayer, m_methodPrefix, m_methodFields))
     307           1 :                 return false;
     308             :         }
     309             :     }
     310             : 
     311          20 :     if (OGR_GT_IsSubClassOf(poDstLayer->GetGeomType(), wkbGeometryCollection))
     312             :     {
     313          11 :         aosOptions.SetNameValue("PROMOTE_TO_MULTI", "YES");
     314             :     }
     315             : 
     316             :     const std::map<std::string, decltype(&OGRLayer::Union)>
     317             :         mapOperationToMethod = {
     318             :             {"union", &OGRLayer::Union},
     319             :             {"intersection", &OGRLayer::Intersection},
     320             :             {"sym-difference", &OGRLayer::SymDifference},
     321             :             {"identity", &OGRLayer::Identity},
     322             :             {"update", &OGRLayer::Update},
     323             :             {"clip", &OGRLayer::Clip},
     324             :             {"erase", &OGRLayer::Erase},
     325         180 :         };
     326             : 
     327          20 :     const auto oIter = mapOperationToMethod.find(m_operation);
     328          20 :     CPLAssert(oIter != mapOperationToMethod.end());
     329          20 :     const auto pFunc = oIter->second;
     330          20 :     bool bOK = (poInputLayer->*pFunc)(poMethodLayer, poDstLayer,
     331          20 :                                       aosOptions.List(), ctxt.m_pfnProgress,
     332          20 :                                       ctxt.m_pProgressData) == OGRERR_NONE;
     333          20 :     if (bOK && poNewRetDS)
     334             :     {
     335          14 :         if (bTemporaryFile)
     336             :         {
     337           2 :             bOK = poNewRetDS->FlushCache() == CE_None;
     338             : #if !defined(__APPLE__)
     339             :             // For some unknown reason, unlinking the file on MacOSX
     340             :             // leads to later "disk I/O error". See https://github.com/OSGeo/gdal/issues/13794
     341           2 :             VSIUnlink(poNewRetDS->GetDescription());
     342             : #endif
     343             :         }
     344             : 
     345          14 :         m_outputDataset.Set(std::move(poNewRetDS));
     346             :     }
     347             : 
     348          20 :     return bOK;
     349             : #else
     350             :     (void)ctxt;
     351             :     ReportError(CE_Failure, CPLE_NotSupported,
     352             :                 "This algorithm is only supported for builds against GEOS");
     353             :     return false;
     354             : #endif
     355             : }
     356             : 
     357             : GDALVectorLayerAlgebraAlgorithmStandalone::
     358             :     ~GDALVectorLayerAlgebraAlgorithmStandalone() = default;
     359             : 
     360             : //! @endcond

Generated by: LCOV version 1.14