Coverage Report

Created: 2026-09-14 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/gdalalg_raster_footprint.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  gdal "raster footprint" 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_footprint.h"
14
15
#include "cpl_conv.h"
16
17
#include "gdal_priv.h"
18
#include "gdal_utils.h"
19
20
//! @cond Doxygen_Suppress
21
22
#ifndef _
23
0
#define _(x) (x)
24
#endif
25
26
/************************************************************************/
27
/*     GDALRasterFootprintAlgorithm::GDALRasterFootprintAlgorithm()     */
28
/************************************************************************/
29
30
GDALRasterFootprintAlgorithm::GDALRasterFootprintAlgorithm(bool standaloneStep)
31
0
    : GDALPipelineStepAlgorithm(
32
0
          NAME, DESCRIPTION, HELP_URL,
33
0
          ConstructorOptions()
34
0
              .SetStandaloneStep(standaloneStep)
35
0
              .SetOutputFormatCreateCapability(GDAL_DCAP_CREATE))
36
0
{
37
0
    if (standaloneStep)
38
0
    {
39
0
        AddProgressArg();
40
0
        AddOpenOptionsArg(&m_openOptions).SetAvailableInPipelineStep(false);
41
0
        AddInputFormatsArg(&m_inputFormats)
42
0
            .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_RASTER})
43
0
            .SetAvailableInPipelineStep(false);
44
0
        AddInputDatasetArg(&m_inputDataset, GDAL_OF_RASTER, true,
45
0
                           _("Input raster dataset"))
46
0
            .SetMaxCount(1)
47
0
            .SetAvailableInPipelineStep(false);
48
49
0
        AddOutputDatasetArg(&m_outputDataset, GDAL_OF_VECTOR)
50
0
            .SetDatasetInputFlags(GADV_NAME | GADV_OBJECT)
51
0
            .SetAvailableInPipelineStep(false);
52
0
        AddOutputFormatArg(&m_format, /* bStreamAllowed = */ false,
53
0
                           /* bGDALGAllowed = */ false)
54
0
            .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES,
55
0
                             {GDAL_DCAP_VECTOR, GDAL_DCAP_CREATE})
56
0
            .SetAvailableInPipelineStep(false);
57
0
        AddCreationOptionsArg(&m_creationOptions)
58
0
            .SetAvailableInPipelineStep(false);
59
0
        AddLayerCreationOptionsArg(&m_layerCreationOptions)
60
0
            .SetAvailableInPipelineStep(false);
61
0
        AddUpdateArg(&m_update)
62
0
            .SetAvailableInPipelineStep(false)
63
0
            .SetHidden();  // needed for correct append execution
64
0
        AddAppendLayerArg(&m_appendLayer).SetAvailableInPipelineStep(false);
65
0
        AddOverwriteArg(&m_overwrite).SetAvailableInPipelineStep(false);
66
0
    }
67
0
    else
68
0
    {
69
0
        AddRasterHiddenInputDatasetArg();
70
0
    }
71
72
0
    m_outputLayerName = "footprint";
73
0
    AddArg(GDAL_ARG_NAME_OUTPUT_LAYER, 0, _("Output layer name"),
74
0
           &m_outputLayerName)
75
0
        .SetDefault(m_outputLayerName);
76
77
0
    AddBandArg(&m_bands);
78
0
    AddArg("combine-bands", 0,
79
0
           _("Defines how the mask bands of the selected bands are combined to "
80
0
             "generate a single mask band, before being vectorized."),
81
0
           &m_combineBands)
82
0
        .SetChoices("union", "intersection")
83
0
        .SetDefault(m_combineBands);
84
0
    AddArg("overview", 0, _("Which overview level of source file must be used"),
85
0
           &m_overview)
86
0
        .SetMutualExclusionGroup("overview-srcnodata")
87
0
        .SetMinValueIncluded(0);
88
0
    AddArg("input-nodata", 0, _("Set nodata values for input bands."),
89
0
           &m_srcNoData)
90
0
        .SetMinCount(1)
