LCOV - code coverage report
Current view: top level - apps - gdalalg_vector_combine.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 176 196 89.8 %
Date: 2026-03-13 03:16:58 Functions: 16 17 94.1 %

          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_error.h"
      16             : #include "gdal_priv.h"
      17             : #include "gdalalg_vector_geom.h"
      18             : #include "ogr_geometry.h"
      19             : 
      20             : #include <algorithm>
      21             : #include <cinttypes>
      22             : #include <optional>
      23             : 
      24             : #ifndef _
      25             : #define _(x) (x)
      26             : #endif
      27             : 
      28             : //! @cond Doxygen_Suppress
      29             : 
      30          62 : GDALVectorCombineAlgorithm::GDALVectorCombineAlgorithm(bool standaloneStep)
      31             :     : GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
      32          62 :                                       standaloneStep)
      33             : {
      34             :     AddArg("group-by", 0,
      35         124 :            _("Names of field(s) by which inputs should be grouped"), &m_groupBy)
      36             :         .AddValidationAction(
      37          15 :             [this]()
      38             :             {
      39          30 :                 auto fields = m_groupBy;
      40             : 
      41          15 :                 std::sort(fields.begin(), fields.end());
      42          15 :                 if (std::adjacent_find(fields.begin(), fields.end()) !=
      43          30 :                     fields.end())
      44             :                 {
      45           1 :                     CPLError(
      46             :                         CE_Failure, CPLE_AppDefined,
      47             :                         "--group-by must be a list of unique field names.");
      48           1 :                     return false;
      49             :                 }
      50          14 :                 return true;
      51          62 :             });
      52             : 
      53             :     AddArg("keep-nested", 0,
      54             :            _("Avoid combining the components of multipart geometries"),
      55          62 :            &m_keepNested);
      56          62 : }
      57             : 
      58             : namespace
      59             : {
      60             : class GDALVectorCombineOutputLayer final
      61             :     : public GDALVectorNonStreamingAlgorithmLayer
      62             : {
      63             :   public:
      64          19 :     explicit GDALVectorCombineOutputLayer(
      65             :         OGRLayer &srcLayer, int geomFieldIndex,
      66             :         const std::vector<std::string> &groupBy, bool keepNested)
      67          19 :         : GDALVectorNonStreamingAlgorithmLayer(srcLayer, geomFieldIndex),
      68          19 :           m_groupBy(groupBy), m_defn(OGRFeatureDefn::CreateFeatureDefn(
      69          19 :                                   srcLayer.GetLayerDefn()->GetName())),
      70          19 :           m_keepNested(keepNested)
      71             :     {
      72          19 :         m_defn->Reference();
      73             : 
      74          19 :         const OGRFeatureDefn *srcDefn = m_srcLayer.GetLayerDefn();
      75             : 
      76             :         // Copy field definitions for attribute fields used in
      77             :         // --group-by. All other attributes are discarded.
      78          25 :         for (const auto &fieldName : m_groupBy)
      79             :         {
      80             :             // RunStep already checked that the field exists
      81           6 :             const auto iField = srcDefn->GetFieldIndex(fieldName.c_str());
      82           6 :             CPLAssert(iField >= 0);
      83             : 
      84           6 :             m_srcFieldIndices.push_back(iField);
      85           6 :             m_defn->AddFieldDefn(srcDefn->GetFieldDefn(iField));
      86             :         }
      87             : 
      88             :         // Create a new geometry field corresponding to each input geometry
      89             :         // field. An appropriate type is worked out below.
      90          19 :         m_defn->SetGeomType(wkbNone);  // Remove default geometry field
      91          39 :         for (const OGRGeomFieldDefn *srcGeomDefn : srcDefn->GetGeomFields())
      92             :         {
      93          20 :             const auto eSrcGeomType = srcGeomDefn->GetType();
      94          20 :             const bool bHasZ = OGR_GT_HasZ(eSrcGeomType);
      95          20 :             const bool bHasM = OGR_GT_HasM(eSrcGeomType);
      96             : 
      97             :             OGRwkbGeometryType eDstGeomType =
      98          20 :                 OGR_GT_SetModifier(wkbGeometryCollection, bHasZ, bHasM);
      99             : 
     100             :             // If the layer claims to have single-part geometries, choose a more
     101             :             // specific output type like "MultiPoint" rather than "GeometryCollection"
     102          36 :             if (wkbFlatten(eSrcGeomType) != wkbUnknown &&
     103          16 :                 !OGR_GT_IsSubClassOf(wkbFlatten(eSrcGeomType),
     104             :                                      wkbGeometryCollection))
     105             :             {
     106          16 :                 eDstGeomType = OGR_GT_GetCollection(eSrcGeomType);
     107             :             }
     108             : 
     109             :             auto dstGeomDefn = std::make_unique<OGRGeomFieldDefn>(
     110          40 :                 srcGeomDefn->GetNameRef(), eDstGeomType);
     111          20 :             dstGeomDefn->SetSpatialRef(srcGeomDefn->GetSpatialRef());
     112          20 :             m_defn->AddGeomFieldDefn(std::move(dstGeomDefn));
     113             :         }
     114          19 :     }
     115             : 
     116          38 :     ~GDALVectorCombineOutputLayer() override
     117          19 :     {
     118          19 :         m_defn->Release();
     119          38 :     }
     120             : 
     121          19 :     GIntBig GetFeatureCount(int bForce) override
     122             :     {
     123          19 :         if (m_poAttrQuery == nullptr && m_poFilterGeom == nullptr)
     124             :         {
     125          13 :             return static_cast<GIntBig>(m_features.size());
     126             :         }
     127             : 
     128           6 :         return OGRLayer::GetFeatureCount(bForce);
     129             :     }
     130             : 
     131         203 :     const OGRFeatureDefn *GetLayerDefn() const override
     132             :     {
     133         203 :         return m_defn;
     134             :     }
     135             : 
     136           4 :     OGRErr IGetExtent(int iGeomField, OGREnvelope *psExtent,
     137             :                       bool bForce) override
     138             :     {
     139           4 :         return m_srcLayer.GetExtent(iGeomField, psExtent, bForce);
     140             :     }
     141             : 
     142           0 :     OGRErr IGetExtent3D(int iGeomField, OGREnvelope3D *psExtent,
     143             :                         bool bForce) override
     144             :     {
     145           0 :         return m_srcLayer.GetExtent3D(iGeomField, psExtent, bForce);
     146             :     }
     147             : 
     148         160 :     std::unique_ptr<OGRFeature> GetNextProcessedFeature() override
     149             :     {
     150         160 :         if (!m_itFeature)
     151             :         {
     152          59 :             m_itFeature = m_features.begin();
     153             :         }
     154             : 
     155         160 :         if (m_itFeature.value() == m_features.end())
     156             :         {
     157          35 :             return nullptr;
     158             :         }
     159             : 
     160             :         std::unique_ptr<OGRFeature> feature(
     161         250 :             m_itFeature.value()->second->Clone());
     162         125 :         feature->SetFID(m_nProcessedFeaturesRead++);
     163         125 :         ++m_itFeature.value();
     164         125 :         return feature;
     165             :     }
     166             : 
     167          19 :     bool Process(GDALProgressFunc pfnProgress, void *pProgressData) override
     168             :     {
     169          19 :         const int nGeomFields = m_srcLayer.GetLayerDefn()->GetGeomFieldCount();
     170             : 
     171             :         const GIntBig nLayerFeatures =
     172          19 :             m_srcLayer.TestCapability(OLCFastFeatureCount)
     173          19 :                 ? m_srcLayer.GetFeatureCount(false)
     174          19 :                 : -1;
     175             :         const double dfInvLayerFeatures =
     176          19 :             1.0 / std::max(1.0, static_cast<double>(nLayerFeatures));
     177             : 
     178          19 :         GIntBig nFeaturesRead = 0;
     179             : 
     180          38 :         std::vector<std::string> fieldValues(m_srcFieldIndices.size());
     181             :         const auto srcDstFieldMap =
     182          38 :             m_defn->ComputeMapForSetFrom(m_srcLayer.GetLayerDefn(), true);
     183          98 :         for (const auto &srcFeature : m_srcLayer)
     184             :         {
     185         127 :             for (size_t iDstField = 0; iDstField < m_srcFieldIndices.size();
     186             :                  iDstField++)
     187             :             {
     188          48 :                 const int iSrcField = m_srcFieldIndices[iDstField];
     189          48 :                 fieldValues[iDstField] =
     190          48 :                     srcFeature->GetFieldAsString(iSrcField);
     191             :             }
     192             : 
     193             :             OGRFeature *dstFeature;
     194             : 
     195          79 :             if (auto it = m_features.find(fieldValues); it == m_features.end())
     196             :             {
     197          31 :                 it = m_features
     198          62 :                          .insert(std::pair(
     199          93 :                              fieldValues, std::make_unique<OGRFeature>(m_defn)))
     200             :                          .first;
     201          31 :                 dstFeature = it->second.get();
     202             : 
     203          31 :                 dstFeature->SetFrom(srcFeature.get(), srcDstFieldMap.data(),
     204             :                                     false);
     205             : 
     206          63 :                 for (int iGeomField = 0; iGeomField < nGeomFields; iGeomField++)
     207             :                 {
     208             :                     OGRGeomFieldDefn *poGeomDefn =
     209          32 :                         m_defn->GetGeomFieldDefn(iGeomField);
     210          32 :                     const auto eGeomType = poGeomDefn->GetType();
     211             : 
     212             :                     std::unique_ptr<OGRGeometry> poGeom(
     213          32 :                         OGRGeometryFactory::createGeometry(eGeomType));
     214          32 :                     poGeom->assignSpatialReference(poGeomDefn->GetSpatialRef());
     215             : 
     216          32 :                     dstFeature->SetGeomField(iGeomField, std::move(poGeom));
     217             :                 }
     218             :             }
     219             :             else
     220             :             {
     221          48 :                 dstFeature = it->second.get();
     222             :             }
     223             : 
     224         160 :             for (int iGeomField = 0; iGeomField < nGeomFields; iGeomField++)
     225             :             {
     226             :                 OGRGeomFieldDefn *poGeomFieldDefn =
     227          81 :                     m_defn->GetGeomFieldDefn(iGeomField);
     228             : 
     229             :                 std::unique_ptr<OGRGeometry> poSrcGeom(
     230          81 :                     srcFeature->StealGeometry(iGeomField));
     231          81 :                 if (poSrcGeom != nullptr && !poSrcGeom->IsEmpty())
     232             :                 {
     233          79 :                     const auto eSrcType = poSrcGeom->getGeometryType();
     234          79 :                     const auto bSrcIsCollection = OGR_GT_IsSubClassOf(
     235             :                         wkbFlatten(eSrcType), wkbGeometryCollection);
     236             :                     const auto bDstIsUntypedCollection =
     237          79 :                         wkbFlatten(poGeomFieldDefn->GetType()) ==
     238          79 :                         wkbGeometryCollection;
     239             : 
     240             :                     // Did this geometry unexpectedly have Z?
     241          79 :                     if (OGR_GT_HasZ(eSrcType) !=
     242          79 :                         OGR_GT_HasZ(poGeomFieldDefn->GetType()))
     243             :                     {
     244           4 :                         AddZ(iGeomField);
     245             :                     }
     246             : 
     247             :                     // Did this geometry unexpectedly have M?
     248          79 :                     if (OGR_GT_HasM(eSrcType) !=
     249          79 :                         OGR_GT_HasM(poGeomFieldDefn->GetType()))
     250             :                     {
     251          10 :                         AddM(iGeomField);
     252             :                     }
     253             : 
     254             :                     // Do we need to change the output from a typed collection
     255             :                     // like MultiPolygon to a generic GeometryCollection?
     256          79 :                     if (m_keepNested && bSrcIsCollection &&
     257           3 :                         !bDstIsUntypedCollection)
     258             :                     {
     259           2 :                         SetTypeGeometryCollection(iGeomField);
     260             :                     }
     261             : 
     262             :                     OGRGeometryCollection *poDstGeom =
     263          79 :                         cpl::down_cast<OGRGeometryCollection *>(
     264             :                             dstFeature->GetGeomFieldRef(iGeomField));
     265             : 
     266          79 :                     if (m_keepNested || !bSrcIsCollection)
     267             :                     {
     268          76 :                         if (poDstGeom->addGeometry(std::move(poSrcGeom)) !=
     269             :                             OGRERR_NONE)
     270             :                         {
     271           0 :                             CPLError(
     272             :                                 CE_Failure, CPLE_AppDefined,
     273             :                                 "Failed to add geometry of type %s to output "
     274             :                                 "feature of type %s",
     275             :                                 OGRGeometryTypeToName(eSrcType),
     276             :                                 OGRGeometryTypeToName(
     277           0 :                                     poDstGeom->getGeometryType()));
     278           0 :                             return false;
     279             :                         }
     280             :                     }
     281             :                     else
     282             :                     {
     283             :                         std::unique_ptr<OGRGeometryCollection>
     284             :                             poSrcGeomCollection(
     285           3 :                                 poSrcGeom.release()->toGeometryCollection());
     286           6 :                         if (poDstGeom->addGeometryComponents(
     287           6 :                                 std::move(poSrcGeomCollection)) != OGRERR_NONE)
     288             :                         {
     289           0 :                             CPLError(CE_Failure, CPLE_AppDefined,
     290             :                                      "Failed to add components from geometry "
     291             :                                      "of type %s to output "
     292             :                                      "feature of type %s",
     293             :                                      OGRGeometryTypeToName(eSrcType),
     294             :                                      OGRGeometryTypeToName(
     295           0 :                                          poDstGeom->getGeometryType()));
     296           0 :                             return false;
     297             :                         }
     298             :                     }
     299             :                 }
     300             :             }
     301             : 
     302          79 :             if (pfnProgress && nLayerFeatures > 0 &&
     303           0 :                 !pfnProgress(static_cast<double>(++nFeaturesRead) *
     304             :                                  dfInvLayerFeatures,
     305             :                              "", pProgressData))
     306             :             {
     307           0 :                 CPLError(CE_Failure, CPLE_UserInterrupt, "Interrupted by user");
     308           0 :                 return false;
     309             :             }
     310             :         }
     311             : 
     312          19 :         if (pfnProgress)
     313             :         {
     314           0 :             pfnProgress(1.0, "", pProgressData);
     315             :         }
     316             : 
     317          19 :         return true;
     318             :     }
     319             : 
     320          37 :     int TestCapability(const char *pszCap) const override
     321             :     {
     322          37 :         if (EQUAL(pszCap, OLCFastFeatureCount))
     323             :         {
     324           0 :             return true;
     325             :         }
     326             : 
     327          37 :         if (EQUAL(pszCap, OLCStringsAsUTF8) ||
     328          24 :             EQUAL(pszCap, OLCFastGetExtent) ||
     329          22 :             EQUAL(pszCap, OLCFastGetExtent3D) ||
     330          22 :             EQUAL(pszCap, OLCCurveGeometries) ||
     331          18 :             EQUAL(pszCap, OLCMeasuredGeometries) ||
     332          14 :             EQUAL(pszCap, OLCZGeometries))
     333             :         {
     334          26 :             return m_srcLayer.TestCapability(pszCap);
     335             :         }
     336             : 
     337          11 :         return false;
     338             :     }
     339             : 
     340          97 :     void ResetReading() override
     341             :     {
     342          97 :         m_itFeature.reset();
     343          97 :         m_nProcessedFeaturesRead = 0;
     344          97 :     }
     345             : 
     346             :     CPL_DISALLOW_COPY_ASSIGN(GDALVectorCombineOutputLayer)
     347             : 
     348             :   private:
     349          10 :     void AddM(int iGeomField)
     350             :     {
     351             :         OGRGeomFieldDefn *poGeomFieldDefn =
     352          10 :             m_defn->GetGeomFieldDefn(iGeomField);
     353          20 :         whileUnsealing(poGeomFieldDefn)
     354          10 :             ->SetType(OGR_GT_SetM(poGeomFieldDefn->GetType()));
     355             : 
     356          20 :         for (auto &[_, poFeature] : m_features)
     357             :         {
     358          10 :             poFeature->GetGeomFieldRef(iGeomField)->setMeasured(true);
     359             :         }
     360          10 :     }
     361             : 
     362           4 :     void AddZ(int iGeomField)
     363             :     {
     364             :         OGRGeomFieldDefn *poGeomFieldDefn =
     365           4 :             m_defn->GetGeomFieldDefn(iGeomField);
     366           8 :         whileUnsealing(poGeomFieldDefn)
     367           4 :             ->SetType(OGR_GT_SetZ(poGeomFieldDefn->GetType()));
     368             : 
     369           8 :         for (auto &[_, poFeature] : m_features)
     370             :         {
     371           4 :             poFeature->GetGeomFieldRef(iGeomField)->set3D(true);
     372             :         }
     373           4 :     }
     374             : 
     375           2 :     void SetTypeGeometryCollection(int iGeomField)
     376             :     {
     377             :         OGRGeomFieldDefn *poGeomFieldDefn =
     378           2 :             m_defn->GetGeomFieldDefn(iGeomField);
     379           2 :         const bool hasZ = OGR_GT_HasZ(poGeomFieldDefn->GetType());
     380           2 :         const bool hasM = OGR_GT_HasM(poGeomFieldDefn->GetType());
     381             : 
     382           4 :         whileUnsealing(poGeomFieldDefn)
     383           2 :             ->SetType(OGR_GT_SetModifier(wkbGeometryCollection, hasZ, hasM));
     384             : 
     385           4 :         for (auto &[_, poFeature] : m_features)
     386             :         {
     387             :             std::unique_ptr<OGRGeometry> poTmpGeom(
     388           2 :                 poFeature->StealGeometry(iGeomField));
     389           4 :             poTmpGeom = OGRGeometryFactory::forceTo(std::move(poTmpGeom),
     390           2 :                                                     poGeomFieldDefn->GetType());
     391           2 :             CPLAssert(poTmpGeom);
     392           2 :             poFeature->SetGeomField(iGeomField, std::move(poTmpGeom));
     393             :         }
     394           2 :     }
     395             : 
     396             :     const std::vector<std::string> m_groupBy{};
     397             :     std::vector<int> m_srcFieldIndices{};
     398             :     std::map<std::vector<std::string>, std::unique_ptr<OGRFeature>>
     399             :         m_features{};
     400             :     std::optional<decltype(m_features)::const_iterator> m_itFeature{};
     401             :     OGRFeatureDefn *const m_defn;
     402             :     GIntBig m_nProcessedFeaturesRead = 0;
     403             :     const bool m_keepNested;
     404             : };
     405             : }  // namespace
     406             : 
     407          20 : bool GDALVectorCombineAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
     408             : {
     409          20 :     auto poSrcDS = m_inputDataset[0].GetDatasetRef();
     410          40 :     auto poDstDS = std::make_unique<GDALVectorNonStreamingAlgorithmDataset>();
     411             : 
     412          40 :     GDALVectorAlgorithmLayerProgressHelper progressHelper(ctxt);
     413             : 
     414          39 :     for (auto &&poSrcLayer : poSrcDS->GetLayers())
     415             :     {
     416          20 :         if (m_inputLayerNames.empty() ||
     417           0 :             std::find(m_inputLayerNames.begin(), m_inputLayerNames.end(),
     418          20 :                       poSrcLayer->GetDescription()) != m_inputLayerNames.end())
     419             :         {
     420          20 :             const auto poSrcLayerDefn = poSrcLayer->GetLayerDefn();
     421          20 :             if (poSrcLayerDefn->GetGeomFieldCount() == 0)
     422             :             {
     423           0 :                 if (m_inputLayerNames.empty())
     424           0 :                     continue;
     425           0 :                 ReportError(CE_Failure, CPLE_AppDefined,
     426             :                             "Specified layer '%s' has no geometry field",
     427           0 :                             poSrcLayer->GetDescription());
     428           0 :                 return false;
     429             :             }
     430             : 
     431             :             // Check that all attributes exist
     432          26 :             for (const auto &fieldName : m_groupBy)
     433             :             {
     434             :                 const int iSrcFieldIndex =
     435           7 :                     poSrcLayerDefn->GetFieldIndex(fieldName.c_str());
     436           7 :                 if (iSrcFieldIndex == -1)
     437             :                 {
     438           1 :                     ReportError(CE_Failure, CPLE_AppDefined,
     439             :                                 "Specified attribute field '%s' does not exist "
     440             :                                 "in layer '%s'",
     441             :                                 fieldName.c_str(),
     442           1 :                                 poSrcLayer->GetDescription());
     443           1 :                     return false;
     444             :                 }
     445             :             }
     446             : 
     447          19 :             progressHelper.AddProcessedLayer(*poSrcLayer);
     448             :         }
     449             :     }
     450             : 
     451          38 :     for ([[maybe_unused]] auto [poSrcLayer, bProcessed, layerProgressFunc,
     452          76 :                                 layerProgressData] : progressHelper)
     453             :     {
     454             :         auto poLayer = std::make_unique<GDALVectorCombineOutputLayer>(
     455          19 :             *poSrcLayer, -1, m_groupBy, m_keepNested);
     456             : 
     457          19 :         if (!poDstDS->AddProcessedLayer(std::move(poLayer), layerProgressFunc,
     458             :                                         layerProgressData.get()))
     459             :         {
     460           0 :             return false;
     461             :         }
     462             :     }
     463             : 
     464          19 :     m_outputDataset.Set(std::move(poDstDS));
     465             : 
     466          19 :     return true;
     467             : }
     468             : 
     469             : GDALVectorCombineAlgorithmStandalone::~GDALVectorCombineAlgorithmStandalone() =
     470             :     default;
     471             : 
     472             : //! @endcond

Generated by: LCOV version 1.14