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_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
    if (!ConvertInputsToGeos(m_srcLayer, m_geomFieldIndex, pfnProgress,
235
                             pProgressData))
236
    {
237
        return false;
238
    }
239
240
    if (!ProcessGeos() || m_poGeosResultAsCollection == nullptr)
241
    {
242
        return false;
243
    }
244
245
#ifdef GDAL_GEOS_NON_STREAMING_ALGORITHM_DATASET_INCREMENTAL
246
    m_nGeosResultSize =
247
        GEOSGetNumGeometries_r(m_poGeosContext, m_poGeosResultAsCollection);
248
    m_papoGeosResults = GEOSGeom_releaseCollection_r(
249
        m_poGeosContext, m_poGeosResultAsCollection, &m_nGeosResultSize);
250
    GEOSGeom_destroy_r(m_poGeosContext, m_poGeosResultAsCollection);
251
    m_poGeosResultAsCollection = nullptr;
252
    CPLAssert(m_apoFeatures.size() == m_nGeosResultSize);
253
#endif
254
255
    return true;
256
}
257
258
std::unique_ptr<OGRFeature>
259
GDALGeosNonStreamingAlgorithmLayer::GetNextProcessedFeature()
260
{
261
    GEOSGeometry *poGeosResult = nullptr;
262
263
    while (poGeosResult == nullptr && m_readPos < m_apoFeatures.size())
264
    {
265
        // Have we already constructed a result OGRGeometry when previously
266
        // accessing this feature?
267
        if (m_apoFeatures[m_readPos]->GetGeometryRef() != nullptr)
268
        {
269
            return std::unique_ptr<OGRFeature>(
270
                m_apoFeatures[m_readPos++]->Clone());
271
        }
272
273
#ifdef GDAL_GEOS_NON_STREAMING_ALGORITHM_DATASET_INCREMENTAL
274
        poGeosResult = m_papoGeosResults[m_readPos];
275
#else
276
        poGeosResult = const_cast<GEOSGeometry *>(GEOSGetGeometryN_r(
277
            m_poGeosContext, m_poGeosResultAsCollection, m_readPos));
278
#endif
279
280
        if (poGeosResult != nullptr)
281
        {
282
            const bool skipFeature =
283
                SkipEmpty() && GEOSisEmpty_r(m_poGeosContext, poGeosResult);
284
285
            if (skipFeature)
286
            {
287
#ifdef GDAL_GEOS_NON_STREAMING_ALGORITHM_DATASET_INCREMENTAL
288
                GEOSGeom_destroy_r(m_poGeosContext, poGeosResult);
289
                m_papoGeosResults[m_readPos] = nullptr;
290
#endif
291
                poGeosResult = nullptr;
292
            }
293
        }
294
295
        m_readPos++;
296
    }
297
298
    if (poGeosResult == nullptr)
299
    {
300
#ifndef GDAL_GEOS_NON_STREAMING_ALGORITHM_DATASET_INCREMENTAL
301
        GEOSGeom_destroy_r(m_poGeosContext, m_poGeosResultAsCollection);
302
        m_poGeosResultAsCollection = nullptr;
303
#endif
304
        return nullptr;
305
    }
306
307
    const auto eLayerGeomType = GetLayerDefn()->GetGeomType();
308
309
    std::unique_ptr<OGRGeometry> poResultGeom(
310
        OGRGeometryFactory::createFromGEOS(m_poGeosContext, poGeosResult));
311
312
#ifdef GDAL_GEOS_NON_STREAMING_ALGORITHM_DATASET_INCREMENTAL
313
    GEOSGeom_destroy_r(m_poGeosContext, poGeosResult);
314
    m_papoGeosResults[m_readPos - 1] = nullptr;
315
#endif
316
317
    if (poResultGeom && eLayerGeomType != wkbUnknown &&
318
        wkbFlatten(poResultGeom->getGeometryType()) !=
319
            wkbFlatten(eLayerGeomType))
320
    {
321
        poResultGeom = OGRGeometryFactory::forceTo(std::move(poResultGeom),
322
                                                   eLayerGeomType);
323
    }
324
325
    if (poResultGeom == nullptr)
326
    {
327
        CPLError(CE_Failure, CPLE_AppDefined,
328
                 "Failed to convert result from GEOS");
329
        return nullptr;
330
    }
331
332
    poResultGeom->assignSpatialReference(
333
        GetLayerDefn()->GetGeomFieldDefn(0)->GetSpatialRef());
334
335
    auto poFeature = m_apoFeatures[m_readPos - 1].get();
336
    poFeature->SetGeometry(std::move(poResultGeom));
337
338
    return std::unique_ptr<OGRFeature>(poFeature->Clone());
339
}
340
341
#undef GDAL_GEOS_NON_STREAMING_ALGORITHM_DATASET_INCREMENTAL
342
343
void GDALGeosNonStreamingAlgorithmLayer::ResetReading()
344
{
345
    m_readPos = 0;
346
}
347
348
#endif
349
350
//! @endcond