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_pansharpen.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  "pansharpen" step of "raster pipeline"
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_pansharpen.h"
14
15
#include "gdal_priv.h"
16
#include "cpl_minixml.h"
17
18
//! @cond Doxygen_Suppress
19
20
#ifndef _
21
0
#define _(x) (x)
22
#endif
23
24
/************************************************************************/
25
/*        GDALRasterPansharpenAlgorithm::GetConstructorOptions()        */
26
/************************************************************************/
27
28
/* static */ GDALRasterPansharpenAlgorithm::ConstructorOptions
29
GDALRasterPansharpenAlgorithm::GetConstructorOptions(bool standaloneStep)
30
0
{
31
0
    ConstructorOptions opts;
32
0
    opts.SetStandaloneStep(standaloneStep);
33
0
    opts.SetAddDefaultArguments(false);
34
0
    opts.SetInputDatasetAlias("panchromatic");
35
0
    opts.SetInputDatasetHelpMsg(_("Input panchromatic raster dataset"));
36
0
    return opts;
37
0
}
38
39
/************************************************************************/
40
/*    GDALRasterPansharpenAlgorithm::GDALRasterPansharpenAlgorithm()    */
41
/************************************************************************/
42
43
GDALRasterPansharpenAlgorithm::GDALRasterPansharpenAlgorithm(
44
    bool standaloneStep)
45
0
    : GDALRasterPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
46
0
                                      GetConstructorOptions(standaloneStep))
