LCOV - code coverage report
Current view: top level - apps - gdalalg_vector_filter.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 222 257 86.4 %
Date: 2026-07-24 18:27:47 Functions: 11 12 91.7 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  "filter" step of "vector pipeline"
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "gdalalg_vector_filter.h"
      14             : 
      15             : #include "gdal_priv.h"
      16             : #include "ogrsf_frmts.h"
      17             : #include "ogr_p.h"
      18             : 
      19             : #include <set>
      20             : 
      21             : //! @cond Doxygen_Suppress
      22             : 
      23             : #ifndef _
      24             : #define _(x) (x)
      25             : #endif
      26             : 
      27             : /************************************************************************/
      28             : /*        GDALVectorFilterAlgorithm::GDALVectorFilterAlgorithm()        */
      29             : /************************************************************************/
      30             : 
      31         123 : GDALVectorFilterAlgorithm::GDALVectorFilterAlgorithm(bool standaloneStep)
      32             :     : GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
      33         123 :                                       standaloneStep)
      34             : {
      35         123 :     auto &layerArg = AddActiveLayerArg(&m_activeLayer);
      36         123 :     AddBBOXArg(&m_bbox);
      37         246 :     AddArg("bbox-crs", 0, _("CRS of bounding box filter"), &m_bboxCrs)
      38         246 :         .SetIsCRSArg()
      39         123 :         .AddHiddenAlias("bbox_srs");
      40             :     AddArg("where", 0,
      41             :            _("Attribute query in a restricted form of the queries used in the "
      42             :              "SQL WHERE statement"),
      43         246 :            &m_where)
      44         123 :         .SetReadFromFileAtSyntaxAllowed()
      45         246 :         .SetMetaVar("<WHERE>|@<filename>")
      46         123 :         .SetRemoveSQLCommentsEnabled()
      47             :         .SetAutoCompleteFunction(
      48          11 :             [this, &layerArg](const std::string &currentValue)
      49         134 :             { return CompleteWhere(layerArg, currentValue); });
      50             :     AddArg("update-extent", 0,
      51             :            _("Update layer extent to take into account the filter"),
      52         123 :            &m_updateExtent);
      53         123 : }
      54             : 
      55             : /************************************************************************/
      56             : /*              GDALVectorFilterAlgorithmLayerChangeExtent              */
      57             : /************************************************************************/
      58             : 
      59             : constexpr const char *const SQL_OPERATORS[] = {
      60             :     "=", "<>", "<", "<=", ">", ">=", "AND", "OR", "LIKE", "BETWEEN"};
      61             : 
      62           7 : static bool IsSQLOperator(const char *pszStr)
      63             : {
      64           7 :     return std::find_if(std::begin(SQL_OPERATORS), std::end(SQL_OPERATORS),
      65          31 :                         [pszStr](const char *pszStr2)
      66          31 :                         { return EQUAL(pszStr, pszStr2); }) !=
      67           7 :            std::end(SQL_OPERATORS);
      68             : }
      69             : 
      70          24 : static std::string GetSQLIdentifier(const std::string &name)
      71             : {
      72          24 :     if (name.find_first_of("'\" ") != std::string::npos)
      73             :     {
      74           0 :         char *pszEscaped = CPLEscapeString(name.c_str(), -1, CPLES_SQLI);
      75           0 :         std::string ret = std::string("\"").append(pszEscaped).append("\"");
      76           0 :         CPLFree(pszEscaped);
      77           0 :         return ret;
      78             :     }
      79             :     else
      80             :     {
      81          24 :         return name;
      82             :     }
      83             : }
      84             : 
      85          30 : static std::string GetSQLStringLiteral(const char *val)
      86             : {
      87          30 :     char *pszEscaped = CPLEscapeString(val, -1, CPLES_SQL);
      88          60 :     std::string ret = std::string("'").append(pszEscaped).append("'");
      89          30 :     CPLFree(pszEscaped);
      90          30 :     return ret;
      91             : }
      92             : 
      93             : std::vector<std::string>
      94          11 : GDALVectorFilterAlgorithm::CompleteWhere(const GDALAlgorithmArg &layerArg,
      95             :                                          const std::string &currentValue) const
      96             : {
      97          11 :     std::vector<std::string> ret;
      98          22 :     if (currentValue.empty() || currentValue[0] != '"' ||
      99          11 :         m_inputDataset.empty())
     100           0 :         return ret;
     101             : 
     102             :     auto poDS = std::unique_ptr<GDALDataset>(
     103          11 :         GDALDataset::Open(m_inputDataset[0].GetName().c_str(),
     104          22 :                           GDAL_OF_VECTOR | GDAL_OF_READONLY));
     105          11 :     if (!poDS)
     106           0 :         return ret;
     107             : 
     108             :     // Collect field names
     109          22 :     std::string layerName;
     110          11 :     if (layerArg.IsExplicitlySet())
     111           2 :         layerName = layerArg.Get<std::string>();
     112          22 :     std::map<std::string, std::vector<OGRLayer *>> fieldNames;
     113          11 :     if (layerName.empty())
     114             :     {
     115          18 :         for (auto *poLayer : poDS->GetLayers())
     116             :         {
     117          37 :             for (const auto *poFieldDefn : poLayer->GetLayerDefn()->GetFields())
     118             :             {
     119          28 :                 fieldNames[poFieldDefn->GetNameRef()].push_back(poLayer);
     120             :             }
     121             :         }
     122             :     }
     123           2 :     else if (auto *poLayer = poDS->GetLayerByName(layerName.c_str()))
     124             :     {
     125           5 :         for (const auto *poFieldDefn : poLayer->GetLayerDefn()->GetFields())
     126             :         {
     127           4 :             fieldNames[poFieldDefn->GetNameRef()].push_back(poLayer);
     128             :         }
     129             :     }
     130          11 :     if (fieldNames.empty())
     131           1 :         return ret;
     132             : 
     133             :     const CPLStringList aosTokens(CSLTokenizeString2(
     134          10 :         currentValue.c_str() + 1, " ",
     135             :         CSLT_HONOURSTRINGS | CSLT_HONOURSINGLEQUOTES | CSLT_PRESERVEQUOTES |
     136          20 :             CSLT_PRESERVEESCAPES | CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES));
     137             : 
     138          20 :     std::string prefix;
     139          10 :     const int nTokens = aosTokens.size();
     140          10 :     if (nTokens > 0 && cpl::contains(fieldNames, aosTokens[nTokens - 1]))
     141             :     {
     142           1 :         prefix = currentValue.substr(1);
     143           1 :         if (!prefix.empty() && prefix.back() != ' ')
     144           0 :             prefix += ' ';
     145          11 :         for (const char *op : SQL_OPERATORS)
     146             :         {
     147          10 :             if (nTokens > 1 && strcmp(op, "AND") == 0)
     148           0 :                 break;
     149          10 :             ret.push_back(prefix + op);
     150             :         }
     151             :     }
     152             :     else
     153             :     {
     154             :         const bool bLastTokenIsSQLOperator =
     155           9 :             nTokens > 0 && IsSQLOperator(aosTokens[nTokens - 1]);
     156           9 :         const int nCompleteTokens =
     157           9 :             nTokens + (bLastTokenIsSQLOperator ? 0 : -1);
     158          23 :         for (int i = 0; i < nCompleteTokens; ++i)
     159             :         {
     160          14 :             if (!prefix.empty())
     161           8 :                 prefix += ' ';
     162          14 :             prefix += aosTokens[i];
     163             :         }
     164           9 :         if (!prefix.empty())
     165           6 :             prefix += ' ';
     166             : 
     167           9 :         const char *pszLastFieldName = nullptr;
     168           9 :         OGRLayer *poLastFieldLayer = nullptr;
     169           9 :         OGRFieldType eLastFieldType = OFTString;
     170           9 :         if (nCompleteTokens >= 2)
     171             :         {
     172           6 :             const char *pszFieldName = aosTokens[nCompleteTokens - 2];
     173           6 :             const auto oIter = fieldNames.find(pszFieldName);
     174           6 :             if (oIter != fieldNames.end() && oIter->second.size() == 1)
     175             :             {
     176             :                 const int nIdx =
     177           5 :                     oIter->second[0]->GetLayerDefn()->GetFieldIndex(
     178           5 :                         pszFieldName);
     179           5 :                 if (nIdx >= 0)
     180             :                 {
     181           5 :                     pszLastFieldName = pszFieldName;
     182           5 :                     poLastFieldLayer = oIter->second[0];
     183           5 :                     eLastFieldType = poLastFieldLayer->GetLayerDefn()
     184           5 :                                          ->GetFieldDefn(nIdx)
     185           5 :                                          ->GetType();
     186             :                 }
     187             :             }
     188             :         }
     189             : 
     190          38 :         for (const auto &[name, layers] : fieldNames)
     191             :         {
     192          29 :             bool canAdd = false;
     193          29 :             if (!pszLastFieldName)
     194             :             {
     195          12 :                 canAdd = true;
     196             :             }
     197          17 :             else if (name != pszLastFieldName)
     198             :             {
     199          12 :                 if (layers.size() > 1)
     200             :                 {
     201           0 :                     canAdd = true;
     202             :                 }
     203          12 :                 else if (layers[0]
     204          12 :                              ->GetLayerDefn()
     205             :                              ->GetFieldDefn(
     206          12 :                                  layers[0]->GetLayerDefn()->GetFieldIndex(
     207          12 :                                      name.c_str()))
     208          12 :                              ->GetType() == eLastFieldType)
     209             :                 {
     210           2 :                     canAdd = true;
     211             :                 }
     212             :             }
     213          29 :             if (canAdd)
     214             :             {
     215          14 :                 ret.push_back(prefix + GetSQLIdentifier(name));
     216             :             }
     217             :         }
     218             : 
     219           9 :         if (pszLastFieldName && poLastFieldLayer)
     220             :         {
     221           5 :             auto poLayer = poLastFieldLayer;
     222           5 :             constexpr int NOT_TOO_LARGE = 1000;
     223           5 :             const auto nFeatureCount = poLayer->GetFeatureCount();
     224           5 :             if (nFeatureCount > 0 && nFeatureCount < NOT_TOO_LARGE)
     225             :             {
     226           5 :                 constexpr int VALUES_COUNT = 10;
     227             :                 const std::string osSQLField =
     228          15 :                     GetSQLIdentifier(pszLastFieldName);
     229             :                 const std::string osSQLLayer =
     230          15 :                     GetSQLIdentifier(poLayer->GetName());
     231           5 :                 if (eLastFieldType == OFTString)
     232             :                 {
     233           6 :                     CPLString osSQL;
     234           3 :                     const char *pszDialect = nullptr;
     235           3 :                     if (GetGDALDriverManager()->GetDriverByName("SQLite"))
     236             :                     {
     237           3 :                         pszDialect = "SQLite";
     238             :                         // Find 10 most frequent strings
     239             :                         osSQL.Printf("SELECT %s, COUNT(%s) cnt FROM %s GROUP "
     240             :                                      "BY %s ORDER BY cnt DESC, %s ASC LIMIT %d",
     241             :                                      osSQLField.c_str(), osSQLField.c_str(),
     242             :                                      osSQLLayer.c_str(), osSQLField.c_str(),
     243           3 :                                      osSQLField.c_str(), VALUES_COUNT + 1);
     244             :                     }
     245             :                     else
     246             :                     {
     247             :                         osSQL.Printf("SELECT DISTINCT %s FROM %s LIMIT %d",
     248             :                                      osSQLField.c_str(), osSQLLayer.c_str(),
     249           0 :                                      VALUES_COUNT + 1);
     250             :                     }
     251             :                     auto poSQLLayer =
     252           3 :                         poDS->ExecuteSQL(osSQL.c_str(), nullptr, pszDialect);
     253           3 :                     if (poSQLLayer)
     254             :                     {
     255           3 :                         int nCount = 0;
     256          34 :                         for (auto &&poFeature : poSQLLayer)
     257             :                         {
     258          31 :                             if (nCount == VALUES_COUNT)
     259             :                             {
     260           1 :                                 ret.push_back(prefix + "'...other values...");
     261           1 :                                 break;
     262             :                             }
     263          30 :                             ret.push_back(prefix +
     264          60 :                                           GetSQLStringLiteral(
     265             :                                               poFeature->GetFieldAsString(0)));
     266          30 :                             nCount++;
     267             :                         }
     268           3 :                         poDS->ReleaseResultSet(poSQLLayer);
     269             :                     }
     270             :                 }
     271           2 :                 else if (eLastFieldType == OFTInteger ||
     272           0 :                          eLastFieldType == OFTInteger64 ||
     273             :                          eLastFieldType == OFTReal)
     274             :                 {
     275           4 :                     CPLString osSQL;
     276           2 :                     const char *pszDialect = nullptr;
     277           3 :                     if (nFeatureCount > VALUES_COUNT + 2 &&
     278           1 :                         GetGDALDriverManager()->GetDriverByName("SQLite"))
     279             :                     {
     280           1 :                         pszDialect = "SQLite";
     281             :                         // Collect lowest and highest values
     282             :                         osSQL.Printf("SELECT DISTINCT %s FROM ("
     283             :                                      "SELECT * FROM (SELECT DISTINCT %s FROM "
     284             :                                      "%s ORDER BY %s ASC LIMIT %d) UNION ALL "
     285             :                                      "SELECT * FROM (SELECT DISTINCT %s FROM "
     286             :                                      "%s ORDER BY %s DESC LIMIT %d)"
     287             :                                      ") x ORDER BY %s",
     288             :                                      osSQLField.c_str(), osSQLField.c_str(),
     289             :                                      osSQLLayer.c_str(), osSQLField.c_str(),
     290             :                                      VALUES_COUNT / 2, osSQLField.c_str(),
     291             :                                      osSQLLayer.c_str(), osSQLField.c_str(),
     292           1 :                                      VALUES_COUNT / 2 + 1, osSQLField.c_str());
     293             :                     }
     294             :                     else
     295             :                     {
     296             :                         osSQL.Printf("SELECT DISTINCT %s FROM %s LIMIT %d",
     297             :                                      osSQLField.c_str(), osSQLLayer.c_str(),
     298           1 :                                      VALUES_COUNT + 1);
     299             :                     }
     300             :                     auto poSQLLayer =
     301           2 :                         poDS->ExecuteSQL(osSQL.c_str(), nullptr, pszDialect);
     302           2 :                     if (poSQLLayer)
     303             :                     {
     304           2 :                         int nCount = 0;
     305          23 :                         for (auto &&poFeature : poSQLLayer)
     306             :                         {
     307          21 :                             if (nCount == VALUES_COUNT)
     308             :                             {
     309           1 :                                 if (pszDialect)
     310             :                                 {
     311           1 :                                     ret.erase(ret.begin() + VALUES_COUNT / 2 +
     312           1 :                                               1);
     313           1 :                                     ret.push_back(
     314           2 :                                         prefix +
     315             :                                         poFeature->GetFieldAsString(0));
     316             :                                 }
     317           1 :                                 ret.push_back(prefix + "...other values...");
     318           1 :                                 break;
     319             :                             }
     320          20 :                             ret.push_back(prefix +
     321             :                                           poFeature->GetFieldAsString(0));
     322          20 :                             nCount++;
     323             :                         }
     324           2 :                         poDS->ReleaseResultSet(poSQLLayer);
     325             :                     }
     326             :                 }
     327             :             }
     328             :         }
     329             :     }
     330          10 :     return ret;
     331             : }
     332             : 
     333             : /************************************************************************/
     334             : /*              GDALVectorFilterAlgorithmLayerChangeExtent              */
     335             : /************************************************************************/
     336             : 
     337             : namespace
     338             : {
     339             : class GDALVectorFilterAlgorithmLayerChangeExtent final
     340             :     : public GDALVectorPipelinePassthroughLayer
     341             : {
     342             :   public:
     343           1 :     GDALVectorFilterAlgorithmLayerChangeExtent(
     344             :         OGRLayer &oSrcLayer, const OGREnvelope3D &sLayerEnvelope)
     345           1 :         : GDALVectorPipelinePassthroughLayer(oSrcLayer),
     346           1 :           m_sLayerEnvelope(sLayerEnvelope)
     347             :     {
     348           1 :     }
     349             : 
     350           1 :     OGRErr IGetExtent(int /*iGeomField*/, OGREnvelope *psExtent,
     351             :                       bool /* bForce */) override
     352             :     {
     353           1 :         if (m_sLayerEnvelope.IsInit())
     354             :         {
     355           1 :             *psExtent = m_sLayerEnvelope;
     356           1 :             return OGRERR_NONE;
     357             :         }
     358             :         else
     359             :         {
     360           0 :             return OGRERR_FAILURE;
     361             :         }
     362             :     }
     363             : 
     364           1 :     OGRErr IGetExtent3D(int /*iGeomField*/, OGREnvelope3D *psExtent,
     365             :                         bool /* bForce */) override
     366             :     {
     367           1 :         if (m_sLayerEnvelope.IsInit())
     368             :         {
     369           1 :             *psExtent = m_sLayerEnvelope;
     370           1 :             return OGRERR_NONE;
     371             :         }
     372             :         else
     373             :         {
     374           0 :             return OGRERR_FAILURE;
     375             :         }
     376             :     }
     377             : 
     378           0 :     int TestCapability(const char *pszCap) const override
     379             :     {
     380           0 :         if (EQUAL(pszCap, OLCFastGetExtent))
     381           0 :             return true;
     382           0 :         return m_srcLayer.TestCapability(pszCap);
     383             :     }
     384             : 
     385             :   private:
     386             :     const OGREnvelope3D m_sLayerEnvelope;
     387             : };
     388             : 
     389             : }  // namespace
     390             : 
     391             : /************************************************************************/
     392             : /*                 GDALVectorFilterAlgorithm::RunStep()                 */
     393             : /************************************************************************/
     394             : 
     395          26 : bool GDALVectorFilterAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
     396             : {
     397          26 :     auto poSrcDS = m_inputDataset[0].GetDatasetRef();
     398          26 :     CPLAssert(poSrcDS);
     399             : 
     400          26 :     CPLAssert(m_outputDataset.GetName().empty());
     401          26 :     CPLAssert(!m_outputDataset.GetDatasetRef());
     402             : 
     403          26 :     const int nLayerCount = poSrcDS->GetLayerCount();
     404             : 
     405          52 :     OGRSpatialReference oBBOX_SRS;
     406          26 :     if (!m_bboxCrs.empty())
     407             :     {
     408           2 :         oBBOX_SRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     409             :         // Already validated
     410           2 :         CPL_IGNORE_RET_VAL(oBBOX_SRS.SetFromUserInput(m_bboxCrs.c_str()));
     411             :     }
     412             : 
     413          26 :     bool ret = true;
     414          26 :     if (m_bbox.size() == 4)
     415             :     {
     416           6 :         const double xmin = m_bbox[0];
     417           6 :         const double ymin = m_bbox[1];
     418           6 :         const double xmax = m_bbox[2];
     419           6 :         const double ymax = m_bbox[3];
     420          11 :         for (int i = 0; i < nLayerCount; ++i)
     421             :         {
     422           6 :             auto poSrcLayer = poSrcDS->GetLayer(i);
     423           6 :             ret = ret && (poSrcLayer != nullptr);
     424           6 :             if (poSrcLayer && (m_activeLayer.empty() ||
     425           0 :                                m_activeLayer == poSrcLayer->GetDescription()))
     426             :             {
     427             : 
     428           6 :                 const auto poLayerSRS = poSrcLayer->GetSpatialRef();
     429           6 :                 if (poLayerSRS && !oBBOX_SRS.IsEmpty())
     430             :                 {
     431           2 :                     auto poCT = OGRCreateCoordinateTransformation(&oBBOX_SRS,
     432             :                                                                   poLayerSRS);
     433           2 :                     if (!poCT)
     434           1 :                         return false;
     435             :                     double xMinLayerSRS;
     436             :                     double yMinLayerSRS;
     437             :                     double xMaxLayerSRS;
     438             :                     double yMaxLayerSRS;
     439           2 :                     if (!poCT->TransformBounds(
     440             :                             xmin, ymin, xmax, ymax, &xMinLayerSRS,
     441           2 :                             &yMinLayerSRS, &xMaxLayerSRS, &yMaxLayerSRS, 21))
     442             :                     {
     443           1 :                         ReportError(CE_Failure, CPLE_AppDefined,
     444             :                                     "Bounding box reprojection failed");
     445           1 :                         return false;
     446             :                     }
     447           1 :                     poSrcLayer->SetSpatialFilterRect(
     448             :                         xMinLayerSRS, yMinLayerSRS, xMaxLayerSRS, yMaxLayerSRS);
     449             :                 }
     450             :                 else
     451             :                 {
     452           4 :                     poSrcLayer->SetSpatialFilterRect(xmin, ymin, xmax, ymax);
     453             :                 }
     454             :             }
     455             :         }
     456             :     }
     457             : 
     458          25 :     if (ret && !m_where.empty())
     459             :     {
     460          40 :         for (int i = 0; i < nLayerCount; ++i)
     461             :         {
     462          22 :             auto poSrcLayer = poSrcDS->GetLayer(i);
     463          22 :             ret = ret && (poSrcLayer != nullptr);
     464          28 :             if (ret && (m_activeLayer.empty() ||
     465           6 :                         m_activeLayer == poSrcLayer->GetDescription()))
     466             :             {
     467          19 :                 ret = poSrcLayer->SetAttributeFilter(m_where.c_str()) ==
     468             :                       OGRERR_NONE;
     469             :             }
     470             :         }
     471             :     }
     472             : 
     473          25 :     if (ret)
     474             :     {
     475             :         auto outDS =
     476          24 :             std::make_unique<GDALVectorPipelineOutputDataset>(*poSrcDS);
     477             : 
     478          24 :         int64_t nTotalFeatures = 0;
     479          24 :         if (m_updateExtent && ctxt.m_pfnProgress)
     480             :         {
     481           0 :             for (int i = 0; ret && i < nLayerCount; ++i)
     482             :             {
     483           0 :                 auto poSrcLayer = poSrcDS->GetLayer(i);
     484           0 :                 ret = (poSrcLayer != nullptr);
     485           0 :                 if (ret)
     486             :                 {
     487           0 :                     if (m_activeLayer.empty() ||
     488           0 :                         m_activeLayer == poSrcLayer->GetDescription())
     489             :                     {
     490           0 :                         if (poSrcLayer->TestCapability(OLCFastFeatureCount))
     491             :                         {
     492           0 :                             const auto nFC = poSrcLayer->GetFeatureCount(false);
     493           0 :                             if (nFC < 0)
     494             :                             {
     495           0 :                                 nTotalFeatures = 0;
     496           0 :                                 break;
     497             :                             }
     498           0 :                             nTotalFeatures += nFC;
     499             :                         }
     500             :                     }
     501             :                 }
     502             :             }
     503             :         }
     504             : 
     505          24 :         int64_t nFeatureCounter = 0;
     506          52 :         for (int i = 0; ret && i < nLayerCount; ++i)
     507             :         {
     508          28 :             auto poSrcLayer = poSrcDS->GetLayer(i);
     509          28 :             ret = (poSrcLayer != nullptr);
     510          28 :             if (ret)
     511             :             {
     512          32 :                 if (m_updateExtent &&
     513           4 :                     (m_activeLayer.empty() ||
     514           2 :                      m_activeLayer == poSrcLayer->GetDescription()))
     515             :                 {
     516           1 :                     OGREnvelope3D sLayerEnvelope, sFeatureEnvelope;
     517           2 :                     for (auto &&poFeature : poSrcLayer)
     518             :                     {
     519           1 :                         const auto poGeom = poFeature->GetGeometryRef();
     520           1 :                         if (poGeom && !poGeom->IsEmpty())
     521             :                         {
     522           1 :                             poGeom->getEnvelope(&sFeatureEnvelope);
     523           1 :                             sLayerEnvelope.Merge(sFeatureEnvelope);
     524             :                         }
     525             : 
     526           1 :                         ++nFeatureCounter;
     527           1 :                         if (nTotalFeatures > 0 && ctxt.m_pfnProgress &&
     528           0 :                             !ctxt.m_pfnProgress(
     529           0 :                                 static_cast<double>(nFeatureCounter) /
     530           0 :                                     static_cast<double>(nTotalFeatures),
     531             :                                 "", ctxt.m_pProgressData))
     532             :                         {
     533           0 :                             ReportError(CE_Failure, CPLE_UserInterrupt,
     534             :                                         "Interrupted by user");
     535           0 :                             return false;
     536             :                         }
     537             :                     }
     538           2 :                     outDS->AddLayer(
     539             :                         *poSrcLayer,
     540             :                         std::make_unique<
     541           2 :                             GDALVectorFilterAlgorithmLayerChangeExtent>(
     542             :                             *poSrcLayer, sLayerEnvelope));
     543             :                 }
     544             :                 else
     545             :                 {
     546          54 :                     outDS->AddLayer(
     547             :                         *poSrcLayer,
     548          54 :                         std::make_unique<GDALVectorPipelinePassthroughLayer>(
     549             :                             *poSrcLayer));
     550             :                 }
     551             :             }
     552             :         }
     553             : 
     554          24 :         if (ret)
     555          24 :             m_outputDataset.Set(std::move(outDS));
     556             :     }
     557             : 
     558          25 :     return ret;
     559             : }
     560             : 
     561             : GDALVectorFilterAlgorithmStandalone::~GDALVectorFilterAlgorithmStandalone() =
     562             :     default;
     563             : 
     564             : //! @endcond

Generated by: LCOV version 1.14