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_edit.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  "edit" step of "raster pipeline"
5
 * Author:   Even Rouault <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "gdalalg_raster_edit.h"
14
15
#include "gdal_priv.h"
16
#include "gdal_utils.h"
17
#include "ogrsf_frmts.h"
18
19
//! @cond Doxygen_Suppress
20
21
#ifndef _
22
0
#define _(x) (x)
23
#endif
24
25
/************************************************************************/
26
/*                           GetGCPFilename()                           */
27
/************************************************************************/
28
29
static std::string GetGCPFilename(const std::vector<std::string> &gcps)
30
0
{
31
0
    if (gcps.size() == 1 && !gcps[0].empty() && gcps[0][0] == '@')
32
0
    {
33
0
        return gcps[0].substr(1);
34
0
    }
35
0
    return std::string();
36
0
}
37
38
/************************************************************************/
39
/*          GDALRasterEditAlgorithm::GDALRasterEditAlgorithm()          */
40
/************************************************************************/
41
42
GDALRasterEditAlgorithm::GDALRasterEditAlgorithm(bool standaloneStep)
43
0
    : GDALRasterPipelineStepAlgorithm(
44
0
          NAME, DESCRIPTION, HELP_URL,
45
0
          ConstructorOptions().SetAddDefaultArguments(false))
46
0
{
47
0
    if (standaloneStep)
48
0
    {
49
0
        AddProgressArg();
50
51
0
        AddArg("dataset", 0,
52
0
               _("Dataset (to be updated in-place, unless --auxiliary)"),
53
0
               &m_dataset, GDAL_OF_RASTER | GDAL_OF_UPDATE)
54
0
            .SetPositional()
55
0
            .SetRequired();
56
0
        AddArg("auxiliary", 0,
57
0
               _("Ask for an auxiliary .aux.xml file to be edited"),
58
0
               &m_readOnly)
59
0
            .AddHiddenAlias("ro")
60
0
            .AddHiddenAlias(GDAL_ARG_NAME_READ_ONLY);
61
0
    }
62
0
    else
63
0
    {
64
0
        AddRasterHiddenInputDatasetArg();
65
0
    }
66
67
0
    AddArg("crs", 0, _("Override CRS (without reprojection)"), &m_overrideCrs)
68
0
        .AddHiddenAlias("a_srs")
69
0
        .AddHiddenAlias("srs")
70
0
        .SetIsCRSArg(/*noneAllowed=*/true);
71
72
0
    AddBBOXArg(&m_bbox);
73
74
0
    AddNodataArg(&m_nodata, /* noneAllowed = */ true);
75
76
0
    {
77
0
        auto &arg = AddArg("metadata", 0, _("Add/update dataset metadata item"),
78
0
                           &m_metadata)
79
0
                        .SetMetaVar("<KEY>=<VALUE>")
80
0
                        .SetPackedValuesAllowed(false);
81
0
        arg.AddValidationAction([this, &arg]()
82
0
                                { return ParseAndValidateKeyValue(arg); });
83
0
        arg.AddHiddenAlias("mo");
84
0
    }
85
86
0
    AddArg("unset-metadata", 0, _("Remove dataset metadata item(s)"),
87
0
           &m_unsetMetadata)
88
0
        .SetMetaVar("<KEY>");
89
90
0
    AddArg("unset-metadata-domain", 0, _("Remove dataset metadata domain(s)"),
91
0
           &m_unsetMetadataDomain)
92
0
        .SetMetaVar("<DOMAIN>");
93
94
0
    AddArg("gcp", 0,
95
0
           _("Add ground control point, formatted as "
96
0
             "pixel,line,easting,northing[,elevation], or @filename"),
97
0
           &m_gcps)
98
0
        .SetPackedValuesAllowed(false)
99
0
        .AddValidationAction(
100
0
            [this]()
101
0
            {
102
0
                if (GetGCPFilename(m_gcps).empty())
103
0
                {
104
0
                    for (const std::string &gcp : m_gcps)
105
0
                    {
106
0
                        const CPLStringList aosTokens(
107
0
                            CSLTokenizeString2(gcp.c_str(), ",", 0));
108
0
                        if (aosTokens.size() != 4 && aosTokens.size() != 5)
109
0
                        {
110
0
                            ReportError(CE_Failure, CPLE_IllegalArg,
111
0
                                        "Bad format for %s", gcp.c_str());
112
0
                            return false;
113
0
                        }
114
0
                        for (int i = 0; i < aosTokens.size(); ++i)
115
0
                        {
116
0
                            if (CPLGetValueType(aosTokens[i]) ==
117
0
                                CPL_VALUE_STRING)
118
0
                            {
119
0
                                ReportError(CE_Failure, CPLE_IllegalArg,
120
0
                                            "Bad format for %s", gcp.c_str());
121
0
                                return false;
122
0
                            }
123
0
                        }
124
0
                    }
125
0
                }
126
0
                return true;
127
0
            });
128
129
0
    if (standaloneStep)
130
0
    {
131
0
        AddArg("stats", 0, _("Compute statistics, using all pixels"), &m_stats)
132
0
            .SetMutualExclusionGroup("stats");
133
0
        AddArg("approx-stats", 0,
134
0
               _("Compute statistics, using a subset of pixels"),
135
0
               &m_approxStats)
136
0
            .SetMutualExclusionGroup("stats");
137
0
        AddArg("hist", 0, _("Compute histogram"), &m_hist);
138
0
    }
139
0
}
140
141
/************************************************************************/
142
/*         GDALRasterEditAlgorithm::~GDALRasterEditAlgorithm()          */
143
/************************************************************************/
144
145
0
GDALRasterEditAlgorithm::~GDALRasterEditAlgorithm() = default;
146
147
/************************************************************************/
148
/*                             ParseGCPs()                              */
149
/************************************************************************/
150
151
std::vector<gdal::GCP> GDALRasterEditAlgorithm::ParseGCPs() const
152
0
{
153
0
    std::vector<gdal::GCP> ret;
154
0
    const std::string osGCPFilename = GetGCPFilename(m_gcps);
155
0
    if (!osGCPFilename.empty())
156
0
    {
157
0
        auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
158
0
            osGCPFilename.c_str(), GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR));
