Coverage Report

Created: 2026-02-14 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/gdalalg_raster_polygonize.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  gdal "raster polygonize" subcommand
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_raster_polygonize.h"
14
#include "gdalalg_vector_write.h"
15
16
#include "cpl_conv.h"
17
#include "gdal_priv.h"
18
#include "gdal_alg.h"
19
#include "ogrsf_frmts.h"
20
21
//! @cond Doxygen_Suppress
22
23
#ifndef _
24
0
#define _(x) (x)
25
#endif
26
27
/************************************************************************/
28
/*    GDALRasterPolygonizeAlgorithm::GDALRasterPolygonizeAlgorithm()    */
29
/************************************************************************/
30
31
GDALRasterPolygonizeAlgorithm::GDALRasterPolygonizeAlgorithm(
32
    bool standaloneStep)
33
0
    : GDALPipelineStepAlgorithm(
34
0
          NAME, DESCRIPTION, HELP_URL,
35
0
          ConstructorOptions()
36
0
              .SetStandaloneStep(standaloneStep)
37
0
              .SetAddUpsertArgument(false)
38
0
              .SetAddSkipErrorsArgument(false)
39
0
              .SetOutputFormatCreateCapability(GDAL_DCAP_CREATE))
40
0
{
41
0
    m_outputLayerName = "polygonize";
42
43
0
    AddProgressArg();
44
0
    if (standaloneStep)
45
0
    {
46
0
        AddRasterInputArgs(false, false);
47
0
        AddVectorOutputArgs(false, false);
48
0
    }
49
0
    else
50
0
    {
51
0
        AddRasterHiddenInputDatasetArg();
52
0
        AddOutputLayerNameArg(/* hiddenForCLI = */ false,
53
0
                              /* shortNameOutputLayerAllowed = */ false);
54
0
    }
55
56
    // gdal_polygonize specific options
57
0
    AddBandArg(&m_band).SetDefault(m_band);
58
0
    AddArg("attribute-name", 0, _("Name of the field with the pixel value"),
59
0
           &m_attributeName)
60
0
        .SetDefault(m_attributeName);
61
62
0
    AddArg("connect-diagonal-pixels", 'c',
63
0
           _("Consider diagonal pixels as connected"), &m_connectDiagonalPixels)
64
0
        .SetDefault(m_connectDiagonalPixels);
65
66
0
    AddArg("commit-interval", 0, _("Commit interval"), &m_commitInterval)
67
0
        .SetHidden();
68
0
}
69
70
bool GDALRasterPolygonizeAlgorithm::CanHandleNextStep(
71
    GDALPipelineStepAlgorithm *poNextStep) const
72
0
{
73
0
    return poNextStep->GetName() == GDALVectorWriteAlgorithm::NAME &&
74
0
           poNextStep->GetOutputFormat() != "stream";
75
0
}
76
77
/************************************************************************/
78
/*               GDALRasterPolygonizeAlgorithm::RunImpl()               */
79
/************************************************************************/
80
81
bool GDALRasterPolygonizeAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
82
                                            void *pProgressData)
