LCOV - code coverage report
Current view: top level - apps - gdalalg_vector_simplify_coverage.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 77 90 85.6 %
Date: 2025-06-19 12:30:01 Functions: 6 6 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             : *
       3             :  * Project:  GDAL
       4             :  * Purpose:  "gdal vector simplify-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_simplify_coverage.h"
      14             : 
      15             : #include "cpl_error.h"
      16             : #include "gdal_priv.h"
      17             : #include "gdalalg_vector_geom.h"
      18             : #include "ogr_geometry.h"
      19             : #include "ogr_geos.h"
      20             : #include "ogrsf_frmts.h"
      21             : 
      22             : #include <cinttypes>
      23             : 
      24             : #ifndef _
      25             : #define _(x) (x)
      26             : #endif
      27             : 
      28             : //! @cond Doxygen_Suppress
      29             : 
      30          29 : GDALVectorSimplifyCoverageAlgorithm::GDALVectorSimplifyCoverageAlgorithm(
      31          29 :     bool standaloneStep)
      32             :     : GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
      33          29 :                                       standaloneStep)
      34             : {
      35          29 :     AddActiveLayerArg(&m_activeLayer);
      36             :     AddArg("tolerance", 0, _("Distance tolerance for simplification."),
      37          58 :            &m_opts.tolerance)
      38          29 :         .SetRequired()
      39          29 :         .SetMinValueIncluded(0);
      40             :     AddArg("preserve-boundary", 0,
      41             :            _("Whether the exterior boundary should be preserved."),
      42          29 :            &m_opts.preserveBoundary);
      43          29 : }
      44             : 
      45             : #if defined HAVE_GEOS &&                                                       \
      46             :     (GEOS_VERSION_MAJOR > 3 ||                                                 \
      47             :      (GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 12))
      48             : 
      49             : class GDALVectorSimplifyCoverageOutputDataset
      50             :     : public GDALVectorNonStreamingAlgorithmDataset
      51             : {
      52             :   public:
      53           8 :     GDALVectorSimplifyCoverageOutputDataset(
      54             :         const GDALVectorSimplifyCoverageAlgorithm::Options &opts)
      55           8 :         : m_opts(opts)
      56             :     {
      57           8 :     }
      58             : 
      59             :     ~GDALVectorSimplifyCoverageOutputDataset() override;
      60             : 
      61           7 :     bool Process(OGRLayer &srcLayer, OGRLayer &dstLayer) override
      62             :     {
      63          14 :         std::vector<OGRFeatureUniquePtr> features;
      64          14 :         std::vector<GEOSGeometry *> geoms;
      65           7 :         m_poGeosContext = OGRGeometry::createGEOSContext();
      66             : 
      67             :         // Copy features from srcLayer into dstLayer, converting
      68             :         // their geometries to GEOS
      69          47 :         for (auto &feature : srcLayer)
      70             :         {
      71          43 :             const OGRGeometry *fgeom = feature->GetGeometryRef();
      72             : 
      73             :             // Avoid segfault with non-polygonal inputs on GEOS < 3.12.2
      74             :             // Later versions produce an error instead
      75             :             const auto eFGType =
      76          43 :                 fgeom ? wkbFlatten(fgeom->getGeometryType()) : wkbUnknown;
      77          43 :             if (eFGType != wkbPolygon && eFGType != wkbMultiPolygon &&
      78           3 :                 eFGType != wkbCurvePolygon && eFGType != wkbMultiSurface)
      79             :             {
      80           3 :                 for (auto &geom : geoms)
      81             :                 {
      82           0 :                     GEOSGeom_destroy_r(m_poGeosContext, geom);
      83             :                 }
      84           3 :                 CPLError(CE_Failure, CPLE_AppDefined,
      85             :                          "Coverage simplification can only be performed on "
      86             :                          "polygonal geometries. Feature %" PRId64
      87             :                          " does not have one",
      88           3 :                          static_cast<int64_t>(feature->GetFID()));
      89           3 :                 return false;
      90             :             }
      91             : 
      92             :             GEOSGeometry *geosGeom =
      93          40 :                 fgeom->exportToGEOS(m_poGeosContext, false);
      94          40 :             if (!geosGeom)
      95             :             {
      96             :                 // should not happen normally
      97           0 :                 for (auto &geom : geoms)
      98             :                 {
      99           0 :                     GEOSGeom_destroy_r(m_poGeosContext, geom);
     100             :                 }
     101           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     102             :                          "Geometry of feature %" PRId64
     103             :                          " failed to convert to GEOS",
     104           0 :                          static_cast<int64_t>(feature->GetFID()));
     105           0 :                 return false;
     106             :             }
     107          40 :             geoms.push_back(geosGeom);
     108             : 
     109          40 :             feature->SetGeometry(nullptr);  // free some memory
     110          40 :             feature->SetFDefnUnsafe(dstLayer.GetLayerDefn());
     111             : 
     112          40 :             features.push_back(std::move(feature));
     113             :         }
     114             : 
     115             :         // Perform coverage simplifciation
     116           4 :         GEOSGeometry *coll = GEOSGeom_createCollection_r(
     117             :             m_poGeosContext, GEOS_GEOMETRYCOLLECTION, geoms.data(),
     118           4 :             static_cast<unsigned int>(geoms.size()));
     119             : 
     120           4 :         if (coll == nullptr)
     121             :         {
     122           0 :             for (auto &geom : geoms)
     123             :             {
     124           0 :                 GEOSGeom_destroy_r(m_poGeosContext, geom);
     125             :             }
     126           0 :             return false;
     127             :         }
     128             : 
     129           8 :         GEOSGeometry *geos_result = GEOSCoverageSimplifyVW_r(
     130           4 :             m_poGeosContext, coll, m_opts.tolerance, m_opts.preserveBoundary);
     131           4 :         GEOSGeom_destroy_r(m_poGeosContext, coll);
     132             : 
     133           4 :         if (geos_result == nullptr)
     134             :         {
     135           0 :             return false;
     136             :         }
     137             : 
     138           4 :         m_papoGeosResults = GEOSGeom_releaseCollection_r(
     139             :             m_poGeosContext, geos_result, &m_nGeosResultSize);
     140           4 :         GEOSGeom_destroy_r(m_poGeosContext, geos_result);
     141           4 :         CPLAssert(features.size() == m_nGeosResultSize);
     142             : 
     143             :         // Create features with the modified geometries
     144          44 :         for (size_t i = 0; i < features.size(); i++)
     145             :         {
     146          40 :             GEOSGeometry *dstGeom = m_papoGeosResults[i];
     147             : 
     148             :             std::unique_ptr<OGRGeometry> poSimplified(
     149          40 :                 OGRGeometryFactory::createFromGEOS(m_poGeosContext, dstGeom));
     150          40 :             GEOSGeom_destroy_r(m_poGeosContext, m_papoGeosResults[i]);
     151          40 :             m_papoGeosResults[i] = nullptr;
     152             : 
     153          40 :             if (poSimplified == nullptr)
     154             :             {
     155           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     156             :                          "Failed to convert result from GEOS");
     157           0 :                 return false;
     158             :             }
     159          80 :             poSimplified->assignSpatialReference(
     160          40 :                 dstLayer.GetLayerDefn()->GetGeomFieldDefn(0)->GetSpatialRef());
     161          40 :             features[i]->SetGeometry(std::move(poSimplified));
     162             : 
     163          40 :             if (dstLayer.CreateFeature(features[i].get()) != CE_None)
     164             :             {
     165           0 :                 return false;
     166             :             }
     167          40 :             features[i].reset();
     168             :         }
     169             : 
     170           4 :         return true;
     171             :     }
     172             : 
     173             :   private:
     174             :     CPL_DISALLOW_COPY_ASSIGN(GDALVectorSimplifyCoverageOutputDataset)
     175             : 
     176             :     const GDALVectorSimplifyCoverageAlgorithm::Options &m_opts;
     177             :     GEOSContextHandle_t m_poGeosContext{nullptr};
     178             :     GEOSGeometry **m_papoGeosResults{nullptr};
     179             :     unsigned int m_nGeosResultSize{0};
     180             : };
     181             : 
     182          16 : GDALVectorSimplifyCoverageOutputDataset::
     183           8 :     ~GDALVectorSimplifyCoverageOutputDataset()
     184             : {
     185           8 :     if (m_poGeosContext != nullptr)
     186             :     {
     187          47 :         for (size_t i = 0; i < m_nGeosResultSize; i++)
     188             :         {
     189          40 :             GEOSGeom_destroy_r(m_poGeosContext, m_papoGeosResults[i]);
     190             :         }
     191           7 :         GEOSFree_r(m_poGeosContext, m_papoGeosResults);
     192           7 :         finishGEOS_r(m_poGeosContext);
     193             :     }
     194          16 : }
     195             : 
     196           8 : bool GDALVectorSimplifyCoverageAlgorithm::RunStep(GDALPipelineStepRunContext &)
     197             : {
     198           8 :     auto poSrcDS = m_inputDataset[0].GetDatasetRef();
     199             :     auto poDstDS =
     200          16 :         std::make_unique<GDALVectorSimplifyCoverageOutputDataset>(m_opts);
     201             : 
     202           8 :     bool bFoundActiveLayer = false;
     203             : 
     204          15 :     for (auto &&poSrcLayer : poSrcDS->GetLayers())
     205             :     {
     206          14 :         if (m_activeLayer.empty() ||
     207           4 :             m_activeLayer == poSrcLayer->GetDescription())
     208             :         {
     209           7 :             if (!poDstDS->AddProcessedLayer(*poSrcLayer))
     210             :             {
     211           3 :                 return false;
     212             :             }
     213           4 :             bFoundActiveLayer = true;
     214             :         }
     215             :         else
     216             :         {
     217           3 :             poDstDS->AddPassThroughLayer(*poSrcLayer);
     218             :         }
     219             :     }
     220             : 
     221           5 :     if (!bFoundActiveLayer)
     222             :     {
     223           1 :         ReportError(CE_Failure, CPLE_AppDefined,
     224             :                     "Specified layer '%s' was not found",
     225             :                     m_activeLayer.c_str());
     226           1 :         return false;
     227             :     }
     228             : 
     229           4 :     m_outputDataset.Set(std::move(poDstDS));
     230             : 
     231           4 :     return true;
     232             : }
     233             : 
     234             : #else
     235             : 
     236             : bool GDALVectorSimplifyCoverageAlgorithm::RunStep(GDALPipelineStepRunContext &)
     237             : {
     238             :     ReportError(CE_Failure, CPLE_AppDefined,
     239             :                 "%s requires GDAL to be built against version 3.12 or later of "
     240             :                 "the GEOS library.",
     241             :                 NAME);
     242             :     return false;
     243             : }
     244             : #endif  // HAVE_GEOS
     245             : 
     246             : GDALVectorSimplifyCoverageAlgorithmStandalone::
     247             :     ~GDALVectorSimplifyCoverageAlgorithmStandalone() = default;
     248             : 
     249             : //! @endcond

Generated by: LCOV version 1.14