159
0
        if (!poDS)
160
0
            return ret;
161
0
        if (poDS->GetLayerCount() != 1)
162
0
        {
163
0
            ReportError(CE_Failure, CPLE_AppDefined,
164
0
                        "GCPs can only be specified for single-layer datasets");
165
0
            return ret;
166
0
        }
167
0
        auto poLayer = poDS->GetLayer(0);
168
0
        const auto poLayerDefn = poLayer->GetLayerDefn();
169
0
        int nIdIdx = -1, nInfoIdx = -1, nColIdx = -1, nLineIdx = -1, nXIdx = -1,
170
0
            nYIdx = -1, nZIdx = -1;
171
172
0
        const struct
173
0
        {
174
0
            int &idx;
175
0
            const char *name;
176
0
            bool required;
177
0
        } aFields[] = {
178
0
            {nIdIdx, "id", false},     {nInfoIdx, "info", false},
179
0
            {nColIdx, "column", true}, {nLineIdx, "line", true},
180
0
            {nXIdx, "x", true},        {nYIdx, "y", true},
181
0
            {nZIdx, "z", false},
182
0
        };
183
184
0
        for (auto &field : aFields)
185
0
        {
186
0
            field.idx = poLayerDefn->GetFieldIndex(field.name);
187
0
            if (field.idx < 0 && field.required)
188
0
            {
189
0
                ReportError(CE_Failure, CPLE_AppDefined,
190
0
                            "Field '%s' cannot be found in '%s'", field.name,
191
0
                            poDS->GetDescription());
192
0
                return ret;
193
0
            }
194
0
        }
