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

Generated by: LCOV version 1.14