LCOV - code coverage report
Current view: top level - apps - gdalalg_vector_layer_algebra.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 169 170 99.4 %
Date: 2026-09-11 05:09:32 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         126 : GDALVectorLayerAlgebraAlgorithm::GDALVectorLayerAlgebraAlgorithm(
      35         126 :     bool standaloneStep)
      36             :     : GDALVectorPipelineStepAlgorithm(
      37             :           NAME, DESCRIPTION, HELP_URL,
      38           0 :           ConstructorOptions()
      39         126 :               .SetStandaloneStep(standaloneStep)
      40         126 :               .SetInputDatasetMaxCount(1)
      41         126 :               .SetAddInputLayerNameArgument(false)
      42         126 :               .SetAddDefaultArguments(false)
      43         126 :               .SetAddUpsertArgument(false)
      44         126 :               .SetAddSkipErrorsArgument(false)
      45         126 :               .SetOutputLayerNameAvailableInPipelineStep(true)
      46         252 :               .SetOutputFormatCreateCapability(GDAL_DCAP_CREATE))
      47             : {
      48         126 :     if (standaloneStep)
      49             :     {
      50          83 :         AddProgressArg();
      51             :     }
      52             : 
      53             :     auto &opArg =
      54         252 :         AddArg("operation", 0, _("Operation to perform"), &m_operation)
      55             :             .SetChoices("union", "intersection", "sym-difference", "identity",
      56         126 :                         "update", "clip", "erase")
      57         126 :             .SetRequired();
      58         126 :     if (standaloneStep)
      59          83 :         opArg.SetPositional();
      60             : 
      61         126 :     if (standaloneStep)
      62             :     {
      63          83 :         AddVectorInputArgs(false);
      64             :     }
      65             :     else
      66             :     {
      67          43 :         AddVectorHiddenInputDatasetArg();
      68             :     }
      69             : 
      70             :     {
      71             :         auto &arg = AddArg("method", 0, _("Method vector dataset"),
      72         252 :                            &m_methodDataset, GDAL_OF_VECTOR)
      73         126 :                         .SetRequired();
      74         126 :         if (standaloneStep)
      75          83 :             arg.SetPositional();
      76             : 
      77         126 :         SetAutoCompleteFunctionForFilename(arg, GDAL_OF_VECTOR);
      78             :     }
      79             : 
      80         126 :     if (standaloneStep)
      81             :     {
      82          83 :         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         126 :            &m_inputLayerName);
      92             : 
      93         126 :     AddArg("method-layer", 0, _("Method layer name"), &m_methodLayerName);
      94             : 
      95         126 :     AddGeometryTypeArg(&m_geometryType);
      96             : 
      97             :     AddArg("input-prefix", 0,
      98         252 :            _("Prefix for fields corresponding to input layer"), &m_inputPrefix)
      99         126 :         .SetCategory(GAAC_ADVANCED);
     100             :     AddArg("input-field", 0, _("Input field(s) to add to output layer"),
     101         252 :            &m_inputFields)
     102         252 :         .SetCategory(GAAC_ADVANCED)
     103         126 :         .SetMutualExclusionGroup("input-field");
     104             :     AddArg("no-input-field", 0, _("Do not add any input field to output layer"),
     105         252 :            &m_noInputFields)
     106         252 :         .SetCategory(GAAC_ADVANCED)
     107         126 :         .SetMutualExclusionGroup("input-field");
     108             :     AddArg("all-input-field", 0, _("Add all input fields to output layer"),
     109         252 :            &m_allInputFields)
     110         252 :         .SetCategory(GAAC_ADVANCED)
     111         126 :         .SetMutualExclusionGroup("input-field");
     112             : 
     113             :     AddArg("method-prefix", 0,
     114             :            _("Prefix for fields corresponding to method layer"),
     115         252 :            &m_methodPrefix)
     116         126 :         .SetCategory(GAAC_ADVANCED);
     117             :     AddArg("method-field", 0, _("Method field(s) to add to output layer"),
     118         252 :            &m_methodFields)
     119         252 :         .SetCategory(GAAC_ADVANCED)
     120         126 :         .SetMutualExclusionGroup("method-field");
     121             :     AddArg("no-method-field", 0,
     122         252 :            _("Do not add any method field to output layer"), &m_noMethodFields)
     123         252 :         .SetCategory(GAAC_ADVANCED)
     124         126 :         .SetMutualExclusionGroup("method-field");
     125             :     AddArg("all-method-field", 0, _("Add all method fields to output layer"),
     126         252 :            &m_allMethodFields)
     127         252 :         .SetCategory(GAAC_ADVANCED)
     128         126 :         .SetMutualExclusionGroup("method-field");
     129         126 : }
     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          38 : bool GDALVectorLayerAlgebraAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
     147             :                                               void *pProgressData)
     148             : {
     149          38 :     GDALPipelineStepRunContext stepCtxt;
     150          38 :     stepCtxt.m_pfnProgress = pfnProgress;
     151          38 :     stepCtxt.m_pProgressData = pProgressData;
     152          38 :     return RunPreStepPipelineValidations() && RunStep(stepCtxt);
     153             : }
     154             : 
     155             : /************************************************************************/
     156             : /*              GDALVectorLayerAlgebraAlgorithm::RunImpl()              */
     157             : /************************************************************************/
     158             : 
     159          41 : bool GDALVectorLayerAlgebraAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
     160             : {
     161             : #ifdef HAVE_GEOS
     162          41 :     auto poSrcDS = m_inputDataset[0].GetDatasetRef();
     163          41 :     CPLAssert(poSrcDS);
     164          41 :     auto poMethodDS = m_methodDataset.GetDatasetRef();
     165          41 :     CPLAssert(poMethodDS);
     166             : 
     167          41 :     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          40 :     auto poWriteStep = ctxt.m_poNextUsableStep ? ctxt.m_poNextUsableStep : this;
     175             : 
     176          40 :     GDALDataset *poDstDS = nullptr;
     177          40 :     bool bTemporaryFile = false;
     178          40 :     std::unique_ptr<GDALDataset> poNewRetDS;
     179          80 :     std::string outputLayerName;
     180          40 :     OGRLayer *poDstLayer = nullptr;
     181          40 :     if (!CreateDatasetSingleOutputLayerIfNeeded(ctxt, "output", poDstDS,
     182             :                                                 bTemporaryFile, poNewRetDS,
     183             :                                                 outputLayerName, poDstLayer))
     184             :     {
     185          11 :         return false;
     186             :     }
     187             : 
     188             :     OGRLayer *poInputLayer;
     189          29 :     if (m_inputLayerName.empty() && poSrcDS->GetLayerCount() == 1)
     190          28 :         poInputLayer = poSrcDS->GetLayer(0);
     191             :     else
     192           1 :         poInputLayer = poSrcDS->GetLayerByName(m_inputLayerName.c_str());
     193          29 :     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          28 :     if (m_methodLayerName.empty() && poMethodDS->GetLayerCount() == 1)
     202          27 :         poMethodLayer = poMethodDS->GetLayer(0);
     203             :     else
     204           1 :         poMethodLayer = poMethodDS->GetLayerByName(m_methodLayerName.c_str());
     205          28 :     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          27 :     const auto poInputSRS = poInputLayer->GetSpatialRef();
     213          27 :     const auto poMethodSRS = poMethodLayer->GetSpatialRef();
     214          27 :     if (poInputSRS && !poMethodSRS)
     215             :     {
     216           1 :         ReportError(
     217             :             CE_Warning, CPLE_AppDefined,
     218             :             "Input layer has a CRS, but method layer has none. Assuming "
     219             :             "geometries of method layer to be expressed in input layer CRS.");
     220             :     }
     221          26 :     else if (!poInputSRS && poMethodSRS)
     222             :     {
     223           1 :         ReportError(
     224             :             CE_Warning, CPLE_AppDefined,
     225             :             "Method layer has a CRS, but input layer has none. Assuming "
     226             :             "geometries of input layer to be expressed in method layer CRS.");
     227             :     }
     228          25 :     else if (poInputSRS && poMethodSRS && !poInputSRS->IsSame(poMethodSRS))
     229             :     {
     230           1 :         ReportError(
     231             :             CE_Warning, CPLE_AppDefined,
     232             :             "Input and method layer have non-equivalent CRS. No on-the-fly "
     233             :             "reprojection is performed, and thus results may be incorrect.");
     234             :     }
     235             : 
     236          27 :     if (!poDstLayer)
     237             :     {
     238             :         const CPLStringList aosLayerCreationOptions(
     239          24 :             poWriteStep->GetLayerCreationOptions());
     240             : 
     241             :         const OGRwkbGeometryType eType =
     242          24 :             !m_geometryType.empty() ? OGRFromOGCGeomType(m_geometryType.c_str())
     243          22 :                                     : poInputLayer->GetGeomType();
     244          48 :         poDstLayer = poDstDS->CreateLayer(outputLayerName.c_str(),
     245          24 :                                           poInputLayer->GetSpatialRef(), eType,
     246             :                                           aosLayerCreationOptions.List());
     247          24 :         if (!poDstLayer)
     248           1 :             return false;
     249             :     }
     250             : 
     251          52 :     CPLStringList aosOptions;
     252             : 
     253          26 :     if (m_inputFields.empty() && !m_noInputFields)
     254          19 :         m_allInputFields = true;
     255             : 
     256          26 :     if (m_methodFields.empty() && !m_noMethodFields && !m_allMethodFields)
     257             :     {
     258          36 :         if (m_operation == "update" || m_operation == "clip" ||
     259          17 :             m_operation == "erase")
     260           3 :             m_noMethodFields = true;
     261             :         else
     262          16 :             m_allMethodFields = true;
     263             :     }
     264             : 
     265          26 :     if (m_noInputFields && m_noMethodFields)
     266             :     {
     267           6 :         aosOptions.SetNameValue("ADD_INPUT_FIELDS", "NO");
     268           6 :         aosOptions.SetNameValue("ADD_METHOD_FIELDS", "NO");
     269             :     }
     270             :     else
     271             :     {
     272             :         // Copy fields from input or method layer to output layer
     273             :         const auto CopyFields =
     274          36 :             [poDstLayer](OGRLayer *poSrcLayer, const std::string &prefix,
     275          81 :                          const std::vector<std::string> &srcFields)
     276             :         {
     277             :             const auto contains =
     278           4 :                 [](const std::vector<std::string> &v, const std::string &s)
     279           4 :             { return std::find(v.begin(), v.end(), s) != v.end(); };
     280             : 
     281          36 :             const auto poOutFDefn = poDstLayer->GetLayerDefn();
     282          36 :             const auto poFDefn = poSrcLayer->GetLayerDefn();
     283          36 :             const int nCount = poFDefn->GetFieldCount();
     284          87 :             for (int i = 0; i < nCount; ++i)
     285             :             {
     286          53 :                 const auto poSrcFieldDefn = poFDefn->GetFieldDefn(i);
     287          53 :                 const char *pszName = poSrcFieldDefn->GetNameRef();
     288          53 :                 if (srcFields.empty() || contains(srcFields, pszName))
     289             :                 {
     290          51 :                     OGRFieldDefn oField(*poSrcFieldDefn);
     291          51 :                     const std::string outName = prefix + pszName;
     292          51 :                     whileUnsealing(&oField)->SetName(outName.c_str());
     293          96 :                     if (poOutFDefn->GetFieldIndex(outName.c_str()) < 0 &&
     294          45 :                         poDstLayer->CreateField(&oField) != OGRERR_NONE)
     295             :                     {
     296           2 :                         return false;
     297             :                     }
     298             :                 }
     299             :             }
     300          38 :             for (const auto &name : srcFields)
     301             :             {
     302           4 :                 if (poFDefn->GetFieldIndex(name.c_str()) < 0)
     303             :                 {
     304           2 :                     CPLError(CE_Warning, CPLE_AppDefined, "Unknown field '%s'",
     305             :                              name.c_str());
     306             :                 }
     307             :             }
     308          34 :             return true;
     309          20 :         };
     310             : 
     311          20 :         if (!m_noInputFields)
     312             :         {
     313          79 :             if (!GetArg("input-prefix")->IsExplicitlySet() &&
     314          79 :                 m_inputPrefix.empty() && !m_noMethodFields)
     315             :             {
     316          16 :                 m_inputPrefix = "input_";
     317             :             }
     318          20 :             if (!m_inputPrefix.empty())
     319             :             {
     320          16 :                 aosOptions.SetNameValue("INPUT_PREFIX", m_inputPrefix.c_str());
     321             :             }
     322          20 :             if (!CopyFields(poInputLayer, m_inputPrefix, m_inputFields))
     323           2 :                 return false;
     324             :         }
     325             : 
     326          19 :         if (!m_noMethodFields)
     327             :         {
     328          63 :             if (!GetArg("method-prefix")->IsExplicitlySet() &&
     329          63 :                 m_methodPrefix.empty() && !m_noInputFields)
     330             :             {
     331          15 :                 m_methodPrefix = "method_";
     332             :             }
     333          16 :             if (!m_methodPrefix.empty())
     334             :             {
     335             :                 aosOptions.SetNameValue("METHOD_PREFIX",
     336          15 :                                         m_methodPrefix.c_str());
     337             :             }
     338          16 :             if (!CopyFields(poMethodLayer, m_methodPrefix, m_methodFields))
     339           1 :                 return false;
     340             :         }
     341             :     }
     342             : 
     343             :     aosOptions.SetNameValue(
     344             :         "OUTPUT_GEOMETRY_TYPE",
     345          24 :         OGRToOGCGeomType(poDstLayer->GetGeomType(), false, true));
     346             : 
     347             :     const std::map<std::string, decltype(&OGRLayer::Union)>
     348             :         mapOperationToMethod = {
     349             :             {"union", &OGRLayer::Union},
     350             :             {"intersection", &OGRLayer::Intersection},
     351             :             {"sym-difference", &OGRLayer::SymDifference},
     352             :             {"identity", &OGRLayer::Identity},
     353             :             {"update", &OGRLayer::Update},
     354             :             {"clip", &OGRLayer::Clip},
     355             :             {"erase", &OGRLayer::Erase},
     356         216 :         };
     357             : 
     358          24 :     const auto oIter = mapOperationToMethod.find(m_operation);
     359          24 :     CPLAssert(oIter != mapOperationToMethod.end());
     360          24 :     const auto pFunc = oIter->second;
     361          24 :     bool bOK = (poInputLayer->*pFunc)(poMethodLayer, poDstLayer,
     362          24 :                                       aosOptions.List(), ctxt.m_pfnProgress,
     363          24 :                                       ctxt.m_pProgressData) == OGRERR_NONE;
     364          24 :     if (bOK && poNewRetDS)
     365             :     {
     366          18 :         if (bTemporaryFile)
     367             :         {
     368           2 :             bOK = poNewRetDS->FlushCache() == CE_None;
     369             : #if !defined(__APPLE__)
     370             :             // For some unknown reason, unlinking the file on MacOSX
     371             :             // leads to later "disk I/O error". See https://github.com/OSGeo/gdal/issues/13794
     372           2 :             VSIUnlink(poNewRetDS->GetDescription());
     373             : #endif
     374             :         }
     375             : 
     376          18 :         m_outputDataset.Set(std::move(poNewRetDS));
     377             :     }
     378             : 
     379          24 :     return bOK;
     380             : #else
     381             :     (void)ctxt;
     382             :     ReportError(CE_Failure, CPLE_NotSupported,
     383             :                 "This algorithm is only supported for builds against GEOS");
     384             :     return false;
     385             : #endif
     386             : }
     387             : 
     388             : GDALVectorLayerAlgebraAlgorithmStandalone::
     389             :     ~GDALVectorLayerAlgebraAlgorithmStandalone() = default;
     390             : 
     391             : //! @endcond

Generated by: LCOV version 1.14