83
0
{
84
0
    GDALPipelineStepRunContext stepCtxt;
85
0
    stepCtxt.m_pfnProgress = pfnProgress;
86
0
    stepCtxt.m_pProgressData = pProgressData;
87
0
    return RunPreStepPipelineValidations() && RunStep(stepCtxt);
88
0
}
89
90
/************************************************************************/
91
/*               GDALRasterPolygonizeAlgorithm::RunStep()               */
92
/************************************************************************/
93
94
bool GDALRasterPolygonizeAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
95
0
{
96
0
    auto poSrcDS = m_inputDataset[0].GetDatasetRef();
97
0
    CPLAssert(poSrcDS);
98
99
0
    auto poWriteStep = ctxt.m_poNextUsableStep ? ctxt.m_poNextUsableStep : this;
100
101
0
    GDALDataset *poDstDS = poWriteStep->GetOutputDataset().GetDatasetRef();
102
0
    std::unique_ptr<GDALDataset> poRetDS;
103
0
    std::string outputFilename = poWriteStep->GetOutputDataset().GetName();
104
0
    GDALDriver *poDstDriver = nullptr;
105
0
    bool bTemporaryFile = false;
106
0
    if (!poDstDS)
107
0
    {
108
0
        auto poDriverManager = GetGDALDriverManager();
109
0
        std::string format = poWriteStep->GetOutputFormat();
110
0
        if (m_standaloneStep || (ctxt.m_poNextUsableStep && format.empty()))
111
0
        {
112
0
            if (format.empty())
113
0
            {
114
0
                const auto aosFormats =
115
0
                    CPLStringList(GDALGetOutputDriversForDatasetName(
116
0
                        outputFilename.c_str(), GDAL_OF_VECTOR,
117
0
                        /* bSingleMatch = */ true,
118
0
                        /* bWarn = */ true));
119
0
                if (aosFormats.size() != 1)
120
0
                {
121
0
                    ReportError(CE_Failure, CPLE_AppDefined,
122
0
                                "Cannot guess driver for %s",
123
0
                                outputFilename.c_str());
124
0
                    return false;
125
0
                }
126
0
                format = aosFormats[0];
127
0
            }
128
0
        }
129
0
        else if (!ctxt.m_poNextUsableStep)
130
0
        {
131
0
            poDstDriver = poDriverManager->GetDriverByName("GPKG");
132
0
            if (poDstDriver)
133
0
            {
134
0
                bTemporaryFile = true;
135
0
                outputFilename =
136
0
                    CPLGenerateTempFilenameSafe("_polygonize") + ".gpkg";
137
0
                format = "GPKG";
138
0
            }
139
0
            else
140
0
                format = "MEM";
141
0
        }
142
143
0
        if (!poDstDriver)
144
0
            poDstDriver = poDriverManager->GetDriverByName(format.c_str());
145
0
        if (!poDstDriver)
146
0
        {
147
0
            ReportError(CE_Failure, CPLE_AppDefined, "Cannot find driver %s",
148
0
                        format.c_str());
149
0
            return false;
150
0
        }
151
152
0
        poRetDS.reset(poDstDriver->Create(
153
0
            outputFilename.c_str(), 0, 0, 0, GDT_Unknown,
154
0
            CPLStringList(poWriteStep->GetCreationOptions()).List()));
155
0
        if (!poRetDS)
156
0
            return false;
157
158
0
        if (bTemporaryFile)
159
0
            poRetDS->MarkSuppressOnClose();
160
161
0
        poDstDS = poRetDS.get();
162
0
    }
163
0
    else
164
0
    {
165
0
        poDstDriver = poDstDS->GetDriver();
166
0
    }
167
168
0
    std::string outputLayerName = poWriteStep->GetOutputLayerName();
169
0
    if (poDstDriver && EQUAL(poDstDriver->GetDescription(), "ESRI Shapefile") &&
170
0
        EQUAL(CPLGetExtensionSafe(poDstDS->GetDescription()).c_str(), "shp") &&
171
0
        poDstDS->GetLayerCount() <= 1)
172
0
    {
173
0
        outputLayerName = CPLGetBasenameSafe(poDstDS->GetDescription());
174
0
    }
175
176
0
    auto poDstLayer = poDstDS->GetLayerByName(outputLayerName.c_str());
177
0
    if (poDstLayer)
178
0
    {
179
0
        if (poWriteStep->GetOverwriteLayer())
180
0
        {
181
0
            int iLayer = -1;
182
0
            const int nLayerCount = poDstDS->GetLayerCount();
183
0
            for (iLayer = 0; iLayer < nLayerCount; iLayer++)
184
0
            {
185
0
                if (poDstDS->GetLayer(iLayer) == poDstLayer)
186
0
                    break;
187
0
            }
188
189
0
            if (iLayer < nLayerCount)
190
0
            {
191
0
                if (poDstDS->DeleteLayer(iLayer) != OGRERR_NONE)
192
0
                {
193
0
                    ReportError(CE_Failure, CPLE_AppDefined,
194
0
                                "Cannot delete layer '%s'",
195
0
                                outputLayerName.c_str());
196
0
                    return false;
197
0
                }
198
0
            }
199
0
            poDstLayer = nullptr;
200
0
        }
201
0
        else if (!poWriteStep->GetAppendLayer())
202
0
        {
203
0
            ReportError(CE_Failure, CPLE_AppDefined,
204
0
                        "Layer '%s' already exists. Specify the "
205
0
                        "--%s option to overwrite it, or --%s "
206
0
                        "to append to it.",
207
0
                        outputLayerName.c_str(), GDAL_ARG_NAME_OVERWRITE_LAYER,
208
0
                        GDAL_ARG_NAME_APPEND);
209
0
            return false;
210
0
        }
211
0
    }
212
0
    else if (poWriteStep->GetAppendLayer() || poWriteStep->GetOverwriteLayer())
213
0
    {
214
0
        ReportError(CE_Failure, CPLE_AppDefined, "Cannot find layer '%s'",
215
0
                    outputLayerName.c_str());
216
0
        return false;
217
0
    }
218
219
0
    auto poSrcBand = poSrcDS->GetRasterBand(m_band);
220
0
    const auto eDT = poSrcBand->GetRasterDataType();
221
222
0
    if (!poDstLayer)
223
0
    {
224
0
        poDstLayer = poDstDS->CreateLayer(
225
0
            outputLayerName.c_str(), poSrcDS->GetSpatialRef(), wkbPolygon,
226
0
            CPLStringList(poWriteStep->GetLayerCreationOptions()).List());
227
0
        if (!poDstLayer)
228
0
        {
229
0
            ReportError(CE_Failure, CPLE_AppDefined, "Cannot create layer '%s'",
230
0
                        outputLayerName.c_str());
231
0
            return false;
232
0
        }
233
234
0
        OGRFieldDefn oFieldDefn(m_attributeName.c_str(),
235
0
                                !GDALDataTypeIsInteger(eDT) ? OFTReal
236
0
                                : eDT == GDT_Int64 || eDT == GDT_UInt64
237
0
                                    ? OFTInteger64
238
0
                                    : OFTInteger);
239
0
        if (poDstLayer->CreateField(&oFieldDefn) != OGRERR_NONE)
240
0
        {
241
0
            ReportError(CE_Failure, CPLE_AppDefined,
242
0
                        "Cannot create field '%s' in layer '%s'",
243
0
                        m_attributeName.c_str(), outputLayerName.c_str());
244
0
            return false;
245
0
        }
246
0
    }
247
248
0
    const int iPixValField =
249
0
        poDstLayer->GetLayerDefn()->GetFieldIndex(m_attributeName.c_str());
250
0
    if (iPixValField < 0)
251
0
    {
252
0
        ReportError(CE_Failure, CPLE_AppDefined,
253
0
                    "Cannot find field '%s' in layer '%s'",
254
0
                    m_attributeName.c_str(), outputLayerName.c_str());
255
0
        return false;
256
0
    }
257
258
0
    CPLStringList aosPolygonizeOptions;
259
0
    if (m_connectDiagonalPixels)
260
0
    {
261
0
        aosPolygonizeOptions.SetNameValue("8CONNECTED", "8");
262
0
    }
263
0
    if (m_commitInterval)
264
0
    {
265
0
        aosPolygonizeOptions.SetNameValue("COMMIT_INTERVAL",
266
0
                                          CPLSPrintf("%d", m_commitInterval));
267
0
    }
268
269
0
    bool ret;
270
0
    if (GDALDataTypeIsInteger(eDT))
271
0
    {
272
0
        ret = GDALPolygonize(GDALRasterBand::ToHandle(poSrcBand),
273
0
                             GDALRasterBand::ToHandle(poSrcBand->GetMaskBand()),
274
0
                             OGRLayer::ToHandle(poDstLayer), iPixValField,
275
0
                             aosPolygonizeOptions.List(), ctxt.m_pfnProgress,
276
0
                             ctxt.m_pProgressData) == CE_None;
277
0
    }
278
0
    else
279
0
    {
280
0
        ret =
281
0
            GDALFPolygonize(GDALRasterBand::ToHandle(poSrcBand),
282
0
                            GDALRasterBand::ToHandle(poSrcBand->GetMaskBand()),
283
0
                            OGRLayer::ToHandle(poDstLayer), iPixValField,
284
0
                            aosPolygonizeOptions.List(), ctxt.m_pfnProgress,
285
0
                            ctxt.m_pProgressData) == CE_None;
286
0
    }
287
288
0
    if (ret && poRetDS)
289
0
    {
290
0
        if (bTemporaryFile)
291
0
        {
292
0
            ret = poRetDS->FlushCache() == CE_None;
293
0
#if !defined(__APPLE__)
294
            // For some unknown reason, unlinking the file on MacOSX
295
            // leads to later "disk I/O error". See https://github.com/OSGeo/gdal/issues/13794
296
0
            VSIUnlink(outputFilename.c_str());
297
0
#endif
298
0
        }
299
300
0
        m_outputDataset.Set(std::move(poRetDS));
301
0
    }
302
303
0
    return ret;
304
0
}
305
306
GDALRasterPolygonizeAlgorithmStandalone::
307
0
    ~GDALRasterPolygonizeAlgorithmStandalone() = default;
308
309
//! @endcond