Coverage Report

Created: 2026-02-14 06:52

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 = GetLayerDefn()->IsSame(srcLayer.GetLayerDefn());
155
156
    for (auto &feature : srcLayer)
157
    {
158
        const OGRGeometry *poSrcGeom = feature->GetGeomFieldRef(geomFieldIndex);
159
160
        if (PolygonsOnly())
161
        {
162
            const auto eFGType = poSrcGeom
163
                                     ? wkbFlatten(poSrcGeom->getGeometryType())
164
                                     : wkbUnknown;
165
            if (eFGType != wkbPolygon && eFGType != wkbMultiPolygon &&
166
                eFGType != wkbCurvePolygon && eFGType != wkbMultiSurface)
167
            {
168
                CPLError(CE_Failure, CPLE_AppDefined,
169
                         "Coverage checking can only be performed on "
170
                         "polygonal geometries. Feature %" PRId64
171
                         " does not have one",
172
                         static_cast<int64_t>(feature->GetFID()));
173
                return false;
174
            }
175
        }
176
177
        if (poSrcGeom)
178
        {
179
            GEOSGeometry *geosGeom =
180
                poSrcGeom->exportToGEOS(m_poGeosContext, false);
181
            if (!geosGeom)
182
            {
183
                // should not happen normally
184
                CPLError(CE_Failure, CPLE_AppDefined,
185
                         "Geometry of feature %" PRId64
186
                         " failed to convert to GEOS",
187
                         static_cast<int64_t>(feature->GetFID()));
188
                return false;
189
            }
190
191
            m_apoGeosInputs.push_back(geosGeom);
192
        }
193
        else
194
        {
195
            m_apoGeosInputs.push_back(GEOSGeom_createEmptyCollection_r(
196
                m_poGeosContext, GEOS_GEOMETRYCOLLECTION));
197
        }
198
199
        feature->SetGeometry(nullptr);  // free some memory
200
201
        if (sameDefn)
202
        {
203
            feature->SetFDefnUnsafe(GetLayerDefn());
204
            m_apoFeatures.push_back(
205
                std::unique_ptr<OGRFeature>(feature.release()));
206
        }
207
        else
208
        {
209
            auto newFeature = std::make_unique<OGRFeature>(GetLayerDefn());
210
            newFeature->SetFrom(feature.get(), true);
211
            newFeature->SetFID(feature->GetFID());
212
            m_apoFeatures.push_back(std::move(newFeature));
213
        }
214
215
        if (pfnProgress && nLayerFeatures > 0 &&
216
            !pfnProgress(static_cast<double>(m_apoFeatures.size()) *
217
                             dfProgressRatio,
218
                         "", pProgressData))
219
        {
220
            CPLError(CE_Failure, CPLE_UserInterrupt, "Interrupted by user");
221
            return false;
222
        }
223
    }
224
225
    return true;
226
}
227
228
bool GDALGeosNonStreamingAlgorithmLayer::Process(GDALProgressFunc pfnProgress,
229
                                                 void *pProgressData)