47
0
{
48
0
    const auto AddSpectralDatasetArg = [this]()
49
0
    {
50
0
        auto &arg = AddArg("spectral", 0, _("Input spectral band dataset"),
51
0
                           &m_spectralDatasets)
52
0
                        .SetPositional()
53
0
                        .SetRequired()
54
0
                        .SetMinCount(1)
55
                        // due to ",band=" comma syntax
56
0
                        .SetAutoOpenDataset(false)
57
                        // due to ",band=" comma syntax
58
0
                        .SetPackedValuesAllowed(false)
59
0
                        .SetMetaVar("SPECTRAL");
60
61
0
        SetAutoCompleteFunctionForFilename(arg, GDAL_OF_RASTER);
62
0
    };
63
64
0
    if (standaloneStep)
65
0
    {
66
0
        AddRasterInputArgs(false, false);
67
0
        AddSpectralDatasetArg();
68
0
        AddProgressArg();
69
0
        AddRasterOutputArgs(false);
70
0
    }
71
0
    else
72
0
    {
73
0
        AddRasterHiddenInputDatasetArg();
74
0
        AddSpectralDatasetArg();
75
0
    }
76
77
0
    AddArg("resampling", 'r', _("Resampling algorithm"), &m_resampling)
78
0
        .SetDefault(m_resampling)
79
0
        .SetChoices("nearest", "bilinear", "cubic", "cubicspline", "lanczos",
80
0
                    "average");
81
0
    AddArg("weights", 0, _("Weight for each input spectral band"), &m_weights);
82
0
    AddArg("nodata", 0, _("Override nodata value of input bands"), &m_nodata);
83
0
    AddArg("bit-depth", 0, _("Override bit depth of input bands"), &m_bitDepth)
84
0
        .SetMinValueIncluded(8);
85
0
    AddArg("spatial-extent-adjustment", 0,
86
0
           _("Select behavior when bands have not the same extent"),
87
0
           &m_spatialExtentAdjustment)
88
0
        .SetDefault(m_spatialExtentAdjustment)
89
0
        .SetChoices("union", "intersection", "none", "none-without-warning");
90
0
    AddNumThreadsArg(&m_numThreads, &m_numThreadsStr);
91
0
}
92
93
/************************************************************************/
94
/*               GDALRasterPansharpenAlgorithm::RunStep()               */
95
/************************************************************************/
96
97
bool GDALRasterPansharpenAlgorithm::RunStep(GDALPipelineStepRunContext &)
98
0
{
99
0
    auto poPanDS = m_inputDataset[0].GetDatasetRef();
100
0
    CPLAssert(poPanDS);
101
0
    CPLAssert(m_outputDataset.GetName().empty());
102
0
    CPLAssert(!m_outputDataset.GetDatasetRef());
103
104
0
    if (poPanDS->GetRasterCount() != 1)
105
0
    {
106
0
        ReportError(CE_Failure, CPLE_AppDefined,
107
0
                    "Input panchromatic dataset must have a single band");
108
0
        return false;
109
0
    }
110
111
    // to keep in this scope to keep datasets of spectral bands open until
112
    // GDALCreatePansharpenedVRT() runs
113
0
    std::vector<std::unique_ptr<GDALDataset, GDALDatasetUniquePtrReleaser>>
114
0
        apoDatasetsToReleaseRef;
115
0
    std::vector<GDALRasterBandH> ahSpectralBands;
116
117
0
    for (auto &spectralDataset : m_spectralDatasets)
118
0
    {
119
0
        if (auto poSpectralDS = spectralDataset.GetDatasetRef())
120
0
        {
121
0
            for (int i = 1; i <= poSpectralDS->GetRasterCount(); ++i)
122
0
            {
123
0
                ahSpectralBands.push_back(
124
0
                    GDALRasterBand::ToHandle(poSpectralDS->GetRasterBand(i)));
125
0
            }
126
0
        }
127
0
        else
128
0
        {
129
0
            const auto &name = spectralDataset.GetName();
130
0
            std::string dsName(name);
131
0
            const auto pos = name.find(",band=");
132
0
            int iBand = 0;
133
0
            if (pos != std::string::npos)
134
0
            {
135
0
                dsName.resize(pos);
136
0
                iBand = atoi(name.c_str() + pos + strlen(",band="));
137
0
            }
138
0
            std::unique_ptr<GDALDataset, GDALDatasetUniquePtrReleaser> poDS(
139
0
                GDALDataset::Open(dsName.c_str(),
140
0
                                  GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
141
0
            if (!poDS)
142
0
                return false;
143
144
0
            if (iBand <= 0)
145
0
            {
146
0
                for (int i = 1; i <= poDS->GetRasterCount(); ++i)
147
0
                {
148
0
                    ahSpectralBands.push_back(
149
0
                        GDALRasterBand::ToHandle(poDS->GetRasterBand(i)));
150
0
                }
151
0
            }
152
0
            else if (iBand > poDS->GetRasterCount())
153
0
            {
154
0
                ReportError(CE_Failure, CPLE_IllegalArg, "Illegal band in '%s'",
155
0
                            name.c_str());
156
0
                return false;
157
0
            }
158
0
            else
159
0
            {
160
0
                ahSpectralBands.push_back(
161
0
                    GDALRasterBand::ToHandle(poDS->GetRasterBand(iBand)));
162
0
            }
163
164
0
            apoDatasetsToReleaseRef.push_back(std::move(poDS));
165
0
        }
166
0
    }
167
168
0
    CPLXMLTreeCloser root(CPLCreateXMLNode(nullptr, CXT_Element, "VRTDataset"));
169
0
    for (int i = 0; i < static_cast<int>(ahSpectralBands.size()); ++i)
170
0
    {
171
0
        auto psBandNode =
172
0
            CPLCreateXMLNode(root.get(), CXT_Element, "VRTRasterBand");
173
0
        CPLAddXMLAttributeAndValue(
174
0
            psBandNode, "dataType",
175
0
            GDALGetDataTypeName(GDALGetRasterDataType(ahSpectralBands[i])));
176
0
        CPLAddXMLAttributeAndValue(psBandNode, "band", CPLSPrintf("%d", i + 1));
177
0
        CPLAddXMLAttributeAndValue(psBandNode, "subClass",
178
0
                                   "VRTPansharpenedRasterBand");
179
0
    }
180
0
    CPLAddXMLAttributeAndValue(root.get(), "subClass",
181
0
                               "VRTPansharpenedDataset");
182
0
    auto psPansharpeningOptionsNode =
183
0
        CPLCreateXMLNode(root.get(), CXT_Element, "PansharpeningOptions");
184
0
    if (!m_weights.empty())
185
0
    {
186
0
        auto psAlgorithmOptionsNode = CPLCreateXMLNode(
187
0
            psPansharpeningOptionsNode, CXT_Element, "AlgorithmOptions");
188
0
        std::string osWeights;
189
0
        for (double w : m_weights)
190
0
        {
191
0
            if (!osWeights.empty())
192
0
                osWeights += ',';
193
0
            osWeights += CPLSPrintf("%.17g", w);
194
0
        }
195
0
        CPLCreateXMLElementAndValue(psAlgorithmOptionsNode, "Weights",
196
0
                                    osWeights.c_str());
197
0
    }
198
0
    CPLCreateXMLElementAndValue(psPansharpeningOptionsNode, "Resampling",
199
0
                                m_resampling.c_str());
200
0
    CPLCreateXMLElementAndValue(psPansharpeningOptionsNode, "NumThreads",
201
0
                                m_numThreadsStr.c_str());
202
0
    if (m_bitDepth > 0)
203
0
    {
204
0
        CPLCreateXMLElementAndValue(psPansharpeningOptionsNode, "BitDepth",
205
0
                                    CPLSPrintf("%d", m_bitDepth));
206
0
    }
207
0
    if (GetArg("nodata")->IsExplicitlySet())
208
0
    {
209
0
        CPLCreateXMLElementAndValue(psPansharpeningOptionsNode, "NoData",
210
0
                                    CPLSPrintf("%.17g", m_nodata));
211
0
    }
212
0
    CPLCreateXMLElementAndValue(
213
0
        psPansharpeningOptionsNode, "SpatialExtentAdjustment",
214
0
        CPLString(m_spatialExtentAdjustment).replaceAll('-', 0).c_str());
215
0
    for (int i = 0; i < static_cast<int>(ahSpectralBands.size()); ++i)
216
0
    {
217
0
        auto psSpectraBandNode = CPLCreateXMLNode(psPansharpeningOptionsNode,
218
0
                                                  CXT_Element, "SpectralBand");
219
0
        CPLAddXMLAttributeAndValue(psSpectraBandNode, "dstBand",
220
0
                                   CPLSPrintf("%d", i + 1));
221
0
    }
222
0
    CPLCharUniquePtr pszXML(CPLSerializeXMLTree(root.get()));
223
0
    auto poVRTDS = std::unique_ptr<GDALDataset>(
224
0
        GDALDataset::FromHandle(GDALCreatePansharpenedVRT(
225
0
            pszXML.get(), GDALRasterBand::ToHandle(poPanDS->GetRasterBand(1)),
226
0
            static_cast<int>(ahSpectralBands.size()), ahSpectralBands.data())));
227
0
    const bool bRet = poVRTDS != nullptr;
228
0
    if (poVRTDS)
229
0
    {
230
0
        m_outputDataset.Set(std::move(poVRTDS));
231
0
    }
232
0
    return bRet;
233
0
}
234
235
GDALRasterPansharpenAlgorithmStandalone::
236
0
    ~GDALRasterPansharpenAlgorithmStandalone() = default;
237
238
//! @endcond