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_check_geometry.cpp
Line
Count
Source
1
/******************************************************************************
2
*
3
 * Project:  GDAL
4
 * Purpose:  "gdal vector check-geometry" subcommand
5
 * Author:   Daniel Baston
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2025, ISciences LLC
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "gdalalg_vector_check_geometry.h"
14
15
#include "cpl_error.h"
16
#include "gdal_priv.h"
17
#include "gdalalg_vector_geom.h"
18
#include "ogr_geometry.h"
19
#include "ogr_geos.h"
20
21
#include <cinttypes>
22
23
#ifndef _
24
0
#define _(x) (x)
25
#endif
26
27
//! @cond Doxygen_Suppress
28
29
GDALVectorCheckGeometryAlgorithm::GDALVectorCheckGeometryAlgorithm(
30
    bool standaloneStep)
31
0
    : GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
32
0
                                      standaloneStep)
33
0
{
34
0
    AddArg("include-field", 0,
35
0
           _("Fields from input layer to include in output (special values: "
36
0
             "ALL and NONE)"),
37
0
           &m_includeFields)
38
0
        .SetDefault("NONE");
39
40
0
    AddArg("include-valid", 0,
41
0
           _("Include valid inputs in output, with empty geometry"),
42
0
           &m_includeValid);
43
44
0
    AddArg("geometry-field", 0, _("Name of geometry field to check"),
45
0
           &m_geomField);
46
0
}
47
48
#ifdef HAVE_GEOS
49
50
class GDALInvalidLocationLayer final : public GDALVectorPipelineOutputLayer
51
{
52
  private:
53
    static constexpr const char *ERROR_DESCRIPTION_FIELD = "error";
54
55
  public:
56
    GDALInvalidLocationLayer(OGRLayer &layer,
57
                             const std::vector<int> &srcFieldIndices,
58
                             bool bSingleLayerOutput, int srcGeomField,
59
                             bool skipValid)
60
        : GDALVectorPipelineOutputLayer(layer),
61
          m_defn(OGRFeatureDefn::CreateFeatureDefn(
62
              bSingleLayerOutput ? "error_location"
63
                                 : std::string("error_location_")
64
                                       .append(layer.GetDescription())
65
                                       .c_str())),
66
          m_geosContext(OGRGeometry::createGEOSContext()),
67
          m_srcGeomField(srcGeomField), m_skipValid(skipValid)
68
    {
69
        m_defn->Reference();
70
        m_defn->SetGeomType(wkbMultiPoint);
71
72
        if (!srcFieldIndices.empty())
73
        {
74
            const OGRFeatureDefn &srcDefn = *layer.GetLayerDefn();
75
            m_srcFieldMap.resize(srcDefn.GetFieldCount(), -1);
76
            int iDstField = 0;
77
            for (int iSrcField : srcFieldIndices)
78
            {
79
                m_defn->AddFieldDefn(srcDefn.GetFieldDefn(iSrcField));
80
                m_srcFieldMap[iSrcField] = iDstField++;
81
            }
82
        }
83
84
        auto poDescriptionFieldDefn =
85
            std::make_unique<OGRFieldDefn>(ERROR_DESCRIPTION_FIELD, OFTString);
86
        m_defn->AddFieldDefn(std::move(poDescriptionFieldDefn));
87
88
        m_defn->GetGeomFieldDefn(0)->SetSpatialRef(
89
            m_srcLayer.GetLayerDefn()
90
                ->GetGeomFieldDefn(m_srcGeomField)
91
                ->GetSpatialRef());
92
    }
93
94
    ~GDALInvalidLocationLayer() override;
95
96
    int TestCapability(const char *) const override
97
    {
98
        return false;
99
    }
100
101
    const OGRFeatureDefn *GetLayerDefn() const override
102
    {
103
        return m_defn;
104
    }
105
106
    std::unique_ptr<OGRFeature> CreateFeatureFromLastError() const
107
    {
108
        auto poErrorFeature = std::make_unique<OGRFeature>(m_defn);
109
110
        std::string msg = CPLGetLastErrorMsg();
111
112
        // Trim GEOS exception name
113
        const auto subMsgPos = msg.find(": ");
114
        if (subMsgPos != std::string::npos)
115
        {
116
            msg = msg.substr(subMsgPos + strlen(": "));
117
        }
118
119
        // Trim newline from end of GEOS exception message
120
        if (!msg.empty() && msg.back() == '\n')
121
        {
122
            msg.pop_back();
123
        }
124
125
        poErrorFeature->SetField(ERROR_DESCRIPTION_FIELD, msg.c_str());
126
127
        CPLErrorReset();
128
129
        return poErrorFeature;
130
    }
131
132
    void TranslateFeature(
133
        std::unique_ptr<OGRFeature> poSrcFeature,
134
        std::vector<std::unique_ptr<OGRFeature>> &apoOutputFeatures) override
135
    {
136
        const OGRGeometry *poGeom =
137
            poSrcFeature->GetGeomFieldRef(m_srcGeomField);
138
        std::unique_ptr<OGRFeature> poErrorFeature;
139
140
        if (poGeom)
141
        {
142
            if (poGeom->getDimension() < 1)
143
            {
144
                CPLErrorOnce(CE_Warning, CPLE_AppDefined,
145
                             "Point geometry passed to 'gdal vector "
146
                             "check-geometry'. Point geometries are "
147
                             "always valid/simple.");
148
            }
149
            else
150
            {
151
                auto eType = wkbFlatten(poGeom->getGeometryType());
152
                GEOSGeometry *poGeosGeom = poGeom->exportToGEOS(m_geosContext);
153
154
                if (!poGeosGeom)
155
                {
156
                    // Try to find a useful message / coordinate from
157
                    // GEOS exception message.
158
                    poErrorFeature = CreateFeatureFromLastError();
159
160
                    if (eType == wkbPolygon)
161
                    {
162
                        const OGRLinearRing *poRing =
163
                            poGeom->toPolygon()->getExteriorRing();
164
                        if (poRing != nullptr && !poRing->IsEmpty())
165
                        {
166
                            auto poPoint = std::make_unique<OGRPoint>();
167
                            poRing->StartPoint(poPoint.get());
168
                            auto poMultiPoint =
169
                                std::make_unique<OGRMultiPoint>();
170
                            poMultiPoint->addGeometry(std::move(poPoint));
171
                            poErrorFeature->SetGeometry(
172
                                std::move(poMultiPoint));
173
                        }
174
                        else
175
                        {
176
                            // TODO get a point from somewhere else?
177
                        }
178
                    }
179
                }
180
                else
181
                {
182
                    char *pszReason = nullptr;
183
                    GEOSGeometry *location = nullptr;
184
                    char ret = 1;
185
                    bool warnAboutGeosVersion = false;
186
                    bool checkedSimple = false;
187
188
                    if (eType == wkbPolygon || eType == wkbMultiPolygon ||
189
                        eType == wkbCurvePolygon || eType == wkbMultiSurface ||
190
                        eType == wkbGeometryCollection)
191
                    {
192
                        ret = GEOSisValidDetail_r(m_geosContext, poGeosGeom, 0,
193
                                                  &pszReason, &location);
194
                    }
195
196
                    if (eType == wkbLineString || eType == wkbMultiLineString ||
197
                        eType == wkbCircularString ||
198
                        eType == wkbCompoundCurve ||
199
                        (ret == 1 && eType == wkbGeometryCollection))
200
                    {
201
                        checkedSimple = true;
202
#if GEOS_VERSION_MAJOR > 3 ||                                                  \
203
    (GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 14)
204
                        ret = GEOSisSimpleDetail_r(m_geosContext, poGeosGeom, 1,
205
                                                   &location);
206
#else
207
                        ret = GEOSisSimple_r(m_geosContext, poGeosGeom);
208
                        warnAboutGeosVersion = true;
209
#endif
210
                    }
211
212
                    GEOSGeom_destroy_r(m_geosContext, poGeosGeom);
213
                    if (ret == 0)
214
                    {
215
                        if (warnAboutGeosVersion)
216
                        {
217
                            CPLErrorOnce(
218
                                CE_Warning, CPLE_AppDefined,
219
                                "Detected a non-simple linear geometry, but "
220
                                "cannot output self-intersection points "
221
                                "because GEOS library version is < 3.14.");
222
                        }
223
224
                        poErrorFeature = std::make_unique<OGRFeature>(m_defn);
225
                        if (pszReason == nullptr)
226
                        {
227
                            if (checkedSimple)
228
                            {
229
                                poErrorFeature->SetField(
230
                                    ERROR_DESCRIPTION_FIELD,
231
                                    "self-intersection");
232
                            }
233
                        }
234
                        else
235
                        {
236
                            poErrorFeature->SetField(ERROR_DESCRIPTION_FIELD,
237
                                                     pszReason);
238
                            GEOSFree_r(m_geosContext, pszReason);
239
                        }
240
241
                        if (location != nullptr)
242
                        {
243
                            std::unique_ptr<OGRGeometry> poErrorGeom(
244
                                OGRGeometryFactory::createFromGEOS(
245
                                    m_geosContext, location));
246
                            GEOSGeom_destroy_r(m_geosContext, location);
247
248
                            if (poErrorGeom->getGeometryType() == wkbPoint)
249
                            {
250
                                auto poMultiPoint =
251
                                    std::make_unique<OGRMultiPoint>();
252
                                poMultiPoint->addGeometry(
253
                                    std::move(poErrorGeom));
254
                                poErrorGeom = std::move(poMultiPoint);
255
                            }
256
257
                            poErrorGeom->assignSpatialReference(
258
                                m_srcLayer.GetLayerDefn()
259
                                    ->GetGeomFieldDefn(m_srcGeomField)
260
                                    ->GetSpatialRef());
261
262
                            poErrorFeature->SetGeometry(std::move(poErrorGeom));
263
                        }
264
                    }
265
                    else if (ret == 2)
266
                    {
267
                        poErrorFeature = CreateFeatureFromLastError();
268
                    }
269
                }
270
            }
271
        }
272
273
        if (!poErrorFeature && !m_skipValid)
274
        {
275
            poErrorFeature = std::make_unique<OGRFeature>(m_defn);
276
            // TODO Set geometry to POINT EMPTY ?
277
        }
278
279
        if (poErrorFeature)
280
        {
281
            if (!m_srcFieldMap.empty())
282
            {
283
                poErrorFeature->SetFieldsFrom(
284
                    poSrcFeature.get(), m_srcFieldMap.data(), false, false);
285
            }
286
            poErrorFeature->SetFID(poSrcFeature->GetFID());
287
            apoOutputFeatures.push_back(std::move(poErrorFeature));
288
        }
289
    }
290
291
    CPL_DISALLOW_COPY_ASSIGN(GDALInvalidLocationLayer)
292
293
  private:
294
    std::vector<int> m_srcFieldMap{};
295
    OGRFeatureDefn *const m_defn;
296
    const GEOSContextHandle_t m_geosContext;
297
    const int m_srcGeomField;
298
    const bool m_skipValid;
299
};
300
301
GDALInvalidLocationLayer::~GDALInvalidLocationLayer()
302
{
303
    m_defn->Release();
304
    finishGEOS_r(m_geosContext);
305
}
306
307
#endif
308
309
bool GDALVectorCheckGeometryAlgorithm::RunStep(GDALPipelineStepRunContext &)
310
0
{
311
#ifdef HAVE_GEOS
312
    auto poSrcDS = m_inputDataset[0].GetDatasetRef();
313
    CPLAssert(poSrcDS);
314
    CPLAssert(m_outputDataset.GetName().empty());
315
    CPLAssert(!m_outputDataset.GetDatasetRef());
316
317
    const bool bSingleLayerOutput = m_inputLayerNames.empty()
318
                                        ? poSrcDS->GetLayerCount() == 1
319
                                        : m_inputLayerNames.size() == 1;
320
321
    auto outDS = std::make_unique<GDALVectorPipelineOutputDataset>(*poSrcDS);
322
    for (auto &&poSrcLayer : poSrcDS->GetLayers())
323
    {
324
        if (m_inputLayerNames.empty() ||
325
            std::find(m_inputLayerNames.begin(), m_inputLayerNames.end(),
326
                      poSrcLayer->GetDescription()) != m_inputLayerNames.end())
327
        {
328
            const auto poSrcLayerDefn = poSrcLayer->GetLayerDefn();
329
            if (poSrcLayerDefn->GetGeomFieldCount() == 0)
330
            {
331
                if (m_inputLayerNames.empty())
332
                    continue;
333
                ReportError(CE_Failure, CPLE_AppDefined,
334
                            "Specified layer '%s' has no geometry field",
335
                            poSrcLayer->GetDescription());
336
                return false;
337
            }
338
339
            const int geomFieldIndex =
340
                m_geomField.empty()
341
                    ? 0
342
                    : poSrcLayerDefn->GetGeomFieldIndex(m_geomField.c_str());
343
344
            if (geomFieldIndex == -1)
345
            {
346
                ReportError(CE_Failure, CPLE_AppDefined,
347
                            "Specified geometry field '%s' does not exist in "
348
                            "layer '%s'",
349
                            m_geomField.c_str(), poSrcLayer->GetDescription());
350
                return false;
351
            }
352
353
            std::vector<int> includeFieldIndices;
354
            if (!GetFieldIndices(m_includeFields,
355
                                 OGRLayer::ToHandle(poSrcLayer),
356
                                 includeFieldIndices))
357
            {
358
                return false;
359
            }
360
361
            outDS->AddLayer(*poSrcLayer,
362
                            std::make_unique<GDALInvalidLocationLayer>(
363
                                *poSrcLayer, includeFieldIndices,
364
                                bSingleLayerOutput, geomFieldIndex,
365
                                !m_includeValid));
366
        }
367
    }
368
369
    m_outputDataset.Set(std::move(outDS));
370
371
    return true;
372
#else
373
0
    ReportError(CE_Failure, CPLE_AppDefined,
374
0
                "%s requires GDAL to be built against the GEOS library.", NAME);
375
0
    return false;
376
0
#endif
377
0
}
378
379
GDALVectorCheckGeometryAlgorithmStandalone::
380
0
    ~GDALVectorCheckGeometryAlgorithmStandalone() = default;
381
382
//! @endcond