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 101 : GDALRasterReprojectAlgorithm::GDALRasterReprojectAlgorithm(bool standaloneStep)
34 : : GDALRasterPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
35 101 : standaloneStep)
36 : {
37 202 : AddArg("src-crs", 's', _("Source CRS"), &m_srsCrs)
38 202 : .SetIsCRSArg()
39 101 : .AddHiddenAlias("s_srs");
40 202 : AddArg("dst-crs", 'd', _("Destination CRS"), &m_dstCrs)
41 202 : .SetIsCRSArg()
42 101 : .AddHiddenAlias("t_srs");
43 :
44 101 : GDALRasterReprojectUtils::AddResamplingArg(this, m_resampling);
45 :
46 : AddArg("resolution", 0, _("Target resolution (in destination CRS units)"),
47 202 : &m_resolution)
48 101 : .SetMinCount(2)
49 101 : .SetMaxCount(2)
50 101 : .SetMinValueExcluded(0)
51 101 : .SetRepeatedArgAllowed(false)
52 101 : .SetDisplayHintAboutRepetition(false)
53 202 : .SetMetaVar("<xres>,<yres>")
54 101 : .SetMutualExclusionGroup("resolution-size");
55 :
56 202 : AddArg("size", 0, _("Target size in pixels"), &m_size)
57 101 : .SetMinCount(2)
58 101 : .SetMaxCount(2)
59 101 : .SetMinValueIncluded(0)
60 101 : .SetRepeatedArgAllowed(false)
61 101 : .SetDisplayHintAboutRepetition(false)
62 202 : .SetMetaVar("<width>,<height>")
63 101 : .SetMutualExclusionGroup("resolution-size");
64 :
65 : auto &arg = AddBBOXArg(&m_bbox,
66 101 : _("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 101 : });
84 :
85 202 : AddArg("bbox-crs", 0, _("CRS of target bounding box"), &m_bboxCrs)
86 202 : .SetIsCRSArg()
87 101 : .AddHiddenAlias("bbox_srs");
88 :
89 : AddArg("target-aligned-pixels", 0,
90 : _("Round target extent to target resolution"),
91 202 : &m_targetAlignedPixels)
92 202 : .AddHiddenAlias("tap")
93 101 : .SetCategory(GAAC_ADVANCED);
94 : AddArg("src-nodata", 0,
95 : _("Set nodata values for input bands ('None' to unset)."),
96 202 : &m_srcNoData)
97 101 : .SetMinCount(1)
98 101 : .SetRepeatedArgAllowed(false)
99 101 : .SetCategory(GAAC_ADVANCED);
100 : AddArg("dst-nodata", 0,
101 : _("Set nodata values for output bands ('None' to unset)."),
102 202 : &m_dstNoData)
103 101 : .SetMinCount(1)
104 101 : .SetRepeatedArgAllowed(false)
105 101 : .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 202 : &m_addAlpha)
110 101 : .SetCategory(GAAC_ADVANCED);
111 :
112 101 : GDALRasterReprojectUtils::AddWarpOptTransformOptErrorThresholdArg(
113 101 : this, m_warpOptions, m_transformOptions, m_errorThreshold);
114 :
115 101 : AddNumThreadsArg(&m_numThreads, &m_numThreadsStr);
116 101 : }
117 :
118 : /************************************************************************/
119 : /* GDALRasterReprojectUtils::AddResamplingArg() */
120 : /************************************************************************/
121 :
122 : /*static */ void
123 157 : GDALRasterReprojectUtils::AddResamplingArg(GDALAlgorithm *alg,
124 : std::string &resampling)
125 : {
126 314 : alg->AddArg("resampling", 'r', _("Resampling method"), &resampling)
127 : .SetChoices("nearest", "bilinear", "cubic", "cubicspline", "lanczos",
128 : "average", "rms", "mode", "min", "max", "med", "q1", "q3",
129 157 : "sum")
130 157 : .SetDefault("nearest")
131 157 : .SetHiddenChoices("near");
132 157 : }
133 :
134 : /************************************************************************/
135 : /* AddWarpOptTransformOptErrorThresholdArg() */
136 : /************************************************************************/
137 :
138 : /* static */
139 157 : 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 314 : alg->AddArg("warp-option", 0, _("Warping option(s)"), &warpOptions)
146 314 : .AddAlias("wo")
147 314 : .SetMetaVar("<NAME>=<VALUE>")
148 314 : .SetCategory(GAAC_ADVANCED)
149 157 : .SetPackedValuesAllowed(false);
150 10 : arg.AddValidationAction([alg, &arg]()
151 167 : { 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 157 : });
160 : }
161 : {
162 : auto &arg = alg->AddArg("transform-option", 0, _("Transform option(s)"),
163 314 : &transformOptions)
164 314 : .AddAlias("to")
165 314 : .SetMetaVar("<NAME>=<VALUE>")
166 314 : .SetCategory(GAAC_ADVANCED)
167 157 : .SetPackedValuesAllowed(false);
168 4 : arg.AddValidationAction([alg, &arg]()
169 161 : { 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 157 : });
179 : }
180 314 : alg->AddArg("error-threshold", 0, _("Error threshold"), &errorThreshold)
181 314 : .AddAlias("et")
182 157 : .SetMinValueIncluded(0)
183 157 : .SetCategory(GAAC_ADVANCED);
184 157 : }
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 :
211 20 : if (ctxt.m_poNextUsableStep)
212 : {
213 17 : CPLAssert(CanHandleNextStep(ctxt.m_poNextUsableStep));
214 17 : outputFilename = ctxt.m_poNextUsableStep->GetOutputDataset().GetName();
215 17 : const auto &format = ctxt.m_poNextUsableStep->GetOutputFormat();
216 17 : if (!format.empty())
217 : {
218 6 : aosOptions.AddString("-of");
219 6 : aosOptions.AddString(format.c_str());
220 : }
221 :
222 17 : bool bFoundNumThreads = false;
223 2 : for (const std::string &co :
224 21 : ctxt.m_poNextUsableStep->GetCreationOptions())
225 : {
226 2 : aosOptions.AddString("-co");
227 2 : if (STARTS_WITH_CI(co.c_str(), "NUM_THREADS="))
228 0 : bFoundNumThreads = true;
229 2 : aosOptions.AddString(co.c_str());
230 : }
231 :
232 : // Forward m_numThreads to GeoTIFF driver if --co NUM_THREADS not
233 : // specified
234 34 : if (!bFoundNumThreads && m_numThreads > 1 &&
235 34 : (EQUAL(format.c_str(), "GTIFF") || EQUAL(format.c_str(), "COG") ||
236 17 : (format.empty() &&
237 28 : EQUAL(CPLGetExtensionSafe(outputFilename.c_str()).c_str(),
238 : "tif"))))
239 : {
240 11 : aosOptions.AddString("-co");
241 11 : aosOptions.AddString(CPLSPrintf("NUM_THREADS=%d", m_numThreads));
242 : }
243 : }
244 : else
245 : {
246 3 : aosOptions.AddString("-of");
247 3 : aosOptions.AddString("VRT");
248 : }
249 20 : if (!m_srsCrs.empty())
250 : {
251 9 : aosOptions.AddString("-s_srs");
252 9 : aosOptions.AddString(m_srsCrs.c_str());
253 : }
254 20 : if (!m_dstCrs.empty())
255 : {
256 13 : aosOptions.AddString("-t_srs");
257 13 : aosOptions.AddString(m_dstCrs.c_str());
258 : }
259 20 : if (!m_resampling.empty())
260 : {
261 20 : aosOptions.AddString("-r");
262 20 : aosOptions.AddString(m_resampling.c_str());
263 : }
264 20 : if (!m_resolution.empty())
265 : {
266 1 : aosOptions.AddString("-tr");
267 1 : aosOptions.AddString(CPLSPrintf("%.17g", m_resolution[0]));
268 1 : aosOptions.AddString(CPLSPrintf("%.17g", m_resolution[1]));
269 : }
270 20 : if (!m_size.empty())
271 : {
272 1 : aosOptions.AddString("-ts");
273 1 : aosOptions.AddString(CPLSPrintf("%d", m_size[0]));
274 1 : aosOptions.AddString(CPLSPrintf("%d", m_size[1]));
275 : }
276 20 : if (!m_bbox.empty())
277 : {
278 2 : aosOptions.AddString("-te");
279 2 : aosOptions.AddString(CPLSPrintf("%.17g", m_bbox[0]));
280 2 : aosOptions.AddString(CPLSPrintf("%.17g", m_bbox[1]));
281 2 : aosOptions.AddString(CPLSPrintf("%.17g", m_bbox[2]));
282 2 : aosOptions.AddString(CPLSPrintf("%.17g", m_bbox[3]));
283 : }
284 20 : if (!m_bboxCrs.empty())
285 : {
286 1 : aosOptions.AddString("-te_srs");
287 1 : aosOptions.AddString(m_bboxCrs.c_str());
288 : }
289 20 : if (m_targetAlignedPixels)
290 : {
291 1 : aosOptions.AddString("-tap");
292 : }
293 20 : if (!m_srcNoData.empty())
294 : {
295 1 : aosOptions.push_back("-srcnodata");
296 2 : std::string s;
297 2 : for (const std::string &v : m_srcNoData)
298 : {
299 1 : if (!s.empty())
300 0 : s += " ";
301 1 : s += v;
302 : }
303 1 : aosOptions.push_back(s);
304 : }
305 20 : if (!m_dstNoData.empty())
306 : {
307 2 : aosOptions.push_back("-dstnodata");
308 4 : std::string s;
309 5 : for (const std::string &v : m_dstNoData)
310 : {
311 3 : if (!s.empty())
312 1 : s += " ";
313 3 : s += v;
314 : }
315 2 : aosOptions.push_back(s);
316 : }
317 20 : if (m_addAlpha)
318 : {
319 1 : aosOptions.AddString("-dstalpha");
320 : }
321 :
322 20 : bool bFoundNumThreads = false;
323 23 : for (const std::string &opt : m_warpOptions)
324 : {
325 3 : aosOptions.AddString("-wo");
326 3 : if (STARTS_WITH_CI(opt.c_str(), "NUM_THREADS="))
327 2 : bFoundNumThreads = true;
328 3 : aosOptions.AddString(opt.c_str());
329 : }
330 20 : if (bFoundNumThreads)
331 : {
332 2 : if (GetArg(GDAL_ARG_NAME_NUM_THREADS)->IsExplicitlySet())
333 : {
334 1 : ReportError(CE_Failure, CPLE_AppDefined,
335 : "--num-threads argument and NUM_THREADS warp options "
336 : "are mutually exclusive.");
337 1 : return false;
338 : }
339 : }
340 : else
341 : {
342 18 : aosOptions.AddString("-wo");
343 18 : aosOptions.AddString(CPLSPrintf("NUM_THREADS=%d", m_numThreads));
344 : }
345 :
346 20 : for (const std::string &opt : m_transformOptions)
347 : {
348 1 : aosOptions.AddString("-to");
349 1 : aosOptions.AddString(opt.c_str());
350 : }
351 19 : if (std::isfinite(m_errorThreshold))
352 : {
353 0 : aosOptions.AddString("-et");
354 0 : aosOptions.AddString(CPLSPrintf("%.17g", m_errorThreshold));
355 : }
356 :
357 19 : bool bOK = false;
358 : GDALWarpAppOptions *psOptions =
359 19 : GDALWarpAppOptionsNew(aosOptions.List(), nullptr);
360 19 : if (psOptions)
361 : {
362 19 : if (ctxt.m_poNextUsableStep)
363 : {
364 16 : GDALWarpAppOptionsSetProgress(psOptions, ctxt.m_pfnProgress,
365 : ctxt.m_pProgressData);
366 : }
367 19 : GDALDatasetH hSrcDS = GDALDataset::ToHandle(poSrcDS);
368 19 : auto poRetDS = GDALDataset::FromHandle(GDALWarp(
369 : outputFilename.c_str(), nullptr, 1, &hSrcDS, psOptions, nullptr));
370 19 : GDALWarpAppOptionsFree(psOptions);
371 19 : bOK = poRetDS != nullptr;
372 19 : if (bOK)
373 : {
374 16 : m_outputDataset.Set(std::unique_ptr<GDALDataset>(poRetDS));
375 : }
376 : }
377 19 : return bOK;
378 : }
379 :
380 : GDALRasterReprojectAlgorithmStandalone::
381 : ~GDALRasterReprojectAlgorithmStandalone() = default;
382 :
383 : //! @endcond
|