LCOV - code coverage report
Current view: top level - apps - gdalalg_vector_combine.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 274 298 91.9 %
Date: 2026-09-11 05:09:32 Functions: 15 16 93.8 %

          Line data    Source code
       1             : /******************************************************************************
       2             : *
       3             :  * Project:  GDAL
       4             :  * Purpose:  "gdal vector combine" subcommand
       5             :  * Author:   Daniel Baston
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2025-2026, ISciences LLC
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "gdalalg_vector_combine.h"
      14             : 
      15             : #include "cpl_enumerate.h"
      16             : #include "cpl_error.h"
      17             : #include "gdal_priv.h"
      18             : #include "gdalalg_vector_geom.h"
      19             : #include "ogr_geometry.h"
      20             : 
      21             : #include <algorithm>
      22             : #include <cinttypes>
      23             : #include <optional>
      24             : 
      25             : #ifndef _
      26             : #define _(x) (x)
      27             : #endif
      28             : 
      29             : //! @cond Doxygen_Suppress
      30             : 
      31         109 : GDALVectorCombineAlgorithm::GDALVectorCombineAlgorithm(bool standaloneStep)
      32             :     : GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
      33         109 :                                       standaloneStep)
      34             : {
      35             :     auto &groupByArg =
      36             :         AddArg("group-by", 0,
      37             :                _("Names of field(s) by which inputs should be grouped"),
      38         218 :                &m_groupBy)
      39         109 :             .SetDuplicateValuesAllowed(false);
      40         218 :     SetAutoCompleteFunctionForFieldName(
      41         109 :         groupByArg, GetArg(GDAL_ARG_NAME_INPUT_LAYER),
      42             :         /* attributeFields = */ true,
      43         109 :         /* geometryFields = */ false, m_inputDataset);
      44             : 
      45             :     AddArg("keep-nested", 0,
      46             :            _("Avoid combining the components of multipart geometries"),
      47         109 :            &m_keepNested);
      48             : 
      49             :     AddArg("add-extra-fields", 0,
      50             :            _("Whether to add extra fields, depending on if they have identical "
      51             :              "values within each group"),
      52         218 :            &m_addExtraFields)
      53         109 :         .SetChoices(NO, SOMETIMES_IDENTICAL, ALWAYS_IDENTICAL)
      54         109 :         .SetDefault(m_addExtraFields)
      55             :         .AddValidationAction(
      56           4 :             [this]()
      57             :             {
      58             :                 // We check the SQLITE driver availability, because we need to
      59             :                 // issue a SQL request using the SQLITE dialect, but that works
      60             :                 // on any source dataset.
      61           8 :                 if (m_addExtraFields != NO &&
      62           4 :                     GetGDALDriverManager()->GetDriverByName("SQLITE") ==
      63             :                         nullptr)
      64             :                 {
      65           0 :                     ReportError(CE_Failure, CPLE_NotSupported,
      66             :                                 "The SQLITE driver must be available for "
      67             :                                 "add-extra-fields=%s",
      68             :                                 m_addExtraFields.c_str());
      69           0 :                     return false;
      70             :                 }
      71           4 :                 return true;
      72         109 :             });
      73         109 : }
      74             : 
      75             : namespace
      76             : {
      77             : class GDALVectorCombineOutputLayer final
      78             :     : public GDALVectorNonStreamingAlgorithmLayer
      79             : {
      80             :     /** Identify which fields have, at least for one group, the same
      81             :      * value within the rows of the group, and add them to the destination
      82             :      * feature definition, after the group-by fields.
      83             :      */
      84           2 :     void IdentifySrcFieldsThatCanBeCopied(GDALDataset &srcDS,
      85             :                                           const std::string &addExtraFields)
      86             :     {
      87           2 :         const OGRFeatureDefn *srcDefn = m_srcLayer.GetLayerDefn();
      88           2 :         if (srcDefn->GetFieldCount() > static_cast<int>(m_groupBy.size()))
      89             :         {
      90           4 :             std::vector<std::pair<std::string, int>> extraFieldCandidates;
      91             : 
      92           2 :             const auto itSrcFields = srcDefn->GetFields();
      93          20 :             for (const auto [iSrcField, srcFieldDefn] :
      94          22 :                  cpl::enumerate(itSrcFields))
      95             :             {
      96          10 :                 const char *fieldName = srcFieldDefn->GetNameRef();
      97          10 :                 if (std::find(m_groupBy.begin(), m_groupBy.end(), fieldName) ==
      98          20 :                     m_groupBy.end())
      99             :                 {
     100             :                     extraFieldCandidates.emplace_back(
     101           8 :                         fieldName, static_cast<int>(iSrcField));
     102             :                 }
     103             :             }
     104             : 
     105           4 :             std::string sql("SELECT ");
     106           2 :             bool addComma = false;
     107          10 :             for (const auto &[fieldName, _] : extraFieldCandidates)
     108             :             {
     109           8 :                 if (addComma)
     110           6 :                     sql += ", ";
     111           8 :                 addComma = true;
     112           8 :                 if (addExtraFields ==
     113             :                     GDALVectorCombineAlgorithm::ALWAYS_IDENTICAL)
     114           4 :                     sql += "MIN(";
     115             :                 else
     116           4 :                     sql += "MAX(";
     117           8 :                 sql += CPLQuotedSQLIdentifier(fieldName.c_str());
     118           8 :                 sql += ')';
     119             :             }
     120           2 :             sql += " FROM (SELECT ";
     121           2 :             addComma = false;
     122          10 :             for (const auto &[fieldName, _] : extraFieldCandidates)
     123             :             {
     124           8 :                 if (addComma)
     125           6 :                     sql += ", ";
     126           8 :                 addComma = true;
     127           8 :                 sql += "(COUNT(DISTINCT COALESCE(";
     128           8 :                 sql += CPLQuotedSQLIdentifier(fieldName.c_str());
     129           8 :                 sql += ", '__NULL__')) == 1) AS ";
     130           8 :                 sql += CPLQuotedSQLIdentifier(fieldName.c_str());
     131             :             }
     132           2 :             sql += " FROM ";
     133           2 :             sql += CPLQuotedSQLIdentifier(GetLayerDefn()->GetName());
     134           2 :             if (!m_groupBy.empty())
     135             :             {
     136           2 :                 sql += " GROUP BY ";
     137           2 :                 addComma = false;
     138           4 :                 for (const auto &fieldName : m_groupBy)
     139             :                 {
     140           2 :                     if (addComma)
     141           0 :                         sql += ", ";
     142           2 :                     addComma = true;
     143           2 :                     sql += CPLQuotedSQLIdentifier(fieldName.c_str());
     144             :                 }
     145             :             }
     146           2 :             sql += ") dummy_table_name";
     147             : 
     148           2 :             auto poSQLyr = srcDS.ExecuteSQL(sql.c_str(), nullptr, "SQLite");
     149           2 :             if (poSQLyr)
     150             :             {
     151             :                 auto poResultFeature =
     152           4 :                     std::unique_ptr<OGRFeature>(poSQLyr->GetNextFeature());
     153           2 :                 if (poResultFeature)
     154             :                 {
     155           2 :                     CPLAssert(poResultFeature->GetFieldCount() ==
     156             :                               static_cast<int>(extraFieldCandidates.size()));
     157          16 :                     for (const auto &[iSqlCol, srcFieldInfo] :
     158          18 :                          cpl::enumerate(extraFieldCandidates))
     159             :                     {
     160           8 :                         const int iSrcField = srcFieldInfo.second;
     161           8 :                         if (poResultFeature->GetFieldAsInteger(
     162           8 :                                 static_cast<int>(iSqlCol)) == 1)
     163             :                         {
     164          10 :                             m_defn->AddFieldDefn(
     165           5 :                                 srcDefn->GetFieldDefn(iSrcField));
     166           5 :                             m_srcExtraFieldIndices.push_back(iSrcField);
     167             :                         }
     168             :                         else
     169             :                         {
     170           3 :                             CPLDebugOnly(
     171             :                                 "gdalalg_vector_combine",
     172             :                                 "Field %s has the same values within a group",
     173             :                                 srcFieldInfo.first.c_str());
     174             :                         }
     175             :                     }
     176             :                 }
     177           2 :                 srcDS.ReleaseResultSet(poSQLyr);
     178             :             }
     179             :         }
     180           2 :     }
     181             : 
     182             :   public:
     183          25 :     explicit GDALVectorCombineOutputLayer(
     184             :         GDALDataset &srcDS, OGRLayer &srcLayer, int geomFieldIndex,
     185             :         const std::vector<std::string> &groupBy, bool keepNested,
     186             :         const std::string &addExtraFields)
     187          25 :         : GDALVectorNonStreamingAlgorithmLayer(srcLayer, geomFieldIndex),
     188             :           m_groupBy(groupBy), m_defn(OGRFeatureDefnRefCountedPtr::makeInstance(
     189          25 :                                   srcLayer.GetLayerDefn()->GetName())),
     190          50 :           m_keepNested(keepNested)
     191             :     {
     192          25 :         const OGRFeatureDefn *srcDefn = m_srcLayer.GetLayerDefn();
     193             : 
     194             :         // Copy field definitions for attribute fields used in
     195             :         // --group-by. All other attributes are discarded.
     196          33 :         for (const auto &fieldName : m_groupBy)
     197             :         {
     198             :             // RunStep already checked that the field exists
     199           8 :             const auto iField = srcDefn->GetFieldIndex(fieldName.c_str());
     200           8 :             CPLAssert(iField >= 0);
     201             : 
     202           8 :             m_srcGroupByFieldIndices.push_back(iField);
     203           8 :             m_defn->AddFieldDefn(srcDefn->GetFieldDefn(iField));
     204             :         }
     205             : 
     206          25 :         if (addExtraFields != GDALVectorCombineAlgorithm::NO)
     207           2 :             IdentifySrcFieldsThatCanBeCopied(srcDS, addExtraFields);
     208             : 
     209             :         // Create a new geometry field corresponding to each input geometry
     210             :         // field. An appropriate type is worked out below.
     211          25 :         m_defn->SetGeomType(wkbNone);  // Remove default geometry field
     212          51 :         for (const OGRGeomFieldDefn *srcGeomDefn : srcDefn->GetGeomFields())
     213             :         {
     214          26 :             const auto eSrcGeomType = srcGeomDefn->GetType();
     215          26 :             const bool bHasZ = CPL_TO_BOOL(OGR_GT_HasZ(eSrcGeomType));
     216          26 :             const bool bHasM = CPL_TO_BOOL(OGR_GT_HasM(eSrcGeomType));
     217             : 
     218             :             OGRwkbGeometryType eDstGeomType =
     219          26 :                 OGR_GT_SetModifier(wkbGeometryCollection, bHasZ, bHasM);
     220             : 
     221             :             // If the layer claims to have single-part geometries, choose a more
     222             :             // specific output type like "MultiPoint" rather than "GeometryCollection"
     223          48 :             if (wkbFlatten(eSrcGeomType) != wkbUnknown &&
     224          22 :                 !OGR_GT_IsSubClassOf(wkbFlatten(eSrcGeomType),
     225             :                                      wkbGeometryCollection))
     226             :             {
     227          20 :                 eDstGeomType = OGR_GT_GetCollection(eSrcGeomType);
     228             :             }
     229             : 
     230             :             auto dstGeomDefn = std::make_unique<OGRGeomFieldDefn>(
     231          52 :                 srcGeomDefn->GetNameRef(), eDstGeomType);
     232          26 :             dstGeomDefn->SetSpatialRef(srcGeomDefn->GetSpatialRef());
     233          26 :             m_defn->AddGeomFieldDefn(std::move(dstGeomDefn));
     234             :         }
     235          25 :     }
     236             : 
     237          19 :     GIntBig GetFeatureCount(int bForce) override
     238             :     {
     239          19 :         if (m_poAttrQuery == nullptr && m_poFilterGeom == nullptr)
     240             :         {
     241          13 :             return static_cast<GIntBig>(m_features.size());
     242             :         }
     243             : 
     244           6 :         return OGRLayer::GetFeatureCount(bForce);
     245             :     }
     246             : 
     247         223 :     const OGRFeatureDefn *GetLayerDefn() const override
     248             :     {
     249         223 :         return m_defn.get();
     250             :     }
     251             : 
     252           4 :     OGRErr IGetExtent(int iGeomField, OGREnvelope *psExtent,
     253             :                       bool bForce) override
     254             :     {
     255           4 :         return m_srcLayer.GetExtent(iGeomField, psExtent, bForce);
     256             :     }
     257             : 
     258           0 :     OGRErr IGetExtent3D(int iGeomField, OGREnvelope3D *psExtent,
     259             :                         bool bForce) override
     260             :     {
     261           0 :         return m_srcLayer.GetExtent3D(iGeomField, psExtent, bForce);
     262             :     }
     263             : 
     264         170 :     std::unique_ptr<OGRFeature> GetNextProcessedFeature() override
     265             :     {
     266         170 :         if (!m_itFeature)
     267             :         {
     268          65 :             m_itFeature = m_features.begin();
     269             :         }
     270             : 
     271         170 :         if (m_itFeature.value() == m_features.end())
     272             :         {
     273          37 :             return nullptr;
     274             :         }
     275             : 
     276             :         std::unique_ptr<OGRFeature> feature(
     277         266 :             m_itFeature.value()->second->Clone());
     278         133 :         feature->SetFID(m_nProcessedFeaturesRead++);
     279         133 :         ++m_itFeature.value();
     280         133 :         return feature;
     281             :     }
     282             : 
     283          25 :     bool Process(GDALProgressFunc pfnProgress, void *pProgressData) override
     284             :     {
     285          25 :         const int nGeomFields = m_srcLayer.GetLayerDefn()->GetGeomFieldCount();
     286             : 
     287             :         const GIntBig nLayerFeatures =
     288          25 :             m_srcLayer.TestCapability(OLCFastFeatureCount)
     289          25 :                 ? m_srcLayer.GetFeatureCount(false)
     290          25 :                 : -1;
     291             :         const double dfInvLayerFeatures =
     292          25 :             1.0 / std::max(1.0, static_cast<double>(nLayerFeatures));
     293             : 
     294          25 :         GIntBig nFeaturesRead = 0;
     295             : 
     296             :         struct PairSourceFeatureUniqueValues
     297             :         {
     298             :             std::unique_ptr<OGRFeature> poSrcFeature{};
     299             :             std::vector<std::optional<std::string>> srcUniqueValues{};
     300             :         };
     301             : 
     302             :         std::map<OGRFeature *, PairSourceFeatureUniqueValues>
     303          50 :             mapDstFeatureToOtherFields;
     304             : 
     305          50 :         std::vector<std::string> fieldValues(m_srcGroupByFieldIndices.size());
     306             :         std::vector<std::string> extraFieldValues(
     307          50 :             m_srcExtraFieldIndices.size());
     308             : 
     309             :         std::vector<int> srcDstFieldMap(
     310          50 :             m_srcLayer.GetLayerDefn()->GetFieldCount(), -1);
     311          16 :         for (const auto [iDstField, iSrcField] :
     312          33 :              cpl::enumerate(m_srcGroupByFieldIndices))
     313             :         {
     314           8 :             srcDstFieldMap[iSrcField] = static_cast<int>(iDstField);
     315             :         }
     316             : 
     317         118 :         for (const auto &srcFeature : m_srcLayer)
     318             :         {
     319         112 :             for (const auto [iDstField, iSrcField] :
     320         205 :                  cpl::enumerate(m_srcGroupByFieldIndices))
     321             :             {
     322          56 :                 fieldValues[iDstField] =
     323          56 :                     srcFeature->GetFieldAsString(iSrcField);
     324             :             }
     325             : 
     326          40 :             for (const auto [iExtraField, iSrcField] :
     327         133 :                  cpl::enumerate(m_srcExtraFieldIndices))
     328             :             {
     329          20 :                 extraFieldValues[iExtraField] =
     330          20 :                     srcFeature->GetFieldAsString(iSrcField);
     331             :             }
     332             : 
     333             :             OGRFeature *dstFeature;
     334             : 
     335          93 :             if (auto it = m_features.find(fieldValues); it == m_features.end())
     336             :             {
     337          39 :                 it = m_features
     338          78 :                          .insert(std::pair(
     339             :                              fieldValues,
     340         117 :                              std::make_unique<OGRFeature>(m_defn.get())))
     341             :                          .first;
     342          39 :                 dstFeature = it->second.get();
     343             : 
     344          39 :                 dstFeature->SetFrom(srcFeature.get(), srcDstFieldMap.data(),
     345             :                                     false);
     346             : 
     347          79 :                 for (int iGeomField = 0; iGeomField < nGeomFields; iGeomField++)
     348             :                 {
     349             :                     OGRGeomFieldDefn *poGeomDefn =
     350          40 :                         m_defn->GetGeomFieldDefn(iGeomField);
     351          40 :                     const auto eGeomType = poGeomDefn->GetType();
     352             : 
     353             :                     std::unique_ptr<OGRGeometry> poGeom(
     354          40 :                         OGRGeometryFactory::createGeometry(eGeomType));
     355          40 :                     poGeom->assignSpatialReference(poGeomDefn->GetSpatialRef());
     356             : 
     357          40 :                     dstFeature->SetGeomField(iGeomField, std::move(poGeom));
     358             :                 }
     359             : 
     360          39 :                 if (!m_srcExtraFieldIndices.empty())
     361             :                 {
     362           8 :                     PairSourceFeatureUniqueValues pair;
     363           4 :                     pair.poSrcFeature.reset(srcFeature->Clone());
     364          14 :                     for (const std::string &s : extraFieldValues)
     365          10 :                         pair.srcUniqueValues.push_back(s);
     366           4 :                     mapDstFeatureToOtherFields[dstFeature] = std::move(pair);
     367             :                 }
     368             :             }
     369             :             else
     370             :             {
     371          54 :                 dstFeature = it->second.get();
     372             : 
     373             :                 // Check that the extra field values for that source feature
     374             :                 // are the same as for other source features of the same group.
     375             :                 // If not the case, cancel the extra field value for that group.
     376          54 :                 if (!m_srcExtraFieldIndices.empty())
     377             :                 {
     378             :                     auto iterOtherFields =
     379           4 :                         mapDstFeatureToOtherFields.find(dstFeature);
     380           4 :                     CPLAssert(iterOtherFields !=
     381             :                               mapDstFeatureToOtherFields.end());
     382             :                     auto &srcUniqueValues =
     383           4 :                         iterOtherFields->second.srcUniqueValues;
     384           4 :                     CPLAssert(srcUniqueValues.size() ==
     385             :                               extraFieldValues.size());
     386          20 :                     for (const auto &[i, sVal] :
     387          24 :                          cpl::enumerate(extraFieldValues))
     388             :                     {
     389          20 :                         if (srcUniqueValues[i].has_value() &&
     390          10 :                             *(srcUniqueValues[i]) != sVal)
     391             :                         {
     392           1 :                             srcUniqueValues[i].reset();
     393             :                         }
     394             :                     }
     395             :                 }
     396             :             }
     397             : 
     398         188 :             for (int iGeomField = 0; iGeomField < nGeomFields; iGeomField++)
     399             :             {
     400             :                 OGRGeomFieldDefn *poGeomFieldDefn =
     401          95 :                     m_defn->GetGeomFieldDefn(iGeomField);
     402             : 
     403             :                 std::unique_ptr<OGRGeometry> poSrcGeom(
     404          95 :                     srcFeature->StealGeometry(iGeomField));
     405          95 :                 if (poSrcGeom != nullptr && !poSrcGeom->IsEmpty())
     406             :                 {
     407          93 :                     const auto eSrcType = poSrcGeom->getGeometryType();
     408          93 :                     const auto bSrcIsCollection = OGR_GT_IsSubClassOf(
     409             :                         wkbFlatten(eSrcType), wkbGeometryCollection);
     410             :                     const auto bDstIsUntypedCollection =
     411          93 :                         wkbFlatten(poGeomFieldDefn->GetType()) ==
     412          93 :                         wkbGeometryCollection;
     413             : 
     414             :                     // Did this geometry unexpectedly have Z?
     415          93 :                     if (OGR_GT_HasZ(eSrcType) !=
     416          93 :                         OGR_GT_HasZ(poGeomFieldDefn->GetType()))
     417             :                     {
     418           4 :                         AddZ(iGeomField);
     419             :                     }
     420             : 
     421             :                     // Did this geometry unexpectedly have M?
     422          93 :                     if (OGR_GT_HasM(eSrcType) !=
     423          93 :                         OGR_GT_HasM(poGeomFieldDefn->GetType()))
     424             :                     {
     425          10 :                         AddM(iGeomField);
     426             :                     }
     427             : 
     428             :                     // Do we need to change the output from a typed collection
     429             :                     // like MultiPolygon to a generic GeometryCollection?
     430          93 :                     if (m_keepNested && bSrcIsCollection &&
     431           3 :                         !bDstIsUntypedCollection)
     432             :                     {
     433           2 :                         SetTypeGeometryCollection(iGeomField);
     434             :                     }
     435             : 
     436             :                     OGRGeometryCollection *poDstGeom =
     437          93 :                         cpl::down_cast<OGRGeometryCollection *>(
     438             :                             dstFeature->GetGeomFieldRef(iGeomField));
     439             : 
     440          93 :                     if (m_keepNested || !bSrcIsCollection)
     441             :                     {
     442             :                         // A Triangle is not an acceptable MultiPolygon
     443             :                         // member. addGeometryDirectly() leaves us its
     444             :                         // ownership then, and we convert it.
     445             :                         OGRErr eErr =
     446          70 :                             poDstGeom->addGeometryDirectly(poSrcGeom.get());
     447          70 :                         if (eErr == OGRERR_NONE)
     448             :                         {
     449          66 :                             CPL_IGNORE_RET_VAL(poSrcGeom.release());
     450             :                         }
     451             :                         else
     452             :                         {
     453           4 :                             eErr = poDstGeom->addGeometry(
     454          12 :                                 OGRGeometryFactory::forceTo(
     455           4 :                                     std::move(poSrcGeom),
     456             :                                     OGR_GT_GetSingle(
     457           4 :                                         poDstGeom->getGeometryType())));
     458             :                         }
     459          70 :                         if (eErr != OGRERR_NONE)
     460             :                         {
     461           0 :                             CPLError(
     462             :                                 CE_Failure, CPLE_AppDefined,
     463             :                                 "Failed to add geometry of type %s to output "
     464             :                                 "feature of type %s",
     465             :                                 OGRGeometryTypeToName(eSrcType),
     466             :                                 OGRGeometryTypeToName(
     467           0 :                                     poDstGeom->getGeometryType()));
     468           0 :                             return false;
     469          70 :                         }
     470             :                     }
     471             :                     else
     472             :                     {
     473             :                         std::unique_ptr<OGRGeometryCollection>
     474             :                             poSrcGeomCollection(
     475          23 :                                 poSrcGeom.release()->toGeometryCollection());
     476          46 :                         if (poDstGeom->addGeometryComponents(
     477          46 :                                 std::move(poSrcGeomCollection)) != OGRERR_NONE)
     478             :                         {
     479           0 :                             CPLError(CE_Failure, CPLE_AppDefined,
     480             :                                      "Failed to add components from geometry "
     481             :                                      "of type %s to output "
     482             :                                      "feature of type %s",
     483             :                                      OGRGeometryTypeToName(eSrcType),
     484             :                                      OGRGeometryTypeToName(
     485           0 :                                          poDstGeom->getGeometryType()));
     486           0 :                             return false;
     487             :                         }
     488             :                     }
     489             :                 }
     490             :             }
     491             : 
     492          93 :             if (pfnProgress && nLayerFeatures > 0 &&
     493           0 :                 !pfnProgress(static_cast<double>(++nFeaturesRead) *
     494             :                                  dfInvLayerFeatures,
     495             :                              "", pProgressData))
     496             :             {
     497           0 :                 CPLError(CE_Failure, CPLE_UserInterrupt, "Interrupted by user");
     498           0 :                 return false;
     499             :             }
     500             :         }
     501             : 
     502             :         // Copy extra fields from source features that have a same value
     503             :         // among each groups
     504           8 :         for (const auto &[poDstFeature, pairSourceFeatureUniqueValues] :
     505          33 :              mapDstFeatureToOtherFields)
     506             :         {
     507          20 :             for (const auto [iExtraField, iSrcField] :
     508          24 :                  cpl::enumerate(m_srcExtraFieldIndices))
     509             :             {
     510             :                 const int iDstField = static_cast<int>(
     511          10 :                     m_srcGroupByFieldIndices.size() + iExtraField);
     512          10 :                 if (pairSourceFeatureUniqueValues.srcUniqueValues[iExtraField])
     513             :                 {
     514             :                     const auto poRawField =
     515             :                         pairSourceFeatureUniqueValues.poSrcFeature
     516           9 :                             ->GetRawFieldRef(iSrcField);
     517           9 :                     poDstFeature->SetField(iDstField, poRawField);
     518             :                 }
     519             :             }
     520             :         }
     521             : 
     522          25 :         if (pfnProgress)
     523             :         {
     524           0 :             pfnProgress(1.0, "", pProgressData);
     525             :         }
     526             : 
     527          25 :         return true;
     528             :     }
     529             : 
     530          39 :     bool TestCapability(const char *pszCap) const override
     531             :     {
     532          39 :         if (EQUAL(pszCap, OLCFastFeatureCount))
     533             :         {
     534           0 :             return true;
     535             :         }
     536             : 
     537          39 :         if (EQUAL(pszCap, OLCStringsAsUTF8) ||
     538          26 :             EQUAL(pszCap, OLCFastGetExtent) ||
     539          24 :             EQUAL(pszCap, OLCFastGetExtent3D) ||
     540          24 :             EQUAL(pszCap, OLCCurveGeometries) ||
     541          20 :             EQUAL(pszCap, OLCMeasuredGeometries) ||
     542          16 :             EQUAL(pszCap, OLCZGeometries))
     543             :         {
     544          26 :             return m_srcLayer.TestCapability(pszCap);
     545             :         }
     546             : 
     547          13 :         return false;
     548             :     }
     549             : 
     550          99 :     void ResetReading() override
     551             :     {
     552          99 :         m_itFeature.reset();
     553          99 :         m_nProcessedFeaturesRead = 0;
     554          99 :     }
     555             : 
     556             :     CPL_DISALLOW_COPY_ASSIGN(GDALVectorCombineOutputLayer)
     557             : 
     558             :   private:
     559          10 :     void AddM(int iGeomField)
     560             :     {
     561             :         OGRGeomFieldDefn *poGeomFieldDefn =
     562          10 :             m_defn->GetGeomFieldDefn(iGeomField);
     563          20 :         whileUnsealing(poGeomFieldDefn)
     564          10 :             ->SetType(OGR_GT_SetM(poGeomFieldDefn->GetType()));
     565             : 
     566          20 :         for (auto &[_, poFeature] : m_features)
     567             :         {
     568          10 :             poFeature->GetGeomFieldRef(iGeomField)->setMeasured(true);
     569             :         }
     570          10 :     }
     571             : 
     572           4 :     void AddZ(int iGeomField)
     573             :     {
     574             :         OGRGeomFieldDefn *poGeomFieldDefn =
     575           4 :             m_defn->GetGeomFieldDefn(iGeomField);
     576           8 :         whileUnsealing(poGeomFieldDefn)
     577           4 :             ->SetType(OGR_GT_SetZ(poGeomFieldDefn->GetType()));
     578             : 
     579           8 :         for (auto &[_, poFeature] : m_features)
     580             :         {
     581           4 :             poFeature->GetGeomFieldRef(iGeomField)->set3D(true);
     582             :         }
     583           4 :     }
     584             : 
     585           2 :     void SetTypeGeometryCollection(int iGeomField)
     586             :     {
     587             :         OGRGeomFieldDefn *poGeomFieldDefn =
     588           2 :             m_defn->GetGeomFieldDefn(iGeomField);
     589           2 :         const bool hasZ = CPL_TO_BOOL(OGR_GT_HasZ(poGeomFieldDefn->GetType()));
     590           2 :         const bool hasM = CPL_TO_BOOL(OGR_GT_HasM(poGeomFieldDefn->GetType()));
     591             : 
     592           4 :         whileUnsealing(poGeomFieldDefn)
     593           2 :             ->SetType(OGR_GT_SetModifier(wkbGeometryCollection, hasZ, hasM));
     594             : 
     595           4 :         for (auto &[_, poFeature] : m_features)
     596             :         {
     597             :             std::unique_ptr<OGRGeometry> poTmpGeom(
     598           2 :                 poFeature->StealGeometry(iGeomField));
     599           4 :             poTmpGeom = OGRGeometryFactory::forceTo(std::move(poTmpGeom),
     600           2 :                                                     poGeomFieldDefn->GetType());
     601           2 :             CPLAssert(poTmpGeom);
     602           2 :             poFeature->SetGeomField(iGeomField, std::move(poTmpGeom));
     603             :         }
     604           2 :     }
     605             : 
     606             :     const std::vector<std::string> m_groupBy{};
     607             :     std::vector<int> m_srcGroupByFieldIndices{};
     608             :     std::vector<int> m_srcExtraFieldIndices{};
     609             :     std::map<std::vector<std::string>, std::unique_ptr<OGRFeature>>
     610             :         m_features{};
     611             :     std::optional<decltype(m_features)::const_iterator> m_itFeature{};
     612             :     const OGRFeatureDefnRefCountedPtr m_defn;
     613             :     GIntBig m_nProcessedFeaturesRead = 0;
     614             :     const bool m_keepNested;
     615             : };
     616             : }  // namespace
     617             : 
     618          26 : bool GDALVectorCombineAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
     619             : {
     620          26 :     auto poSrcDS = m_inputDataset[0].GetDatasetRef();
     621             :     auto poDstDS =
     622          52 :         std::make_unique<GDALVectorNonStreamingAlgorithmDataset>(*poSrcDS);
     623             : 
     624          52 :     GDALVectorAlgorithmLayerProgressHelper progressHelper(ctxt);
     625             : 
     626          51 :     for (auto &&poSrcLayer : poSrcDS->GetLayers())
     627             :     {
     628          26 :         if (m_inputLayerNames.empty() ||
     629           0 :             std::find(m_inputLayerNames.begin(), m_inputLayerNames.end(),
     630          26 :                       poSrcLayer->GetDescription()) != m_inputLayerNames.end())
     631             :         {
     632          26 :             const auto poSrcLayerDefn = poSrcLayer->GetLayerDefn();
     633          26 :             if (poSrcLayerDefn->GetGeomFieldCount() == 0)
     634             :             {
     635           0 :                 if (m_inputLayerNames.empty())
     636           0 :                     continue;
     637           0 :                 ReportError(CE_Failure, CPLE_AppDefined,
     638             :                             "Specified layer '%s' has no geometry field",
     639           0 :                             poSrcLayer->GetDescription());
     640           0 :                 return false;
     641             :             }
     642             : 
     643             :             // Check that all attributes exist
     644          34 :             for (const auto &fieldName : m_groupBy)
     645             :             {
     646             :                 const int iSrcFieldIndex =
     647           9 :                     poSrcLayerDefn->GetFieldIndex(fieldName.c_str());
     648           9 :                 if (iSrcFieldIndex == -1)
     649             :                 {
     650           1 :                     ReportError(CE_Failure, CPLE_AppDefined,
     651             :                                 "Specified attribute field '%s' does not exist "
     652             :                                 "in layer '%s'",
     653             :                                 fieldName.c_str(),
     654           1 :                                 poSrcLayer->GetDescription());
     655           1 :                     return false;
     656             :                 }
     657             :             }
     658             : 
     659          25 :             progressHelper.AddProcessedLayer(*poSrcLayer);
     660             :         }
     661             :     }
     662             : 
     663          50 :     for ([[maybe_unused]] auto [poSrcLayer, bProcessed, layerProgressFunc,
     664         100 :                                 layerProgressData] : progressHelper)
     665             :     {
     666             :         auto poLayer = std::make_unique<GDALVectorCombineOutputLayer>(
     667           0 :             *poSrcDS, *poSrcLayer, -1, m_groupBy, m_keepNested,
     668          25 :             m_addExtraFields);
     669             : 
     670          25 :         if (!poDstDS->AddProcessedLayer(std::move(poLayer), layerProgressFunc,
     671             :                                         layerProgressData.get()))
     672             :         {
     673           0 :             return false;
     674             :         }
     675             :     }
     676             : 
     677          25 :     m_outputDataset.Set(std::move(poDstDS));
     678             : 
     679          25 :     return true;
     680             : }
     681             : 
     682             : GDALVectorCombineAlgorithmStandalone::~GDALVectorCombineAlgorithmStandalone() =
     683             :     default;
     684             : 
     685             : //! @endcond

Generated by: LCOV version 1.14