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