Coverage Report

Created: 2026-07-14 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/gdalalg_raster_contour.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  gdal "raster contour" subcommand
5
 * Author:   Alessandro Pasotti <elpaso at itopen dot it>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2025, Alessandro Pasotti <elpaso at itopen dot it>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include <cmath>
14
15
#include "gdalalg_raster_contour.h"
16
17
#include "cpl_conv.h"
18
#include "gdal_priv.h"
19
#include "gdal_utils.h"
20
#include "gdal_alg.h"
21
#include "gdal_utils_priv.h"
22
23
//! @cond Doxygen_Suppress
24
25
#ifndef _
26
0
#define _(x) (x)
27
#endif
28
29
/************************************************************************/
30
/*       GDALRasterContourAlgorithm::GDALRasterContourAlgorithm()       */
31
/************************************************************************/
32
33
GDALRasterContourAlgorithm::GDALRasterContourAlgorithm(bool standaloneStep)
34
0
    : GDALPipelineStepAlgorithm(
35
0
          NAME, DESCRIPTION, HELP_URL,
36
0
          ConstructorOptions()
37
0
              .SetStandaloneStep(standaloneStep)
38
0
              .SetAddAppendLayerArgument(false)
39
0
              .SetAddOverwriteLayerArgument(false)
40
0
              .SetAddUpdateArgument(false)
41
0
              .SetAddUpsertArgument(false)
42
0
              .SetAddSkipErrorsArgument(false)
43
0
              .SetOutputLayerNameAvailableInPipelineStep(true)
44
0
              .SetOutputFormatCreateCapability(GDAL_DCAP_CREATE))
45
0
{
46
0
    m_outputLayerName = "contour";
47
48
0
    if (standaloneStep)
49
0
    {
50
0
        AddProgressArg();
51
0
        AddRasterInputArgs(false, false);
52
0
        AddVectorOutputArgs(false, false);
53
0
    }
54
0
    else
55
0
    {
56
0
        AddRasterHiddenInputDatasetArg();
57
0
        AddOutputLayerNameArg(/* hiddenForCLI = */ false,
58
0
                              /* shortNameOutputLayerAllowed = */ false);
59
0
    }
60
61
    // gdal_contour specific options
62
0
    AddBandArg(&m_band).SetDefault(1);
63
64
0
    AddArg("elevation-name", 0, _("Name of the elevation field"),
65
0
           &m_elevAttributeName);
66
0
    AddArg("min-name", 0, _("Name of the minimum elevation field"), &m_amin);
67
0
    AddArg("max-name", 0, _("Name of the maximum elevation field"), &m_amax);
68
0
    AddArg("3d", 0, _("Force production of 3D vectors instead of 2D"), &m_3d);
69
70
0
    AddArg("input-nodata", 0, _("Input pixel value to treat as 'nodata'"),
71
0
           &m_sNodata)
72
0
        .AddHiddenAlias("src-nodata");
73
0
    AddArg("interval", 0, _("Elevation interval between contours"), &m_interval)
74
0
        .SetMutualExclusionGroup("levels")
75
0
        .SetMinValueExcluded(0);
76
0
    AddArg("levels", 0, _("List of contour levels"), &m_levels)
77
0
        .SetDuplicateValuesAllowed(false)
78
0
        .SetMutualExclusionGroup("levels");
79
0
    AddArg("exp-base", 'e', _("Base for exponential contour level generation"),
80
0
           &m_expBase)
81
0
        .SetMutualExclusionGroup("levels");
82
0
    AddArg("offset", 0, _("Offset to apply to contour levels"), &m_offset)
83
0
        .AddAlias("off");
84
0
    AddArg("polygonize", 'p', _("Create polygons instead of lines"),
85
0
           &m_polygonize);
86
0
    AddArg("group-transactions", 0,
87
0
           _("Group n features per transaction (default 100 000)"),
88
0
           &m_groupTransactions)
89
0
        .SetMinValueIncluded(0);
90
0
}
91
92
/************************************************************************/
93
/*                GDALRasterContourAlgorithm::RunImpl()                 */
94
/************************************************************************/
95
96
bool GDALRasterContourAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
97
                                         void *pProgressData)
