LCOV - code coverage report
Current view: top level - apps - gdalalg_vector_geom.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 116 133 87.2 %
Date: 2026-01-11 15:50:51 Functions: 8 9 88.9 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  Base classes for some geometry-related vector algorithms
       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_geom.h"
      14             : #include "cpl_enumerate.h"
      15             : 
      16             : #include <algorithm>
      17             : #include <cinttypes>
      18             : 
      19             : //! @cond Doxygen_Suppress
      20             : 
      21             : #ifndef _
      22             : #define _(x) (x)
      23             : #endif
      24             : 
      25             : /************************************************************************/
      26             : /*                 GDALVectorGeomAbstractAlgorithm()                    */
      27             : /************************************************************************/
      28             : 
      29         350 : GDALVectorGeomAbstractAlgorithm::GDALVectorGeomAbstractAlgorithm(
      30             :     const std::string &name, const std::string &description,
      31         350 :     const std::string &helpURL, bool standaloneStep, OptionsBase &opts)
      32             :     : GDALVectorPipelineStepAlgorithm(name, description, helpURL,
      33             :                                       standaloneStep),
      34         350 :       m_activeLayer(opts.m_activeLayer)
      35             : {
      36         350 :     AddActiveLayerArg(&opts.m_activeLayer);
      37             :     AddArg("active-geometry", 0,
      38             :            _("Geometry field name to which to restrict the processing (if not "
      39             :              "specified, all)"),
      40         350 :            &opts.m_geomField);
      41         350 : }
      42             : 
      43             : /************************************************************************/
      44             : /*               GDALVectorGeomAbstractAlgorithm::RunStep()             */
      45             : /************************************************************************/
      46             : 
      47          52 : bool GDALVectorGeomAbstractAlgorithm::RunStep(GDALPipelineStepRunContext &)
      48             : {
      49          52 :     auto poSrcDS = m_inputDataset[0].GetDatasetRef();
      50          52 :     CPLAssert(poSrcDS);
      51          52 :     CPLAssert(m_outputDataset.GetName().empty());
      52          52 :     CPLAssert(!m_outputDataset.GetDatasetRef());
      53             : 
      54          52 :     auto outDS = std::make_unique<GDALVectorPipelineOutputDataset>(*poSrcDS);
      55             : 
      56         107 :     for (auto &&poSrcLayer : poSrcDS->GetLayers())
      57             :     {
      58          61 :         if (m_activeLayer.empty() ||
      59           6 :             m_activeLayer == poSrcLayer->GetDescription())
      60             :         {
      61          52 :             outDS->AddLayer(*poSrcLayer, CreateAlgLayer(*poSrcLayer));
      62             :         }
      63             :         else
      64             :         {
      65           6 :             outDS->AddLayer(
      66             :                 *poSrcLayer,
      67           6 :                 std::make_unique<GDALVectorPipelinePassthroughLayer>(
      68             :                     *poSrcLayer));
      69             :         }
      70             :     }
      71             : 
      72          52 :     m_outputDataset.Set(std::move(outDS));
      73             : 
      74         104 :     return true;
      75             : }
      76             : 
      77             : #ifdef HAVE_GEOS
      78             : 
      79             : /************************************************************************/
      80             : /*                    GDALGeosNonStreamingAlgorithmDataset              */
      81             : /************************************************************************/
      82             : 
      83          31 : GDALGeosNonStreamingAlgorithmDataset::GDALGeosNonStreamingAlgorithmDataset()
      84          31 :     : m_poGeosContext{OGRGeometry::createGEOSContext()}
      85             : {
      86          31 : }
      87             : 
      88          31 : GDALGeosNonStreamingAlgorithmDataset::~GDALGeosNonStreamingAlgorithmDataset()
      89             : {
      90          31 :     Cleanup();
      91          31 :     if (m_poGeosContext != nullptr)
      92             :     {
      93          31 :         finishGEOS_r(m_poGeosContext);
      94             :     }
      95          31 : }
      96             : 
      97          59 : void GDALGeosNonStreamingAlgorithmDataset::Cleanup()
      98             : {
      99          59 :     m_apoFeatures.clear();
     100             : 
     101          59 :     if (m_poGeosContext != nullptr)
     102             :     {
     103          59 :         for (auto &poGeom : m_apoGeosInputs)
     104             :         {
     105           0 :             GEOSGeom_destroy_r(m_poGeosContext, poGeom);
     106             :         }
     107          59 :         m_apoGeosInputs.clear();
     108             : 
     109          59 :         if (m_poGeosContext != nullptr)
     110             :         {
     111          59 :             GEOSGeom_destroy_r(m_poGeosContext, m_poGeosResultAsCollection);
     112          59 :             m_poGeosResultAsCollection = nullptr;
     113             :         }
     114             : 
     115         175 :         for (size_t i = 0; i < m_nGeosResultSize; i++)
     116             :         {
     117         116 :             GEOSGeom_destroy_r(m_poGeosContext, m_papoGeosResults[i]);
     118             :         }
     119          59 :         m_nGeosResultSize = 0;
     120             : 
     121          59 :         if (m_papoGeosResults != nullptr)
     122             :         {
     123          20 :             GEOSFree_r(m_poGeosContext, m_papoGeosResults);
     124          20 :             m_papoGeosResults = nullptr;
     125             :         }
     126             :     }
     127          59 : }
     128             : 
     129          28 : bool GDALGeosNonStreamingAlgorithmDataset::ConvertInputsToGeos(
     130             :     OGRLayer &srcLayer, OGRLayer &dstLayer, int geomFieldIndex, bool sameDefn,
     131             :     GDALProgressFunc pfnProgress, void *pProgressData)
     132             : {
     133          28 :     const GIntBig nLayerFeatures = srcLayer.TestCapability(OLCFastFeatureCount)
     134          28 :                                        ? srcLayer.GetFeatureCount(false)
     135          28 :                                        : -1;
     136             :     const double dfInvLayerFeatures =
     137          28 :         1.0 / std::max(1.0, static_cast<double>(nLayerFeatures));
     138          28 :     const double dfProgressRatio = dfInvLayerFeatures * 0.5;
     139             : 
     140         144 :     for (auto &feature : srcLayer)
     141             :     {
     142         122 :         const OGRGeometry *poSrcGeom = feature->GetGeomFieldRef(geomFieldIndex);
     143             : 
     144         122 :         if (PolygonsOnly())
     145             :         {
     146             :             const auto eFGType = poSrcGeom
     147         122 :                                      ? wkbFlatten(poSrcGeom->getGeometryType())
     148         122 :                                      : wkbUnknown;
     149         122 :             if (eFGType != wkbPolygon && eFGType != wkbMultiPolygon &&
     150           6 :                 eFGType != wkbCurvePolygon && eFGType != wkbMultiSurface)
     151             :             {
     152           6 :                 CPLError(CE_Failure, CPLE_AppDefined,
     153             :                          "Coverage checking can only be performed on "
     154             :                          "polygonal geometries. Feature %" PRId64
     155             :                          " does not have one",
     156           6 :                          static_cast<int64_t>(feature->GetFID()));
     157           6 :                 return false;
     158             :             }
     159             :         }
     160             : 
     161         116 :         if (poSrcGeom)
     162             :         {
     163             :             GEOSGeometry *geosGeom =
     164         116 :                 poSrcGeom->exportToGEOS(m_poGeosContext, false);
     165         116 :             if (!geosGeom)
     166             :             {
     167             :                 // should not happen normally
     168           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     169             :                          "Geometry of feature %" PRId64
     170             :                          " failed to convert to GEOS",
     171           0 :                          static_cast<int64_t>(feature->GetFID()));
     172           0 :                 return false;
     173             :             }
     174             : 
     175         116 :             m_apoGeosInputs.push_back(geosGeom);
     176             :         }
     177             :         else
     178             :         {
     179           0 :             m_apoGeosInputs.push_back(GEOSGeom_createEmptyCollection_r(
     180             :                 m_poGeosContext, GEOS_GEOMETRYCOLLECTION));
     181             :         }
     182             : 
     183         116 :         feature->SetGeometry(nullptr);  // free some memory
     184             : 
     185         116 :         if (sameDefn)
     186             :         {
     187          84 :             feature->SetFDefnUnsafe(dstLayer.GetLayerDefn());
     188          84 :             m_apoFeatures.push_back(
     189         168 :                 std::unique_ptr<OGRFeature>(feature.release()));
     190             :         }
     191             :         else
     192             :         {
     193             :             auto newFeature =
     194          64 :                 std::make_unique<OGRFeature>(dstLayer.GetLayerDefn());
     195          32 :             newFeature->SetFrom(feature.get(), true);
     196          32 :             newFeature->SetFID(feature->GetFID());
     197          32 :             m_apoFeatures.push_back(std::move(newFeature));
     198             :         }
     199             : 
     200         116 :         if (pfnProgress && nLayerFeatures > 0 &&
     201           0 :             !pfnProgress(static_cast<double>(m_apoFeatures.size()) *
     202             :                              dfProgressRatio,
     203             :                          "", pProgressData))
     204             :         {
     205           0 :             ReportError(CE_Failure, CPLE_UserInterrupt, "Interrupted by user");
     206           0 :             return false;
     207             :         }
     208             :     }
     209             : 
     210          22 :     return true;
     211             : }
     212             : 
     213          22 : bool GDALGeosNonStreamingAlgorithmDataset::ConvertOutputsFromGeos(
     214             :     OGRLayer &dstLayer, GDALProgressFunc pfnProgress, void *pProgressData,
     215             :     double dfProgressStart, double dfProgressRatio)
     216             : {
     217             :     const OGRSpatialReference *poResultSRS =
     218          22 :         dstLayer.GetLayerDefn()->GetGeomFieldDefn(0)->GetSpatialRef();
     219             : 
     220             : // GEOSGeom_releaseCollection allows us to take ownership of the contents of
     221             : // a GeometryCollection. We can then incrementally free the geometries as
     222             : // we write them to features. It requires GEOS >= 3.12.
     223             : #if GEOS_VERSION_MAJOR > 3 ||                                                  \
     224             :     (GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 12)
     225             : #define GDAL_GEOS_NON_STREAMING_ALGORITHM_DATASET_INCREMENTAL
     226             : #endif
     227             : 
     228          22 :     const auto eLayerGeomType = dstLayer.GetLayerDefn()->GetGeomType();
     229             : 
     230             : #ifdef GDAL_GEOS_NON_STREAMING_ALGORITHM_DATASET_INCREMENTAL
     231          22 :     m_nGeosResultSize =
     232          22 :         GEOSGetNumGeometries_r(m_poGeosContext, m_poGeosResultAsCollection);
     233          22 :     m_papoGeosResults = GEOSGeom_releaseCollection_r(
     234             :         m_poGeosContext, m_poGeosResultAsCollection, &m_nGeosResultSize);
     235          22 :     GEOSGeom_destroy_r(m_poGeosContext, m_poGeosResultAsCollection);
     236          22 :     m_poGeosResultAsCollection = nullptr;
     237          22 :     CPLAssert(m_apoFeatures.size() == m_nGeosResultSize);
     238             : 
     239             :     // Create features with the modified geometries
     240         138 :     for (const auto &[i, poFeature] : cpl::enumerate(m_apoFeatures))
     241             :     {
     242         116 :         GEOSGeometry *poGeosResult = m_papoGeosResults[i];
     243             : #else
     244             :     auto nGeoms =
     245             :         GEOSGetNumGeometries_r(m_poGeosContext, m_poGeosResultAsCollection);
     246             :     for (decltype(nGeoms) i = 0; i < nGeoms; i++)
     247             :     {
     248             :         auto &poFeature = m_apoFeatures[i];
     249             :         GEOSGeometry *poGeosResult = const_cast<GEOSGeometry *>(
     250             :             GEOSGetGeometryN_r(m_poGeosContext, m_poGeosResultAsCollection, i));
     251             : #endif
     252           0 :         std::unique_ptr<OGRGeometry> poResultGeom;
     253             : 
     254             :         bool skipFeature =
     255         116 :             SkipEmpty() && GEOSisEmpty_r(m_poGeosContext, poGeosResult);
     256             : 
     257         116 :         if (!skipFeature)
     258             :         {
     259         103 :             poResultGeom.reset(OGRGeometryFactory::createFromGEOS(
     260             :                 m_poGeosContext, poGeosResult));
     261             : 
     262         206 :             if (poResultGeom && eLayerGeomType != wkbUnknown &&
     263         103 :                 wkbFlatten(poResultGeom->getGeometryType()) !=
     264         103 :                     wkbFlatten(eLayerGeomType))
     265             :             {
     266          19 :                 poResultGeom.reset(OGRGeometryFactory::forceTo(
     267             :                     poResultGeom.release(), eLayerGeomType));
     268             :             }
     269             : 
     270         103 :             if (poResultGeom == nullptr)
     271             :             {
     272           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     273             :                          "Failed to convert result from GEOS");
     274           0 :                 return false;
     275             :             }
     276             :         }
     277             : 
     278             : #ifdef GDAL_GEOS_NON_STREAMING_ALGORITHM_DATASET_INCREMENTAL
     279         116 :         GEOSGeom_destroy_r(m_poGeosContext, m_papoGeosResults[i]);
     280         116 :         m_papoGeosResults[i] = nullptr;
     281             : #undef GDAL_GEOS_NON_STREAMING_ALGORITHM_DATASET_INCREMENTAL
     282             : #endif
     283             : 
     284         116 :         if (!skipFeature)
     285             :         {
     286         103 :             poResultGeom->assignSpatialReference(poResultSRS);
     287         103 :             poFeature->SetGeometry(std::move(poResultGeom));
     288             : 
     289         103 :             if (dstLayer.CreateFeature(std::move(poFeature)) != CE_None)
     290             :             {
     291           0 :                 return false;
     292             :             }
     293             :         }
     294             :         else
     295             :         {
     296          13 :             poFeature.reset();
     297             :         }
     298             : 
     299         116 :         if (pfnProgress &&
     300           0 :             !pfnProgress(dfProgressStart +
     301           0 :                              static_cast<double>(i) * dfProgressRatio,
     302             :                          "", pProgressData))
     303             :         {
     304           0 :             ReportError(CE_Failure, CPLE_UserInterrupt, "Interrupted by user");
     305           0 :             return false;
     306             :         }
     307             :     }
     308             : 
     309          22 :     return true;
     310             : }
     311             : 
     312          28 : bool GDALGeosNonStreamingAlgorithmDataset::Process(OGRLayer &srcLayer,
     313             :                                                    OGRLayer &dstLayer,
     314             :                                                    int geomFieldIndex,
     315             :                                                    GDALProgressFunc pfnProgress,
     316             :                                                    void *pProgressData)
     317             : {
     318          28 :     Cleanup();
     319             : 
     320             :     const bool sameDefn =
     321          28 :         dstLayer.GetLayerDefn()->IsSame(srcLayer.GetLayerDefn());
     322             : 
     323          28 :     if (!ConvertInputsToGeos(srcLayer, dstLayer, geomFieldIndex, sameDefn,
     324             :                              pfnProgress, pProgressData))
     325             :     {
     326           6 :         return false;
     327             :     }
     328             : 
     329          22 :     if (!ProcessGeos() || m_poGeosResultAsCollection == nullptr)
     330             :     {
     331           0 :         return false;
     332             :     }
     333             : 
     334          22 :     const GIntBig nLayerFeatures = srcLayer.TestCapability(OLCFastFeatureCount)
     335          22 :                                        ? srcLayer.GetFeatureCount(false)
     336          22 :                                        : -1;
     337          22 :     const double dfProgressStart = nLayerFeatures > 0 ? 0.5 : 0.0;
     338             :     const double dfProgressRatio =
     339          22 :         (nLayerFeatures > 0 ? 0.5 : 1.0) /
     340          22 :         std::max(1.0, static_cast<double>(m_apoFeatures.size()));
     341          22 :     return ConvertOutputsFromGeos(dstLayer, pfnProgress, pProgressData,
     342          22 :                                   dfProgressStart, dfProgressRatio);
     343             : }
     344             : 
     345             : #endif
     346             : 
     347             : //! @endcond

Generated by: LCOV version 1.14