LCOV - code coverage report
Current view: top level - apps - gdalalg_vector_clip.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 129 133 97.0 %
Date: 2026-04-23 19:47:11 Functions: 6 6 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  "clip" step of "vector pipeline", or "gdal vector clip" standalone
       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_clip.h"
      14             : 
      15             : #include "gdal_priv.h"
      16             : #include "gdal_utils.h"
      17             : #include "ogrsf_frmts.h"
      18             : 
      19             : #include <algorithm>
      20             : 
      21             : //! @cond Doxygen_Suppress
      22             : 
      23             : #ifndef _
      24             : #define _(x) (x)
      25             : #endif
      26             : 
      27             : /************************************************************************/
      28             : /*          GDALVectorClipAlgorithm::GDALVectorClipAlgorithm()          */
      29             : /************************************************************************/
      30             : 
      31         118 : GDALVectorClipAlgorithm::GDALVectorClipAlgorithm(bool standaloneStep)
      32             :     : GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
      33         118 :                                       standaloneStep)
      34             : {
      35         118 :     AddActiveLayerArg(&m_activeLayer);
      36         118 :     AddBBOXArg(&m_bbox, _("Clipping bounding box as xmin,ymin,xmax,ymax"))
      37         118 :         .SetMutualExclusionGroup("bbox-geometry-like");
      38         236 :     AddArg("bbox-crs", 0, _("CRS of clipping bounding box"), &m_bboxCrs)
      39         236 :         .SetIsCRSArg()
      40         118 :         .AddHiddenAlias("bbox_srs");
      41         236 :     AddArg("geometry", 0, _("Clipping geometry (WKT or GeoJSON)"), &m_geometry)
      42         118 :         .SetMutualExclusionGroup("bbox-geometry-like");
      43         236 :     AddArg("geometry-crs", 0, _("CRS of clipping geometry"), &m_geometryCrs)
      44         236 :         .SetIsCRSArg()
      45         118 :         .AddHiddenAlias("geometry_srs");
      46             :     AddArg("like", 0, _("Dataset to use as a template for bounds"),
      47         236 :            &m_likeDataset, GDAL_OF_RASTER | GDAL_OF_VECTOR)
      48         236 :         .SetMetaVar("DATASET")
      49         118 :         .SetMutualExclusionGroup("bbox-geometry-like");
      50             :     AddArg("like-sql", 0, ("SELECT statement to run on the 'like' dataset"),
      51         236 :            &m_likeSQL)
      52         236 :         .SetMetaVar("SELECT-STATEMENT")
      53         118 :         .SetMutualExclusionGroup("sql-where");
      54             :     AddArg("like-layer", 0, ("Name of the layer of the 'like' dataset"),
      55         236 :            &m_likeLayer)
      56         118 :         .SetMetaVar("LAYER-NAME");
      57             :     AddArg("like-where", 0, ("WHERE SQL clause to run on the 'like' dataset"),
      58         236 :            &m_likeWhere)
      59         236 :         .SetMetaVar("WHERE-EXPRESSION")
      60         118 :         .SetMutualExclusionGroup("sql-where");
      61         118 : }
      62             : 
      63             : /************************************************************************/
      64             : /*                     GDALVectorClipAlgorithmLayer                     */
      65             : /************************************************************************/
      66             : 
      67             : namespace
      68             : {
      69             : class GDALVectorClipAlgorithmLayer final : public GDALVectorPipelineOutputLayer
      70             : {
      71             :   public:
      72          32 :     GDALVectorClipAlgorithmLayer(OGRLayer &oSrcLayer,
      73             :                                  std::unique_ptr<OGRGeometry> poClipGeom)
      74          32 :         : GDALVectorPipelineOutputLayer(oSrcLayer),
      75          32 :           m_poClipGeom(std::move(poClipGeom)),
      76          32 :           m_eSrcLayerGeomType(oSrcLayer.GetGeomType()),
      77          64 :           m_eFlattenSrcLayerGeomType(wkbFlatten(m_eSrcLayerGeomType)),
      78          32 :           m_bSrcLayerGeomTypeIsCollection(CPL_TO_BOOL(OGR_GT_IsSubClassOf(
      79          32 :               m_eFlattenSrcLayerGeomType, wkbGeometryCollection))),
      80          96 :           m_poFeatureDefn(oSrcLayer.GetLayerDefn()->Clone())
      81             :     {
      82          32 :         SetDescription(oSrcLayer.GetDescription());
      83          32 :         SetMetadata(oSrcLayer.GetMetadata());
      84          32 :         oSrcLayer.SetSpatialFilter(m_poClipGeom.get());
      85          32 :     }
      86             : 
      87         426 :     const OGRFeatureDefn *GetLayerDefn() const override
      88             :     {
      89         426 :         return m_poFeatureDefn.get();
      90             :     }
      91             : 
      92         500 :     void TranslateFeature(
      93             :         std::unique_ptr<OGRFeature> poSrcFeature,
      94             :         std::vector<std::unique_ptr<OGRFeature>> &apoOutFeatures) override
      95             :     {
      96           0 :         std::unique_ptr<OGRGeometry> poIntersection;
      97         500 :         auto poGeom = poSrcFeature->GetGeometryRef();
      98             : 
      99         500 :         if (poGeom == nullptr)
     100           0 :             return;
     101             : 
     102         500 :         poIntersection.reset(poGeom->Intersection(m_poClipGeom.get()));
     103         500 :         if (!poIntersection)
     104           0 :             return;
     105        1000 :         poIntersection->assignSpatialReference(
     106         500 :             m_poFeatureDefn->GetGeomFieldDefn(0)->GetSpatialRef());
     107             : 
     108         500 :         poSrcFeature->SetFDefnUnsafe(m_poFeatureDefn.get());
     109             : 
     110         500 :         const auto eSrcGeomType = wkbFlatten(poGeom->getGeometryType());
     111             :         const auto eFeatGeomType =
     112         500 :             wkbFlatten(poIntersection->getGeometryType());
     113         500 :         if (eFeatGeomType != eSrcGeomType &&
     114           8 :             m_eFlattenSrcLayerGeomType != wkbUnknown &&
     115           8 :             m_eFlattenSrcLayerGeomType != eFeatGeomType)
     116             :         {
     117             :             // If the intersection is a collection of geometry and the
     118             :             // layer geometry type is of non-collection type, create
     119             :             // one feature per element of the collection.
     120          10 :             if (!m_bSrcLayerGeomTypeIsCollection &&
     121           2 :                 OGR_GT_IsSubClassOf(eFeatGeomType, wkbGeometryCollection))
     122             :             {
     123             :                 auto poGeomColl = std::unique_ptr<OGRGeometryCollection>(
     124           2 :                     poIntersection.release()->toGeometryCollection());
     125           3 :                 for (const auto *poSubGeom : poGeomColl.get())
     126             :                 {
     127             :                     auto poDstFeature =
     128           4 :                         std::unique_ptr<OGRFeature>(poSrcFeature->Clone());
     129           2 :                     poDstFeature->SetGeometry(poSubGeom);
     130           2 :                     apoOutFeatures.push_back(std::move(poDstFeature));
     131             :                 }
     132             :             }
     133           7 :             else if (OGR_GT_GetCollection(eFeatGeomType) ==
     134           7 :                      m_eFlattenSrcLayerGeomType)
     135             :             {
     136          10 :                 poIntersection = OGRGeometryFactory::forceTo(
     137          10 :                     std::move(poIntersection), m_eSrcLayerGeomType);
     138           5 :                 poSrcFeature->SetGeometry(std::move(poIntersection));
     139           5 :                 apoOutFeatures.push_back(std::move(poSrcFeature));
     140             :             }
     141           2 :             else if (m_eFlattenSrcLayerGeomType == wkbGeometryCollection)
     142             :             {
     143           2 :                 auto poGeomColl = std::make_unique<OGRGeometryCollection>();
     144           1 :                 poGeomColl->addGeometry(std::move(poIntersection));
     145           1 :                 poSrcFeature->SetGeometry(std::move(poGeomColl));
     146           1 :                 apoOutFeatures.push_back(std::move(poSrcFeature));
     147           8 :             }
     148             :             // else discard geometries of incompatible type with the
     149             :             // layer geometry type
     150             :         }
     151             :         else
     152             :         {
     153         492 :             poSrcFeature->SetGeometry(std::move(poIntersection));
     154         492 :             apoOutFeatures.push_back(std::move(poSrcFeature));
     155             :         }
     156             :     }
     157             : 
     158          62 :     int TestCapability(const char *pszCap) const override
     159             :     {
     160          62 :         if (EQUAL(pszCap, OLCStringsAsUTF8) ||
     161          49 :             EQUAL(pszCap, OLCCurveGeometries) || EQUAL(pszCap, OLCZGeometries))
     162          20 :             return m_srcLayer.TestCapability(pszCap);
     163          42 :         return false;
     164             :     }
     165             : 
     166             :   private:
     167             :     std::unique_ptr<OGRGeometry> const m_poClipGeom{};
     168             :     const OGRwkbGeometryType m_eSrcLayerGeomType;
     169             :     const OGRwkbGeometryType m_eFlattenSrcLayerGeomType;
     170             :     const bool m_bSrcLayerGeomTypeIsCollection;
     171             :     const OGRFeatureDefnRefCountedPtr m_poFeatureDefn;
     172             : 
     173             :     CPL_DISALLOW_COPY_ASSIGN(GDALVectorClipAlgorithmLayer)
     174             : };
     175             : 
     176             : }  // namespace
     177             : 
     178             : /************************************************************************/
     179             : /*                  GDALVectorClipAlgorithm::RunStep()                  */
     180             : /************************************************************************/
     181             : 
     182          39 : bool GDALVectorClipAlgorithm::RunStep(GDALPipelineStepRunContext &)
     183             : {
     184          39 :     auto poSrcDS = m_inputDataset[0].GetDatasetRef();
     185          39 :     CPLAssert(poSrcDS);
     186             : 
     187          39 :     CPLAssert(m_outputDataset.GetName().empty());
     188          39 :     CPLAssert(!m_outputDataset.GetDatasetRef());
     189             : 
     190          39 :     const int nLayerCount = poSrcDS->GetLayerCount();
     191          39 :     bool bSrcLayerHasSRS = false;
     192          59 :     for (int i = 0; i < nLayerCount; ++i)
     193             :     {
     194          38 :         auto poSrcLayer = poSrcDS->GetLayer(i);
     195          38 :         if (poSrcLayer &&
     196          40 :             (m_activeLayer.empty() ||
     197          78 :              m_activeLayer == poSrcLayer->GetDescription()) &&
     198          37 :             poSrcLayer->GetSpatialRef())
     199             :         {
     200          18 :             bSrcLayerHasSRS = true;
     201          18 :             break;
     202             :         }
     203             :     }
     204             : 
     205          78 :     auto [poClipGeom, errMsg] = GetClipGeometry();
     206          39 :     if (!poClipGeom)
     207             :     {
     208           8 :         ReportError(CE_Failure, CPLE_AppDefined, "%s", errMsg.c_str());
     209           8 :         return false;
     210             :     }
     211          31 :     if (!poClipGeom->IsValid())
     212             :     {
     213           1 :         ReportError(CE_Failure, CPLE_AppDefined,
     214             :                     "Clipping geometry is invalid. You can attempt to correct "
     215             :                     "it with 'gdal vector make-valid'.");
     216           1 :         return false;
     217             :     }
     218             : 
     219          30 :     auto poLikeDS = m_likeDataset.GetDatasetRef();
     220          31 :     if (bSrcLayerHasSRS && !poClipGeom->getSpatialReference() && poLikeDS &&
     221           1 :         poLikeDS->GetLayerCount() == 0)
     222             :     {
     223           1 :         ReportError(CE_Warning, CPLE_AppDefined,
     224             :                     "Dataset '%s' has no CRS. Assuming its CRS is the "
     225             :                     "same as the input vector.",
     226           1 :                     poLikeDS->GetDescription());
     227             :     }
     228             : 
     229          60 :     auto outDS = std::make_unique<GDALVectorPipelineOutputDataset>(*poSrcDS);
     230             : 
     231          63 :     for (int i = 0; i < nLayerCount; ++i)
     232             :     {
     233          35 :         const auto poSrcLayer = poSrcDS->GetLayer(i);
     234          35 :         if (poSrcLayer == nullptr)
     235             :         {
     236           0 :             return false;
     237             :         }
     238             : 
     239          37 :         if (m_activeLayer.empty() ||
     240           2 :             m_activeLayer == poSrcLayer->GetDescription())
     241             :         {
     242             :             const OGRSpatialReference *clipSRS =
     243          34 :                 poClipGeom->getSpatialReference();
     244          34 :             const OGRSpatialReference *layerSRS = poSrcLayer->GetSpatialRef();
     245             : 
     246             :             auto poClipGeomForLayer =
     247          34 :                 std::unique_ptr<OGRGeometry>(poClipGeom->clone());
     248          34 :             if (clipSRS && layerSRS && !clipSRS->IsSame(layerSRS))
     249             :             {
     250          14 :                 if (poClipGeomForLayer->transformTo(
     251          14 :                         poSrcLayer->GetSpatialRef()) != OGRERR_NONE)
     252             :                 {
     253           1 :                     ReportError(CE_Failure, CPLE_AppDefined,
     254             :                                 "Could not transform clipping geometry to "
     255             :                                 "layer SRS.");
     256           1 :                     return false;
     257             :                 }
     258             : 
     259           6 :                 if (!poClipGeomForLayer->IsValid())
     260             :                 {
     261           1 :                     ReportError(
     262             :                         CE_Failure, CPLE_AppDefined,
     263             :                         "Clipping geometry became invalid upon "
     264             :                         "transformation to the layer SRS. You can "
     265             :                         "try transforming the geometry and repairing it "
     266             :                         "separately with 'gdal vector reproject' "
     267             :                         "and 'gdal vector make-valid'.");
     268           1 :                     return false;
     269             :                 }
     270             :             }
     271             : 
     272          64 :             outDS->AddLayer(*poSrcLayer,
     273          64 :                             std::make_unique<GDALVectorClipAlgorithmLayer>(
     274          32 :                                 *poSrcLayer, std::move(poClipGeomForLayer)));
     275             :         }
     276             :         else
     277             :         {
     278           2 :             outDS->AddLayer(
     279             :                 *poSrcLayer,
     280           2 :                 std::make_unique<GDALVectorPipelinePassthroughLayer>(
     281             :                     *poSrcLayer));
     282             :         }
     283             :     }
     284             : 
     285          28 :     m_outputDataset.Set(std::move(outDS));
     286             : 
     287          28 :     return true;
     288             : }
     289             : 
     290             : GDALVectorClipAlgorithmStandalone::~GDALVectorClipAlgorithmStandalone() =
     291             :     default;
     292             : 
     293             : //! @endcond

Generated by: LCOV version 1.14