Coverage Report

Created: 2026-10-28 07:09

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