Coverage Report

Created: 2026-09-14 06:50

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