Coverage Report

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