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