Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: "reproject" 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_reproject.h"
14 : #include "gdalalg_raster_write.h"
15 :
16 : #include "gdal_alg.h"
17 : #include "gdal_priv.h"
18 : #include "gdal_utils.h"
19 : #include "gdalwarper.h"
20 :
21 : #include <cmath>
22 :
23 : //! @cond Doxygen_Suppress
24 :
25 : #ifndef _
26 : #define _(x) (x)
27 : #endif
28 :
29 : /************************************************************************/
30 : /* GDALRasterReprojectAlgorithm::GDALRasterReprojectAlgorithm() */
31 : /************************************************************************/
32 :
33 99 : GDALRasterReprojectAlgorithm::GDALRasterReprojectAlgorithm(bool standaloneStep)
34 : : GDALRasterPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
35 99 : standaloneStep)
36 : {
37 198 : AddArg("src-crs", 's', _("Source CRS"), &m_srsCrs)
38 198 : .SetIsCRSArg()
39 99 : .AddHiddenAlias("s_srs");
40 198 : AddArg("dst-crs", 'd', _("Destination CRS"), &m_dstCrs)
41 198 : .SetIsCRSArg()
42 99 : .AddHiddenAlias("t_srs");
43 :
44 99 : GDALRasterReprojectUtils::AddResamplingArg(this, m_resampling);
45 :
46 : AddArg("resolution", 0, _("Target resolution (in destination CRS units)"),
47 198 : &m_resolution)
48 99 : .SetMinCount(2)
49 99 : .SetMaxCount(2)
50 99 : .SetMinValueExcluded(0)
51 99 : .SetRepeatedArgAllowed(false)
52 99 : .SetDisplayHintAboutRepetition(false)
53 198 : .SetMetaVar("<xres>,<yres>")
54 99 : .SetMutualExclusionGroup("resolution-size");
55 :
56 198 : AddArg("size", 0, _("Target size in pixels"), &m_size)
57 99 : .SetMinCount(2)
58 99 : .SetMaxCount(2)
59 99 : .SetMinValueIncluded(0)
60 99 : .SetRepeatedArgAllowed(false)
61 99 : .SetDisplayHintAboutRepetition(false)
62 198 : .SetMetaVar("<width>,<height>")
63 99 : .SetMutualExclusionGroup("resolution-size");
64 :
65 : auto &arg = AddBBOXArg(&m_bbox,
66 99 : _("Target bounding box (in destination CRS units)"));
67 :
68 : arg.AddValidationAction(
69 10 : [this, &arg]()
70 : {
71 : // Validate it's not empty
72 8 : const std::vector<double> &bbox = arg.Get<std::vector<double>>();
73 8 : if ((bbox[0] >= bbox[2]) || (bbox[1] >= bbox[3]))
74 : {
75 2 : ReportError(CE_Failure, CPLE_AppDefined,
76 : "Invalid bounding box specified");
77 2 : return false;
78 : }
79 : else
80 : {
81 6 : return true;
82 : }
83 99 : });
84 :
85 198 : AddArg("bbox-crs", 0, _("CRS of target bounding box"), &m_bboxCrs)
86 198 : .SetIsCRSArg()
87 99 : .AddHiddenAlias("bbox_srs");
88 :
89 : AddArg("target-aligned-pixels", 0,
90 : _("Round target extent to target resolution"),
91 198 : &m_targetAlignedPixels)
92 198 : .AddHiddenAlias("tap")
93 99 : .SetCategory(GAAC_ADVANCED);
94 : AddArg("src-nodata", 0,
95 : _("Set nodata values for input bands ('None' to unset)."),
96 198 : &m_srcNoData)
97 99 : .SetMinCount(1)
98 99 : .SetRepeatedArgAllowed(false)
99 99 : .SetCategory(GAAC_ADVANCED);
100 : AddArg("dst-nodata", 0,
101 : _("Set nodata values for output bands ('None' to unset)."),
102 198 : &m_dstNoData)
103 99 : .SetMinCount(1)
104 99 : .SetRepeatedArgAllowed(false)
105 99 : .SetCategory(GAAC_ADVANCED);
106 : AddArg("add-alpha", 0,
107 : _("Adds an alpha mask band to the destination when the source "
108 : "raster have none."),
109 198 : &m_addAlpha)
110 99 : .SetCategory(GAAC_ADVANCED);
111 :
112 99 : GDALRasterReprojectUtils::AddWarpOptTransformOptErrorThresholdArg(
113 99 : this, m_warpOptions, m_transformOptions, m_errorThreshold);
114 :
115 99 : AddNumThreadsArg(&m_numThreads, &m_numThreadsStr);
116 99 : }
117 :
118 : /************************************************************************/
119 : /* GDALRasterReprojectUtils::AddResamplingArg() */
120 : /************************************************************************/
121 :
122 : /*static */ void
123 153 : GDALRasterReprojectUtils::AddResamplingArg(GDALAlgorithm *alg,
124 : std::string &resampling)
125 : {
126 306 : alg->AddArg("resampling", 'r', _("Resampling method"), &resampling)
127 : .SetChoices("nearest", "bilinear", "cubic", "cubicspline", "lanczos",
128 : "average", "rms", "mode", "min", "max", "med", "q1", "q3",
129 153 : "sum")
130 153 : .SetDefault("nearest")
131 153 : .SetHiddenChoices("near");
132 153 : }
133 :
134 : /************************************************************************/
135 : /* AddWarpOptTransformOptErrorThresholdArg() */
136 : /************************************************************************/
137 :
138 : /* static */
139 153 : void GDALRasterReprojectUtils::AddWarpOptTransformOptErrorThresholdArg(
140 : GDALAlgorithm *alg, std::vector<std::string> &warpOptions,
141 : std::vector<std::string> &transformOptions, double &errorThreshold)
142 : {
143 : {
144 : auto &arg =
145 306 : alg->AddArg("warp-option", 0, _("Warping option(s)"), &warpOptions)
146 306 : .AddAlias("wo")
147 306 : .SetMetaVar("<NAME>=<VALUE>")
148 306 : .SetCategory(GAAC_ADVANCED)
149 153 : .SetPackedValuesAllowed(false);
150 10 : arg.AddValidationAction([alg, &arg]()
151 163 : { return alg->ParseAndValidateKeyValue(arg); });
152 : arg.SetAutoCompleteFunction(
153 0 : [](const std::string ¤tValue)
154 : {
155 0 : std::vector<std::string> ret;
156 0 : GDALAlgorithm::AddOptionsSuggestions(GDALWarpGetOptionList(), 0,
157 : currentValue, ret);
158 0 : return ret;
159 153 : });
160 : }
161 : {
162 : auto &arg = alg->AddArg("transform-option", 0, _("Transform option(s)"),
163 306 : &transformOptions)
164 306 : .AddAlias("to")
165 306 : .SetMetaVar("<NAME>=<VALUE>")
166 306 : .SetCategory(GAAC_ADVANCED)
167 153 : .SetPackedValuesAllowed(false);
168 4 : arg.AddValidationAction([alg, &arg]()
169 157 : { return alg->ParseAndValidateKeyValue(arg); });
170 : arg.SetAutoCompleteFunction(
171 0 : [](const std::string ¤tValue)
172 : {
173 0 : std::vector<std::string> ret;
174 0 : GDALAlgorithm::AddOptionsSuggestions(
175 : GDALGetGenImgProjTranformerOptionList(), 0, currentValue,
176 : ret);
177 0 : return ret;
178 153 : });
179 : }
180 306 : alg->AddArg("error-threshold", 0, _("Error threshold"), &errorThreshold)
181 306 : .AddAlias("et")
182 153 : .SetMinValueIncluded(0)
183 153 : .SetCategory(GAAC_ADVANCED);
184 153 : }
185 :
186 : /************************************************************************/
187 : /* GDALRasterReprojectAlgorithm::CanHandleNextStep() */
188 : /************************************************************************/
189 :
190 35 : bool GDALRasterReprojectAlgorithm::CanHandleNextStep(
191 : GDALPipelineStepAlgorithm *poNextStep) const
192 : {
193 69 : return poNextStep->GetName() == GDALRasterWriteAlgorithm::NAME &&
194 69 : poNextStep->GetOutputFormat() != "stream";
195 : }
196 :
197 : /************************************************************************/
198 : /* GDALRasterReprojectAlgorithm::RunStep() */
199 : /************************************************************************/
200 :
201 20 : bool GDALRasterReprojectAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
202 : {
203 20 : auto poSrcDS = m_inputDataset[0].GetDatasetRef();
204 20 : CPLAssert(poSrcDS);
205 20 : CPLAssert(m_outputDataset.GetName().empty());
206 20 : CPLAssert(!m_outputDataset.GetDatasetRef());
207 :
208 40 : CPLStringList aosOptions;
209 40 : std::string outputFilename;
210 20 : if (ctxt.m_poNextUsableStep)
211 : {
212 17 : CPLAssert(CanHandleNextStep(ctxt.m_poNextUsableStep));
213 17 : outputFilename = ctxt.m_poNextUsableStep->GetOutputDataset().GetName();
214 17 : const auto &format = ctxt.m_poNextUsableStep->GetOutputFormat();
215 17 : if (!format.empty())
216 : {
217 6 : aosOptions.AddString("-of");
218 6 : aosOptions.AddString(format.c_str());
219 : }
220 :
221 17 : bool bFoundNumThreads = false;
222 2 : for (const std::string &co :
223 21 : ctxt.m_poNextUsableStep->GetCreationOptions())
224 : {
225 2 : aosOptions.AddString("-co");
226 2 : if (STARTS_WITH_CI(co.c_str(), "NUM_THREADS="))
227 0 : bFoundNumThreads = true;
228 2 : aosOptions.AddString(co.c_str());
229 : }
230 :
231 : // Forward m_numThreads to GeoTIFF driver if --co NUM_THREADS not
232 : // specified
233 34 : if (!bFoundNumThreads && m_numThreads > 1 &&
234 34 : (EQUAL(format.c_str(), "GTIFF") || EQUAL(format.c_str(), "COG") ||
235 17 : (format.empty() &&
236 28 : EQUAL(CPLGetExtensionSafe(outputFilename.c_str()).c_str(),
237 : "tif"))))
238 : {
239 11 : aosOptions.AddString("-co");
240 11 : aosOptions.AddString(CPLSPrintf("NUM_THREADS=%d", m_numThreads));
241 : }
242 : }
243 : else
244 : {
245 3 : aosOptions.AddString("-of");
246 3 : aosOptions.AddString("VRT");
247 : }
248 20 : if (!m_srsCrs.empty())
249 : {
250 9 : aosOptions.AddString("-s_srs");
251 9 : aosOptions.AddString(m_srsCrs.c_str());
252 : }
253 20 : if (!m_dstCrs.empty())
254 : {
255 13 : aosOptions.AddString("-t_srs");
256 13 : aosOptions.AddString(m_dstCrs.c_str());
257 : }
258 20 : if (!m_resampling.empty())
259 : {
260 20 : aosOptions.AddString("-r");
261 20 : aosOptions.AddString(m_resampling.c_str());
262 : }
263 20 : if (!m_resolution.empty())
264 : {
265 1 : aosOptions.AddString("-tr");
266 1 : aosOptions.AddString(CPLSPrintf("%.17g", m_resolution[0]));
267 1 : aosOptions.AddString(CPLSPrintf("%.17g", m_resolution[1]));
268 : }
269 20 : if (!m_size.empty())
270 : {
271 1 : aosOptions.AddString("-ts");
272 1 : aosOptions.AddString(CPLSPrintf("%d", m_size[0]));
273 1 : aosOptions.AddString(CPLSPrintf("%d", m_size[1]));
274 : }
275 20 : if (!m_bbox.empty())
276 : {
277 2 : aosOptions.AddString("-te");
278 2 : aosOptions.AddString(CPLSPrintf("%.17g", m_bbox[0]));
279 2 : aosOptions.AddString(CPLSPrintf("%.17g", m_bbox[1]));
280 2 : aosOptions.AddString(CPLSPrintf("%.17g", m_bbox[2]));
281 2 : aosOptions.AddString(CPLSPrintf("%.17g", m_bbox[3]));
282 : }
283 20 : if (!m_bboxCrs.empty())
284 : {
285 1 : aosOptions.AddString("-te_srs");
286 1 : aosOptions.AddString(m_bboxCrs.c_str());
287 : }
288 20 : if (m_targetAlignedPixels)
289 : {
290 1 : aosOptions.AddString("-tap");
291 : }
292 20 : if (!m_srcNoData.empty())
293 : {
294 1 : aosOptions.push_back("-srcnodata");
295 2 : std::string s;
296 2 : for (const std::string &v : m_srcNoData)
297 : {
298 1 : if (!s.empty())
299 0 : s += " ";
300 1 : s += v;
301 : }
302 1 : aosOptions.push_back(s);
303 : }
304 20 : if (!m_dstNoData.empty())
305 : {
306 2 : aosOptions.push_back("-dstnodata");
307 4 : std::string s;
308 5 : for (const std::string &v : m_dstNoData)
309 : {
310 3 : if (!s.empty())
311 1 : s += " ";
312 3 : s += v;
313 : }
314 2 : aosOptions.push_back(s);
315 : }
316 20 : if (m_addAlpha)
317 : {
318 1 : aosOptions.AddString("-dstalpha");
319 : }
320 :
321 20 : bool bFoundNumThreads = false;
322 23 : for (const std::string &opt : m_warpOptions)
323 : {
324 3 : aosOptions.AddString("-wo");
325 3 : if (STARTS_WITH_CI(opt.c_str(), "NUM_THREADS="))
326 2 : bFoundNumThreads = true;
327 3 : aosOptions.AddString(opt.c_str());
328 : }
329 20 : if (bFoundNumThreads)
330 : {
331 2 : if (GetArg(GDAL_ARG_NAME_NUM_THREADS)->IsExplicitlySet())
332 : {
333 1 : ReportError(CE_Failure, CPLE_AppDefined,
334 : "--num-threads argument and NUM_THREADS warp options "
335 : "are mutually exclusive.");
336 1 : return false;
337 : }
338 : }
339 : else
340 : {
341 18 : aosOptions.AddString("-wo");
342 18 : aosOptions.AddString(CPLSPrintf("NUM_THREADS=%d", m_numThreads));
343 : }
344 :
345 20 : for (const std::string &opt : m_transformOptions)
346 : {
347 1 : aosOptions.AddString("-to");
348 1 : aosOptions.AddString(opt.c_str());
349 : }
350 19 : if (std::isfinite(m_errorThreshold))
351 : {
352 0 : aosOptions.AddString("-et");
353 0 : aosOptions.AddString(CPLSPrintf("%.17g", m_errorThreshold));
354 : }
355 :
356 19 : bool bOK = false;
357 : GDALWarpAppOptions *psOptions =
358 19 : GDALWarpAppOptionsNew(aosOptions.List(), nullptr);
359 19 : if (psOptions)
360 : {
361 19 : if (ctxt.m_poNextUsableStep)
362 : {
363 16 : GDALWarpAppOptionsSetProgress(psOptions, ctxt.m_pfnProgress,
364 : ctxt.m_pProgressData);
365 : }
366 19 : GDALDatasetH hSrcDS = GDALDataset::ToHandle(poSrcDS);
367 19 : auto poRetDS = GDALDataset::FromHandle(GDALWarp(
368 : outputFilename.c_str(), nullptr, 1, &hSrcDS, psOptions, nullptr));
369 19 : GDALWarpAppOptionsFree(psOptions);
370 19 : bOK = poRetDS != nullptr;
371 19 : if (bOK)
372 : {
373 16 : m_outputDataset.Set(std::unique_ptr<GDALDataset>(poRetDS));
374 : }
375 : }
376 19 : return bOK;
377 : }
378 :
379 : GDALRasterReprojectAlgorithmStandalone::
380 : ~GDALRasterReprojectAlgorithmStandalone() = default;
381 :
382 : //! @endcond
|