Coverage Report

Created: 2026-02-14 09:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/gdalalg_raster_create.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  gdal "raster create" 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_create.h"
14
15
#include "cpl_conv.h"
16
#include "gdal_priv.h"
17
#include "gdal_utils.h"
18
#include "ogr_spatialref.h"
19
20
//! @cond Doxygen_Suppress
21
22
#ifndef _
23
0
#define _(x) (x)
24
#endif
25
26
/************************************************************************/
27
/*        GDALRasterCreateAlgorithm::GDALRasterCreateAlgorithm()        */
28
/************************************************************************/
29
30
GDALRasterCreateAlgorithm::GDALRasterCreateAlgorithm(
31
    bool standaloneStep) noexcept
32
0
    : GDALRasterPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
33
0
                                      ConstructorOptions()
34
0
                                          .SetStandaloneStep(standaloneStep)
35
0
                                          .SetAddDefaultArguments(false)
36
0
                                          .SetAutoOpenInputDatasets(true)
37
0
                                          .SetInputDatasetAlias("like")
38
0
                                          .SetInputDatasetRequired(false)
39
0
                                          .SetInputDatasetMaxCount(1))
40
0
{
41
0
    AddRasterInputArgs(false, false);
42
0
    if (standaloneStep)
43
0
    {
44
0
        AddProgressArg();
45
0
        AddRasterOutputArgs(false);
46
0
    }
47
48
0
    AddArg("size", 0, _("Output size in pixels"), &m_size)
49
0
        .SetMinCount(2)
50
0
        .SetMaxCount(2)
51
0
        .SetMinValueIncluded(0)
52
0
        .SetRepeatedArgAllowed(false)
53
0
        .SetDisplayHintAboutRepetition(false)
54
0
        .SetMetaVar("<width>,<height>");
55
0
    AddArg("band-count", 0, _("Number of bands"), &m_bandCount)
56
0
        .SetDefault(m_bandCount)
57
0
        .SetMinValueIncluded(0);
58
0
    AddOutputDataTypeArg(&m_type).SetDefault(m_type);
59
60
0
    AddNodataArg(&m_nodata, /* noneAllowed = */ true);
61
62
0
    AddArg("burn", 0, _("Burn value"), &m_burnValues);
63
0
    AddArg("crs", 0, _("Set CRS"), &m_crs)
64
0
        .AddHiddenAlias("a_srs")
65
0
        .SetIsCRSArg(/*noneAllowed=*/true);
66
0
    AddBBOXArg(&m_bbox);
67
68
0
    {
69
0
        auto &arg = AddArg("metadata", 0, _("Add metadata item"), &m_metadata)
70
0
                        .SetMetaVar("<KEY>=<VALUE>")
71
0
                        .SetPackedValuesAllowed(false);
72
0
        arg.AddValidationAction([this, &arg]()
73
0
                                { return ParseAndValidateKeyValue(arg); });
74
0
        arg.AddHiddenAlias("mo");
75
0
    }
76
0
    AddArg("copy-metadata", 0, _("Copy metadata from input dataset"),
77
0
           &m_copyMetadata);
78
0
    AddArg("copy-overviews", 0,
79
0
           _("Create same overview levels as input dataset"), &m_copyOverviews);
80
0
}
81
82
/************************************************************************/
83
/*                 GDALRasterCreateAlgorithm::RunImpl()                 */
84
/************************************************************************/
85
86
bool GDALRasterCreateAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
87
                                        void *pProgressData)
