LCOV - code coverage report
Current view: top level - apps - gdalalg_vector_rename_layer.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 182 187 97.3 %
Date: 2026-04-23 19:47:11 Functions: 17 17 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  "rename-layer" step of "vector pipeline"
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2026, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "gdalalg_vector_rename_layer.h"
      14             : 
      15             : //! @cond Doxygen_Suppress
      16             : 
      17             : #include <map>
      18             : 
      19             : #include "cpl_string.h"
      20             : 
      21             : #ifndef _
      22             : #define _(x) (x)
      23             : #endif
      24             : 
      25             : /************************************************************************/
      26             : /*   GDALVectorRenameLayerAlgorithm::GDALVectorRenameLayerAlgorithm()   */
      27             : /************************************************************************/
      28             : 
      29          95 : GDALVectorRenameLayerAlgorithm::GDALVectorRenameLayerAlgorithm(
      30          95 :     bool standaloneStep)
      31             :     : GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
      32           0 :                                       ConstructorOptions()
      33          95 :                                           .SetStandaloneStep(standaloneStep)
      34         190 :                                           .SetAddInputLayerNameArgument(false))
      35             : {
      36          95 :     AddLayerNameArg(&m_inputLayerName);
      37          95 :     if (!standaloneStep)
      38             :     {
      39          35 :         AddOutputLayerNameArg(/* hiddenForCLI = */ false,
      40             :                               /* shortNameOutputLayerAllowed = */ false);
      41             :     }
      42          95 :     AddArg("ascii", 0, _("Force names to ASCII character"), &m_ascii);
      43             :     AddArg("lower-case", 0,
      44             :            _("Force names to lower case (only on ASCII characters)"),
      45          95 :            &m_lowerCase);
      46             :     AddArg("filename-compatible", 0, _("Force names to be usable as filenames"),
      47          95 :            &m_filenameCompatible);
      48             :     AddArg("reserved-characters", 0, _("Reserved character(s) to be removed"),
      49          95 :            &m_reservedChars);
      50             :     AddArg("replacement-character", 0,
      51             :            _("Replacement character when ASCII conversion not possible"),
      52         190 :            &m_replacementChar)
      53          95 :         .SetMaxCharCount(1);
      54         190 :     AddArg("max-length", 0, _("Maximum length of layer names"), &m_maxLength)
      55          95 :         .SetMinValueIncluded(1);
      56             : 
      57          95 :     AddValidationAction(
      58         156 :         [this]()
      59             :         {
      60          24 :             if (!m_inputLayerName.empty() && m_outputLayerName.empty())
      61             :             {
      62           1 :                 ReportError(CE_Failure, CPLE_AppDefined,
      63             :                             "Argument output-layer must be specified when "
      64             :                             "input-layer is specified");
      65           1 :                 return false;
      66             :             }
      67             : 
      68          23 :             if (!m_inputDataset.empty() && m_inputDataset[0].GetDatasetRef())
      69             :             {
      70          21 :                 auto poSrcDS = m_inputDataset[0].GetDatasetRef();
      71          26 :                 if (!m_inputLayerName.empty() &&
      72           5 :                     poSrcDS->GetLayerByName(m_inputLayerName.c_str()) ==
      73             :                         nullptr)
      74             :                 {
      75           1 :                     ReportError(CE_Failure, CPLE_AppDefined,
      76             :                                 "Input layer '%s' does not exist",
      77             :                                 m_inputLayerName.c_str());
      78           1 :                     return false;
      79             :                 }
      80             : 
      81          27 :                 if (!m_outputLayerName.empty() && m_inputLayerName.empty() &&
      82           7 :                     poSrcDS->GetLayerCount() >= 2)
      83             :                 {
      84           1 :                     ReportError(CE_Failure, CPLE_AppDefined,
      85             :                                 "Argument input-layer must be specified when "
      86             :                                 "output-layer is specified and there is more "
      87             :                                 "than one layer");
      88           1 :                     return false;
      89             :                 }
      90             :             }
      91             : 
      92          21 :             return true;
      93             :         });
      94          95 : }
      95             : 
      96             : namespace
      97             : {
      98             : 
      99             : /************************************************************************/
     100             : /*                 GDALVectorRenameLayerAlgorithmLayer                  */
     101             : /************************************************************************/
     102             : 
     103             : class GDALVectorRenameLayerAlgorithmLayer final
     104             :     : public GDALVectorPipelineOutputLayer
     105             : {
     106             :   private:
     107             :     const OGRFeatureDefnRefCountedPtr m_poFeatureDefn;
     108             : 
     109             :     CPL_DISALLOW_COPY_ASSIGN(GDALVectorRenameLayerAlgorithmLayer)
     110             : 
     111         195 :     void TranslateFeature(
     112             :         std::unique_ptr<OGRFeature> poSrcFeature,
     113             :         std::vector<std::unique_ptr<OGRFeature>> &apoOutFeatures) override
     114             :     {
     115         195 :         poSrcFeature->SetFDefnUnsafe(m_poFeatureDefn.get());
     116         195 :         apoOutFeatures.push_back(std::move(poSrcFeature));
     117         195 :     }
     118             : 
     119             :   public:
     120          20 :     explicit GDALVectorRenameLayerAlgorithmLayer(
     121             :         OGRLayer &oSrcLayer, const std::string &osOutputLayerName)
     122          20 :         : GDALVectorPipelineOutputLayer(oSrcLayer),
     123          20 :           m_poFeatureDefn(oSrcLayer.GetLayerDefn()->Clone())
     124             :     {
     125          20 :         m_poFeatureDefn->SetName(osOutputLayerName.c_str());
     126          20 :         const int nGeomFieldCount = m_poFeatureDefn->GetGeomFieldCount();
     127          20 :         const auto poSrcLayerDefn = oSrcLayer.GetLayerDefn();
     128          39 :         for (int i = 0; i < nGeomFieldCount; ++i)
     129             :         {
     130          38 :             m_poFeatureDefn->GetGeomFieldDefn(i)->SetSpatialRef(
     131          19 :                 poSrcLayerDefn->GetGeomFieldDefn(i)->GetSpatialRef());
     132             :         }
     133          20 :         SetDescription(m_poFeatureDefn->GetName());
     134          20 :         SetMetadata(oSrcLayer.GetMetadata());
     135          20 :     }
     136             : 
     137         311 :     const OGRFeatureDefn *GetLayerDefn() const override
     138             :     {
     139         311 :         return m_poFeatureDefn.get();
     140             :     }
     141             : 
     142          13 :     GIntBig GetFeatureCount(int bForce) override
     143             :     {
     144          13 :         return m_srcLayer.GetFeatureCount(bForce);
     145             :     }
     146             : 
     147           4 :     OGRErr IGetExtent(int iGeomField, OGREnvelope *psExtent,
     148             :                       bool bForce) override
     149             :     {
     150           4 :         return m_srcLayer.GetExtent(iGeomField, psExtent, bForce);
     151             :     }
     152             : 
     153          14 :     OGRErr SetIgnoredFields(CSLConstList papszFields) override
     154             :     {
     155          14 :         return m_srcLayer.SetIgnoredFields(papszFields);
     156             :     }
     157             : 
     158          34 :     OGRErr SetAttributeFilter(const char *pszAttributeFilter) override
     159             :     {
     160          34 :         OGRLayer::SetAttributeFilter(pszAttributeFilter);
     161          34 :         return m_srcLayer.SetAttributeFilter(pszAttributeFilter);
     162             :     }
     163             : 
     164           1 :     OGRGeometry *GetSpatialFilter() override
     165             :     {
     166           1 :         return m_srcLayer.GetSpatialFilter();
     167             :     }
     168             : 
     169          26 :     OGRErr ISetSpatialFilter(int iGeomField, const OGRGeometry *poGeom) override
     170             :     {
     171          26 :         return m_srcLayer.SetSpatialFilter(iGeomField, poGeom);
     172             :     }
     173             : 
     174           8 :     OGRFeature *GetFeature(GIntBig nFID) override
     175             :     {
     176             :         auto poSrcFeature =
     177          16 :             std::unique_ptr<OGRFeature>(m_srcLayer.GetFeature(nFID));
     178           8 :         if (!poSrcFeature)
     179           3 :             return nullptr;
     180           5 :         poSrcFeature->SetFDefnUnsafe(m_poFeatureDefn.get());
     181           5 :         return poSrcFeature.release();
     182             :     }
     183             : 
     184          48 :     int TestCapability(const char *pszCap) const override
     185             :     {
     186          48 :         return m_srcLayer.TestCapability(pszCap);
     187             :     }
     188             : };
     189             : 
     190             : /************************************************************************/
     191             : /*                GDALVectorRenameLayerAlgorithmDataset                 */
     192             : /************************************************************************/
     193             : 
     194             : class GDALVectorRenameLayerAlgorithmDataset final
     195             :     : public GDALVectorPipelineOutputDataset
     196             : {
     197             :   public:
     198          17 :     GDALVectorRenameLayerAlgorithmDataset(
     199             :         GDALDataset &oSrcDS, const std::vector<std::string> &aosNewLayerNames)
     200          17 :         : GDALVectorPipelineOutputDataset(oSrcDS)
     201             :     {
     202          17 :         const int nLayerCount = oSrcDS.GetLayerCount();
     203          17 :         CPLAssert(aosNewLayerNames.size() == static_cast<size_t>(nLayerCount));
     204          52 :         for (int i = 0; i < nLayerCount; ++i)
     205             :         {
     206          70 :             m_mapOldLayerNameToNew[oSrcDS.GetLayer(i)->GetName()] =
     207          70 :                 aosNewLayerNames[i];
     208             :         }
     209          17 :     }
     210             : 
     211             :     const GDALRelationship *
     212             :     GetRelationship(const std::string &name) const override;
     213             : 
     214             :   private:
     215             :     std::map<std::string, std::string> m_mapOldLayerNameToNew{};
     216             :     mutable std::map<std::string, std::unique_ptr<GDALRelationship>>
     217             :         m_relationships{};
     218             : };
     219             : 
     220             : /************************************************************************/
     221             : /*                          GetRelationship()                           */
     222             : /************************************************************************/
     223             : 
     224           6 : const GDALRelationship *GDALVectorRenameLayerAlgorithmDataset::GetRelationship(
     225             :     const std::string &name) const
     226             : {
     227           6 :     const auto oIterRelationships = m_relationships.find(name);
     228           6 :     if (oIterRelationships != m_relationships.end())
     229           1 :         return oIterRelationships->second.get();
     230             : 
     231           5 :     const GDALRelationship *poSrcRelationShip = m_srcDS.GetRelationship(name);
     232           5 :     if (!poSrcRelationShip)
     233           1 :         return nullptr;
     234             :     const auto oIterLeftTableName =
     235           4 :         m_mapOldLayerNameToNew.find(poSrcRelationShip->GetLeftTableName());
     236             :     const auto oIterRightTableName =
     237           4 :         m_mapOldLayerNameToNew.find(poSrcRelationShip->GetRightTableName());
     238             :     const auto oIterMappingTableName =
     239           4 :         m_mapOldLayerNameToNew.find(poSrcRelationShip->GetMappingTableName());
     240           4 :     if (oIterLeftTableName == m_mapOldLayerNameToNew.end() &&
     241           4 :         oIterRightTableName == m_mapOldLayerNameToNew.end() &&
     242           4 :         oIterMappingTableName == m_mapOldLayerNameToNew.end())
     243             :     {
     244           0 :         return poSrcRelationShip;
     245             :     }
     246             : 
     247             :     auto poNewRelationship =
     248           4 :         std::make_unique<GDALRelationship>(*poSrcRelationShip);
     249           4 :     if (oIterLeftTableName != m_mapOldLayerNameToNew.end())
     250           4 :         poNewRelationship->SetLeftTableName(oIterLeftTableName->second);
     251           4 :     if (oIterRightTableName != m_mapOldLayerNameToNew.end())
     252           4 :         poNewRelationship->SetRightTableName(oIterRightTableName->second);
     253           4 :     if (oIterMappingTableName != m_mapOldLayerNameToNew.end())
     254           4 :         poNewRelationship->SetMappingTableName(oIterMappingTableName->second);
     255             : 
     256           8 :     return m_relationships.insert({name, std::move(poNewRelationship)})
     257           4 :         .first->second.get();
     258             : }
     259             : 
     260             : }  // namespace
     261             : 
     262             : /************************************************************************/
     263             : /*                       TruncateUTF8ToMaxChar()                        */
     264             : /************************************************************************/
     265             : 
     266           7 : static void TruncateUTF8ToMaxChar(std::string &osStr, size_t maxCharCount)
     267             : {
     268           7 :     size_t nCharacterCount = 0;
     269          28 :     for (size_t i = 0; i < osStr.size(); ++i)
     270             :     {
     271             :         // Is it first byte of a UTF-8 character?
     272          28 :         if ((osStr[i] & 0xc0) != 0x80)
     273             :         {
     274          23 :             ++nCharacterCount;
     275          23 :             if (nCharacterCount == maxCharCount)
     276             :             {
     277           7 :                 osStr.resize(i + 1);
     278           7 :                 break;
     279             :             }
     280             :         }
     281             :     }
     282           7 : }
     283             : 
     284             : /************************************************************************/
     285             : /*              GDALVectorRenameLayerAlgorithm::RunStep()               */
     286             : /************************************************************************/
     287             : 
     288          17 : bool GDALVectorRenameLayerAlgorithm::RunStep(GDALPipelineStepRunContext &)
     289             : {
     290          17 :     auto poSrcDS = m_inputDataset[0].GetDatasetRef();
     291          17 :     CPLAssert(poSrcDS);
     292             : 
     293          17 :     CPLAssert(m_outputDataset.GetName().empty());
     294          17 :     CPLAssert(!m_outputDataset.GetDatasetRef());
     295             : 
     296             :     // First pass over layer names to create new layer names matching specified
     297             :     // constraints
     298          34 :     std::vector<std::string> aosNames;
     299          34 :     std::map<std::string, int> oMapCountNames;
     300          17 :     bool bNonUniqueNames = false;
     301          17 :     const int nLayerCount = poSrcDS->GetLayerCount();
     302          52 :     for (int i = 0; i < nLayerCount; ++i)
     303             :     {
     304          35 :         const OGRLayer *poSrcLayer = poSrcDS->GetLayer(i);
     305          66 :         if ((m_inputLayerName == poSrcLayer->GetDescription() ||
     306          70 :              nLayerCount == 1) &&
     307          14 :             !m_outputLayerName.empty())
     308             :         {
     309           8 :             aosNames.push_back(m_outputLayerName);
     310             :         }
     311             :         else
     312             :         {
     313          54 :             std::string osName(poSrcLayer->GetDescription());
     314          27 :             if (!m_reservedChars.empty())
     315             :             {
     316           2 :                 std::string osNewName;
     317          11 :                 for (char c : osName)
     318             :                 {
     319          10 :                     if (m_reservedChars.find(c) != std::string::npos)
     320             :                     {
     321           1 :                         if (!m_replacementChar.empty())
     322           1 :                             osNewName += m_replacementChar;
     323             :                     }
     324             :                     else
     325             :                     {
     326           9 :                         osNewName += c;
     327             :                     }
     328             :                 }
     329           1 :                 osName = std::move(osNewName);
     330             :             }
     331          27 :             if (m_filenameCompatible)
     332             :             {
     333           1 :                 osName = CPLLaunderForFilenameSafe(
     334           1 :                     osName, m_replacementChar.c_str()[0]);
     335             :             }
     336          27 :             if (m_ascii)
     337             :             {
     338           2 :                 char *pszStr = CPLUTF8ForceToASCII(
     339           2 :                     osName.c_str(), m_replacementChar.c_str()[0]);
     340           2 :                 osName = pszStr;
     341           2 :                 CPLFree(pszStr);
     342             :             }
     343          27 :             if (m_lowerCase)
     344             :             {
     345          10 :                 for (char &c : osName)
     346             :                 {
     347           9 :                     if (c >= 'A' && c <= 'Z')
     348           9 :                         c = c - 'A' + 'a';
     349             :                 }
     350             :             }
     351          27 :             if (m_maxLength > 0)
     352             :             {
     353           5 :                 TruncateUTF8ToMaxChar(osName, m_maxLength);
     354             :             }
     355          27 :             if (++oMapCountNames[osName] > 1)
     356           3 :                 bNonUniqueNames = true;
     357          27 :             aosNames.push_back(std::move(osName));
     358             :         }
     359             :     }
     360             : 
     361             :     // Extra optional pass if some names are not unique
     362          17 :     if (bNonUniqueNames)
     363             :     {
     364           6 :         std::map<std::string, int> oMapCurCounter;
     365           3 :         bool bUniquenessPossible = true;
     366           9 :         for (auto &osName : aosNames)
     367             :         {
     368           6 :             const int nCountForName = oMapCountNames[osName];
     369           6 :             if (nCountForName > 1)
     370             :             {
     371           6 :                 const int nCounter = ++oMapCurCounter[osName];
     372          12 :                 std::string osSuffix("_");
     373           6 :                 if (nCountForName <= 9)
     374           6 :                     osSuffix += CPLSPrintf("%d", nCounter);
     375           0 :                 else if (nCountForName <= 99)
     376           0 :                     osSuffix += CPLSPrintf("%02d", nCounter);
     377             :                 else
     378           0 :                     osSuffix += CPLSPrintf("%03d", nCounter);
     379           6 :                 const size_t nNameLen = CPLStrlenUTF8Ex(osName.c_str());
     380          10 :                 if (m_maxLength > 0 && nNameLen + osSuffix.size() >
     381           4 :                                            static_cast<size_t>(m_maxLength))
     382             :                 {
     383           4 :                     if (nNameLen > osSuffix.size())
     384             :                     {
     385           2 :                         TruncateUTF8ToMaxChar(osName,
     386           2 :                                               nNameLen - osSuffix.size());
     387           2 :                         osName += osSuffix;
     388             :                     }
     389           2 :                     else if (bUniquenessPossible)
     390             :                     {
     391           1 :                         ReportError(CE_Warning, CPLE_AppDefined,
     392             :                                     "Cannot create unique name for '%s' while "
     393             :                                     "respecting %d maximum length",
     394             :                                     osName.c_str(), m_maxLength);
     395           1 :                         bUniquenessPossible = false;
     396             :                     }
     397             :                 }
     398             :                 else
     399             :                 {
     400           2 :                     osName += osSuffix;
     401             :                 }
     402             :             }
     403             :         }
     404             :     }
     405             : 
     406             :     auto outDS = std::make_unique<GDALVectorRenameLayerAlgorithmDataset>(
     407          17 :         *poSrcDS, aosNames);
     408             : 
     409             :     // Final pass to create output layers
     410          52 :     for (int i = 0; i < nLayerCount; ++i)
     411             :     {
     412          35 :         OGRLayer *poSrcLayer = poSrcDS->GetLayer(i);
     413          35 :         if (poSrcLayer->GetDescription() != aosNames[i])
     414             :         {
     415             :             auto poLayer =
     416             :                 std::make_unique<GDALVectorRenameLayerAlgorithmLayer>(
     417          20 :                     *poSrcLayer, aosNames[i]);
     418          20 :             outDS->AddLayer(*poSrcLayer, std::move(poLayer));
     419             :         }
     420             :         else
     421             :         {
     422          30 :             outDS->AddLayer(
     423             :                 *poSrcLayer,
     424          30 :                 std::make_unique<GDALVectorPipelinePassthroughLayer>(
     425             :                     *poSrcLayer));
     426             :         }
     427             :     }
     428             : 
     429          17 :     m_outputDataset.Set(std::move(outDS));
     430             : 
     431          34 :     return true;
     432             : }
     433             : 
     434             : GDALVectorRenameLayerAlgorithmStandalone::
     435             :     ~GDALVectorRenameLayerAlgorithmStandalone() = default;
     436             : 
     437             : //! @endcond

Generated by: LCOV version 1.14