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