Coverage Report

Created: 2026-04-01 06:20

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