98
0
{
99
0
    GDALPipelineStepRunContext stepCtxt;
100
0
    stepCtxt.m_pfnProgress = pfnProgress;
101
0
    stepCtxt.m_pProgressData = pProgressData;
102
0
    return RunPreStepPipelineValidations() && RunStep(stepCtxt);
103
0
}
104
105
/************************************************************************/
106
/*                GDALRasterContourAlgorithm::RunStep()                 */
107
/************************************************************************/
108
109
bool GDALRasterContourAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
110
0
{
111
0
    CPLErrorReset();
112
113
0
    auto poSrcDS = m_inputDataset[0].GetDatasetRef();
114
0
    CPLAssert(poSrcDS);
115
116
0
    CPLAssert(!m_outputDataset.GetDatasetRef());
117
118
0
    CPLStringList aosOptions;
119
120
0
    std::string outputFilename;
121
0
    if (m_standaloneStep)
122
0
    {
123
0
        outputFilename = m_outputDataset.GetName();
124
0
        if (!m_format.empty())
125
0
        {
126
0
            aosOptions.AddString("-of");
127
0
            aosOptions.AddString(m_format.c_str());
128
0
        }
129
130
0
        for (const auto &co : m_creationOptions)
131
0
        {
132
0
            aosOptions.push_back("-co");
133
0
            aosOptions.push_back(co.c_str());
134
0
        }
135
136
0
        for (const auto &co : m_layerCreationOptions)
137
0
        {
138
0
            aosOptions.push_back("-lco");
139
0
            aosOptions.push_back(co.c_str());
140
0
        }
141
0
    }
142
0
    else
143
0
    {
144
0
        if (GetGDALDriverManager()->GetDriverByName("GPKG"))
145
0
        {
146
0
            aosOptions.AddString("-of");
147
0
            aosOptions.AddString("GPKG");
148
149
0
            outputFilename = CPLGenerateTempFilenameSafe("_contour") + ".gpkg";
150
0
        }
151
0
        else
152
0
        {
153
0
            aosOptions.AddString("-of");
154
0
            aosOptions.AddString("MEM");
155
0
        }
156
0
    }
157
158
0
    if (m_band > 0)
159
0
    {
160
0
        aosOptions.AddString("-b");
161
0
        aosOptions.AddString(CPLSPrintf("%d", m_band));
162
0
    }
163
0
    if (!m_elevAttributeName.empty())
164
0
    {
165
0
        aosOptions.AddString("-a");
166
0
        aosOptions.AddString(m_elevAttributeName);
167
0
    }
168
0
    if (!m_amin.empty())
169
0
    {
170
0
        aosOptions.AddString("-amin");
171
0
        aosOptions.AddString(m_amin);
172
0
    }
173
0
    if (!m_amax.empty())
174
0
    {
175
0
        aosOptions.AddString("-amax");
176
0
        aosOptions.AddString(m_amax);
177
0
    }
178
0
    if (m_3d)
179
0
    {
180
0
        aosOptions.AddString("-3d");
181
0
    }
182
0
    if (!std::isnan(m_sNodata))
183
0
    {
184
0
        aosOptions.AddString("-snodata");
185
0
        aosOptions.AddString(CPLSPrintf("%.16g", m_sNodata));
186
0
    }
187
0
    if (m_levels.size() > 0)
188
0
    {
189
0
        for (const auto &level : m_levels)
190
0
        {
191
0
            aosOptions.AddString("-fl");
192
0
            aosOptions.AddString(level);
193
0
        }
194
0
    }
195
0
    if (!std::isnan(m_interval))
196
0
    {
197
0
        aosOptions.AddString("-i");
198
0
        aosOptions.AddString(CPLSPrintf("%.16g", m_interval));
199
0
    }
200
0
    if (m_expBase > 0)
201
0
    {
202
0
        aosOptions.AddString("-e");
203
0
        aosOptions.AddString(CPLSPrintf("%d", m_expBase));
204
0
    }
205
0
    if (!std::isnan(m_offset))
206
0
    {
207
0
        aosOptions.AddString("-off");
208
0
        aosOptions.AddString(CPLSPrintf("%.16g", m_offset));
209
0
    }
210
0
    if (m_polygonize)
211
0
    {
212
0
        aosOptions.AddString("-p");
213
0
    }
214
0
    if (!m_outputLayerName.empty())
215
0
    {
216
0
        aosOptions.AddString("-nln");
217
0
        aosOptions.AddString(m_outputLayerName);
218
0
    }
219
220
    // Check that one of --interval, --levels, --exp-base is specified
221
0
    if (m_levels.size() == 0 && std::isnan(m_interval) && m_expBase == 0)
222
0
    {
223
0
        ReportError(
224
0
            CE_Failure, CPLE_AppDefined,
225
0
            "One of 'interval', 'levels', 'exp-base' must be specified.");
226
0
        return false;
227
0
    }
228
229
    // Check that interval is not negative
230
0
    if (!std::isnan(m_interval) && m_interval < 0)
231
0
    {
232
0
        ReportError(CE_Failure, CPLE_AppDefined,
233
0
                    "Interval must be a positive number.");
234
0
        return false;
235
0
    }
236
237
0
    aosOptions.AddString(m_inputDataset[0].GetName());
238
0
    aosOptions.AddString(outputFilename);
239
240
0
    bool bRet = false;
241
0
    GDALContourOptionsForBinary optionsForBinary;
242
0
    std::unique_ptr<GDALContourOptions, decltype(&GDALContourOptionsFree)>
243
0
        psOptions{GDALContourOptionsNew(aosOptions.List(), &optionsForBinary),
244
0
                  GDALContourOptionsFree};
245
0
    if (psOptions)
246
0
    {
247
0
        GDALDatasetH hSrcDS{poSrcDS};
248
0
        GDALRasterBandH hBand{nullptr};
249
0
        GDALDatasetH hDstDS{m_outputDataset.GetDatasetRef()};
250
0
        OGRLayerH hLayer{nullptr};
251
0
        char **papszStringOptions = nullptr;
252
253
0
        bRet = GDALContourProcessOptions(psOptions.get(), &papszStringOptions,
254
0
                                         &hSrcDS, &hBand, &hDstDS,
255
0
                                         &hLayer) == CE_None;
256
257
0
        if (bRet)
258
0
        {
259
0
            bRet = GDALContourGenerateEx(hBand, hLayer, papszStringOptions,
260
0
                                         ctxt.m_pfnProgress,
261
0
                                         ctxt.m_pProgressData) == CE_None;
262
0
        }
263
264
0
        CSLDestroy(papszStringOptions);
265
266
0
        auto poDstDS = GDALDataset::FromHandle(hDstDS);
267
0
        if (bRet)
268
0
        {
269
0
            bRet = poDstDS != nullptr;
270
0
        }
271
0
        if (poDstDS && !m_standaloneStep && !outputFilename.empty())
272
0
        {
273
0
            poDstDS->MarkSuppressOnClose();
274
0
            if (bRet)
275
0
                bRet = poDstDS->FlushCache() == CE_None;
276
0
#if !defined(__APPLE__)
277
            // For some unknown reason, unlinking the file on MacOSX
278
            // leads to later "disk I/O error". See https://github.com/OSGeo/gdal/issues/13794
279
0
            VSIUnlink(outputFilename.c_str());
280
0
#endif
281
0
        }
282
0
        m_outputDataset.Set(std::unique_ptr<GDALDataset>(poDstDS));
283
0
    }
284
285
0
    return bRet;
286
0
}
287
288
0
GDALRasterContourAlgorithmStandalone::~GDALRasterContourAlgorithmStandalone() =
289
    default;
290
291
//! @endcond