230
{
231
    Cleanup();
232
233
    if (!ConvertInputsToGeos(m_srcLayer, m_geomFieldIndex, pfnProgress,
234
                             pProgressData))
235
    {
236
        return false;
237
    }
238
239
    if (!ProcessGeos() || m_poGeosResultAsCollection == nullptr)
240
    {
241
        return false;
242
    }
243
244
#ifdef GDAL_GEOS_NON_STREAMING_ALGORITHM_DATASET_INCREMENTAL
245
    m_nGeosResultSize =
246
        GEOSGetNumGeometries_r(m_poGeosContext, m_poGeosResultAsCollection);
247
    m_papoGeosResults = GEOSGeom_releaseCollection_r(
248
        m_poGeosContext, m_poGeosResultAsCollection, &m_nGeosResultSize);
249
    GEOSGeom_destroy_r(m_poGeosContext, m_poGeosResultAsCollection);
250
    m_poGeosResultAsCollection = nullptr;
251
    CPLAssert(m_apoFeatures.size() == m_nGeosResultSize);
252
#endif
253
254
    return true;
255
}
256
257
std::unique_ptr<OGRFeature>
258
GDALGeosNonStreamingAlgorithmLayer::GetNextProcessedFeature()
259
{
260
    GEOSGeometry *poGeosResult = nullptr;
261
262
    while (poGeosResult == nullptr && m_readPos < m_apoFeatures.size())
263
    {
264
        // Have we already constructed a result OGRGeometry when previously
265
        // accessing this feature?
266
        if (m_apoFeatures[m_readPos]->GetGeometryRef() != nullptr)
267
        {
268
            return std::unique_ptr<OGRFeature>(
269
                m_apoFeatures[m_readPos++]->Clone());
270
        }
271
272
#ifdef GDAL_GEOS_NON_STREAMING_ALGORITHM_DATASET_INCREMENTAL
273
        poGeosResult = m_papoGeosResults[m_readPos];
274
#else
275
        poGeosResult = const_cast<GEOSGeometry *>(GEOSGetGeometryN_r(
276
            m_poGeosContext, m_poGeosResultAsCollection, m_readPos));
277
#endif
278
279
        if (poGeosResult != nullptr)
280
        {
281
            const bool skipFeature =
282
                SkipEmpty() && GEOSisEmpty_r(m_poGeosContext, poGeosResult);
283
284
            if (skipFeature)
285
            {
286
#ifdef GDAL_GEOS_NON_STREAMING_ALGORITHM_DATASET_INCREMENTAL
287
                GEOSGeom_destroy_r(m_poGeosContext, poGeosResult);
288
                m_papoGeosResults[m_readPos] = nullptr;
289
#endif
290
                poGeosResult = nullptr;
291
            }
292
        }
293
294
        m_readPos++;
295
    }
296
297
    if (poGeosResult == nullptr)
298
    {
299
#ifndef GDAL_GEOS_NON_STREAMING_ALGORITHM_DATASET_INCREMENTAL
300
        GEOSGeom_destroy_r(m_poGeosContext, m_poGeosResultAsCollection);
301
        m_poGeosResultAsCollection = nullptr;
302
#endif
303
        return nullptr;
304
    }
305
306
    const auto eLayerGeomType = GetLayerDefn()->GetGeomType();
307
308
    std::unique_ptr<OGRGeometry> poResultGeom(
309
        OGRGeometryFactory::createFromGEOS(m_poGeosContext, poGeosResult));
310
311
#ifdef GDAL_GEOS_NON_STREAMING_ALGORITHM_DATASET_INCREMENTAL
312
    GEOSGeom_destroy_r(m_poGeosContext, poGeosResult);
313
    m_papoGeosResults[m_readPos - 1] = nullptr;
314
#endif
315
316
    if (poResultGeom && eLayerGeomType != wkbUnknown &&
317
        wkbFlatten(poResultGeom->getGeometryType()) !=
318
            wkbFlatten(eLayerGeomType))
319
    {
320
        poResultGeom = OGRGeometryFactory::forceTo(std::move(poResultGeom),
321
                                                   eLayerGeomType);
322
    }
323
324
    if (poResultGeom == nullptr)
325
    {
326
        CPLError(CE_Failure, CPLE_AppDefined,
327
                 "Failed to convert result from GEOS");
328
        return nullptr;
329
    }
330
331
    poResultGeom->assignSpatialReference(
332
        GetLayerDefn()->GetGeomFieldDefn(0)->GetSpatialRef());
333
334
    auto poFeature = m_apoFeatures[m_readPos - 1].get();
335
    poFeature->SetGeometry(std::move(poResultGeom));
336
337
    return std::unique_ptr<OGRFeature>(poFeature->Clone());
338
}
339
340
#undef GDAL_GEOS_NON_STREAMING_ALGORITHM_DATASET_INCREMENTAL
341
342
void GDALGeosNonStreamingAlgorithmLayer::ResetReading()
343
{
344
    m_readPos = 0;
345
}
346
347
#endif
348
349
//! @endcond