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 : AddBandArg(&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("src-nodata", 0, _("Set nodata values for input bands."),
91 62 : &m_srcNoData)
92 31 : .SetMinCount(1)
93 31 : .SetRepeatedArgAllowed(false);
94 : AddArg("dst-nodata", 0,
95 62 : _("Set nodata values at the destination band level."), &m_dstNoData)
96 31 : .SetMinCount(1)
97 31 : .SetRepeatedArgAllowed(false);
98 : AddArg("hide-nodata", 0,
99 : _("Makes the destination band not report the NoData."),
100 31 : &m_hideNoData);
101 : AddArg("add-alpha", 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 24 : bool GDALRasterMosaicAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
113 : void *pProgressData)
114 : {
115 24 : CPLAssert(!m_outputDataset.GetDatasetRef());
116 :
117 48 : std::vector<GDALDatasetH> ahInputDatasets;
118 48 : CPLStringList aosInputDatasetNames;
119 24 : bool foundByRef = false;
120 24 : bool foundByName = false;
121 56 : for (auto &ds : m_inputDatasets)
122 : {
123 33 : if (ds.GetDatasetRef())
124 : {
125 20 : foundByRef = true;
126 20 : ahInputDatasets.push_back(
127 20 : GDALDataset::ToHandle(ds.GetDatasetRef()));
128 : }
129 13 : else if (!ds.GetName().empty())
130 : {
131 13 : foundByName = true;
132 13 : if (ds.GetName()[0] == '@')
133 : {
134 : auto f = VSIVirtualHandleUniquePtr(
135 2 : VSIFOpenL(ds.GetName().c_str() + 1, "r"));
136 2 : if (!f)
137 : {
138 1 : ReportError(CE_Failure, CPLE_FileIO, "Cannot open %s",
139 1 : ds.GetName().c_str() + 1);
140 1 : return false;
141 : }
142 2 : while (const char *filename = CPLReadLineL(f.get()))
143 : {
144 1 : aosInputDatasetNames.push_back(filename);
145 1 : }
146 : }
147 11 : else if (ds.GetName().find_first_of("*?[") != std::string::npos)
148 : {
149 1 : CPLStringList aosMatches(VSIGlob(ds.GetName().c_str(), nullptr,
150 2 : pfnProgress, pProgressData));
151 2 : for (const char *pszStr : aosMatches)
152 : {
153 1 : aosInputDatasetNames.push_back(pszStr);
154 : }
155 : }
156 : else
157 : {
158 20 : std::string osDatasetName = ds.GetName();
159 10 : if (!GetReferencePathForRelativePaths().empty())
160 : {
161 0 : osDatasetName = GDALDataset::BuildFilename(
162 : osDatasetName.c_str(),
163 0 : GetReferencePathForRelativePaths().c_str(), true);
164 : }
165 10 : aosInputDatasetNames.push_back(osDatasetName.c_str());
166 : }
167 : }
168 : }
169 23 : if (foundByName && foundByRef)
170 : {
171 0 : ReportError(CE_Failure, CPLE_NotSupported,
172 : "Input datasets should be provided either all by reference "
173 : "or all by name");
174 0 : return false;
175 : }
176 :
177 : const bool bVRTOutput =
178 29 : m_outputDataset.GetName().empty() || EQUAL(m_format.c_str(), "VRT") ||
179 57 : EQUAL(m_format.c_str(), "stream") ||
180 28 : EQUAL(CPLGetExtensionSafe(m_outputDataset.GetName().c_str()).c_str(),
181 : "VRT");
182 :
183 46 : CPLStringList aosOptions;
184 23 : aosOptions.push_back("-strict");
185 :
186 23 : aosOptions.push_back("-program_name");
187 23 : aosOptions.push_back("gdal raster mosaic");
188 :
189 : const auto aosTokens =
190 46 : CPLStringList(CSLTokenizeString2(m_resolution.c_str(), ",", 0));
191 23 : if (aosTokens.size() == 2)
192 : {
193 2 : aosOptions.push_back("-tr");
194 2 : aosOptions.push_back(aosTokens[0]);
195 2 : aosOptions.push_back(aosTokens[1]);
196 : }
197 : else
198 : {
199 21 : aosOptions.push_back("-resolution");
200 21 : aosOptions.push_back(m_resolution);
201 : }
202 :
203 23 : if (!m_bbox.empty())
204 : {
205 1 : aosOptions.push_back("-te");
206 1 : aosOptions.push_back(CPLSPrintf("%.17g", m_bbox[0]));
207 1 : aosOptions.push_back(CPLSPrintf("%.17g", m_bbox[1]));
208 1 : aosOptions.push_back(CPLSPrintf("%.17g", m_bbox[2]));
209 1 : aosOptions.push_back(CPLSPrintf("%.17g", m_bbox[3]));
210 : }
211 23 : if (m_targetAlignedPixels)
212 : {
213 1 : aosOptions.push_back("-tap");
214 : }
215 23 : if (!m_srcNoData.empty())
216 : {
217 2 : aosOptions.push_back("-srcnodata");
218 4 : std::string s;
219 4 : for (double v : m_srcNoData)
220 : {
221 2 : if (!s.empty())
222 0 : s += " ";
223 2 : s += CPLSPrintf("%.17g", v);
224 : }
225 2 : aosOptions.push_back(s);
226 : }
227 23 : if (!m_dstNoData.empty())
228 : {
229 2 : aosOptions.push_back("-vrtnodata");
230 4 : std::string s;
231 4 : for (double v : m_dstNoData)
232 : {
233 2 : if (!s.empty())
234 0 : s += " ";
235 2 : s += CPLSPrintf("%.17g", v);
236 : }
237 2 : aosOptions.push_back(s);
238 : }
239 23 : if (bVRTOutput)
240 : {
241 22 : for (const auto &co : m_creationOptions)
242 : {
243 2 : aosOptions.push_back("-co");
244 2 : aosOptions.push_back(co);
245 : }
246 : }
247 25 : for (const int b : m_bands)
248 : {
249 2 : aosOptions.push_back("-b");
250 2 : aosOptions.push_back(CPLSPrintf("%d", b));
251 : }
252 23 : if (m_addAlpha)
253 : {
254 0 : aosOptions.push_back("-addalpha");
255 : }
256 23 : if (m_hideNoData)
257 : {
258 1 : aosOptions.push_back("-hidenodata");
259 : }
260 :
261 : GDALBuildVRTOptions *psOptions =
262 23 : GDALBuildVRTOptionsNew(aosOptions.List(), nullptr);
263 23 : if (bVRTOutput)
264 : {
265 20 : GDALBuildVRTOptionsSetProgress(psOptions, pfnProgress, pProgressData);
266 : }
267 :
268 : auto poOutDS = std::unique_ptr<GDALDataset>(GDALDataset::FromHandle(
269 23 : GDALBuildVRT(EQUAL(m_format.c_str(), "stream") ? ""
270 22 : : bVRTOutput ? m_outputDataset.GetName().c_str()
271 : : "",
272 12 : foundByName ? aosInputDatasetNames.size()
273 11 : : static_cast<int>(m_inputDatasets.size()),
274 34 : ahInputDatasets.empty() ? nullptr : ahInputDatasets.data(),
275 113 : aosInputDatasetNames.List(), psOptions, nullptr)));
276 23 : GDALBuildVRTOptionsFree(psOptions);
277 23 : bool bOK = poOutDS != nullptr;
278 23 : if (bOK)
279 : {
280 21 : if (bVRTOutput)
281 : {
282 18 : m_outputDataset.Set(std::move(poOutDS));
283 : }
284 : else
285 : {
286 6 : CPLStringList aosTranslateOptions;
287 3 : if (!m_format.empty())
288 : {
289 2 : aosTranslateOptions.AddString("-of");
290 2 : aosTranslateOptions.AddString(m_format.c_str());
291 : }
292 4 : for (const auto &co : m_creationOptions)
293 : {
294 1 : aosTranslateOptions.AddString("-co");
295 1 : aosTranslateOptions.AddString(co.c_str());
296 : }
297 :
298 : GDALTranslateOptions *psTranslateOptions =
299 3 : GDALTranslateOptionsNew(aosTranslateOptions.List(), nullptr);
300 3 : GDALTranslateOptionsSetProgress(psTranslateOptions, pfnProgress,
301 : pProgressData);
302 :
303 : auto poFinalDS =
304 : std::unique_ptr<GDALDataset>(GDALDataset::FromHandle(
305 3 : GDALTranslate(m_outputDataset.GetName().c_str(),
306 : GDALDataset::ToHandle(poOutDS.get()),
307 9 : psTranslateOptions, nullptr)));
308 3 : GDALTranslateOptionsFree(psTranslateOptions);
309 :
310 3 : bOK = poFinalDS != nullptr;
311 3 : if (bOK)
312 : {
313 3 : m_outputDataset.Set(std::move(poFinalDS));
314 : }
315 : }
316 : }
317 :
318 23 : return bOK;
319 : }
320 :
321 : //! @endcond
|