Coverage Report

Created: 2026-04-01 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/gdalalg_vector_check_coverage.cpp
Line
Count
Source
1
/******************************************************************************
2
*
3
 * Project:  GDAL
4
 * Purpose:  "gdal vector check-coverage" 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_coverage.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
GDALVectorCheckCoverageAlgorithm::GDALVectorCheckCoverageAlgorithm(
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-valid", 0,
38
0
           _("Include valid inputs in output, with empty geometry"),
39
0
           &m_includeValid);
40
41
0
    AddArg("geometry-field", 0, _("Name of geometry field to check"),
42
0
           &m_geomField);
43
44
0
    AddArg("maximum-gap-width", 0, _("Maximum width of a gap to be flagged"),
45
0
           &m_maximumGapWidth)
46
0
        .SetMinValueIncluded(0);
47
0
}
48
49
#if defined HAVE_GEOS &&                                                       \
50
    (GEOS_VERSION_MAJOR > 3 ||                                                 \
51
     (GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 12))
52
53
class GDALVectorCheckCoverageOutputLayer final
54
    : public GDALGeosNonStreamingAlgorithmLayer
55
{
56
  public:
57
    explicit GDALVectorCheckCoverageOutputLayer(OGRLayer &srcLayer,
58
                                                int geomFieldIndex,
59
                                                const std::string &name,
60
                                                double maximumGapWidth,
61
                                                bool includeValid)
62
        : GDALGeosNonStreamingAlgorithmLayer(srcLayer, geomFieldIndex),
63
          m_defn(OGRFeatureDefn::CreateFeatureDefn(name.c_str())),
64
          m_maximumGapWidth(maximumGapWidth), m_includeValid(includeValid)
65
    {
66
        m_defn->Reference();
67
68
        const OGRFeatureDefn *poSrcLayerDefn = srcLayer.GetLayerDefn();
69
        m_defn->SetGeomType(wkbMultiLineString);
70
        m_defn->GetGeomFieldDefn(0)->SetSpatialRef(
71
            poSrcLayerDefn->GetGeomFieldDefn(geomFieldIndex)->GetSpatialRef());
72
    }
73
74
    ~GDALVectorCheckCoverageOutputLayer() override;
75
76
    const OGRFeatureDefn *GetLayerDefn() const override
77
    {
78
        return m_defn;
79
    }
80
81
    int TestCapability(const char *) const override
82
    {
83
        return false;
84
    }
85
86
    bool PolygonsOnly() const override
87
    {
88
        return true;
89
    }
90
91
    bool SkipEmpty() const override
92
    {
93
        return !m_includeValid;
94
    }
95
96
    bool ProcessGeos() override
97
    {
98
        // Perform coverage checking
99
        GEOSGeometry *coll = GEOSGeom_createCollection_r(
100
            m_poGeosContext, GEOS_GEOMETRYCOLLECTION, m_apoGeosInputs.data(),
101
            static_cast<unsigned int>(m_apoGeosInputs.size()));
102
103
        if (coll == nullptr)
104
        {
105
            return false;
106
        }
107
108
        m_apoGeosInputs.clear();
109
110
        int geos_result =
111
            GEOSCoverageIsValid_r(m_poGeosContext, coll, m_maximumGapWidth,
112
                                  &m_poGeosResultAsCollection);
113
        GEOSGeom_destroy_r(m_poGeosContext, coll);
114
115
        CPLDebug("CoverageIsValid", "%d", geos_result);
116
117
        return geos_result != 2;
118
    }
119
120
  private:
121
    OGRFeatureDefn *m_defn;
122
    const double m_maximumGapWidth;
123
    const bool m_includeValid;
124
125
    CPL_DISALLOW_COPY_ASSIGN(GDALVectorCheckCoverageOutputLayer)
126
};
127
128
GDALVectorCheckCoverageOutputLayer::~GDALVectorCheckCoverageOutputLayer()
129
{
130
    if (m_defn != nullptr)
131
    {
132
        m_defn->Release();
133
    }
134
}
135
136
bool GDALVectorCheckCoverageAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
137
{
138
    auto poSrcDS = m_inputDataset[0].GetDatasetRef();
139
    auto poDstDS =
140
        std::make_unique<GDALVectorNonStreamingAlgorithmDataset>(*poSrcDS);
141
142
    const bool bSingleLayerOutput = m_inputLayerNames.empty()
143
                                        ? poSrcDS->GetLayerCount() == 1
144
                                        : m_inputLayerNames.size() == 1;
145
146
    GDALVectorAlgorithmLayerProgressHelper progressHelper(ctxt);
147
148
    for (auto &&poSrcLayer : poSrcDS->GetLayers())
149
    {
150
        if (m_inputLayerNames.empty() ||
151
            std::find(m_inputLayerNames.begin(), m_inputLayerNames.end(),
152
                      poSrcLayer->GetDescription()) != m_inputLayerNames.end())
153
        {
154
            const auto poSrcLayerDefn = poSrcLayer->GetLayerDefn();
155
            if (poSrcLayerDefn->GetGeomFieldCount() == 0)
156
            {
157
                if (m_inputLayerNames.empty())
158
                    continue;
159
                ReportError(CE_Failure, CPLE_AppDefined,
160
                            "Specified layer '%s' has no geometry field",
161
                            poSrcLayer->GetDescription());
162
                return false;
163
            }
164
165
            progressHelper.AddProcessedLayer(*poSrcLayer);
166
        }
167
    }
168
169
    for ([[maybe_unused]] auto [poSrcLayer, bProcessed, layerProgressFunc,
170
                                layerProgressData] : progressHelper)
171
    {
172
        const auto poSrcLayerDefn = poSrcLayer->GetLayerDefn();
173
        const int geomFieldIndex =
174
            m_geomField.empty()
175
                ? 0
176
                : poSrcLayerDefn->GetGeomFieldIndex(m_geomField.c_str());
177
178
        if (geomFieldIndex == -1)
179
        {
180
            ReportError(CE_Failure, CPLE_AppDefined,
181
                        "Specified geometry field '%s' does not exist in "
182
                        "layer '%s'",
183
                        m_geomField.c_str(), poSrcLayer->GetDescription());
184
            return false;
185
        }
186
187
        std::string layerName = bSingleLayerOutput
188
                                    ? "invalid_edge"
189
                                    : std::string("invalid_edge_")
190
                                          .append(poSrcLayer->GetDescription());
191
192
        auto poLayer = std::make_unique<GDALVectorCheckCoverageOutputLayer>(
193
            *poSrcLayer, geomFieldIndex, layerName, m_maximumGapWidth,
194
            m_includeValid);
195
196
        if (!poDstDS->AddProcessedLayer(std::move(poLayer), layerProgressFunc,
197
                                        layerProgressData.get()))
198
        {
199
            return false;
200
        }
201
    }
202
203
    m_outputDataset.Set(std::move(poDstDS));
204
205
    return true;
206
}
207
208
#else
209
210
bool GDALVectorCheckCoverageAlgorithm::RunStep(GDALPipelineStepRunContext &)
211
0
{
212
0
    ReportError(CE_Failure, CPLE_AppDefined,
213
0
                "%s requires GDAL to be built against version 3.12 or later of "
214
0
                "the GEOS library.",
215
0
                NAME);
216
0
    return false;
217
0
}
218
#endif  // HAVE_GEOS
219
220
GDALVectorCheckCoverageAlgorithmStandalone::
221
0
    ~GDALVectorCheckCoverageAlgorithmStandalone() = default;
222
223
//! @endcond