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

Generated by: LCOV version 1.14