195
0
        for (auto &&poFeature : poLayer)
196
0
        {
197
0
            gdal::GCP gcp;
198
0
            if (nIdIdx >= 0)
199
0
                gcp.SetId(poFeature->GetFieldAsString(nIdIdx));
200
0
            if (nInfoIdx >= 0)
201
0
                gcp.SetInfo(poFeature->GetFieldAsString(nInfoIdx));
202
0
            gcp.Pixel() = poFeature->GetFieldAsDouble(nColIdx);
203
0
            gcp.Line() = poFeature->GetFieldAsDouble(nLineIdx);
204
0
            gcp.X() = poFeature->GetFieldAsDouble(nXIdx);
205
0
            gcp.Y() = poFeature->GetFieldAsDouble(nYIdx);
206
0
            if (nZIdx >= 0 && poFeature->IsFieldSetAndNotNull(nZIdx))
207
0
                gcp.Z() = poFeature->GetFieldAsDouble(nZIdx);
208
0
            ret.push_back(std::move(gcp));
209
0
        }
210
0
    }
211
0
    else
212
0
    {
213
0
        for (const std::string &gcpStr : m_gcps)
214
0
        {
215
0
            const CPLStringList aosTokens(
216
0
                CSLTokenizeString2(gcpStr.c_str(), ",", 0));
217
            // Verified by validation action
218
0
            CPLAssert(aosTokens.size() == 4 || aosTokens.size() == 5);
219
0
            gdal::GCP gcp;
220
0
            gcp.Pixel() = CPLAtof(aosTokens[0]);
221
0
            gcp.Line() = CPLAtof(aosTokens[1]);
222
0
            gcp.X() = CPLAtof(aosTokens[2]);
223
0
            gcp.Y() = CPLAtof(aosTokens[3]);
224
0
            if (aosTokens.size() == 5)
225
0
                gcp.Z() = CPLAtof(aosTokens[4]);
226
0
            ret.push_back(std::move(gcp));
227
0
        }
228
0
    }
229
0
    return ret;