88
0
{
89
0
    GDALPipelineStepRunContext stepCtxt;
90
0
    stepCtxt.m_pfnProgress = pfnProgress;
91
0
    stepCtxt.m_pProgressData = pProgressData;
92
0
    return RunPreStepPipelineValidations() && RunStep(stepCtxt);
93
0
}
94
95
/************************************************************************/
96
/*                 GDALRasterCreateAlgorithm::RunStep()                 */
97
/************************************************************************/
98
99
bool GDALRasterCreateAlgorithm::RunStep(GDALPipelineStepRunContext &)
100
0
{
101
0
    CPLAssert(!m_outputDataset.GetDatasetRef());
102
103
0
    if (m_standaloneStep)
104
0
    {
105
0
        if (m_format.empty())
106
0
        {
107
0
            const auto aosFormats =
108
0
                CPLStringList(GDALGetOutputDriversForDatasetName(
109
0
                    m_outputDataset.GetName().c_str(), GDAL_OF_RASTER,
110
0
                    /* bSingleMatch = */ true,
111
0
                    /* bWarn = */ true));
112
0
            if (aosFormats.size() != 1)
113
0
            {
114
0
                ReportError(CE_Failure, CPLE_AppDefined,
115
0
                            "Cannot guess driver for %s",
116
0
                            m_outputDataset.GetName().c_str());
117
0
                return false;
118
0
            }
119
0
            m_format = aosFormats[0];
120
0
        }
121
0
    }
122
0
    else
123
0
    {
124
0
        m_format = "MEM";
125
0
    }
126
127
0
    OGRSpatialReference oSRS;
128
129
0
    GDALGeoTransform gt;
130
0
    bool bGTValid = false;
131
132
0
    GDALDataset *poSrcDS = m_inputDataset.empty()
133
0
                               ? nullptr
134
0
                               : m_inputDataset.front().GetDatasetRef();
135
0
    if (poSrcDS)
136
0
    {
137
0
        if (m_size.empty())
138
0
        {
139
0
            m_size = std::vector<int>{poSrcDS->GetRasterXSize(),
140
0
                                      poSrcDS->GetRasterYSize()};
141
0
        }
142
143
0
        if (!GetArg("band-count")->IsExplicitlySet())
144
0
        {
145
0
            m_bandCount = poSrcDS->GetRasterCount();
146
0
        }
147
148
0
        if (!GetArg("datatype")->IsExplicitlySet())
149
0
        {
150
0
            if (m_bandCount > 0)
151
0
            {
152
0
                m_type = GDALGetDataTypeName(
153
0
                    poSrcDS->GetRasterBand(1)->GetRasterDataType());
154
0
            }
155
0
        }
156
157
0
        if (m_crs.empty())
158
0
        {
159
0
            if (const auto poSRS = poSrcDS->GetSpatialRef())
160
0
                oSRS = *poSRS;
161
0
        }
162
163
0
        if (m_bbox.empty())
164
0
        {
165
0
            bGTValid = poSrcDS->GetGeoTransform(gt) == CE_None;
166
0
        }
167
168
0
        if (m_nodata.empty() && m_bandCount > 0)
169
0
        {
170
0
            int bNoData = false;
171
0
            const double dfNoData =
172
0
                poSrcDS->GetRasterBand(1)->GetNoDataValue(&bNoData);
173
0
            if (bNoData)
174
0
                m_nodata = CPLSPrintf("%.17g", dfNoData);
175
0
        }
176
0
    }
177
178
0
    if (m_size.empty())
179
0
    {
180
0
        ReportError(CE_Failure, CPLE_IllegalArg,
181
0
                    "Argument 'size' should be specified, or 'like' dataset "
182
0
                    "should be specified");
183
0
        return false;
184
0
    }
185
186
0
    if (!m_burnValues.empty() && m_burnValues.size() != 1 &&
187
0
        static_cast<int>(m_burnValues.size()) != m_bandCount)
188
0
    {
189
0
        if (m_bandCount == 1)
190
0
        {
191
0
            ReportError(CE_Failure, CPLE_IllegalArg,
192
0
                        "One value should be provided for argument "
193
0
                        "'burn', given there is one band");
194
0
        }
195
0
        else
196
0
        {
197
0
            ReportError(CE_Failure, CPLE_IllegalArg,
198
0
                        "One or %d values should be provided for argument "
199
0
                        "'burn', given there are %d bands",
200
0
                        m_bandCount, m_bandCount);
201
0
        }
202
0
        return false;
203
0
    }
204
205
0
    auto poDriver = GetGDALDriverManager()->GetDriverByName(m_format.c_str());
206
0
    if (!poDriver)
207
0
    {
208
        // shouldn't happen given checks done in GDALAlgorithm
209
0
        ReportError(CE_Failure, CPLE_AppDefined, "Cannot find driver %s",
210
0
                    m_format.c_str());
211
0
        return false;
212
0
    }
213
214
0
    if (m_appendRaster)
215
0
    {
216
0
        if (poDriver->GetMetadataItem(GDAL_DCAP_CREATE_SUBDATASETS) == nullptr)
217
0
        {
218
0
            ReportError(CE_Failure, CPLE_NotSupported,
219
0
                        "-append option not supported for driver %s",
220
0
                        poDriver->GetDescription());
221
0
            return false;
222
0
        }
223
0
        m_creationOptions.push_back("APPEND_SUBDATASET=YES");
224
0
    }
225
226
0
    auto poRetDS = std::unique_ptr<GDALDataset>(poDriver->Create(
227
0
        m_outputDataset.GetName().c_str(), m_size[0], m_size[1], m_bandCount,
228
0
        GDALGetDataTypeByName(m_type.c_str()),
229
0
        CPLStringList(m_creationOptions).List()));
230
0
    if (!poRetDS)
231
0
    {
232
0
        return false;
233
0
    }
234
235
0
    if (!m_crs.empty() && m_crs != "none" && m_crs != "null")
236
0
    {
237
0
        oSRS.SetFromUserInput(m_crs.c_str());
238
0
        oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
239
0
    }
240
241
0
    if (!oSRS.IsEmpty())
242
0
    {
243
0
        if (poRetDS->SetSpatialRef(&oSRS) != CE_None)
244
0
        {
245
0
            ReportError(CE_Failure, CPLE_AppDefined, "Setting CRS failed");
246
0
            return false;
247
0
        }
248
0
    }
249
250
0
    if (!m_bbox.empty())
251
0
    {
252
0
        if (poRetDS->GetRasterXSize() == 0 || poRetDS->GetRasterYSize() == 0)
253
0
        {
254
0
            ReportError(CE_Failure, CPLE_AppDefined,
255
0
                        "Cannot set extent because one of dataset height or "
256
0
                        "width is null");
257
0
            return false;
258
0
        }
259
0
        bGTValid = true;
260
0
        gt.xorig = m_bbox[0];
261
0
        gt.xscale = (m_bbox[2] - m_bbox[0]) / poRetDS->GetRasterXSize();
262
0
        gt.xrot = 0;
263
0
        gt.yorig = m_bbox[3];
264
0
        gt.yrot = 0;
265
0
        gt.yscale = -(m_bbox[3] - m_bbox[1]) / poRetDS->GetRasterYSize();
266
0
    }
267
0
    if (bGTValid)
268
0
    {
269
0
        if (poRetDS->SetGeoTransform(gt) != CE_None)
270
0
        {
271
0
            ReportError(CE_Failure, CPLE_AppDefined, "Setting extent failed");
272
0
            return false;
273
0
        }
274
0
    }
275
276
0
    if (!m_nodata.empty() && !EQUAL(m_nodata.c_str(), "none"))
277
0
    {
278
0
        for (int i = 0; i < poRetDS->GetRasterCount(); ++i)
279
0
        {
280
0
            bool bCannotBeExactlyRepresented = false;
281
0
            if (poRetDS->GetRasterBand(i + 1)->SetNoDataValueAsString(
282
0
                    m_nodata.c_str(), &bCannotBeExactlyRepresented) != CE_None)
283
0
            {
284
0
                if (bCannotBeExactlyRepresented)
285
0
                {
286
0
                    ReportError(CE_Failure, CPLE_AppDefined,
287
0
                                "Setting nodata value failed as it cannot be "
288
0
                                "represented on its data type");
289
0
                }
290
0
                else
291
0
                {
292
0
                    ReportError(CE_Failure, CPLE_AppDefined,
293
0
                                "Setting nodata value failed");
294
0
                }
295
0
                return false;
296
0
            }
297
0
        }
298
0
    }
299
300
0
    if (m_copyMetadata)
301
0
    {
302
0
        if (!poSrcDS)
303
0
        {
304
0
            ReportError(CE_Failure, CPLE_AppDefined,
305
0
                        "Argument 'copy-metadata' can only be set when an "
306
0
                        "input dataset is set");
307
0
            return false;
308
0
        }
309
0
        {
310
0
            const CPLStringList aosDomains(poSrcDS->GetMetadataDomainList());
311
0
            for (const char *domain : aosDomains)
312
0
            {
313
0
                if (!EQUAL(domain, "IMAGE_STRUCTURE"))
314
0
                {
315
0
                    if (poRetDS->SetMetadata(poSrcDS->GetMetadata(domain),
316
0
                                             domain) != CE_None)
317
0
                    {
318
0
                        ReportError(CE_Failure, CPLE_AppDefined,
319
0
                                    "Cannot copy '%s' metadata domain", domain);
320
0
                        return false;
321
0
                    }
322
0
                }
323
0
            }
324
0
        }
325
0
        for (int i = 0; i < m_bandCount; ++i)
326
0
        {
327
0
            const CPLStringList aosDomains(
328
0
                poSrcDS->GetRasterBand(i + 1)->GetMetadataDomainList());
329
0
            for (const char *domain : aosDomains)
330
0
            {
331
0
                if (!EQUAL(domain, "IMAGE_STRUCTURE"))
332
0
                {
333
0
                    if (poRetDS->GetRasterBand(i + 1)->SetMetadata(
334
0
                            poSrcDS->GetRasterBand(i + 1)->GetMetadata(domain),
335
0
                            domain) != CE_None)
336
0
                    {
337
0
                        ReportError(
338
0
                            CE_Failure, CPLE_AppDefined,
339
0
                            "Cannot copy '%s' metadata domain for band %d",
340
0
                            domain, i + 1);
341
0
                        return false;
342
0
                    }
343
0
                }
344
0
            }
345
0
        }
346
0
    }
347
348
0
    const CPLStringList aosMD(m_metadata);
349
0
    for (const auto &[key, value] : cpl::IterateNameValue(aosMD))
350
0
    {
351
0
        if (poRetDS->SetMetadataItem(key, value) != CE_None)
352
0
        {
353
0
            ReportError(CE_Failure, CPLE_AppDefined,
354
0
                        "SetMetadataItem('%s', '%s') failed", key, value);
355
0
            return false;
356
0
        }
357
0
    }
358
359
0
    if (m_copyOverviews && m_bandCount > 0)
360
0
    {
361
0
        if (!poSrcDS)
362
0
        {
363
0
            ReportError(CE_Failure, CPLE_AppDefined,
364
0
                        "Argument 'copy-overviews' can only be set when an "
365
0
                        "input dataset is set");
366
0
            return false;
367
0
        }
368
0
        if (poSrcDS->GetRasterXSize() != poRetDS->GetRasterXSize() ||
369
0
            poSrcDS->GetRasterYSize() != poRetDS->GetRasterYSize())
370
0
        {
371
0
            ReportError(CE_Failure, CPLE_AppDefined,
372
0
                        "Argument 'copy-overviews' can only be set when the "
373
0
                        "input and output datasets have the same dimension");
374
0
            return false;
375
0
        }
376
0
        const int nOverviewCount =
377
0
            poSrcDS->GetRasterBand(1)->GetOverviewCount();
378
0
        std::vector<int> anLevels;
379
0
        for (int i = 0; i < nOverviewCount; ++i)
380
0
        {
381
0
            const auto poOvrBand = poSrcDS->GetRasterBand(1)->GetOverview(i);
382
0
            const int nOvrFactor = GDALComputeOvFactor(
383
0
                poOvrBand->GetXSize(), poSrcDS->GetRasterXSize(),
384
0
                poOvrBand->GetYSize(), poSrcDS->GetRasterYSize());
385
0
            anLevels.push_back(nOvrFactor);
386
0
        }
387
0
        if (poRetDS->BuildOverviews(
388
0
                "NONE", nOverviewCount, anLevels.data(),
389
0
                /* nListBands = */ 0, /* panBandList = */ nullptr,
390
0
                /* pfnProgress = */ nullptr, /* pProgressData = */ nullptr,
391
0
                /* options = */ nullptr) != CE_None)
392
0
        {
393
0
            ReportError(CE_Failure, CPLE_AppDefined,
394
0
                        "Creating overview(s) failed");
395
0
            return false;
396
0
        }
397
0
    }
398
399
0
    if (!m_burnValues.empty())
400
0
    {
401
0
        for (int i = 0; i < m_bandCount; ++i)
402
0
        {
403
0
            const int burnValueIdx = m_burnValues.size() == 1 ? 0 : i;
404
0
            const auto poDstBand = poRetDS->GetRasterBand(i + 1);
405
            // cppcheck-suppress negativeContainerIndex
406
0
            if (poDstBand->Fill(m_burnValues[burnValueIdx]) != CE_None)
407
0
            {
408
0
                ReportError(CE_Failure, CPLE_AppDefined,
409
0
                            "Setting burn value failed");
410
0
                return false;
411
0
            }
412
0
        }
413
0
        if (poRetDS->FlushCache(false) != CE_None)
414
0
        {
415
0
            ReportError(CE_Failure, CPLE_AppDefined,
416
0
                        "Setting burn value failed");
417
0
            return false;
418
0
        }
419
0
    }
420
421
0
    m_outputDataset.Set(std::move(poRetDS));
422
423
0
    return true;
424
0
}
425
426
0
GDALRasterCreateAlgorithmStandalone::~GDALRasterCreateAlgorithmStandalone() =
427
    default;
428
429
//! @endcond