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