LCOV - code coverage report
Current view: top level - apps - gdalalg_vector_layer_algebra.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 197 197 100.0 %
Date: 2025-09-10 17:48:50 Functions: 4 4 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             : 
      15             : #include "cpl_conv.h"
      16             : #include "gdal_priv.h"
      17             : #include "gdal_utils.h"
      18             : #include "ogr_api.h"
      19             : #include "ogrsf_frmts.h"
      20             : 
      21             : #include <algorithm>
      22             : 
      23             : //! @cond Doxygen_Suppress
      24             : 
      25             : #ifndef _
      26             : #define _(x) (x)
      27             : #endif
      28             : 
      29             : /************************************************************************/
      30             : /*                 GDALVectorLayerAlgebraAlgorithm()                    */
      31             : /************************************************************************/
      32             : 
      33          37 : GDALVectorLayerAlgebraAlgorithm::GDALVectorLayerAlgebraAlgorithm()
      34          37 :     : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
      35             : {
      36          37 :     AddProgressArg();
      37             : 
      38          74 :     AddArg("operation", 0, _("Operation to perform"), &m_operation)
      39             :         .SetChoices("union", "intersection", "sym-difference", "identity",
      40          37 :                     "update", "clip", "erase")
      41          37 :         .SetRequired()
      42          37 :         .SetPositional();
      43             : 
      44          37 :     AddOutputFormatArg(&m_format).AddMetadataItem(
      45         111 :         GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_VECTOR, GDAL_DCAP_CREATE});
      46          37 :     AddOpenOptionsArg(&m_openOptions);
      47          37 :     AddInputFormatsArg(&m_inputFormats)
      48          74 :         .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_VECTOR});
      49          37 :     AddInputDatasetArg(&m_inputDataset, GDAL_OF_VECTOR);
      50             : 
      51             :     {
      52             :         auto &arg = AddArg("method", 0, _("Method vector dataset"),
      53          74 :                            &m_methodDataset, GDAL_OF_VECTOR)
      54          37 :                         .SetPositional()
      55          37 :                         .SetRequired();
      56             : 
      57          37 :         SetAutoCompleteFunctionForFilename(arg, GDAL_OF_VECTOR);
      58             :     }
      59          37 :     AddOutputDatasetArg(&m_outputDataset, GDAL_OF_VECTOR)
      60          37 :         .SetDatasetInputFlags(GADV_NAME | GADV_OBJECT);
      61          37 :     AddCreationOptionsArg(&m_creationOptions);
      62          37 :     AddLayerCreationOptionsArg(&m_layerCreationOptions);
      63          37 :     AddOverwriteArg(&m_overwrite);
      64          37 :     AddUpdateArg(&m_update);
      65          37 :     AddOverwriteLayerArg(&m_overwriteLayer);
      66          37 :     AddAppendLayerArg(&m_appendLayer);
      67             : 
      68             :     AddArg(GDAL_ARG_NAME_INPUT_LAYER, 0, _("Input layer name"),
      69          37 :            &m_inputLayerName);
      70          37 :     AddArg("method-layer", 0, _("Method layer name"), &m_methodLayerName);
      71          37 :     AddOutputLayerNameArg(&m_outputLayerName)
      72          37 :         .AddHiddenAlias("nln");  // For ogr2ogr nostalgic people
      73             : 
      74          37 :     AddGeometryTypeArg(&m_geometryType);
      75             : 
      76             :     AddArg("input-prefix", 0,
      77          74 :            _("Prefix for fields corresponding to input layer"), &m_inputPrefix)
      78          37 :         .SetCategory(GAAC_ADVANCED);
      79             :     AddArg("input-field", 0, _("Input field(s) to add to output layer"),
      80          74 :            &m_inputFields)
      81          74 :         .SetCategory(GAAC_ADVANCED)
      82          37 :         .SetMutualExclusionGroup("input-field");
      83             :     AddArg("no-input-field", 0, _("Do not add any input field to output layer"),
      84          74 :            &m_noInputFields)
      85          74 :         .SetCategory(GAAC_ADVANCED)
      86          37 :         .SetMutualExclusionGroup("input-field");
      87             :     AddArg("all-input-field", 0, _("Add all input fields to output layer"),
      88          74 :            &m_allInputFields)
      89          74 :         .SetCategory(GAAC_ADVANCED)
      90          37 :         .SetMutualExclusionGroup("input-field");
      91             : 
      92             :     AddArg("method-prefix", 0,
      93             :            _("Prefix for fields corresponding to method layer"),
      94          74 :            &m_methodPrefix)
      95          37 :         .SetCategory(GAAC_ADVANCED);
      96             :     AddArg("method-field", 0, _("Method field(s) to add to output layer"),
      97          74 :            &m_methodFields)
      98          74 :         .SetCategory(GAAC_ADVANCED)
      99          37 :         .SetMutualExclusionGroup("method-field");
     100             :     AddArg("no-method-field", 0,
     101          74 :            _("Do not add any method field to output layer"), &m_noMethodFields)
     102          74 :         .SetCategory(GAAC_ADVANCED)
     103          37 :         .SetMutualExclusionGroup("method-field");
     104             :     AddArg("all-method-field", 0, _("Add all method fields to output layer"),
     105          74 :            &m_allMethodFields)
     106          74 :         .SetCategory(GAAC_ADVANCED)
     107          37 :         .SetMutualExclusionGroup("method-field");
     108          37 : }
     109             : 
     110             : /************************************************************************/
     111             : /*               GDALVectorLayerAlgebraAlgorithm::RunImpl()             */
     112             : /************************************************************************/
     113             : 
     114          35 : bool GDALVectorLayerAlgebraAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
     115             :                                               void *pProgressData)
     116             : {
     117             : #ifdef HAVE_GEOS
     118          35 :     auto poSrcDS = m_inputDataset.GetDatasetRef();
     119          35 :     CPLAssert(poSrcDS);
     120          35 :     auto poMethodDS = m_methodDataset.GetDatasetRef();
     121          35 :     CPLAssert(poMethodDS);
     122             : 
     123          35 :     if (poSrcDS == poMethodDS)
     124             :     {
     125           1 :         ReportError(CE_Failure, CPLE_NotSupported,
     126             :                     "Input and method datasets must be different");
     127           1 :         return false;
     128             :     }
     129             : 
     130          34 :     auto poDstDS = m_outputDataset.GetDatasetRef();
     131          34 :     std::unique_ptr<GDALDataset> poDstDSUniquePtr;
     132          34 :     const bool bNewDataset = poDstDS == nullptr;
     133          34 :     if (poDstDS == nullptr)
     134             :     {
     135          19 :         if (m_format.empty())
     136             :         {
     137             :             const CPLStringList aosFormats(GDALGetOutputDriversForDatasetName(
     138          11 :                 m_outputDataset.GetName().c_str(), GDAL_OF_VECTOR,
     139             :                 /* bSingleMatch = */ true,
     140          11 :                 /* bEmitWarning = */ true));
     141          11 :             if (aosFormats.size() != 1)
     142             :             {
     143           1 :                 ReportError(CE_Failure, CPLE_AppDefined,
     144             :                             "Cannot guess driver for %s",
     145           1 :                             m_outputDataset.GetName().c_str());
     146           1 :                 return false;
     147             :             }
     148          10 :             m_format = aosFormats[0];
     149             :         }
     150             : 
     151             :         auto poOutDrv =
     152          18 :             GetGDALDriverManager()->GetDriverByName(m_format.c_str());
     153          18 :         if (!poOutDrv)
     154             :         {
     155             :             // shouldn't happen given checks done in GDALAlgorithm unless
     156             :             // someone deregister the driver between ParseCommandLineArgs() and
     157             :             // Run()
     158           1 :             ReportError(CE_Failure, CPLE_AppDefined, "Driver %s does not exist",
     159             :                         m_format.c_str());
     160           1 :             return false;
     161             :         }
     162             : 
     163          17 :         const CPLStringList aosCreationOptions(m_creationOptions);
     164          34 :         poDstDSUniquePtr.reset(
     165          17 :             poOutDrv->Create(m_outputDataset.GetName().c_str(), 0, 0, 0,
     166             :                              GDT_Unknown, aosCreationOptions.List()));
     167          17 :         poDstDS = poDstDSUniquePtr.get();
     168          17 :         if (!poDstDS)
     169           1 :             return false;
     170             :     }
     171             : 
     172          31 :     OGRLayer *poDstLayer = nullptr;
     173             : 
     174          31 :     if (m_outputLayerName.empty())
     175             :     {
     176          24 :         if (bNewDataset)
     177             :         {
     178          16 :             auto poOutDrv = poDstDS->GetDriver();
     179          16 :             if (poOutDrv && EQUAL(poOutDrv->GetDescription(), "ESRI Shapefile"))
     180             :                 m_outputLayerName =
     181           5 :                     CPLGetBasenameSafe(m_outputDataset.GetName().c_str());
     182             :             else
     183          11 :                 m_outputLayerName = "output";
     184             :         }
     185           8 :         else if (m_appendLayer)
     186             :         {
     187           3 :             if (poDstDS->GetLayerCount() == 1)
     188           2 :                 poDstLayer = poDstDS->GetLayer(0);
     189             :             else
     190             :             {
     191           1 :                 ReportError(CE_Failure, CPLE_AppDefined,
     192             :                             "--output-layer should be specified");
     193           1 :                 return false;
     194             :             }
     195             :         }
     196           5 :         else if (m_overwriteLayer)
     197             :         {
     198           3 :             if (poDstDS->GetLayerCount() == 1)
     199             :             {
     200           2 :                 if (poDstDS->DeleteLayer(0) != OGRERR_NONE)
     201             :                 {
     202           1 :                     return false;
     203             :                 }
     204             :             }
     205             :             else
     206             :             {
     207           1 :                 ReportError(CE_Failure, CPLE_AppDefined,
     208             :                             "--output-layer should be specified");
     209           1 :                 return false;
     210             :             }
     211             :         }
     212             :         else
     213             :         {
     214           2 :             ReportError(CE_Failure, CPLE_AppDefined,
     215             :                         "--output-layer should be specified");
     216           2 :             return false;
     217             :         }
     218             :     }
     219           7 :     else if (m_overwriteLayer)
     220             :     {
     221           3 :         const int nLayerIdx = poDstDS->GetLayerIndex(m_outputLayerName.c_str());
     222           3 :         if (nLayerIdx < 0)
     223             :         {
     224           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     225             :                         "Layer '%s' does not exist", m_outputLayerName.c_str());
     226           1 :             return false;
     227             :         }
     228           2 :         if (poDstDS->DeleteLayer(nLayerIdx) != OGRERR_NONE)
     229             :         {
     230           1 :             return false;
     231             :         }
     232             :     }
     233           4 :     else if (m_appendLayer)
     234             :     {
     235           2 :         poDstLayer = poDstDS->GetLayerByName(m_outputLayerName.c_str());
     236           2 :         if (!poDstLayer)
     237             :         {
     238           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     239             :                         "Layer '%s' does not exist", m_outputLayerName.c_str());
     240           1 :             return false;
     241             :         }
     242             :     }
     243             : 
     244          23 :     if (!bNewDataset && m_update && !m_appendLayer && !m_overwriteLayer)
     245             :     {
     246           2 :         poDstLayer = poDstDS->GetLayerByName(m_outputLayerName.c_str());
     247           2 :         if (poDstLayer)
     248             :         {
     249           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     250             :                         "Output layer '%s' already exists. Specify "
     251             :                         "--%s, --%s, --%s or "
     252             :                         "--%s + --output-layer with a different name",
     253             :                         m_outputLayerName.c_str(), GDAL_ARG_NAME_OVERWRITE,
     254             :                         GDAL_ARG_NAME_OVERWRITE_LAYER, GDAL_ARG_NAME_APPEND,
     255             :                         GDAL_ARG_NAME_UPDATE);
     256           1 :             return false;
     257             :         }
     258             :     }
     259             : 
     260             :     OGRLayer *poInputLayer;
     261          22 :     if (m_inputLayerName.empty() && poSrcDS->GetLayerCount() == 1)
     262          21 :         poInputLayer = poSrcDS->GetLayer(0);
     263             :     else
     264           1 :         poInputLayer = poSrcDS->GetLayerByName(m_inputLayerName.c_str());
     265          22 :     if (!poInputLayer)
     266             :     {
     267           1 :         ReportError(CE_Failure, CPLE_AppDefined, "Cannot get input layer '%s'",
     268             :                     m_inputLayerName.c_str());
     269           1 :         return false;
     270             :     }
     271             : 
     272             :     OGRLayer *poMethodLayer;
     273          21 :     if (m_methodLayerName.empty() && poMethodDS->GetLayerCount() == 1)
     274          20 :         poMethodLayer = poMethodDS->GetLayer(0);
     275             :     else
     276           1 :         poMethodLayer = poMethodDS->GetLayerByName(m_methodLayerName.c_str());
     277          21 :     if (!poMethodLayer)
     278             :     {
     279           1 :         ReportError(CE_Failure, CPLE_AppDefined, "Cannot get method layer '%s'",
     280             :                     m_methodLayerName.c_str());
     281           1 :         return false;
     282             :     }
     283             : 
     284          20 :     if (bNewDataset || !m_appendLayer)
     285             :     {
     286          17 :         const CPLStringList aosLayerCreationOptions(m_layerCreationOptions);
     287             : 
     288             :         const OGRwkbGeometryType eType =
     289          17 :             !m_geometryType.empty() ? OGRFromOGCGeomType(m_geometryType.c_str())
     290          16 :                                     : poInputLayer->GetGeomType();
     291          34 :         poDstLayer = poDstDS->CreateLayer(m_outputLayerName.c_str(),
     292          17 :                                           poInputLayer->GetSpatialRef(), eType,
     293             :                                           aosLayerCreationOptions.List());
     294             :     }
     295          20 :     if (!poDstLayer)
     296           1 :         return false;
     297             : 
     298          38 :     CPLStringList aosOptions;
     299             : 
     300          19 :     if (m_inputFields.empty() && !m_noInputFields)
     301          12 :         m_allInputFields = true;
     302             : 
     303          19 :     if (m_methodFields.empty() && !m_noMethodFields && !m_allMethodFields)
     304             :     {
     305          22 :         if (m_operation == "update" || m_operation == "clip" ||
     306          10 :             m_operation == "erase")
     307           3 :             m_noMethodFields = true;
     308             :         else
     309           9 :             m_allMethodFields = true;
     310             :     }
     311             : 
     312          19 :     if (m_noInputFields && m_noMethodFields)
     313             :     {
     314           6 :         aosOptions.SetNameValue("ADD_INPUT_FIELDS", "NO");
     315           6 :         aosOptions.SetNameValue("ADD_METHOD_FIELDS", "NO");
     316             :     }
     317             :     else
     318             :     {
     319             :         // Copy fields from input or method layer to output layer
     320             :         const auto CopyFields =
     321          22 :             [poDstLayer](OGRLayer *poSrcLayer, const std::string &prefix,
     322          53 :                          const std::vector<std::string> &srcFields)
     323             :         {
     324             :             const auto contains =
     325           4 :                 [](const std::vector<std::string> &v, const std::string &s)
     326           4 :             { return std::find(v.begin(), v.end(), s) != v.end(); };
     327             : 
     328          22 :             const auto poOutFDefn = poDstLayer->GetLayerDefn();
     329          22 :             const auto poFDefn = poSrcLayer->GetLayerDefn();
     330          22 :             const int nCount = poFDefn->GetFieldCount();
     331          59 :             for (int i = 0; i < nCount; ++i)
     332             :             {
     333          39 :                 const auto poSrcFieldDefn = poFDefn->GetFieldDefn(i);
     334          39 :                 const char *pszName = poSrcFieldDefn->GetNameRef();
     335          39 :                 if (srcFields.empty() || contains(srcFields, pszName))
     336             :                 {
     337          37 :                     OGRFieldDefn oField(*poSrcFieldDefn);
     338          37 :                     const std::string outName = prefix + pszName;
     339          37 :                     whileUnsealing(&oField)->SetName(outName.c_str());
     340          68 :                     if (poOutFDefn->GetFieldIndex(outName.c_str()) < 0 &&
     341          31 :                         poDstLayer->CreateField(&oField) != OGRERR_NONE)
     342             :                     {
     343           2 :                         return false;
     344             :                     }
     345             :                 }
     346             :             }
     347          20 :             return true;
     348          13 :         };
     349             : 
     350          13 :         if (!m_noInputFields)
     351             :         {
     352          51 :             if (!GetArg("input-prefix")->IsExplicitlySet() &&
     353          51 :                 m_inputPrefix.empty() && !m_noMethodFields)
     354             :             {
     355           9 :                 m_inputPrefix = "input_";
     356             :             }
     357          13 :             if (!m_inputPrefix.empty())
     358             :             {
     359           9 :                 aosOptions.SetNameValue("INPUT_PREFIX", m_inputPrefix.c_str());
     360             :             }
     361          13 :             if (!CopyFields(poInputLayer, m_inputPrefix, m_inputFields))
     362           2 :                 return false;
     363             :         }
     364             : 
     365          12 :         if (!m_noMethodFields)
     366             :         {
     367          35 :             if (!GetArg("method-prefix")->IsExplicitlySet() &&
     368          35 :                 m_methodPrefix.empty() && !m_noInputFields)
     369             :             {
     370           8 :                 m_methodPrefix = "method_";
     371             :             }
     372           9 :             if (!m_methodPrefix.empty())
     373             :             {
     374             :                 aosOptions.SetNameValue("METHOD_PREFIX",
     375           8 :                                         m_methodPrefix.c_str());
     376             :             }
     377           9 :             if (!CopyFields(poMethodLayer, m_methodPrefix, m_methodFields))
     378           1 :                 return false;
     379             :         }
     380             :     }
     381             : 
     382          17 :     if (OGR_GT_IsSubClassOf(poDstLayer->GetGeomType(), wkbGeometryCollection))
     383             :     {
     384           1 :         aosOptions.SetNameValue("PROMOTE_TO_MULTI", "YES");
     385             :     }
     386             : 
     387             :     const std::map<std::string, decltype(&OGRLayer::Union)>
     388             :         mapOperationToMethod = {
     389             :             {"union", &OGRLayer::Union},
     390             :             {"intersection", &OGRLayer::Intersection},
     391             :             {"sym-difference", &OGRLayer::SymDifference},
     392             :             {"identity", &OGRLayer::Identity},
     393             :             {"update", &OGRLayer::Update},
     394             :             {"clip", &OGRLayer::Clip},
     395             :             {"erase", &OGRLayer::Erase},
     396         153 :         };
     397             : 
     398          17 :     const auto oIter = mapOperationToMethod.find(m_operation);
     399          17 :     CPLAssert(oIter != mapOperationToMethod.end());
     400          17 :     const auto pFunc = oIter->second;
     401             :     const bool bOK =
     402          17 :         (poInputLayer->*pFunc)(poMethodLayer, poDstLayer, aosOptions.List(),
     403          17 :                                pfnProgress, pProgressData) == OGRERR_NONE;
     404          17 :     if (bOK && !m_outputDataset.GetDatasetRef())
     405             :     {
     406          11 :         m_outputDataset.Set(std::move(poDstDSUniquePtr));
     407             :     }
     408             : 
     409          17 :     return bOK;
     410             : #else
     411             :     (void)pfnProgress;
     412             :     (void)pProgressData;
     413             :     ReportError(CE_Failure, CPLE_NotSupported,
     414             :                 "This algorithm is only supported for builds against GEOS");
     415             :     return false;
     416             : #endif
     417             : }
     418             : 
     419             : //! @endcond

Generated by: LCOV version 1.14