Coverage Report

Created: 2026-07-25 06:50

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