91
0
        .SetRepeatedArgAllowed(false)
92
0
        .AddHiddenAlias("src-nodata")
93
0
        .SetMutualExclusionGroup("overview-srcnodata");
94
0
    AddArg("coordinate-system", 0, _("Target coordinate system"),
95
0
           &m_coordinateSystem)
96
0
        .SetChoices("georeferenced", "pixel");
97
0
    AddArg(GDAL_ARG_NAME_OUTPUT_CRS, 0, _("Output CRS"), &m_dstCrs)
98
0
        .SetIsCRSArg()
99
0
        .AddHiddenAlias("dst-crs")
100
0
        .AddHiddenAlias("t_srs");
101
0
    AddArg("split-multipolygons", 0,
102
0
           _("Whether to split multipolygons as several features each with one "
103
0
             "single polygon"),
104
0
           &m_splitMultiPolygons);
105
0
    AddArg("convex-hull", 0,
106
0
           _("Whether to compute the convex hull of the footprint"),
107
0
           &m_convexHull);
108
0
    AddArg("densify-distance", 0,
109
0
           _("Maximum distance between 2 consecutive points of the output "
110
0
             "geometry."),
111
0
           &m_densifyVal)
112
0
        .SetMinValueExcluded(0);
113
0
    AddArg(
114
0
        "simplify-tolerance", 0,
115
0
        _("Tolerance used to merge consecutive points of the output geometry."),
116
0
        &m_simplifyVal)
117
0
        .SetMinValueExcluded(0);
118
0
    AddArg("min-ring-area", 0, _("Minimum value for the area of a ring"),
119
0
           &m_minRingArea)
120
0
        .SetMinValueIncluded(0);
121
0
    AddArg("max-points", 0,
122
0
           _("Maximum number of points of each output geometry"), &m_maxPoints)
123
0
        .SetDefault(m_maxPoints)
124
0
        .AddValidationAction(
125
0
            [this]()
126
0
            {
127
0
                if (m_maxPoints != "unlimited")
128
0
                {
129
0
                    char *endptr = nullptr;
130
0
                    const auto nVal =
131
0
                        std::strtoll(m_maxPoints.c_str(), &endptr, 10);
132
0
                    if (nVal < 4 ||
133
0
                        endptr != m_maxPoints.c_str() + m_maxPoints.size())
134
0
                    {
135
0
                        ReportError(
136
0
                            CE_Failure, CPLE_IllegalArg,
137
0
                            "Value of 'max-points' should be a positive "
138
0
                            "integer greater or equal to 4, or 'unlimited'");
139
0
                        return false;
140
0
                    }
141
0
                }
142
0
                return true;
143
0
            });
144
0
    AddArg("location-field", 0,
145
0
           _("Name of the field where the path of the input dataset will be "
146
0
             "stored."),
147
0
           &m_locationField)
148
0
        .SetDefault(m_locationField)
149
0
        .SetMutualExclusionGroup("location");
150
0
    AddArg("no-location-field", 0,
151
0
           _("Disable creating a field with the path of the input dataset"),
152
0
           &m_noLocation)
153
0
        .SetMutualExclusionGroup("location");
154
0
    AddAbsolutePathArg(&m_writeAbsolutePaths);
155
156
0
    AddValidationAction(
157
0
        [this]
158
0
        {
159
0
            if (m_inputDataset.size() == 1)
160
0
            {
161
0
                if (auto poSrcDS = m_inputDataset[0].GetDatasetRef())
162
0
                {
163
0
                    const int nOvrCount =
164
0
                        poSrcDS->GetRasterBand(1)->GetOverviewCount();
165
0
                    if (m_overview >= 0 && poSrcDS->GetRasterCount() > 0 &&
166
0
                        m_overview >= nOvrCount)
167
0
                    {
168
0
                        if (nOvrCount == 0)
169
0
                        {
170
0
                            ReportError(
171
0
                                CE_Failure, CPLE_IllegalArg,
172
0
                                "Source dataset has no overviews. "
173
0
                                "Argument 'overview' should not be specified.");
174
0
                        }
175
0
                        else
176
0
                        {
177
0
                            ReportError(
178
0
                                CE_Failure, CPLE_IllegalArg,
179
0
                                "Source dataset has only %d overview levels. "
180
0
                                "'overview' "
181
0
                                "value should be strictly lower than this "
182
0
                                "number.",
183
0
                                nOvrCount);
184
0
                        }
185
0
                        return false;
186
0
                    }
187
0
                }
188
0
            }
189
0
            return true;
190
0
        });
