Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: gdal "raster create" subcommand
5 : * Author: Even Rouault <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "gdalalg_raster_create.h"
14 :
15 : #include "cpl_conv.h"
16 : #include "gdal_priv.h"
17 : #include "gdal_utils.h"
18 : #include "ogr_spatialref.h"
19 :
20 : //! @cond Doxygen_Suppress
21 :
22 : #ifndef _
23 : #define _(x) (x)
24 : #endif
25 :
26 : /************************************************************************/
27 : /* GDALRasterCreateAlgorithm::GDALRasterCreateAlgorithm() */
28 : /************************************************************************/
29 :
30 42 : GDALRasterCreateAlgorithm::GDALRasterCreateAlgorithm()
31 42 : : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
32 : {
33 42 : AddProgressArg();
34 42 : AddOutputFormatArg(&m_outputFormat)
35 : .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES,
36 126 : {GDAL_DCAP_RASTER, GDAL_DCAP_CREATE});
37 42 : AddOpenOptionsArg(&m_openOptions);
38 42 : AddInputFormatsArg(&m_inputFormats)
39 84 : .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_RASTER});
40 42 : AddInputDatasetArg(&m_inputDataset, GDAL_OF_RASTER, false).AddAlias("like");
41 42 : AddOutputDatasetArg(&m_outputDataset, GDAL_OF_RASTER);
42 42 : AddCreationOptionsArg(&m_creationOptions);
43 42 : const char *exclusionGroup = "overwrite-append";
44 42 : AddOverwriteArg(&m_overwrite).SetMutualExclusionGroup(exclusionGroup);
45 : AddArg("append", 0, _("Append as a subdataset to existing output"),
46 84 : &m_append)
47 42 : .SetDefault(false)
48 42 : .SetMutualExclusionGroup(exclusionGroup);
49 84 : AddArg("size", 0, _("Output size in pixels"), &m_size)
50 42 : .SetMinCount(2)
51 42 : .SetMaxCount(2)
52 42 : .SetMinValueIncluded(0)
53 42 : .SetRepeatedArgAllowed(false)
54 42 : .SetDisplayHintAboutRepetition(false)
55 42 : .SetMetaVar("<width>,<height>");
56 84 : AddArg("band-count", 0, _("Number of bands"), &m_bandCount)
57 42 : .SetDefault(m_bandCount)
58 42 : .SetMinValueIncluded(0);
59 42 : AddOutputDataTypeArg(&m_type).SetDefault(m_type);
60 :
61 42 : AddNodataDataTypeArg(&m_nodata, /* noneAllowed = */ true);
62 :
63 42 : AddArg("burn", 0, _("Burn value"), &m_burnValues);
64 84 : AddArg("crs", 0, _("Set CRS"), &m_crs)
65 84 : .AddHiddenAlias("a_srs")
66 42 : .SetIsCRSArg(/*noneAllowed=*/true);
67 42 : AddBBOXArg(&m_bbox);
68 :
69 : {
70 84 : auto &arg = AddArg("metadata", 0, _("Add metadata item"), &m_metadata)
71 42 : .SetMetaVar("<KEY>=<VALUE>");
72 12 : arg.AddValidationAction([this, &arg]()
73 54 : { return ValidateKeyValue(arg); });
74 42 : arg.AddHiddenAlias("mo");
75 : }
76 : AddArg("copy-metadata", 0, _("Copy metadata from input dataset"),
77 42 : &m_copyMetadata);
78 : AddArg("copy-overviews", 0,
79 42 : _("Create same overview levels as input dataset"), &m_copyOverviews);
80 42 : }
81 :
82 : /************************************************************************/
83 : /* GDALRasterCreateAlgorithm::RunImpl() */
84 : /************************************************************************/
85 :
86 40 : bool GDALRasterCreateAlgorithm::RunImpl(GDALProgressFunc /* pfnProgress */,
87 : void * /*pProgressData */)
88 : {
89 40 : if (m_outputDataset.GetDatasetRef())
90 : {
91 0 : CPLError(CE_Failure, CPLE_NotSupported,
92 : "gdal raster create does not support outputting to an "
93 : "already opened output dataset");
94 0 : return false;
95 : }
96 :
97 : VSIStatBufL sStat;
98 47 : if (!m_append && !m_outputDataset.GetName().empty() &&
99 12 : (VSIStatL(m_outputDataset.GetName().c_str(), &sStat) == 0 ||
100 45 : std::unique_ptr<GDALDataset>(
101 10 : GDALDataset::Open(m_outputDataset.GetName().c_str()))))
102 : {
103 2 : if (!m_overwrite)
104 : {
105 1 : ReportError(CE_Failure, CPLE_AppDefined,
106 : "File '%s' already exists. Specify the --overwrite "
107 : "option to overwrite it, or --append to append to it.",
108 1 : m_outputDataset.GetName().c_str());
109 1 : return false;
110 : }
111 : else
112 : {
113 1 : VSIUnlink(m_outputDataset.GetName().c_str());
114 : }
115 : }
116 :
117 39 : if (m_outputFormat.empty())
118 : {
119 : const auto aosFormats =
120 : CPLStringList(GDALGetOutputDriversForDatasetName(
121 6 : m_outputDataset.GetName().c_str(), GDAL_OF_RASTER,
122 : /* bSingleMatch = */ true,
123 6 : /* bWarn = */ true));
124 6 : if (aosFormats.size() != 1)
125 : {
126 1 : ReportError(CE_Failure, CPLE_AppDefined,
127 : "Cannot guess driver for %s",
128 1 : m_outputDataset.GetName().c_str());
129 1 : return false;
130 : }
131 5 : m_outputFormat = aosFormats[0];
132 : }
133 :
134 76 : OGRSpatialReference oSRS;
135 :
136 38 : double adfGT[6] = {0};
137 38 : bool bGTValid = false;
138 :
139 38 : auto poSrcDS = m_inputDataset.GetDatasetRef();
140 38 : if (poSrcDS)
141 : {
142 11 : if (m_size.empty())
143 : {
144 27 : m_size = std::vector<int>{poSrcDS->GetRasterXSize(),
145 18 : poSrcDS->GetRasterYSize()};
146 : }
147 :
148 11 : if (!GetArg("band-count")->IsExplicitlySet())
149 : {
150 10 : m_bandCount = poSrcDS->GetRasterCount();
151 : }
152 :
153 11 : if (!GetArg("datatype")->IsExplicitlySet())
154 : {
155 10 : if (m_bandCount > 0)
156 : {
157 : m_type = GDALGetDataTypeName(
158 10 : poSrcDS->GetRasterBand(1)->GetRasterDataType());
159 : }
160 : }
161 :
162 11 : if (m_crs.empty())
163 : {
164 9 : if (const auto poSRS = poSrcDS->GetSpatialRef())
165 9 : oSRS = *poSRS;
166 : }
167 :
168 11 : if (m_bbox.empty())
169 : {
170 10 : bGTValid = poSrcDS->GetGeoTransform(adfGT) == CE_None;
171 : }
172 :
173 11 : if (m_nodata.empty() && m_bandCount > 0)
174 : {
175 10 : int bNoData = false;
176 : const double dfNoData =
177 10 : poSrcDS->GetRasterBand(1)->GetNoDataValue(&bNoData);
178 10 : if (bNoData)
179 10 : m_nodata = CPLSPrintf("%.17g", dfNoData);
180 : }
181 : }
182 :
183 38 : if (m_size.empty())
184 : {
185 1 : ReportError(CE_Failure, CPLE_IllegalArg,
186 : "Argument 'size' should be specified, or 'like' dataset "
187 : "should be specified");
188 1 : return false;
189 : }
190 :
191 51 : if (!m_burnValues.empty() && m_burnValues.size() != 1 &&
192 14 : static_cast<int>(m_burnValues.size()) != m_bandCount)
193 : {
194 2 : if (m_bandCount == 1)
195 : {
196 1 : ReportError(CE_Failure, CPLE_IllegalArg,
197 : "One value should be provided for argument "
198 : "'burn', given there is one band");
199 : }
200 : else
201 : {
202 1 : ReportError(CE_Failure, CPLE_IllegalArg,
203 : "One or %d values should be provided for argument "
204 : "'burn', given there are %d bands",
205 : m_bandCount, m_bandCount);
206 : }
207 2 : return false;
208 : }
209 :
210 : auto poDriver =
211 35 : GetGDALDriverManager()->GetDriverByName(m_outputFormat.c_str());
212 35 : if (!poDriver)
213 : {
214 : // shouldn't happen given checks done in GDALAlgorithm
215 0 : ReportError(CE_Failure, CPLE_AppDefined, "Cannot find driver %s",
216 : m_outputFormat.c_str());
217 0 : return false;
218 : }
219 :
220 35 : if (m_append)
221 : {
222 2 : if (poDriver->GetMetadataItem(GDAL_DCAP_CREATE_SUBDATASETS) == nullptr)
223 : {
224 1 : ReportError(CE_Failure, CPLE_NotSupported,
225 : "-append option not supported for driver %s",
226 1 : poDriver->GetDescription());
227 1 : return false;
228 : }
229 1 : m_creationOptions.push_back("APPEND_SUBDATASET=YES");
230 : }
231 :
232 : auto poRetDS = std::unique_ptr<GDALDataset>(poDriver->Create(
233 102 : m_outputDataset.GetName().c_str(), m_size[0], m_size[1], m_bandCount,
234 : GDALGetDataTypeByName(m_type.c_str()),
235 68 : CPLStringList(m_creationOptions).List()));
236 34 : if (!poRetDS)
237 : {
238 1 : return false;
239 : }
240 :
241 33 : if (!m_crs.empty() && m_crs != "none" && m_crs != "null")
242 : {
243 13 : oSRS.SetFromUserInput(m_crs.c_str());
244 13 : oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
245 : }
246 :
247 33 : if (!oSRS.IsEmpty())
248 : {
249 22 : if (poRetDS->SetSpatialRef(&oSRS) != CE_None)
250 : {
251 0 : ReportError(CE_Failure, CPLE_AppDefined, "Setting CRS failed");
252 0 : return false;
253 : }
254 : }
255 :
256 33 : if (!m_bbox.empty())
257 : {
258 13 : if (poRetDS->GetRasterXSize() == 0 || poRetDS->GetRasterYSize() == 0)
259 : {
260 0 : ReportError(CE_Failure, CPLE_AppDefined,
261 : "Cannot set extent because one of dataset height or "
262 : "width is null");
263 0 : return false;
264 : }
265 13 : bGTValid = true;
266 13 : adfGT[0] = m_bbox[0];
267 13 : adfGT[1] = (m_bbox[2] - m_bbox[0]) / poRetDS->GetRasterXSize();
268 13 : adfGT[2] = 0;
269 13 : adfGT[3] = m_bbox[3];
270 13 : adfGT[4] = 0;
271 13 : adfGT[5] = -(m_bbox[3] - m_bbox[1]) / poRetDS->GetRasterYSize();
272 : }
273 33 : if (bGTValid)
274 : {
275 23 : if (poRetDS->SetGeoTransform(adfGT) != CE_None)
276 : {
277 0 : ReportError(CE_Failure, CPLE_AppDefined, "Setting extent failed");
278 0 : return false;
279 : }
280 : }
281 :
282 33 : if (!m_nodata.empty() && !EQUAL(m_nodata.c_str(), "none"))
283 : {
284 68 : for (int i = 0; i < poRetDS->GetRasterCount(); ++i)
285 : {
286 45 : bool bCannotBeExactlyRepresented = false;
287 45 : if (poRetDS->GetRasterBand(i + 1)->SetNoDataValueAsString(
288 45 : m_nodata.c_str(), &bCannotBeExactlyRepresented) != CE_None)
289 : {
290 1 : if (bCannotBeExactlyRepresented)
291 : {
292 1 : ReportError(CE_Failure, CPLE_AppDefined,
293 : "Setting nodata value failed as it cannot be "
294 : "represented on its data type");
295 : }
296 : else
297 : {
298 0 : ReportError(CE_Failure, CPLE_AppDefined,
299 : "Setting nodata value failed");
300 : }
301 1 : return false;
302 : }
303 : }
304 : }
305 :
306 32 : if (m_copyMetadata)
307 : {
308 2 : if (!poSrcDS)
309 : {
310 1 : ReportError(CE_Failure, CPLE_AppDefined,
311 : "Argument 'copy-metadata' can only be set when an "
312 : "input dataset is set");
313 1 : return false;
314 : }
315 : {
316 1 : const CPLStringList aosDomains(poSrcDS->GetMetadataDomainList());
317 4 : for (const char *domain : aosDomains)
318 : {
319 3 : if (!EQUAL(domain, "IMAGE_STRUCTURE"))
320 : {
321 4 : if (poRetDS->SetMetadata(poSrcDS->GetMetadata(domain),
322 4 : domain) != CE_None)
323 : {
324 0 : ReportError(CE_Failure, CPLE_AppDefined,
325 : "Cannot copy '%s' metadata domain", domain);
326 0 : return false;
327 : }
328 : }
329 : }
330 : }
331 3 : for (int i = 0; i < m_bandCount; ++i)
332 : {
333 : const CPLStringList aosDomains(
334 2 : poSrcDS->GetRasterBand(i + 1)->GetMetadataDomainList());
335 3 : for (const char *domain : aosDomains)
336 : {
337 1 : if (!EQUAL(domain, "IMAGE_STRUCTURE"))
338 : {
339 2 : if (poRetDS->GetRasterBand(i + 1)->SetMetadata(
340 1 : poSrcDS->GetRasterBand(i + 1)->GetMetadata(domain),
341 2 : domain) != CE_None)
342 : {
343 0 : ReportError(
344 : CE_Failure, CPLE_AppDefined,
345 : "Cannot copy '%s' metadata domain for band %d",
346 : domain, i + 1);
347 0 : return false;
348 : }
349 : }
350 : }
351 : }
352 : }
353 :
354 62 : const CPLStringList aosMD(m_metadata);
355 43 : for (const auto &[key, value] : cpl::IterateNameValue(aosMD))
356 : {
357 12 : if (poRetDS->SetMetadataItem(key, value) != CE_None)
358 : {
359 0 : ReportError(CE_Failure, CPLE_AppDefined,
360 : "SetMetadataItem('%s', '%s') failed", key, value);
361 0 : return false;
362 : }
363 : }
364 :
365 31 : if (m_copyOverviews && m_bandCount > 0)
366 : {
367 3 : if (!poSrcDS)
368 : {
369 1 : ReportError(CE_Failure, CPLE_AppDefined,
370 : "Argument 'copy-overviews' can only be set when an "
371 : "input dataset is set");
372 2 : return false;
373 : }
374 3 : if (poSrcDS->GetRasterXSize() != poRetDS->GetRasterXSize() ||
375 1 : poSrcDS->GetRasterYSize() != poRetDS->GetRasterYSize())
376 : {
377 1 : ReportError(CE_Failure, CPLE_AppDefined,
378 : "Argument 'copy-overviews' can only be set when the "
379 : "input and output datasets have the same dimension");
380 1 : return false;
381 : }
382 : const int nOverviewCount =
383 1 : poSrcDS->GetRasterBand(1)->GetOverviewCount();
384 1 : std::vector<int> anLevels;
385 2 : for (int i = 0; i < nOverviewCount; ++i)
386 : {
387 1 : const auto poOvrBand = poSrcDS->GetRasterBand(1)->GetOverview(i);
388 1 : const int nOvrFactor = GDALComputeOvFactor(
389 : poOvrBand->GetXSize(), poSrcDS->GetRasterXSize(),
390 1 : poOvrBand->GetYSize(), poSrcDS->GetRasterYSize());
391 1 : anLevels.push_back(nOvrFactor);
392 : }
393 2 : if (poRetDS->BuildOverviews(
394 1 : "NONE", nOverviewCount, anLevels.data(),
395 : /* nListBands = */ 0, /* panBandList = */ nullptr,
396 : /* pfnProgress = */ nullptr, /* pProgressData = */ nullptr,
397 1 : /* options = */ nullptr) != CE_None)
398 : {
399 0 : ReportError(CE_Failure, CPLE_AppDefined,
400 : "Creating overview(s) failed");
401 0 : return false;
402 : }
403 : }
404 :
405 29 : if (!m_burnValues.empty())
406 : {
407 36 : for (int i = 0; i < m_bandCount; ++i)
408 : {
409 24 : const int burnValueIdx = m_burnValues.size() == 1 ? 0 : i;
410 24 : const auto poDstBand = poRetDS->GetRasterBand(i + 1);
411 : // cppcheck-suppress negativeContainerIndex
412 24 : if (poDstBand->Fill(m_burnValues[burnValueIdx]) != CE_None)
413 : {
414 0 : ReportError(CE_Failure, CPLE_AppDefined,
415 : "Setting burn value failed");
416 0 : return false;
417 : }
418 : }
419 12 : if (poRetDS->FlushCache(false) != CE_None)
420 : {
421 0 : ReportError(CE_Failure, CPLE_AppDefined,
422 : "Setting burn value failed");
423 0 : return false;
424 : }
425 : }
426 :
427 29 : m_outputDataset.Set(std::move(poRetDS));
428 :
429 29 : return true;
430 : }
431 :
432 : //! @endcond
|