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_dissolve.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  "gdal vector dissolve"
5
 * Author:   Dan Baston
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2025, ISciences LLC
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "gdalalg_vector_dissolve.h"
14
15
#include "gdal_priv.h"
16
#include "ogrsf_frmts.h"
17
18
#include <cinttypes>
19
20
//! @cond Doxygen_Suppress
21
22
#ifndef _
23
#define _(x) (x)
24
#endif
25
26
/************************************************************************/
27
/*                    GDALVectorDissolveAlgorithm()                     */
28
/************************************************************************/
29
30
GDALVectorDissolveAlgorithm::GDALVectorDissolveAlgorithm(bool standaloneStep)
31
0
    : GDALVectorGeomAbstractAlgorithm(NAME, DESCRIPTION, HELP_URL,
32
0
                                      standaloneStep, m_opts)
33
0
{
34
0
}
35
36
#ifdef HAVE_GEOS
37
38
namespace
39
{
40
41
/************************************************************************/
42
/*                   GDALVectorDissolveAlgorithmLayer                   */
43
/************************************************************************/
44
45
class GDALVectorDissolveAlgorithmLayer final
46
    : public GDALVectorGeomOneToOneAlgorithmLayer<GDALVectorDissolveAlgorithm>
47
{
48
  public:
49
    GDALVectorDissolveAlgorithmLayer(
50
        OGRLayer &oSrcLayer, const GDALVectorDissolveAlgorithm::Options &opts)
51
        : GDALVectorGeomOneToOneAlgorithmLayer<GDALVectorDissolveAlgorithm>(
52
              oSrcLayer, opts)
53
    {
54
    }
55
56
  protected:
57
    using GDALVectorGeomOneToOneAlgorithmLayer::TranslateFeature;
58
59
    std::unique_ptr<OGRFeature>
60
    TranslateFeature(std::unique_ptr<OGRFeature> poSrcFeature) const override;
61
62
  private:
63
};
64
65
/************************************************************************/
66
/*                          TranslateFeature()                          */
67
/************************************************************************/
68
69
std::unique_ptr<OGRGeometry> LineMerge(const OGRMultiLineString *poGeom)
70
{
71
    GEOSContextHandle_t hContext = OGRGeometry::createGEOSContext();
72
    GEOSGeometry *hGeosGeom = poGeom->exportToGEOS(hContext);
73
    if (!hGeosGeom)
74
    {
75
        OGRGeometry::freeGEOSContext(hContext);
76
        return nullptr;
77
    }
78
79
    GEOSGeometry *hGeosResult = GEOSLineMerge_r(hContext, hGeosGeom);
80
    GEOSGeom_destroy_r(hContext, hGeosGeom);
81
82
    if (!hGeosResult)
83
    {
84
        OGRGeometry::freeGEOSContext(hContext);
85
        return nullptr;
86
    }
87
88
    std::unique_ptr<OGRGeometry> ret(
89
        OGRGeometryFactory::createFromGEOS(hContext, hGeosResult));
90
    GEOSGeom_destroy_r(hContext, hGeosResult);
91
    OGRGeometry::freeGEOSContext(hContext);
92
93
    if (ret)
94
    {
95
        const auto eRetType = wkbFlatten(ret->getGeometryType());
96
        if (eRetType != wkbLineString && eRetType != wkbMultiLineString)
97
        {
98
            CPLError(CE_Failure, CPLE_AppDefined,
99
                     "LineMerge returned a geometry of type %s, expected "
100
                     "LineString or MultiLineString",
101
                     OGRGeometryTypeToName(eRetType));
102
            return nullptr;
103
        }
104
    }
105
106
    return ret;
107
}
108
109
std::unique_ptr<OGRFeature> GDALVectorDissolveAlgorithmLayer::TranslateFeature(
110
    std::unique_ptr<OGRFeature> poSrcFeature) const
111
{
112
    const int nGeomFieldCount = poSrcFeature->GetGeomFieldCount();
113
    for (int iGeomField = 0; iGeomField < nGeomFieldCount; ++iGeomField)
114
    {
115
        if (IsSelectedGeomField(iGeomField))
116
        {
117
            if (auto poGeom = std::unique_ptr<OGRGeometry>(
118
                    poSrcFeature->StealGeometry(iGeomField)))
119
            {
120
                poGeom.reset(poGeom->UnaryUnion());
121
                if (!poGeom)
122
                {
123
                    CPLError(CE_Failure, CPLE_AppDefined,
124
                             "Failed to perform union of geometry on feature "
125
                             "%" PRId64,
126
                             static_cast<int64_t>(poSrcFeature->GetFID()));
127
                    return nullptr;
128
                }
129
130
                const auto eResultType = wkbFlatten(poGeom->getGeometryType());
131
132
                if (eResultType == wkbMultiLineString)
133
                {
134
                    poGeom = LineMerge(poGeom->toMultiLineString());
135
                    if (!poGeom)
136
                    {
137
                        CPLError(CE_Failure, CPLE_AppDefined,
138
                                 "Failed to merge lines of feature %" PRId64,
139
                                 static_cast<int64_t>(poSrcFeature->GetFID()));
140
                        return nullptr;
141
                    }
142
                }
143
                else if (eResultType == wkbGeometryCollection)
144
                {
145
                    OGRGeometryCollection *poColl =
146
                        poGeom->toGeometryCollection();
147
148
                    OGRMultiLineString oMLS;
149
150
                    const auto nGeoms = poColl->getNumGeometries();
151
                    for (int i = nGeoms - 1; i >= 0; i--)
152
                    {
153
                        const auto eComponentType = wkbFlatten(
154
                            poColl->getGeometryRef(i)->getGeometryType());
155
                        if (eComponentType == wkbLineString)
156
                        {
157
                            oMLS.addGeometryDirectly(poColl->stealGeometry(i)
158
                                                         .release()
159
                                                         ->toLineString());
160
                        }
161
                    }
162
163
                    if (oMLS.getNumGeometries() > 0)
164
                    {
165
                        std::unique_ptr<OGRGeometry> poMerged =
166
                            LineMerge(&oMLS);
167
                        if (!poMerged)
168
                        {
169
                            CPLError(
170
                                CE_Failure, CPLE_AppDefined,
171
                                "Failed to merge lines of feature %" PRId64,
172
                                static_cast<int64_t>(poSrcFeature->GetFID()));
173
                            return nullptr;
174
                        }
175
176
                        const auto eMergedType =
177
                            wkbFlatten(poMerged->getGeometryType());
178
                        if (eMergedType == wkbLineString)
179
                        {
180
                            poColl->addGeometry(std::move(poMerged));
181
                        }
182
                        else  // eMergedType == wkbMultiLineString
183
                        {
184
                            OGRMultiLineString *poMergedMLS =
185
                                poMerged->toMultiLineString();
186
                            const auto nMergedGeoms =
187
                                poMergedMLS->getNumGeometries();
188
                            for (int i = nMergedGeoms - 1; i >= 0; i--)
189
                            {
190
                                poColl->addGeometryDirectly(
191
                                    poMergedMLS->stealGeometry(i)
192
                                        .release()
193
                                        ->toLineString());
194
                            }
195
                        }
196
                    }
197
                }
198
199
                if (poGeom)
200
                {
201
                    poGeom->assignSpatialReference(
202
                        m_srcLayer.GetLayerDefn()
203
                            ->GetGeomFieldDefn(iGeomField)
204
                            ->GetSpatialRef());
205
                    poSrcFeature->SetGeomField(iGeomField, std::move(poGeom));
206
                }
207
            }
208
        }
209
    }
210
211
    return poSrcFeature;
212
}
213
214
}  // namespace
215
216
#endif  // HAVE_GEOS
217
218
/************************************************************************/
219
/*            GDALVectorDissolveAlgorithm::CreateAlgLayer()             */
220
/************************************************************************/
221
222
std::unique_ptr<OGRLayerWithTranslateFeature>
223
GDALVectorDissolveAlgorithm::CreateAlgLayer([[maybe_unused]] OGRLayer &srcLayer)
224
0
{
225
#ifdef HAVE_GEOS
226
    return std::make_unique<GDALVectorDissolveAlgorithmLayer>(srcLayer, m_opts);
227
#else
228
0
    CPLAssert(false);
229
0
    return nullptr;
230
0
#endif
231
0
}
232
233
/************************************************************************/
234
/*                GDALVectorDissolveAlgorithm::RunStep()                */
235
/************************************************************************/
236
237
bool GDALVectorDissolveAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
238
0
{
239
#ifdef HAVE_GEOS
240
    return GDALVectorGeomAbstractAlgorithm::RunStep(ctxt);
241
#else
242
0
    (void)ctxt;
243
0
    ReportError(CE_Failure, CPLE_NotSupported,
244
0
                "This algorithm is only supported for builds against GEOS");
245
0
    return false;
246
0
#endif
247
0
}
248
249
GDALVectorDissolveAlgorithmStandalone::
250
0
    ~GDALVectorDissolveAlgorithmStandalone() = default;
251
252
//! @endcond