LCOV - code coverage report
Current view: top level - apps - gdalalg_vector_concat.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 182 184 98.9 %
Date: 2025-06-19 12:30:01 Functions: 13 13 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  gdal "vector concat" 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_concat.h"
      14             : #include "gdalalg_vector_write.h"
      15             : 
      16             : #include "cpl_conv.h"
      17             : #include "gdal_priv.h"
      18             : #include "gdal_utils.h"
      19             : #include "ogrsf_frmts.h"
      20             : 
      21             : #include "ogrlayerdecorator.h"
      22             : #include "ogrunionlayer.h"
      23             : #include "ogrwarpedlayer.h"
      24             : 
      25             : #include <algorithm>
      26             : #include <set>
      27             : 
      28             : //! @cond Doxygen_Suppress
      29             : 
      30             : #ifndef _
      31             : #define _(x) (x)
      32             : #endif
      33             : 
      34             : /************************************************************************/
      35             : /*        GDALVectorConcatAlgorithm::GDALVectorConcatAlgorithm()        */
      36             : /************************************************************************/
      37             : 
      38          46 : GDALVectorConcatAlgorithm::GDALVectorConcatAlgorithm(bool bStandalone)
      39             :     : GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
      40           0 :                                       ConstructorOptions()
      41          46 :                                           .SetStandaloneStep(bStandalone)
      42          92 :                                           .SetInputDatasetMaxCount(INT_MAX))
      43             : {
      44          46 :     if (!bStandalone)
      45             :     {
      46          19 :         AddVectorInputArgs(/* hiddenForCLI = */ false);
      47             :     }
      48             : 
      49             :     AddArg(
      50             :         "mode", 0,
      51             :         _("Determine the strategy to create output layers from source layers "),
      52          92 :         &m_mode)
      53          46 :         .SetChoices("merge-per-layer-name", "stack", "single")
      54          46 :         .SetDefault(m_mode);
      55             :     AddArg("output-layer", 0,
      56             :            _("Name of the output vector layer (single mode), or template to "
      57             :              "name the output vector layers (stack mode)"),
      58          46 :            &m_layerNameTemplate);
      59             :     AddArg("source-layer-field-name", 0,
      60             :            _("Name of the new field to add to contain identificoncation of the "
      61             :              "source layer, with value determined from "
      62             :              "'source-layer-field-content'"),
      63          46 :            &m_sourceLayerFieldName);
      64             :     AddArg("source-layer-field-content", 0,
      65             :            _("A string, possibly using {AUTO_NAME}, {DS_NAME}, {DS_BASENAME}, "
      66             :              "{DS_INDEX}, {LAYER_NAME}, {LAYER_INDEX}"),
      67          46 :            &m_sourceLayerFieldContent);
      68             :     AddArg("field-strategy", 0,
      69             :            _("How to determine target fields from source fields"),
      70          92 :            &m_fieldStrategy)
      71          46 :         .SetChoices("union", "intersection")
      72          46 :         .SetDefault(m_fieldStrategy);
      73          92 :     AddArg("src-crs", 's', _("Source CRS"), &m_srsCrs)
      74          92 :         .SetIsCRSArg()
      75          46 :         .AddHiddenAlias("s_srs");
      76          92 :     AddArg("dst-crs", 'd', _("Destination CRS"), &m_dstCrs)
      77          92 :         .SetIsCRSArg()
      78          46 :         .AddHiddenAlias("t_srs");
      79          46 : }
      80             : 
      81             : /************************************************************************/
      82             : /*                   GDALVectorConcatOutputDataset                      */
      83             : /************************************************************************/
      84             : 
      85             : class GDALVectorConcatOutputDataset final : public GDALDataset
      86             : {
      87             :     std::vector<std::unique_ptr<OGRLayer>> m_layers{};
      88             : 
      89             :   public:
      90          25 :     GDALVectorConcatOutputDataset() = default;
      91             : 
      92          32 :     void AddLayer(std::unique_ptr<OGRLayer> layer)
      93             :     {
      94          32 :         m_layers.push_back(std::move(layer));
      95          32 :     }
      96             : 
      97             :     int GetLayerCount() override;
      98             : 
      99          55 :     OGRLayer *GetLayer(int idx) override
     100             :     {
     101          55 :         return idx >= 0 && idx < GetLayerCount() ? m_layers[idx].get()
     102          55 :                                                  : nullptr;
     103             :     }
     104             : 
     105          30 :     int TestCapability(const char *pszCap) override
     106             :     {
     107          30 :         if (EQUAL(pszCap, ODsCCurveGeometries) ||
     108          28 :             EQUAL(pszCap, ODsCMeasuredGeometries) ||
     109          27 :             EQUAL(pszCap, ODsCZGeometries))
     110             :         {
     111           4 :             return true;
     112             :         }
     113          26 :         return false;
     114             :     }
     115             : };
     116             : 
     117         109 : int GDALVectorConcatOutputDataset::GetLayerCount()
     118             : {
     119         109 :     return static_cast<int>(m_layers.size());
     120             : }
     121             : 
     122             : /************************************************************************/
     123             : /*                     GDALVectorConcatRenamedLayer                     */
     124             : /************************************************************************/
     125             : 
     126             : class GDALVectorConcatRenamedLayer final : public OGRLayerDecorator
     127             : {
     128             :   public:
     129           7 :     GDALVectorConcatRenamedLayer(OGRLayer *poSrcLayer,
     130             :                                  const std::string &newName)
     131           7 :         : OGRLayerDecorator(poSrcLayer, false), m_newName(newName)
     132             :     {
     133           7 :     }
     134             : 
     135             :     const char *GetName() override;
     136             : 
     137             :   private:
     138             :     const std::string m_newName;
     139             : };
     140             : 
     141           7 : const char *GDALVectorConcatRenamedLayer::GetName()
     142             : {
     143           7 :     return m_newName.c_str();
     144             : }
     145             : 
     146             : /************************************************************************/
     147             : /*                         BuildLayerName()                             */
     148             : /************************************************************************/
     149             : 
     150          16 : static std::string BuildLayerName(const std::string &layerNameTemplate,
     151             :                                   int dsIdx, const std::string &dsName,
     152             :                                   int lyrIdx, const std::string &lyrName)
     153             : {
     154          32 :     CPLString ret = layerNameTemplate;
     155          32 :     std::string baseName;
     156             :     VSIStatBufL sStat;
     157          16 :     if (VSIStatL(dsName.c_str(), &sStat) == 0)
     158           1 :         baseName = CPLGetBasenameSafe(dsName.c_str());
     159             : 
     160          16 :     if (baseName == lyrName)
     161             :     {
     162           1 :         ret = ret.replaceAll("{AUTO_NAME}", baseName);
     163             :     }
     164             :     else
     165             :     {
     166             :         ret = ret.replaceAll("{AUTO_NAME}",
     167          30 :                              std::string(baseName.empty() ? dsName : baseName)
     168          15 :                                  .append("_")
     169          15 :                                  .append(lyrName));
     170             :     }
     171             : 
     172             :     ret =
     173          16 :         ret.replaceAll("{DS_BASENAME}", !baseName.empty() ? baseName : dsName);
     174          16 :     ret = ret.replaceAll("{DS_NAME}", dsName);
     175          16 :     ret = ret.replaceAll("{DS_INDEX}", CPLSPrintf("%d", dsIdx));
     176          16 :     ret = ret.replaceAll("{LAYER_NAME}", lyrName);
     177          16 :     ret = ret.replaceAll("{LAYER_INDEX}", CPLSPrintf("%d", lyrIdx));
     178             : 
     179          32 :     return std::string(std::move(ret));
     180             : }
     181             : 
     182             : /************************************************************************/
     183             : /*                   GDALVectorConcatAlgorithm::RunStep()               */
     184             : /************************************************************************/
     185             : 
     186          27 : bool GDALVectorConcatAlgorithm::RunStep(GDALPipelineStepRunContext &)
     187             : {
     188          27 :     std::unique_ptr<OGRSpatialReference> poSrcCRS;
     189          27 :     if (!m_srsCrs.empty())
     190             :     {
     191           1 :         poSrcCRS = std::make_unique<OGRSpatialReference>();
     192           1 :         poSrcCRS->SetFromUserInput(m_srsCrs.c_str());
     193           1 :         poSrcCRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     194             :     }
     195             : 
     196          54 :     OGRSpatialReference oDstCRS;
     197          27 :     if (!m_dstCrs.empty())
     198             :     {
     199           4 :         oDstCRS.SetFromUserInput(m_dstCrs.c_str());
     200           4 :         oDstCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     201             :     }
     202             : 
     203             :     struct LayerDesc
     204             :     {
     205             :         int iDS = 0;
     206             :         int iLayer = 0;
     207             : 
     208             :         GDALDataset *
     209           7 :         GetDataset(std::vector<GDALArgDatasetValue> &inputDatasets) const
     210             :         {
     211           7 :             return inputDatasets[iDS].GetDatasetRef();
     212             :         }
     213             : 
     214             :         OGRLayer *
     215          46 :         GetLayer(std::vector<GDALArgDatasetValue> &inputDatasets) const
     216             :         {
     217          46 :             return inputDatasets[iDS].GetDatasetRef()->GetLayer(iLayer);
     218             :         }
     219             :     };
     220             : 
     221          27 :     if (m_layerNameTemplate.empty())
     222             :     {
     223          24 :         if (m_mode == "single")
     224           1 :             m_layerNameTemplate = "merged";
     225          23 :         else if (m_mode == "stack")
     226           2 :             m_layerNameTemplate = "{AUTO_NAME}";
     227             :     }
     228           3 :     else if (m_mode == "merge-per-layer-name")
     229             :     {
     230           1 :         ReportError(CE_Failure, CPLE_IllegalArg,
     231             :                     "'layer-name' name argument cannot be specified in "
     232             :                     "mode=merge-per-layer-name");
     233           1 :         return false;
     234             :     }
     235             : 
     236          26 :     if (m_sourceLayerFieldContent.empty())
     237          20 :         m_sourceLayerFieldContent = "{AUTO_NAME}";
     238           6 :     else if (m_sourceLayerFieldName.empty())
     239           1 :         m_sourceLayerFieldName = "source_ds_lyr";
     240             : 
     241             :     // First pass on input layers
     242          52 :     std::map<std::string, std::vector<LayerDesc>> allLayerNames;
     243          26 :     int iDS = 0;
     244          65 :     for (auto &srcDS : m_inputDataset)
     245             :     {
     246          40 :         int iLayer = 0;
     247          87 :         for (const auto &poLayer : srcDS.GetDatasetRef()->GetLayers())
     248             :         {
     249          50 :             if (m_inputLayerNames.empty() ||
     250           0 :                 std::find(m_inputLayerNames.begin(), m_inputLayerNames.end(),
     251          50 :                           poLayer->GetName()) != m_inputLayerNames.end())
     252             :             {
     253          52 :                 if (!m_dstCrs.empty() && m_srsCrs.empty() &&
     254           5 :                     poLayer->GetSpatialRef() == nullptr)
     255             :                 {
     256           1 :                     ReportError(
     257             :                         CE_Failure, CPLE_AppDefined,
     258             :                         "Layer '%s' of '%s' has no spatial reference system",
     259           1 :                         poLayer->GetName(),
     260           1 :                         srcDS.GetDatasetRef()->GetDescription());
     261           1 :                     return false;
     262             :                 }
     263          46 :                 LayerDesc layerDesc;
     264          46 :                 layerDesc.iDS = iDS;
     265          46 :                 layerDesc.iLayer = iLayer;
     266             :                 const std::string outLayerName =
     267          46 :                     m_mode == "single" ? m_layerNameTemplate
     268          40 :                     : m_mode == "merge-per-layer-name"
     269          31 :                         ? std::string(poLayer->GetName())
     270             :                         : BuildLayerName(
     271           9 :                               m_layerNameTemplate, iDS,
     272          18 :                               srcDS.GetDatasetRef()->GetDescription(), iLayer,
     273         168 :                               poLayer->GetName());
     274          46 :                 CPLDebugOnly("gdal_vector_concat", "%s,%s->%s",
     275             :                              srcDS.GetDatasetRef()->GetDescription(),
     276             :                              poLayer->GetName(), outLayerName.c_str());
     277          46 :                 allLayerNames[outLayerName].push_back(std::move(layerDesc));
     278             :             }
     279          47 :             ++iLayer;
     280             :         }
     281          39 :         ++iDS;
     282             :     }
     283             : 
     284          25 :     auto poUnionDS = std::make_unique<GDALVectorConcatOutputDataset>();
     285             : 
     286          25 :     bool ret = true;
     287          57 :     for (const auto &[outLayerName, listOfLayers] : allLayerNames)
     288             :     {
     289          32 :         const int nLayerCount = static_cast<int>(listOfLayers.size());
     290             :         std::unique_ptr<OGRLayer *, VSIFreeReleaser> papoSrcLayers(
     291             :             static_cast<OGRLayer **>(
     292          64 :                 CPLCalloc(nLayerCount, sizeof(OGRLayer *))));
     293          78 :         for (int i = 0; i < nLayerCount; ++i)
     294             :         {
     295          46 :             const auto poSrcLayer = listOfLayers[i].GetLayer(m_inputDataset);
     296          46 :             if (m_sourceLayerFieldName.empty())
     297             :             {
     298          39 :                 papoSrcLayers.get()[i] = poSrcLayer;
     299             :             }
     300             :             else
     301             :             {
     302             :                 const std::string newSrcLayerName = BuildLayerName(
     303           7 :                     m_sourceLayerFieldContent, listOfLayers[i].iDS,
     304           7 :                     listOfLayers[i]
     305           7 :                         .GetDataset(m_inputDataset)
     306           7 :                         ->GetDescription(),
     307          35 :                     listOfLayers[i].iLayer, poSrcLayer->GetName());
     308           7 :                 ret = !newSrcLayerName.empty() && ret;
     309             :                 auto poTmpLayer =
     310             :                     std::make_unique<GDALVectorConcatRenamedLayer>(
     311           7 :                         poSrcLayer, newSrcLayerName);
     312           7 :                 m_tempLayersKeeper.push_back(std::move(poTmpLayer));
     313           7 :                 papoSrcLayers.get()[i] = m_tempLayersKeeper.back().get();
     314             :             }
     315             :         }
     316             : 
     317             :         // Auto-wrap source layers if needed
     318          32 :         if (!m_dstCrs.empty())
     319             :         {
     320           8 :             for (int i = 0; i < nLayerCount; ++i)
     321             :             {
     322             :                 OGRSpatialReference *poSrcLayerCRS;
     323           5 :                 if (poSrcCRS)
     324           1 :                     poSrcLayerCRS = poSrcCRS.get();
     325             :                 else
     326           4 :                     poSrcLayerCRS = papoSrcLayers.get()[i]->GetSpatialRef();
     327           5 :                 if (!poSrcLayerCRS->IsSame(&oDstCRS))
     328             :                 {
     329             :                     auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
     330             :                         OGRCreateCoordinateTransformation(poSrcLayerCRS,
     331           6 :                                                           &oDstCRS));
     332             :                     auto poReversedCT =
     333             :                         std::unique_ptr<OGRCoordinateTransformation>(
     334             :                             OGRCreateCoordinateTransformation(&oDstCRS,
     335           6 :                                                               poSrcLayerCRS));
     336           3 :                     ret = (poCT != nullptr) && (poReversedCT != nullptr);
     337           3 :                     if (ret)
     338             :                     {
     339           3 :                         m_tempLayersKeeper.push_back(
     340           3 :                             std::make_unique<OGRWarpedLayer>(
     341           3 :                                 papoSrcLayers.get()[i], /* iGeomField = */ 0,
     342           3 :                                 /*bTakeOwnership = */ false, poCT.release(),
     343           3 :                                 poReversedCT.release()));
     344           3 :                         papoSrcLayers.get()[i] =
     345           3 :                             m_tempLayersKeeper.back().get();
     346             :                     }
     347             :                 }
     348             :             }
     349             :         }
     350             : 
     351             :         auto poUnionLayer = std::make_unique<OGRUnionLayer>(
     352          32 :             outLayerName.c_str(), nLayerCount, papoSrcLayers.release(),
     353          64 :             /* bTakeLayerOwnership = */ false);
     354             : 
     355          32 :         if (!m_sourceLayerFieldName.empty())
     356             :         {
     357           7 :             poUnionLayer->SetSourceLayerFieldName(
     358             :                 m_sourceLayerFieldName.c_str());
     359             :         }
     360             : 
     361             :         const FieldUnionStrategy eStrategy =
     362          32 :             m_fieldStrategy == "union" ? FIELD_UNION_ALL_LAYERS
     363          32 :                                        : FIELD_INTERSECTION_ALL_LAYERS;
     364          32 :         poUnionLayer->SetFields(eStrategy, 0, nullptr, 0, nullptr);
     365             : 
     366          32 :         poUnionDS->AddLayer(std::move(poUnionLayer));
     367             :     }
     368             : 
     369          25 :     if (ret)
     370             :     {
     371          25 :         m_outputDataset.Set(std::move(poUnionDS));
     372             :     }
     373          25 :     return ret;
     374             : }
     375             : 
     376             : /************************************************************************/
     377             : /*                GDALVectorConcatAlgorithm::RunImpl()                  */
     378             : /************************************************************************/
     379             : 
     380          52 : bool GDALVectorConcatAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
     381             :                                         void *pProgressData)
     382             : {
     383          52 :     if (m_standaloneStep)
     384             :     {
     385          26 :         GDALVectorWriteAlgorithm writeAlg;
     386         364 :         for (auto &arg : writeAlg.GetArgs())
     387             :         {
     388         338 :             if (arg->GetName() != "output-layer")
     389             :             {
     390         312 :                 auto stepArg = GetArg(arg->GetName());
     391         312 :                 if (stepArg && stepArg->IsExplicitlySet())
     392             :                 {
     393          52 :                     arg->SetSkipIfAlreadySet(true);
     394          52 :                     arg->SetFrom(*stepArg);
     395             :                 }
     396             :             }
     397             :         }
     398             : 
     399             :         // Already checked by GDALAlgorithm::Run()
     400          26 :         CPLAssert(!m_executionForStreamOutput ||
     401             :                   EQUAL(m_format.c_str(), "stream"));
     402             : 
     403          26 :         m_standaloneStep = false;
     404          26 :         bool ret = Run(pfnProgress, pProgressData);
     405          26 :         m_standaloneStep = true;
     406          26 :         if (ret)
     407             :         {
     408          24 :             if (m_format == "stream")
     409             :             {
     410           2 :                 ret = true;
     411             :             }
     412             :             else
     413             :             {
     414          22 :                 writeAlg.m_inputDataset.clear();
     415          22 :                 writeAlg.m_inputDataset.resize(1);
     416          22 :                 writeAlg.m_inputDataset[0].Set(m_outputDataset.GetDatasetRef());
     417          22 :                 if (writeAlg.Run(pfnProgress, pProgressData))
     418             :                 {
     419          22 :                     m_outputDataset.Set(
     420             :                         writeAlg.m_outputDataset.GetDatasetRef());
     421          22 :                     ret = true;
     422             :                 }
     423             :             }
     424             :         }
     425             : 
     426          26 :         return ret;
     427             :     }
     428             :     else
     429             :     {
     430          26 :         GDALPipelineStepRunContext stepCtxt;
     431          26 :         stepCtxt.m_pfnProgress = pfnProgress;
     432          26 :         stepCtxt.m_pProgressData = pProgressData;
     433          26 :         return RunStep(stepCtxt);
     434             :     }
     435             : }
     436             : 
     437             : GDALVectorConcatAlgorithmStandalone::~GDALVectorConcatAlgorithmStandalone() =
     438             :     default;
     439             : 
     440             : //! @endcond

Generated by: LCOV version 1.14