Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: gdal "raster mosaic" subcommand
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_mosaic.h"
14 :
15 : #include "cpl_conv.h"
16 : #include "cpl_vsi_virtual.h"
17 :
18 : #include "gdal_priv.h"
19 : #include "gdal_utils.h"
20 :
21 : //! @cond Doxygen_Suppress
22 :
23 : #ifndef _
24 : #define _(x) (x)
25 : #endif
26 :
27 : /************************************************************************/
28 : /* GDALRasterMosaicAlgorithm::GDALRasterMosaicAlgorithm() */
29 : /************************************************************************/
30 :
31 27 : GDALRasterMosaicAlgorithm::GDALRasterMosaicAlgorithm()
32 27 : : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
33 : {
34 27 : AddProgressArg();
35 27 : AddOutputFormatArg(&m_format);
36 : AddArg(GDAL_ARG_NAME_INPUT, 'i',
37 : _("Input raster datasets (or specify a @<filename> to point to a "
38 : "file containing filenames)"),
39 54 : &m_inputDatasets, GDAL_OF_RASTER)
40 27 : .SetPositional()
41 27 : .SetMinCount(1)
42 27 : .SetAutoOpenDataset(false)
43 27 : .SetMetaVar("INPUTS");
44 27 : AddOutputDatasetArg(&m_outputDataset, GDAL_OF_RASTER);
45 27 : AddCreationOptionsArg(&m_creationOptions);
46 27 : AddArg("band", 'b', _("Specify input band(s) number."), &m_bands);
47 27 : AddOverwriteArg(&m_overwrite);
48 : {
49 : auto &arg =
50 : AddArg("resolution", 0,
51 : _("Target resolution (in destination CRS units)"),
52 54 : &m_resolution)
53 27 : .SetDefault("same")
54 27 : .SetMetaVar("<xres>,<yres>|same|average|highest|lowest");
55 : arg.AddValidationAction(
56 11 : [this, &arg]()
57 : {
58 16 : const std::string val = arg.Get<std::string>();
59 13 : if (val != "average" && val != "highest" && val != "lowest" &&
60 5 : val != "same")
61 : {
62 : const auto aosTokens =
63 5 : CPLStringList(CSLTokenizeString2(val.c_str(), ",", 0));
64 5 : if (aosTokens.size() != 2 ||
65 3 : CPLGetValueType(aosTokens[0]) == CPL_VALUE_STRING ||
66 3 : CPLGetValueType(aosTokens[1]) == CPL_VALUE_STRING ||
67 10 : CPLAtof(aosTokens[0]) <= 0 ||
68 2 : CPLAtof(aosTokens[1]) <= 0)
69 : {
70 3 : ReportError(CE_Failure, CPLE_AppDefined,
71 : "resolution: two comma separated positive "
72 : "values should be provided, or 'same', "
73 : "'average', 'highest' or 'lowest'");
74 3 : return false;
75 : }
76 : }
77 5 : return true;
78 27 : });
79 : }
80 : AddBBOXArg(&m_bbox, _("Target bounding box as xmin,ymin,xmax,ymax (in "
81 27 : "destination CRS units)"));
82 : AddArg("target-aligned-pixels", 0,
83 : _("Round target extent to target resolution"),
84 54 : &m_targetAlignedPixels)
85 27 : .AddHiddenAlias("tap");
86 : AddArg("srcnodata", 0, _("Set nodata values for input bands."),
87 54 : &m_srcNoData)
88 27 : .SetMinCount(1)
89 27 : .SetRepeatedArgAllowed(false);
90 : AddArg("dstnodata", 0,
91 54 : _("Set nodata values at the destination band level."), &m_dstNoData)
92 27 : .SetMinCount(1)
93 27 : .SetRepeatedArgAllowed(false);
94 : AddArg("hidenodata", 0,
95 : _("Makes the destination band not report the NoData."),
96 27 : &m_hideNoData);
97 : AddArg("addalpha", 0,
98 : _("Adds an alpha mask band to the destination when the source "
99 : "raster have "
100 : "none."),
101 27 : &m_addAlpha);
102 27 : }
103 :
104 : /************************************************************************/
105 : /* GDALRasterMosaicAlgorithm::RunImpl() */
106 : /************************************************************************/
107 :
108 24 : bool GDALRasterMosaicAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
109 : void *pProgressData)
110 : {
111 24 : if (m_outputDataset.GetDatasetRef())
112 : {
113 1 : ReportError(CE_Failure, CPLE_NotSupported,
114 : "gdal raster mosaic does not support outputting to an "
115 : "already opened output dataset");
116 1 : return false;
117 : }
118 :
119 46 : std::vector<GDALDatasetH> ahInputDatasets;
120 46 : CPLStringList aosInputDatasetNames;
121 23 : bool foundByRef = false;
122 23 : bool foundByName = false;
123 53 : for (auto &ds : m_inputDatasets)
124 : {
125 31 : if (ds.GetDatasetRef())
126 : {
127 18 : foundByRef = true;
128 18 : ahInputDatasets.push_back(
129 18 : GDALDataset::ToHandle(ds.GetDatasetRef()));
130 : }
131 13 : else if (!ds.GetName().empty())
132 : {
133 13 : foundByName = true;
134 13 : if (ds.GetName()[0] == '@')
135 : {
136 : auto f = VSIVirtualHandleUniquePtr(
137 2 : VSIFOpenL(ds.GetName().c_str() + 1, "r"));
138 2 : if (!f)
139 : {
140 1 : ReportError(CE_Failure, CPLE_FileIO, "Cannot open %s",
141 1 : ds.GetName().c_str() + 1);
142 1 : return false;
143 : }
144 2 : while (const char *filename = CPLReadLineL(f.get()))
145 : {
146 1 : aosInputDatasetNames.push_back(filename);
147 1 : }
148 : }
149 11 : else if (ds.GetName().find_first_of("*?[") != std::string::npos)
150 : {
151 1 : CPLStringList aosMatches(VSIGlob(ds.GetName().c_str(), nullptr,
152 2 : pfnProgress, pProgressData));
153 2 : for (const char *pszStr : aosMatches)
154 : {
155 1 : aosInputDatasetNames.push_back(pszStr);
156 : }
157 : }
158 : else
159 : {
160 10 : aosInputDatasetNames.push_back(ds.GetName().c_str());
161 : }
162 : }
163 : }
164 22 : if (foundByName && foundByRef)
165 : {
166 0 : ReportError(CE_Failure, CPLE_NotSupported,
167 : "Input datasets should be provided either all by reference "
168 : "or all by name");
169 0 : return false;
170 : }
171 :
172 : VSIStatBufL sStat;
173 27 : if (!m_overwrite && !m_outputDataset.GetName().empty() &&
174 9 : (VSIStatL(m_outputDataset.GetName().c_str(), &sStat) == 0 ||
175 26 : std::unique_ptr<GDALDataset>(
176 8 : GDALDataset::Open(m_outputDataset.GetName().c_str()))))
177 : {
178 1 : ReportError(CE_Failure, CPLE_AppDefined,
179 : "File '%s' already exists. Specify the --overwrite "
180 : "option to overwrite it.",
181 1 : m_outputDataset.GetName().c_str());
182 1 : return false;
183 : }
184 :
185 : const bool bVRTOutput =
186 26 : m_outputDataset.GetName().empty() || EQUAL(m_format.c_str(), "VRT") ||
187 26 : EQUAL(CPLGetExtensionSafe(m_outputDataset.GetName().c_str()).c_str(),
188 : "VRT");
189 :
190 42 : CPLStringList aosOptions;
191 21 : aosOptions.push_back("-strict");
192 :
193 21 : aosOptions.push_back("-program_name");
194 21 : aosOptions.push_back("gdal raster mosaic");
195 :
196 : const auto aosTokens =
197 42 : CPLStringList(CSLTokenizeString2(m_resolution.c_str(), ",", 0));
198 21 : if (aosTokens.size() == 2)
199 : {
200 2 : aosOptions.push_back("-tr");
201 2 : aosOptions.push_back(aosTokens[0]);
202 2 : aosOptions.push_back(aosTokens[1]);
203 : }
204 : else
205 : {
206 19 : aosOptions.push_back("-resolution");
207 19 : aosOptions.push_back(m_resolution);
208 : }
209 :
210 21 : if (!m_bbox.empty())
211 : {
212 1 : aosOptions.push_back("-te");
213 1 : aosOptions.push_back(CPLSPrintf("%.17g", m_bbox[0]));
214 1 : aosOptions.push_back(CPLSPrintf("%.17g", m_bbox[1]));
215 1 : aosOptions.push_back(CPLSPrintf("%.17g", m_bbox[2]));
216 1 : aosOptions.push_back(CPLSPrintf("%.17g", m_bbox[3]));
217 : }
218 21 : if (m_targetAlignedPixels)
219 : {
220 1 : aosOptions.push_back("-tap");
221 : }
222 21 : if (!m_srcNoData.empty())
223 : {
224 2 : aosOptions.push_back("-srcnodata");
225 4 : std::string s;
226 4 : for (double v : m_srcNoData)
227 : {
228 2 : if (!s.empty())
229 0 : s += " ";
230 2 : s += CPLSPrintf("%.17g", v);
231 : }
232 2 : aosOptions.push_back(s);
233 : }
234 21 : if (!m_dstNoData.empty())
235 : {
236 2 : aosOptions.push_back("-vrtnodata");
237 4 : std::string s;
238 4 : for (double v : m_dstNoData)
239 : {
240 2 : if (!s.empty())
241 0 : s += " ";
242 2 : s += CPLSPrintf("%.17g", v);
243 : }
244 2 : aosOptions.push_back(s);
245 : }
246 21 : if (bVRTOutput)
247 : {
248 20 : for (const auto &co : m_creationOptions)
249 : {
250 2 : aosOptions.push_back("-co");
251 2 : aosOptions.push_back(co);
252 : }
253 : }
254 23 : for (const int b : m_bands)
255 : {
256 2 : aosOptions.push_back("-b");
257 2 : aosOptions.push_back(CPLSPrintf("%d", b));
258 : }
259 21 : if (m_addAlpha)
260 : {
261 0 : aosOptions.push_back("-addalpha");
262 : }
263 21 : if (m_hideNoData)
264 : {
265 1 : aosOptions.push_back("-hidenodata");
266 : }
267 :
268 : GDALBuildVRTOptions *psOptions =
269 21 : GDALBuildVRTOptionsNew(aosOptions.List(), nullptr);
270 21 : if (bVRTOutput)
271 : {
272 18 : GDALBuildVRTOptionsSetProgress(psOptions, pfnProgress, pProgressData);
273 : }
274 :
275 : auto poOutDS = std::unique_ptr<GDALDataset>(GDALDataset::FromHandle(
276 18 : GDALBuildVRT(bVRTOutput ? m_outputDataset.GetName().c_str() : "",
277 11 : foundByName ? aosInputDatasetNames.size()
278 10 : : static_cast<int>(m_inputDatasets.size()),
279 31 : ahInputDatasets.empty() ? nullptr : ahInputDatasets.data(),
280 91 : aosInputDatasetNames.List(), psOptions, nullptr)));
281 21 : GDALBuildVRTOptionsFree(psOptions);
282 21 : bool bOK = poOutDS != nullptr;
283 21 : if (bOK)
284 : {
285 19 : if (bVRTOutput)
286 : {
287 16 : m_outputDataset.Set(std::move(poOutDS));
288 : }
289 : else
290 : {
291 6 : CPLStringList aosTranslateOptions;
292 3 : if (!m_format.empty())
293 : {
294 2 : aosTranslateOptions.AddString("-of");
295 2 : aosTranslateOptions.AddString(m_format.c_str());
296 : }
297 4 : for (const auto &co : m_creationOptions)
298 : {
299 1 : aosTranslateOptions.AddString("-co");
300 1 : aosTranslateOptions.AddString(co.c_str());
301 : }
302 :
303 : GDALTranslateOptions *psTranslateOptions =
304 3 : GDALTranslateOptionsNew(aosTranslateOptions.List(), nullptr);
305 3 : GDALTranslateOptionsSetProgress(psTranslateOptions, pfnProgress,
306 : pProgressData);
307 :
308 : auto poFinalDS =
309 : std::unique_ptr<GDALDataset>(GDALDataset::FromHandle(
310 3 : GDALTranslate(m_outputDataset.GetName().c_str(),
311 : GDALDataset::ToHandle(poOutDS.get()),
312 9 : psTranslateOptions, nullptr)));
313 3 : GDALTranslateOptionsFree(psTranslateOptions);
314 :
315 3 : bOK = poFinalDS != nullptr;
316 3 : if (bOK)
317 : {
318 3 : m_outputDataset.Set(std::move(poFinalDS));
319 : }
320 : }
321 : }
322 :
323 21 : return bOK;
324 : }
325 :
326 : //! @endcond
|