LCOV - code coverage report
Current view: top level - apps - gdalalg_vector_clean_coverage.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 77 94 81.9 %
Date: 2026-02-01 11:59:10 Functions: 11 11 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             : *
       3             :  * Project:  GDAL
       4             :  * Purpose:  "gdal vector clean-coverage" subcommand
       5             :  * Author:   Daniel Baston
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2025, ISciences LLC
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "gdalalg_vector_clean_coverage.h"
      14             : 
      15             : #include "cpl_error.h"
      16             : #include "gdal_priv.h"
      17             : #include "gdalalg_vector_geom.h"
      18             : #include "ogr_geos.h"
      19             : #include "ogrsf_frmts.h"
      20             : 
      21             : #include <cinttypes>
      22             : 
      23             : #ifndef _
      24             : #define _(x) (x)
      25             : #endif
      26             : 
      27             : //! @cond Doxygen_Suppress
      28             : 
      29          59 : GDALVectorCleanCoverageAlgorithm::GDALVectorCleanCoverageAlgorithm(
      30          59 :     bool standaloneStep)
      31             :     : GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
      32          59 :                                       standaloneStep)
      33             : {
      34          59 :     AddActiveLayerArg(&m_activeLayer);
      35             :     AddArg("snapping-distance", 0, _("Distance tolerance for snapping nodes"),
      36         118 :            &m_opts.snappingTolerance)
      37          59 :         .SetMinValueIncluded(0);
      38             : 
      39             :     AddArg("merge-strategy", 0,
      40             :            _("Algorithm to assign overlaps to neighboring polygons"),
      41         118 :            &m_opts.mergeStrategy)
      42         295 :         .SetChoices({"longest-border", "max-area", "min-area", "min-index"});
      43             : 
      44             :     AddArg("maximum-gap-width", 0, _("Maximum width of a gap to be closed"),
      45         118 :            &m_opts.maximumGapWidth)
      46          59 :         .SetMinValueIncluded(0);
      47          59 : }
      48             : 
      49             : #if defined HAVE_GEOS &&                                                       \
      50             :     (GEOS_VERSION_MAJOR > 3 ||                                                 \
      51             :      (GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 14))
      52             : 
      53             : class GDALVectorCleanCoverageOutputLayer final
      54             :     : public GDALGeosNonStreamingAlgorithmLayer
      55             : {
      56             :   public:
      57          13 :     GDALVectorCleanCoverageOutputLayer(
      58             :         OGRLayer &srcLayer, int geomFieldIndex,
      59             :         const GDALVectorCleanCoverageAlgorithm::Options &opts)
      60          13 :         : GDALGeosNonStreamingAlgorithmLayer(srcLayer, geomFieldIndex),
      61          13 :           m_opts(opts), m_cleanParams(GetCoverageCleanParams())
      62             :     {
      63          13 :     }
      64             : 
      65             :     ~GDALVectorCleanCoverageOutputLayer() override;
      66             : 
      67         333 :     const OGRFeatureDefn *GetLayerDefn() const override
      68             :     {
      69         333 :         return m_srcLayer.GetLayerDefn();
      70             :     }
      71             : 
      72          37 :     int TestCapability(const char *pszCap) const override
      73             :     {
      74          37 :         if (EQUAL(pszCap, OLCFastFeatureCount))
      75             :         {
      76           0 :             return m_srcLayer.TestCapability(pszCap);
      77             :         }
      78             : 
      79          37 :         return false;
      80             :     }
      81             : 
      82          13 :     GEOSCoverageCleanParams *GetCoverageCleanParams() const
      83             :     {
      84             :         GEOSCoverageCleanParams *params =
      85          13 :             GEOSCoverageCleanParams_create_r(m_poGeosContext);
      86             : 
      87          13 :         if (!params)
      88             :         {
      89           0 :             CPLError(CE_Failure, CPLE_AppDefined,
      90             :                      "Failed to create coverage clean parameters");
      91           0 :             return nullptr;
      92             :         }
      93             : 
      94          13 :         if (!GEOSCoverageCleanParams_setSnappingDistance_r(
      95          13 :                 m_poGeosContext, params, m_opts.snappingTolerance))
      96             :         {
      97           0 :             CPLError(CE_Failure, CPLE_AppDefined,
      98             :                      "Failed to set snapping tolerance");
      99           0 :             GEOSCoverageCleanParams_destroy_r(m_poGeosContext, params);
     100           0 :             return nullptr;
     101             :         }
     102             : 
     103          13 :         if (!GEOSCoverageCleanParams_setGapMaximumWidth_r(
     104          13 :                 m_poGeosContext, params, m_opts.maximumGapWidth))
     105             :         {
     106           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     107             :                      "Failed to set maximum gap width");
     108           0 :             GEOSCoverageCleanParams_destroy_r(m_poGeosContext, params);
     109           0 :             return nullptr;
     110             :         }
     111             : 
     112             :         int mergeStrategy;
     113          13 :         if (m_opts.mergeStrategy == "longest-border")
     114             :         {
     115          10 :             mergeStrategy = GEOS_MERGE_LONGEST_BORDER;
     116             :         }
     117           3 :         else if (m_opts.mergeStrategy == "max-area")
     118             :         {
     119           1 :             mergeStrategy = GEOS_MERGE_MAX_AREA;
     120             :         }
     121           2 :         else if (m_opts.mergeStrategy == "min-area")
     122             :         {
     123           1 :             mergeStrategy = GEOS_MERGE_MIN_AREA;
     124             :         }
     125           1 :         else if (m_opts.mergeStrategy == "min-index")
     126             :         {
     127           1 :             mergeStrategy = GEOS_MERGE_MIN_INDEX;
     128             :         }
     129             :         else
     130             :         {
     131           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     132             :                      "Unknown overlap merge strategy: %s",
     133           0 :                      m_opts.mergeStrategy.c_str());
     134           0 :             GEOSCoverageCleanParams_destroy_r(m_poGeosContext, params);
     135           0 :             return nullptr;
     136             :         }
     137             : 
     138          13 :         if (!GEOSCoverageCleanParams_setOverlapMergeStrategy_r(
     139          13 :                 m_poGeosContext, params, mergeStrategy))
     140             :         {
     141           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     142             :                      "Failed to set overlap merge strategy");
     143           0 :             GEOSCoverageCleanParams_destroy_r(m_poGeosContext, params);
     144           0 :             return nullptr;
     145             :         }
     146             : 
     147          13 :         return params;
     148             :     }
     149             : 
     150          47 :     bool PolygonsOnly() const override
     151             :     {
     152          47 :         return true;
     153             :     }
     154             : 
     155          34 :     bool SkipEmpty() const override
     156             :     {
     157          34 :         return false;
     158             :     }
     159             : 
     160          10 :     bool ProcessGeos() override
     161             :     {
     162             :         // Perform coverage cleaning
     163          10 :         GEOSGeometry *coll = GEOSGeom_createCollection_r(
     164             :             m_poGeosContext, GEOS_GEOMETRYCOLLECTION, m_apoGeosInputs.data(),
     165          10 :             static_cast<unsigned int>(m_apoGeosInputs.size()));
     166             : 
     167          10 :         if (coll == nullptr)
     168             :         {
     169           0 :             return false;
     170             :         }
     171             : 
     172          10 :         m_apoGeosInputs.clear();
     173             : 
     174          10 :         m_poGeosResultAsCollection =
     175          10 :             GEOSCoverageCleanWithParams_r(m_poGeosContext, coll, m_cleanParams);
     176          10 :         GEOSGeom_destroy_r(m_poGeosContext, coll);
     177             : 
     178          10 :         return m_poGeosResultAsCollection != nullptr;
     179             :     }
     180             : 
     181             :     CPL_DISALLOW_COPY_ASSIGN(GDALVectorCleanCoverageOutputLayer)
     182             : 
     183             :   private:
     184             :     const GDALVectorCleanCoverageAlgorithm::Options &m_opts;
     185             :     GEOSCoverageCleanParams *m_cleanParams;
     186             : };
     187             : 
     188          26 : GDALVectorCleanCoverageOutputLayer::~GDALVectorCleanCoverageOutputLayer()
     189             : {
     190          13 :     if (m_poGeosContext != nullptr)
     191             :     {
     192          13 :         GEOSCoverageCleanParams_destroy_r(m_poGeosContext, m_cleanParams);
     193             :     }
     194          26 : }
     195             : 
     196          14 : bool GDALVectorCleanCoverageAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
     197             : {
     198          14 :     auto poSrcDS = m_inputDataset[0].GetDatasetRef();
     199          28 :     auto poDstDS = std::make_unique<GDALVectorNonStreamingAlgorithmDataset>();
     200             : 
     201          28 :     GDALVectorAlgorithmLayerProgressHelper progressHelper(ctxt);
     202             : 
     203          30 :     for (auto &&poSrcLayer : poSrcDS->GetLayers())
     204             :     {
     205          20 :         if (m_activeLayer.empty() ||
     206           4 :             m_activeLayer == poSrcLayer->GetDescription())
     207             :         {
     208          13 :             progressHelper.AddProcessedLayer(*poSrcLayer);
     209             :         }
     210             :         else
     211             :         {
     212           3 :             progressHelper.AddPassThroughLayer(*poSrcLayer);
     213             :         }
     214             :     }
     215             : 
     216          14 :     if (!progressHelper.HasProcessedLayers())
     217             :     {
     218           1 :         ReportError(CE_Failure, CPLE_AppDefined,
     219             :                     "Specified layer '%s' was not found",
     220             :                     m_activeLayer.c_str());
     221           1 :         return false;
     222             :     }
     223             : 
     224          25 :     for (auto [poSrcLayer, bProcessed, layerProgressFunc, layerProgressData] :
     225          35 :          progressHelper)
     226             :     {
     227          14 :         if (bProcessed)
     228             :         {
     229          13 :             constexpr int geomFieldIndex = 0;  // TODO parametrize
     230             : 
     231             :             auto poLayer = std::make_unique<GDALVectorCleanCoverageOutputLayer>(
     232          13 :                 *poSrcLayer, geomFieldIndex, m_opts);
     233             : 
     234          13 :             if (!poDstDS->AddProcessedLayer(std::move(poLayer),
     235             :                                             layerProgressFunc,
     236             :                                             layerProgressData.get()))
     237             :             {
     238           3 :                 return false;
     239             :             }
     240             :         }
     241             :         else
     242             :         {
     243           1 :             poDstDS->AddPassThroughLayer(*poSrcLayer);
     244             :         }
     245             :     }
     246             : 
     247          10 :     m_outputDataset.Set(std::move(poDstDS));
     248             : 
     249          10 :     return true;
     250             : }
     251             : 
     252             : #else
     253             : 
     254             : bool GDALVectorCleanCoverageAlgorithm::RunStep(GDALPipelineStepRunContext &)
     255             : {
     256             :     ReportError(CE_Failure, CPLE_AppDefined,
     257             :                 "%s requires GDAL to be built against version 3.14 or later of "
     258             :                 "the GEOS library.",
     259             :                 NAME);
     260             :     return false;
     261             : }
     262             : #endif  // HAVE_GEOS
     263             : 
     264             : GDALVectorCleanCoverageAlgorithmStandalone::
     265             :     ~GDALVectorCleanCoverageAlgorithmStandalone() = default;
     266             : 
     267             : //! @endcond

Generated by: LCOV version 1.14