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