191
0
}
192
193
/************************************************************************/
194
/*               GDALRasterFootprintAlgorithm::RunImpl()                */
195
/************************************************************************/
196
197
bool GDALRasterFootprintAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
198
                                           void *pProgressData)
199
0
{
200
0
    GDALPipelineStepRunContext stepCtxt;
201
0
    stepCtxt.m_pfnProgress = pfnProgress;
202
0
    stepCtxt.m_pProgressData = pProgressData;
203
0
    return RunPreStepPipelineValidations() && RunStep(stepCtxt);
204
0
}
205
206
/************************************************************************/
207
/*               GDALRasterFootprintAlgorithm::RunStep()                */
208
/************************************************************************/
209
210
bool GDALRasterFootprintAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
211
0
{
212
0
    auto poSrcDS = m_inputDataset[0].GetDatasetRef();
213
0
    CPLAssert(poSrcDS);
214
215
0
    CPLStringList aosOptions;
216
217
0
    std::string outputFilename;
218
0
    if (m_standaloneStep)
219
0
    {
220
0
        outputFilename = m_outputDataset.GetName();
221
0
        if (!m_format.empty())
222
0
        {
223
0
            aosOptions.AddString("-of");
224
0
            aosOptions.AddString(m_format.c_str());
225
0
        }
226
227
0
        for (const auto &co : m_creationOptions)
228
0
        {
229
0
            aosOptions.push_back("-dsco");
230
0
            aosOptions.push_back(co.c_str());
231
0
        }
232
233
0
        for (const auto &co : m_layerCreationOptions)
234
0
        {
235
0
            aosOptions.push_back("-lco");
236
0
            aosOptions.push_back(co.c_str());
237
0
        }
238
0
    }
239
0
    else
240
0
    {
241
0
        if (GetGDALDriverManager()->GetDriverByName("GPKG"))
242
0
        {
243
0
            aosOptions.AddString("-of");
244
0
            aosOptions.AddString("GPKG");
245
246
0
            outputFilename =
247
0
                CPLGenerateTempFilenameSafe("_footprint") + ".gpkg";
248
0
        }
249
0
        else
250
0
        {
251
0
            aosOptions.AddString("-of");
252
0
            aosOptions.AddString("MEM");
253
0
        }
254
0
    }
255
256
0
    for (int band : m_bands)
257
0
    {
258
0
        aosOptions.push_back("-b");
259
0
        aosOptions.push_back(CPLSPrintf("%d", band));
260
0
    }
261
262
0
    aosOptions.push_back("-combine_bands");
263
0
    aosOptions.push_back(m_combineBands);
264
265
0
    if (m_overview >= 0)
266
0
    {
267
0
        aosOptions.push_back("-ovr");
268
0
        aosOptions.push_back(CPLSPrintf("%d", m_overview));
269
0
    }
270
271
0
    if (!m_srcNoData.empty())
272
0
    {
273
0
        aosOptions.push_back("-srcnodata");
274
0
        std::string s;
275
0
        for (double v : m_srcNoData)
276
0
        {
277
0
            if (!s.empty())
278
0
                s += " ";
279
0
            s += CPLSPrintf("%.17g", v);
280
0
        }
281
0
        aosOptions.push_back(s);
282
0
    }
283
284
0
    if (m_coordinateSystem == "pixel")
285
0
    {
286
0
        aosOptions.push_back("-t_cs");
287
0
        aosOptions.push_back("pixel");
288
0
    }
289
0
    else if (m_coordinateSystem == "georeferenced")
290
0
    {
291
0
        aosOptions.push_back("-t_cs");
292
0
        aosOptions.push_back("georef");
293
0
    }
294
295
0
    if (!m_dstCrs.empty())
296
0
    {
297
0
        aosOptions.push_back("-t_srs");
298
0
        aosOptions.push_back(m_dstCrs);
299
0
    }
300
301
0
    if (GetArg(GDAL_ARG_NAME_OUTPUT_LAYER)->IsExplicitlySet())
302
0
    {
303
0
        aosOptions.push_back("-lyr_name");
304
0
        aosOptions.push_back(m_outputLayerName.c_str());
305
0
    }
306
307
0
    if (m_splitMultiPolygons)
308
0
        aosOptions.push_back("-split_polys");
309
310
0
    if (m_convexHull)
311
0
        aosOptions.push_back("-convex_hull");
312
313
0
    if (m_densifyVal > 0)
314
0
    {
315
0
        aosOptions.push_back("-densify");
316
0
        aosOptions.push_back(CPLSPrintf("%.17g", m_densifyVal));
317
0
    }
318
319
0
    if (m_simplifyVal > 0)
320
0
    {
321
0
        aosOptions.push_back("-simplify");
322
0
        aosOptions.push_back(CPLSPrintf("%.17g", m_simplifyVal));
323
0
    }
324
325
0
    aosOptions.push_back("-min_ring_area");
326
0
    aosOptions.push_back(CPLSPrintf("%.17g", m_minRingArea));
327
328
0
    aosOptions.push_back("-max_points");
329
0
    aosOptions.push_back(m_maxPoints);
330
331
0
    if (m_noLocation)
332
0
    {
333
0
        aosOptions.push_back("-no_location");
334
0
    }
335
0
    else
336
0
    {
337
0
        aosOptions.push_back("-location_field_name");
338
0
        aosOptions.push_back(m_locationField);
339
340
0
        if (m_writeAbsolutePaths)
341
0
            aosOptions.push_back("-write_absolute_path");
342
0
    }
343
344
0
    bool bOK = false;
345
0
    std::unique_ptr<GDALFootprintOptions, decltype(&GDALFootprintOptionsFree)>
346
0
        psOptions{GDALFootprintOptionsNew(aosOptions.List(), nullptr),
347
0
                  GDALFootprintOptionsFree};
348
0
    if (psOptions)
349
0
    {
350
0
        GDALFootprintOptionsSetProgress(psOptions.get(), ctxt.m_pfnProgress,
351
0
                                        ctxt.m_pProgressData);
352
353
0
        GDALDatasetH hSrcDS = GDALDataset::ToHandle(poSrcDS);
354
0
        GDALDatasetH hDstDS =
355
0
            GDALDataset::ToHandle(m_outputDataset.GetDatasetRef());
356
0
        auto poRetDS = GDALDataset::FromHandle(GDALFootprint(
357
0
            outputFilename.c_str(), hDstDS, hSrcDS, psOptions.get(), nullptr));
358
0
        if ((bOK = (poRetDS != nullptr)) && !hDstDS)
359
0
        {
360
0
            if (!m_standaloneStep && !outputFilename.empty())
361
0
            {
362
0
                bOK = poRetDS->FlushCache() == CE_None;
363
0
#if !defined(__APPLE__)
364
                // For some unknown reason, unlinking the file on MacOSX
365
                // leads to later "disk I/O error". See https://github.com/OSGeo/gdal/issues/13794
366
0
                VSIUnlink(outputFilename.c_str());
367
0
#endif
368
0
                poRetDS->MarkSuppressOnClose();
369
0
            }
370
0
            m_outputDataset.Set(std::unique_ptr<GDALDataset>(poRetDS));
371
0
        }
372
0
    }
373
374
0
    return bOK;
375
0
}
376
377
GDALRasterFootprintAlgorithmStandalone::
378
0
    ~GDALRasterFootprintAlgorithmStandalone() = default;
379
380
//! @endcond