230
0
}
231
232
/************************************************************************/
233
/*                  GDALRasterEditAlgorithm::RunStep()                  */
234
/************************************************************************/
235
236
bool GDALRasterEditAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
237
0
{
238
0
    GDALDataset *poDS = m_dataset.GetDatasetRef();
239
0
    if (poDS)
240
0
    {
241
0
        if (poDS->GetAccess() != GA_Update && !m_readOnly)
242
0
        {
243
0
            ReportError(CE_Failure, CPLE_AppDefined,
244
0
                        "Dataset should be opened in update mode unless "
245
0
                        "--auxiliary is set");
246
0
            return false;
247
0
        }
248
0
    }
249
0
    else
250
0
    {
251
0
        const auto poSrcDS = m_inputDataset[0].GetDatasetRef();
252
0
        CPLAssert(poSrcDS);
253
0
        CPLAssert(m_outputDataset.GetName().empty());
254
0
        CPLAssert(!m_outputDataset.GetDatasetRef());
255
256
0
        CPLStringList aosOptions;
257
0
        aosOptions.push_back("-of");
258
0
        aosOptions.push_back("VRT");
259
0
        GDALTranslateOptions *psOptions =
260
0
            GDALTranslateOptionsNew(aosOptions.List(), nullptr);
261
0
        GDALDatasetH hSrcDS = GDALDataset::ToHandle(poSrcDS);
262
0
        auto poRetDS = GDALDataset::FromHandle(
263
0
            GDALTranslate("", hSrcDS, psOptions, nullptr));
264
0
        GDALTranslateOptionsFree(psOptions);
265
0
        m_outputDataset.Set(std::unique_ptr<GDALDataset>(poRetDS));
266
0
        poDS = m_outputDataset.GetDatasetRef();
267
0
    }
268
269
0
    bool ret = poDS != nullptr;
270
271
0
    if (poDS)
272
0
    {
273
0
        if (m_overrideCrs == "null" || m_overrideCrs == "none")
274
0
        {
275
0
            if (poDS->SetSpatialRef(nullptr) != CE_None)
276
0
            {
277
0
                ReportError(CE_Failure, CPLE_AppDefined,
278
0
                            "SetSpatialRef(%s) failed", m_overrideCrs.c_str());
279
0
                return false;
280
0
            }
281
0
        }
282
0
        else if (!m_overrideCrs.empty() && m_gcps.empty())
283
0
        {
284
0
            OGRSpatialReference oSRS;
285
0
            oSRS.SetFromUserInput(m_overrideCrs.c_str());
286
0
            oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
287
0
            if (poDS->SetSpatialRef(&oSRS) != CE_None)
288
0
            {
289
0
                ReportError(CE_Failure, CPLE_AppDefined,
290
0
                            "SetSpatialRef(%s) failed", m_overrideCrs.c_str());
291
0
                return false;
292
0
            }
293
0
        }
294
295
0
        if (!m_bbox.empty())
296
0
        {
297
0
            if (poDS->GetRasterXSize() == 0 || poDS->GetRasterYSize() == 0)
298
0
            {
299
0
                ReportError(CE_Failure, CPLE_AppDefined,
300
0
                            "Cannot set extent because one of dataset height "
301
0
                            "or width is null");
302
0
                return false;
303
0
            }
304
0
            GDALGeoTransform gt;
305
0
            gt.xorig = m_bbox[0];
306
0
            gt.xscale = (m_bbox[2] - m_bbox[0]) / poDS->GetRasterXSize();
307
0
            gt.xrot = 0;
308
0
            gt.yorig = m_bbox[3];
309
0
            gt.yrot = 0;
310
0
            gt.yscale = -(m_bbox[3] - m_bbox[1]) / poDS->GetRasterYSize();
311
0
            if (poDS->SetGeoTransform(gt) != CE_None)
312
0
            {
313
0
                ReportError(CE_Failure, CPLE_AppDefined,
314
0
                            "Setting extent failed");
315
0
                return false;
316
0
            }
317
0
        }
318
319
0
        if (!m_nodata.empty())
320
0
        {
321
0
            for (int i = 0; i < poDS->GetRasterCount(); ++i)
322
0
            {
323
0
                if (EQUAL(m_nodata.c_str(), "none"))
324
0
                    poDS->GetRasterBand(i + 1)->DeleteNoDataValue();
325
0
                else
326
0
                    poDS->GetRasterBand(i + 1)->SetNoDataValue(
327
0
                        CPLAtof(m_nodata.c_str()));
328
0
            }
329
0
        }
330
331
0
        const CPLStringList aosMD(m_metadata);
332
0
        for (const auto &[key, value] : cpl::IterateNameValue(aosMD))
333
0
        {
334
0
            if (poDS->SetMetadataItem(key, value) != CE_None)
335
0
            {
336
0
                ReportError(CE_Failure, CPLE_AppDefined,
337
0
                            "SetMetadataItem('%s', '%s') failed", key, value);
338
0
                return false;
339
0
            }
340
0
        }
341
342
0
        for (const std::string &key : m_unsetMetadata)
343
0
        {
344
0
            if (poDS->SetMetadataItem(key.c_str(), nullptr) != CE_None)
345
0
            {
346
0
                ReportError(CE_Failure, CPLE_AppDefined,
347
0
                            "SetMetadataItem('%s', NULL) failed", key.c_str());
348
0
                return false;
349
0
            }
350
0
        }
351
352
0
        for (const std::string &domain : m_unsetMetadataDomain)
353
0
        {
354
0
            if (poDS->SetMetadata(nullptr, domain.c_str()) != CE_None)
355
0
            {
356
0
                ReportError(CE_Failure, CPLE_AppDefined,
357
0
                            "SetMetadata(NULL, '%s') failed", domain.c_str());
358
0
                return false;
359
0
            }
360
0
        }
361
362
0
        if (!m_gcps.empty())
363
0
        {
364
0
            const auto gcps = ParseGCPs();
365
0
            if (gcps.empty())
366
0
                return false;  // error already emitted by ParseGCPs()
367
368
0
            OGRSpatialReference oSRS;
369
0
            if (!m_overrideCrs.empty())
370
0
            {
371
0
                oSRS.SetFromUserInput(m_overrideCrs.c_str());
372
0
                oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
373
0
            }
374
375
0
            if (poDS->SetGCPs(static_cast<int>(gcps.size()), gcps[0].c_ptr(),
376
0
                              oSRS.IsEmpty() ? nullptr : &oSRS) != CE_None)
377
0
            {
378
0
                ReportError(CE_Failure, CPLE_AppDefined, "Setting GCPs failed");
379
0
                return false;
380
0
            }
381
0
        }
382
383
0
        const int nBands = poDS->GetRasterCount();
384
0
        int nCurProgress = 0;
385
0
        const double dfTotalProgress =
386
0
            ((m_stats || m_approxStats) ? nBands : 0) + (m_hist ? nBands : 0);
387
0
        if (m_stats || m_approxStats)
388
0
        {
389
0
            for (int i = 0; (i < nBands) && ret; ++i)
390
0
            {
391
0
                void *pScaledProgress = GDALCreateScaledProgress(
392
0
                    nCurProgress / dfTotalProgress,
393
0
                    (nCurProgress + 1) / dfTotalProgress, ctxt.m_pfnProgress,
394
0
                    ctxt.m_pProgressData);
395
0
                ++nCurProgress;
396
0
                double dfMin = 0.0;
397
0
                double dfMax = 0.0;
398
0
                double dfMean = 0.0;
399
0
                double dfStdDev = 0.0;
400
0
                ret = poDS->GetRasterBand(i + 1)->ComputeStatistics(
401
0
                          m_approxStats, &dfMin, &dfMax, &dfMean, &dfStdDev,
402
0
                          pScaledProgress ? GDALScaledProgress : nullptr,
403
0
                          pScaledProgress) == CE_None;
404
0
                GDALDestroyScaledProgress(pScaledProgress);
405
0
            }
406
0
        }
407
408
0
        if (m_hist)
409
0
        {
410
0
            for (int i = 0; (i < nBands) && ret; ++i)
411
0
            {
412
0
                void *pScaledProgress = GDALCreateScaledProgress(
413
0
                    nCurProgress / dfTotalProgress,
414
0
                    (nCurProgress + 1) / dfTotalProgress, ctxt.m_pfnProgress,
415
0
                    ctxt.m_pProgressData);
416
0
                ++nCurProgress;
417
0
                double dfMin = 0.0;
418
0
                double dfMax = 0.0;
419
0
                int nBucketCount = 0;
420
0
                GUIntBig *panHistogram = nullptr;
421
0
                ret = poDS->GetRasterBand(i + 1)->GetDefaultHistogram(
422
0
                          &dfMin, &dfMax, &nBucketCount, &panHistogram, TRUE,
423
0
                          pScaledProgress ? GDALScaledProgress : nullptr,
424
0
                          pScaledProgress) == CE_None;
425
0
                if (ret)
426
0
                {
427
0
                    ret = poDS->GetRasterBand(i + 1)->SetDefaultHistogram(
428
0
                              dfMin, dfMax, nBucketCount, panHistogram) ==
429
0
                          CE_None;
430
0
                }
431
0
                CPLFree(panHistogram);
432
0
                GDALDestroyScaledProgress(pScaledProgress);
433
0
            }
434
0
        }
435
0
    }
436
437
0
    return poDS != nullptr;
438
0
}
439
440
GDALRasterEditAlgorithmStandalone::~GDALRasterEditAlgorithmStandalone() =
441
    default;
442
443
//! @endcond