/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 |