Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: "edit" step of "raster pipeline"
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_edit.h"
14 :
15 : #include "gdal_priv.h"
16 : #include "gdal_utils.h"
17 : #include "ogrsf_frmts.h"
18 :
19 : #include <optional>
20 :
21 : //! @cond Doxygen_Suppress
22 :
23 : #ifndef _
24 : #define _(x) (x)
25 : #endif
26 :
27 : /************************************************************************/
28 : /* GetGCPFilename() */
29 : /************************************************************************/
30 :
31 29 : static std::string GetGCPFilename(const std::vector<std::string> &gcps)
32 : {
33 29 : if (gcps.size() == 1 && !gcps[0].empty() && gcps[0][0] == '@')
34 : {
35 12 : return gcps[0].substr(1);
36 : }
37 17 : return std::string();
38 : }
39 :
40 : /************************************************************************/
41 : /* GDALRasterEditAlgorithm::GDALRasterEditAlgorithm() */
42 : /************************************************************************/
43 :
44 217 : GDALRasterEditAlgorithm::GDALRasterEditAlgorithm(bool standaloneStep)
45 : : GDALRasterPipelineStepAlgorithm(
46 : NAME, DESCRIPTION, HELP_URL,
47 217 : ConstructorOptions().SetAddDefaultArguments(false))
48 : {
49 217 : if (standaloneStep)
50 : {
51 127 : AddProgressArg();
52 :
53 : AddArg("dataset", 0,
54 : _("Dataset (to be updated in-place, unless --auxiliary)"),
55 254 : &m_dataset, GDAL_OF_RASTER | GDAL_OF_UPDATE)
56 127 : .SetPositional()
57 127 : .SetRequired()
58 127 : .SetAvailableInPipelineStep(false);
59 127 : AddOpenOptionsArg(&m_openOptions).SetAvailableInPipelineStep(false);
60 : AddArg("auxiliary", 0,
61 : _("Ask for an auxiliary .aux.xml file to be edited"),
62 254 : &m_readOnly)
63 254 : .AddHiddenAlias("ro")
64 254 : .AddHiddenAlias(GDAL_ARG_NAME_READ_ONLY)
65 127 : .SetAvailableInPipelineStep(false);
66 : }
67 : else
68 : {
69 90 : AddRasterHiddenInputDatasetArg();
70 : }
71 :
72 217 : AddBandArg(&m_band, _("Active band (1-based index)"));
73 :
74 434 : AddArg("crs", 0, _("Override CRS (without reprojection)"), &m_overrideCrs)
75 434 : .AddHiddenAlias("a_srs")
76 434 : .AddHiddenAlias("srs")
77 217 : .SetIsCRSArg(/*noneAllowed=*/true);
78 :
79 217 : AddBBOXArg(&m_bbox);
80 :
81 217 : AddNodataArg(&m_nodata, /* noneAllowed = */ true);
82 :
83 : AddArg("color-interpretation", 0, _("Set band color interpretation"),
84 434 : &m_colorInterpretation)
85 434 : .SetMetaVar("[all|<BAND>=]<COLOR-INTEPRETATION>")
86 : .SetAutoCompleteFunction(
87 5 : [this](const std::string &s)
88 : {
89 3 : std::vector<std::string> ret;
90 3 : int nValues = 0;
91 3 : const auto paeVals = GDALGetColorInterpretationList(&nValues);
92 3 : if (s.find('=') == std::string::npos)
93 : {
94 2 : ret.push_back("all=");
95 2 : if (auto poDS = m_dataset.GetDatasetRef())
96 : {
97 2 : for (int i = 0; i < poDS->GetRasterCount(); ++i)
98 1 : ret.push_back(std::to_string(i + 1).append("="));
99 : }
100 70 : for (int i = 0; i < nValues; ++i)
101 136 : ret.push_back(
102 68 : GDALGetColorInterpretationName(paeVals[i]));
103 : }
104 : else
105 : {
106 35 : for (int i = 0; i < nValues; ++i)
107 68 : ret.push_back(
108 34 : GDALGetColorInterpretationName(paeVals[i]));
109 : }
110 6 : return ret;
111 217 : });
112 :
113 434 : AddArg("color-map", 0, _("Color map filename"), &m_colorMap)
114 434 : .SetMutualExclusionGroup("color-table")
115 217 : .AddHiddenAlias("color-table");
116 :
117 : const auto ValidationActionScaleOffset =
118 44 : [this](const char *argName, const std::vector<std::string> &values)
119 : {
120 106 : for (const std::string &s : values)
121 : {
122 74 : bool valid = true;
123 74 : const auto nPos = s.find('=');
124 74 : if (nPos != std::string::npos)
125 : {
126 40 : if (CPLGetValueType(s.substr(0, nPos).c_str()) !=
127 58 : CPL_VALUE_INTEGER ||
128 38 : CPLGetValueType(s.substr(nPos + 1).c_str()) ==
129 : CPL_VALUE_STRING)
130 : {
131 4 : valid = false;
132 : }
133 : }
134 54 : else if (CPLGetValueType(s.c_str()) == CPL_VALUE_STRING)
135 : {
136 2 : valid = false;
137 : }
138 74 : if (!valid)
139 : {
140 6 : ReportError(CE_Failure, CPLE_IllegalArg,
141 : "Invalid value '%s' for '%s'", s.c_str(), argName);
142 6 : return false;
143 : }
144 : }
145 32 : return true;
146 217 : };
147 :
148 434 : AddArg("scale", 0, _("Override band scale factor"), &m_scale)
149 434 : .SetMetaVar("[<BAND>=]<SCALE>")
150 : .AddValidationAction(
151 19 : [this, ValidationActionScaleOffset]()
152 236 : { return ValidationActionScaleOffset("scale", m_scale); });
153 :
154 434 : AddArg("offset", 0, _("Override band offset constant"), &m_offset)
155 434 : .SetMetaVar("[<BAND>=]<OFFSET>")
156 : .AddValidationAction(
157 19 : [this, ValidationActionScaleOffset]()
158 236 : { return ValidationActionScaleOffset("offset", m_offset); });
159 :
160 : {
161 : auto &arg = AddArg("metadata", 0, _("Add/update dataset metadata item"),
162 434 : &m_metadata)
163 434 : .SetMetaVar("<KEY>=<VALUE>")
164 217 : .SetPackedValuesAllowed(false);
165 46 : arg.AddValidationAction([this, &arg]()
166 263 : { return ParseAndValidateKeyValue(arg); });
167 217 : arg.AddHiddenAlias("mo");
168 : }
169 :
170 : AddArg("unset-color-table", 0,
171 : _("Unset color table on 1st band or the one specified with --band"),
172 434 : &m_unsetColorTable)
173 217 : .SetMutualExclusionGroup("color-table");
174 :
175 : AddArg("unset-metadata", 0, _("Remove dataset metadata item(s)"),
176 434 : &m_unsetMetadata)
177 217 : .SetMetaVar("<KEY>");
178 :
179 : AddArg("unset-metadata-domain", 0, _("Remove dataset metadata domain(s)"),
180 434 : &m_unsetMetadataDomain)
181 217 : .SetMetaVar("<DOMAIN>");
182 :
183 : AddArg("gcp", 0,
184 : _("Add ground control point, formatted as "
185 : "pixel,line,easting,northing[,elevation], or @filename"),
186 434 : &m_gcps)
187 217 : .SetPackedValuesAllowed(false)
188 : .AddValidationAction(
189 36 : [this]()
190 : {
191 21 : if (GetGCPFilename(m_gcps).empty())
192 : {
193 28 : for (const std::string &gcp : m_gcps)
194 : {
195 : const CPLStringList aosTokens(
196 17 : CSLTokenizeString2(gcp.c_str(), ",", 0));
197 17 : if (aosTokens.size() != 4 && aosTokens.size() != 5)
198 : {
199 1 : ReportError(CE_Failure, CPLE_IllegalArg,
200 : "Bad format for %s", gcp.c_str());
201 1 : return false;
202 : }
203 85 : for (int i = 0; i < aosTokens.size(); ++i)
204 : {
205 70 : if (CPLGetValueType(aosTokens[i]) ==
206 : CPL_VALUE_STRING)
207 : {
208 1 : ReportError(CE_Failure, CPLE_IllegalArg,
209 : "Bad format for %s", gcp.c_str());
210 1 : return false;
211 : }
212 : }
213 : }
214 : }
215 19 : return true;
216 217 : });
217 :
218 217 : if (standaloneStep)
219 : {
220 254 : AddArg("stats", 0, _("Compute statistics, using all pixels"), &m_stats)
221 127 : .SetMutualExclusionGroup("stats");
222 : AddArg("approx-stats", 0,
223 : _("Compute statistics, using a subset of pixels"),
224 254 : &m_approxStats)
225 127 : .SetMutualExclusionGroup("stats");
226 127 : AddArg("hist", 0, _("Compute histogram"), &m_hist);
227 : }
228 217 : }
229 :
230 : /************************************************************************/
231 : /* GDALRasterEditAlgorithm::~GDALRasterEditAlgorithm() */
232 : /************************************************************************/
233 :
234 : GDALRasterEditAlgorithm::~GDALRasterEditAlgorithm() = default;
235 :
236 : /************************************************************************/
237 : /* ParseGCPs() */
238 : /************************************************************************/
239 :
240 8 : std::vector<gdal::GCP> GDALRasterEditAlgorithm::ParseGCPs() const
241 : {
242 8 : std::vector<gdal::GCP> ret;
243 16 : const std::string osGCPFilename = GetGCPFilename(m_gcps);
244 8 : if (!osGCPFilename.empty())
245 : {
246 : auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
247 4 : osGCPFilename.c_str(), GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR));
248 4 : if (!poDS)
249 1 : return ret;
250 3 : if (poDS->GetLayerCount() != 1)
251 : {
252 1 : ReportError(CE_Failure, CPLE_AppDefined,
253 : "GCPs can only be specified for single-layer datasets");
254 1 : return ret;
255 : }
256 2 : auto poLayer = poDS->GetLayer(0);
257 2 : const auto poLayerDefn = poLayer->GetLayerDefn();
258 2 : int nIdIdx = -1, nInfoIdx = -1, nColIdx = -1, nLineIdx = -1, nXIdx = -1,
259 2 : nYIdx = -1, nZIdx = -1;
260 :
261 : const struct
262 : {
263 : int &idx;
264 : const char *name;
265 : bool required;
266 2 : } aFields[] = {
267 : {nIdIdx, "id", false}, {nInfoIdx, "info", false},
268 : {nColIdx, "column", true}, {nLineIdx, "line", true},
269 : {nXIdx, "x", true}, {nYIdx, "y", true},
270 : {nZIdx, "z", false},
271 2 : };
272 :
273 11 : for (auto &field : aFields)
274 : {
275 10 : field.idx = poLayerDefn->GetFieldIndex(field.name);
276 10 : if (field.idx < 0 && field.required)
277 : {
278 3 : ReportError(CE_Failure, CPLE_AppDefined,
279 1 : "Field '%s' cannot be found in '%s'", field.name,
280 1 : poDS->GetDescription());
281 1 : return ret;
282 : }
283 : }
284 2 : for (auto &&poFeature : poLayer)
285 : {
286 2 : gdal::GCP gcp;
287 1 : if (nIdIdx >= 0)
288 1 : gcp.SetId(poFeature->GetFieldAsString(nIdIdx));
289 1 : if (nInfoIdx >= 0)
290 1 : gcp.SetInfo(poFeature->GetFieldAsString(nInfoIdx));
291 1 : gcp.Pixel() = poFeature->GetFieldAsDouble(nColIdx);
292 1 : gcp.Line() = poFeature->GetFieldAsDouble(nLineIdx);
293 1 : gcp.X() = poFeature->GetFieldAsDouble(nXIdx);
294 1 : gcp.Y() = poFeature->GetFieldAsDouble(nYIdx);
295 1 : if (nZIdx >= 0 && poFeature->IsFieldSetAndNotNull(nZIdx))
296 1 : gcp.Z() = poFeature->GetFieldAsDouble(nZIdx);
297 1 : ret.push_back(std::move(gcp));
298 : }
299 : }
300 : else
301 : {
302 10 : for (const std::string &gcpStr : m_gcps)
303 : {
304 : const CPLStringList aosTokens(
305 12 : CSLTokenizeString2(gcpStr.c_str(), ",", 0));
306 : // Verified by validation action
307 6 : CPLAssert(aosTokens.size() == 4 || aosTokens.size() == 5);
308 12 : gdal::GCP gcp;
309 6 : gcp.Pixel() = CPLAtof(aosTokens[0]);
310 6 : gcp.Line() = CPLAtof(aosTokens[1]);
311 6 : gcp.X() = CPLAtof(aosTokens[2]);
312 6 : gcp.Y() = CPLAtof(aosTokens[3]);
313 6 : if (aosTokens.size() == 5)
314 3 : gcp.Z() = CPLAtof(aosTokens[4]);
315 6 : ret.push_back(std::move(gcp));
316 : }
317 : }
318 5 : return ret;
319 : }
320 :
321 : /************************************************************************/
322 : /* GDALRasterEditAlgorithm::RunStep() */
323 : /************************************************************************/
324 :
325 88 : bool GDALRasterEditAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
326 : {
327 88 : GDALDataset *poDS = m_dataset.GetDatasetRef();
328 88 : if (poDS)
329 : {
330 : // Standalone mode
331 69 : if (poDS->GetAccess() != GA_Update && !m_readOnly)
332 : {
333 1 : ReportError(CE_Failure, CPLE_AppDefined,
334 : "Dataset should be opened in update mode unless "
335 : "--auxiliary is set");
336 1 : return false;
337 : }
338 : }
339 : else
340 : {
341 : // Pipeline mode
342 19 : const auto poSrcDS = m_inputDataset[0].GetDatasetRef();
343 19 : CPLAssert(poSrcDS);
344 19 : CPLAssert(m_outputDataset.GetName().empty());
345 19 : CPLAssert(!m_outputDataset.GetDatasetRef());
346 19 : auto poDriver = poSrcDS->GetDriver();
347 23 : if (poDriver && EQUAL(poDriver->GetDescription(), "VRT") &&
348 4 : poSrcDS->GetDescription()[0] == '\0')
349 : {
350 : // We can directly edit an anonymous input VRT file
351 : // and we actually need to do that since the generic code path
352 : // in the other branch will try to serialize and deserialize a XML
353 : // file pointing to an anonymous source.
354 4 : m_outputDataset.Set(poSrcDS);
355 : }
356 : else
357 : {
358 : // Create a in-memory VRT to avoid modifying the source dataset.
359 15 : CPLStringList aosOptions;
360 15 : aosOptions.push_back("-of");
361 15 : aosOptions.push_back("VRT");
362 : GDALTranslateOptions *psOptions =
363 15 : GDALTranslateOptionsNew(aosOptions.List(), nullptr);
364 15 : GDALDatasetH hSrcDS = GDALDataset::ToHandle(poSrcDS);
365 15 : auto poRetDS = GDALDataset::FromHandle(
366 : GDALTranslate("", hSrcDS, psOptions, nullptr));
367 15 : GDALTranslateOptionsFree(psOptions);
368 15 : m_outputDataset.Set(std::unique_ptr<GDALDataset>(poRetDS));
369 : }
370 19 : poDS = m_outputDataset.GetDatasetRef();
371 : }
372 :
373 87 : bool ret = poDS != nullptr;
374 :
375 87 : if (poDS)
376 : {
377 87 : if (m_overrideCrs == "null" || m_overrideCrs == "none")
378 : {
379 3 : if (poDS->SetSpatialRef(nullptr) != CE_None)
380 : {
381 1 : ReportError(CE_Failure, CPLE_AppDefined,
382 : "SetSpatialRef(%s) failed", m_overrideCrs.c_str());
383 38 : return false;
384 : }
385 : }
386 84 : else if (!m_overrideCrs.empty() && m_gcps.empty())
387 : {
388 4 : OGRSpatialReference oSRS;
389 4 : oSRS.SetFromUserInput(m_overrideCrs.c_str());
390 4 : oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
391 4 : if (poDS->SetSpatialRef(&oSRS) != CE_None)
392 : {
393 1 : ReportError(CE_Failure, CPLE_AppDefined,
394 : "SetSpatialRef(%s) failed", m_overrideCrs.c_str());
395 1 : return false;
396 : }
397 : }
398 :
399 85 : if (!m_bbox.empty())
400 : {
401 4 : if (poDS->GetRasterXSize() == 0 || poDS->GetRasterYSize() == 0)
402 : {
403 1 : ReportError(CE_Failure, CPLE_AppDefined,
404 : "Cannot set extent because one of dataset height "
405 : "or width is null");
406 2 : return false;
407 : }
408 3 : GDALGeoTransform gt;
409 3 : gt.xorig = m_bbox[0];
410 3 : gt.xscale = (m_bbox[2] - m_bbox[0]) / poDS->GetRasterXSize();
411 3 : gt.xrot = 0;
412 3 : gt.yorig = m_bbox[3];
413 3 : gt.yrot = 0;
414 3 : gt.yscale = -(m_bbox[3] - m_bbox[1]) / poDS->GetRasterYSize();
415 3 : if (poDS->SetGeoTransform(gt) != CE_None)
416 : {
417 1 : ReportError(CE_Failure, CPLE_AppDefined,
418 : "Setting extent failed");
419 1 : return false;
420 : }
421 : }
422 :
423 83 : if (!m_nodata.empty())
424 : {
425 18 : for (int i = 0; i < poDS->GetRasterCount(); ++i)
426 : {
427 10 : if (EQUAL(m_nodata.c_str(), "none"))
428 2 : poDS->GetRasterBand(i + 1)->DeleteNoDataValue();
429 : else
430 16 : poDS->GetRasterBand(i + 1)->SetNoDataValue(
431 8 : CPLAtof(m_nodata.c_str()));
432 : }
433 : }
434 :
435 83 : if (!m_colorMap.empty())
436 : {
437 0 : std::unique_ptr<GDALDataset> poAuxDS;
438 : {
439 7 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
440 14 : poAuxDS = std::unique_ptr<GDALDataset>(
441 7 : GDALDataset::Open(m_colorMap.c_str(), GDAL_OF_RASTER));
442 : }
443 7 : if (m_band == 0)
444 7 : m_band = 1;
445 7 : if (poDS->GetRasterCount() < m_band)
446 : {
447 1 : ReportError(CE_Failure, CPLE_AppDefined,
448 : "Band %d is not valid for %s", m_band,
449 1 : poDS->GetDescription());
450 1 : return false;
451 : }
452 6 : if (poAuxDS)
453 : {
454 3 : if (poAuxDS->GetRasterCount() == 0)
455 : {
456 1 : ReportError(CE_Failure, CPLE_AppDefined,
457 : "%s has no raster band", m_colorMap.c_str());
458 1 : return false;
459 : }
460 2 : const auto poCT = poAuxDS->GetRasterBand(1)->GetColorTable();
461 2 : if (!poCT)
462 : {
463 1 : ReportError(CE_Failure, CPLE_AppDefined,
464 : "%s has no color table", m_colorMap.c_str());
465 1 : return false;
466 : }
467 1 : if (poDS->GetRasterBand(m_band)->SetColorTable(poCT) != CE_None)
468 : {
469 0 : return false;
470 : }
471 1 : if (m_colorInterpretation.empty())
472 : {
473 1 : poDS->GetRasterBand(m_band)->SetColorInterpretation(
474 1 : GCI_PaletteIndex);
475 : }
476 : }
477 : else
478 : {
479 : std::vector<GDALColorAssociation> asColors =
480 : GDALLoadTextColorMap(m_colorMap.c_str(),
481 3 : poDS->GetRasterBand(m_band));
482 3 : if (asColors.empty())
483 : {
484 1 : ReportError(CE_Failure, CPLE_AppDefined,
485 : "%s has no color table", m_colorMap.c_str());
486 1 : return false;
487 : }
488 2 : GDALColorTable oCT;
489 3 : for (const auto &sColor : asColors)
490 : {
491 2 : if (!(sColor.dfVal >= 0 && sColor.dfVal < 65536 &&
492 2 : static_cast<int>(sColor.dfVal) == sColor.dfVal))
493 : {
494 1 : ReportError(CE_Failure, CPLE_AppDefined,
495 : "Value %f of color map is not compatible "
496 : "of an integer index",
497 1 : sColor.dfVal);
498 1 : return false;
499 : }
500 : GDALColorEntry entry;
501 1 : entry.c1 = static_cast<short>(sColor.nR);
502 1 : entry.c2 = static_cast<short>(sColor.nG);
503 1 : entry.c3 = static_cast<short>(sColor.nB);
504 1 : entry.c4 = static_cast<short>(sColor.nA);
505 1 : oCT.SetColorEntry(static_cast<int>(sColor.dfVal), &entry);
506 : }
507 1 : if (poDS->GetRasterBand(m_band)->SetColorTable(&oCT) != CE_None)
508 : {
509 0 : return false;
510 : }
511 1 : if (m_colorInterpretation.empty())
512 : {
513 1 : poDS->GetRasterBand(m_band)->SetColorInterpretation(
514 1 : GCI_PaletteIndex);
515 : }
516 : }
517 : }
518 76 : else if (m_unsetColorTable)
519 : {
520 2 : if (m_band == 0)
521 2 : m_band = 1;
522 2 : if (poDS->GetRasterCount() < m_band)
523 : {
524 1 : ReportError(CE_Failure, CPLE_AppDefined,
525 : "Band %d is not valid for %s", m_band,
526 1 : poDS->GetDescription());
527 1 : return false;
528 : }
529 1 : if (poDS->GetRasterBand(m_band)->SetColorTable(nullptr) != CE_None)
530 : {
531 0 : return false;
532 : }
533 1 : if (m_colorInterpretation.empty())
534 : {
535 1 : poDS->GetRasterBand(m_band)->SetColorInterpretation(
536 1 : GCI_Undefined);
537 : }
538 : }
539 :
540 77 : if (!m_colorInterpretation.empty())
541 : {
542 : const auto GetColorInterp =
543 28 : [this](const char *pszStr) -> std::optional<GDALColorInterp>
544 : {
545 25 : if (EQUAL(pszStr, "undefined"))
546 0 : return GCI_Undefined;
547 : const GDALColorInterp eInterp =
548 25 : GDALGetColorInterpretationByName(pszStr);
549 25 : if (eInterp != GCI_Undefined)
550 22 : return eInterp;
551 3 : ReportError(CE_Failure, CPLE_NotSupported,
552 : "Unsupported color interpretation: %s", pszStr);
553 3 : return {};
554 18 : };
555 :
556 18 : if (m_colorInterpretation.size() == 1 &&
557 20 : poDS->GetRasterCount() > 1 &&
558 2 : !cpl::starts_with(m_colorInterpretation[0], "all="))
559 : {
560 1 : ReportError(
561 : CE_Failure, CPLE_NotSupported,
562 : "With several bands, specify as many color interpretation "
563 : "as bands, one or many values of the form "
564 : "<band_number>=<color> or a single value all=<color>");
565 12 : return false;
566 : }
567 : else
568 : {
569 17 : int nBandIter = 0;
570 17 : bool bSyntaxAll = false;
571 17 : bool bSyntaxExplicitBand = false;
572 17 : bool bSyntaxImplicitBand = false;
573 39 : for (const std::string &token : m_colorInterpretation)
574 : {
575 : const CPLStringList aosTokens(
576 29 : CSLTokenizeString2(token.c_str(), "=", 0));
577 29 : if (aosTokens.size() == 2 && EQUAL(aosTokens[0], "all"))
578 : {
579 4 : bSyntaxAll = true;
580 4 : const auto eColorInterp = GetColorInterp(aosTokens[1]);
581 4 : if (!eColorInterp)
582 : {
583 1 : return false;
584 : }
585 : else
586 : {
587 10 : for (int i = 0; i < poDS->GetRasterCount(); ++i)
588 : {
589 7 : if (poDS->GetRasterBand(i + 1)
590 7 : ->SetColorInterpretation(
591 14 : *eColorInterp) != CE_None)
592 0 : return false;
593 : }
594 : }
595 : }
596 25 : else if (aosTokens.size() == 2)
597 : {
598 9 : bSyntaxExplicitBand = true;
599 9 : const int nBand = atoi(aosTokens[0]);
600 9 : if (nBand <= 0 || nBand > poDS->GetRasterCount())
601 : {
602 2 : ReportError(CE_Failure, CPLE_NotSupported,
603 : "Invalid band number '%s' in '%s'",
604 : aosTokens[0], token.c_str());
605 3 : return false;
606 : }
607 7 : const auto eColorInterp = GetColorInterp(aosTokens[1]);
608 7 : if (!eColorInterp)
609 : {
610 1 : return false;
611 : }
612 6 : else if (poDS->GetRasterBand(nBand)
613 6 : ->SetColorInterpretation(*eColorInterp) !=
614 : CE_None)
615 : {
616 0 : return false;
617 : }
618 : }
619 : else
620 : {
621 16 : bSyntaxImplicitBand = true;
622 16 : ++nBandIter;
623 16 : if (nBandIter > poDS->GetRasterCount())
624 : {
625 2 : ReportError(CE_Failure, CPLE_IllegalArg,
626 : "More color interpretation values "
627 : "specified than bands in the dataset");
628 3 : return false;
629 : }
630 14 : const auto eColorInterp = GetColorInterp(token.c_str());
631 14 : if (!eColorInterp)
632 : {
633 1 : return false;
634 : }
635 13 : else if (poDS->GetRasterBand(nBandIter)
636 13 : ->SetColorInterpretation(*eColorInterp) !=
637 : CE_None)
638 : {
639 0 : return false;
640 : }
641 : }
642 : }
643 20 : if ((bSyntaxAll ? 1 : 0) + (bSyntaxExplicitBand ? 1 : 0) +
644 10 : (bSyntaxImplicitBand ? 1 : 0) !=
645 : 1)
646 : {
647 3 : ReportError(CE_Failure, CPLE_IllegalArg,
648 : "Mix of different syntaxes to specify color "
649 : "interpretation");
650 3 : return false;
651 : }
652 7 : if (bSyntaxImplicitBand && nBandIter != poDS->GetRasterCount())
653 : {
654 1 : ReportError(CE_Failure, CPLE_IllegalArg,
655 : "Less color interpretation values specified "
656 : "than bands in the dataset");
657 1 : return false;
658 : }
659 : }
660 : }
661 :
662 : const auto ScaleOffsetSetterLambda =
663 16 : [this, poDS](const char *argName,
664 : const std::vector<std::string> &values,
665 84 : CPLErr (GDALRasterBand::*Setter)(double))
666 : {
667 16 : if (values.size() == 1 && values[0].find('=') == std::string::npos)
668 : {
669 2 : const double dfScale = CPLAtof(values[0].c_str());
670 8 : for (int i = 0; i < poDS->GetRasterCount(); ++i)
671 : {
672 6 : if ((poDS->GetRasterBand(i + 1)->*Setter)(dfScale) !=
673 : CE_None)
674 0 : return false;
675 : }
676 : }
677 : else
678 : {
679 14 : int nBandIter = 0;
680 14 : bool bSyntaxExplicitBand = false;
681 14 : bool bSyntaxImplicitBand = false;
682 40 : for (const std::string &token : values)
683 : {
684 : const CPLStringList aosTokens(
685 32 : CSLTokenizeString2(token.c_str(), "=", 0));
686 32 : if (aosTokens.size() == 2)
687 : {
688 8 : bSyntaxExplicitBand = true;
689 8 : const int nBand = atoi(aosTokens[0]);
690 8 : if (nBand <= 0 || nBand > poDS->GetRasterCount())
691 : {
692 4 : ReportError(CE_Failure, CPLE_NotSupported,
693 : "Invalid band number '%s' in '%s'",
694 : aosTokens[0], token.c_str());
695 4 : return false;
696 : }
697 4 : const double dfScale = CPLAtof(aosTokens[1]);
698 4 : if ((poDS->GetRasterBand(nBand)->*Setter)(dfScale) !=
699 : CE_None)
700 : {
701 0 : return false;
702 : }
703 : }
704 : else
705 : {
706 24 : bSyntaxImplicitBand = true;
707 24 : ++nBandIter;
708 24 : if (nBandIter > poDS->GetRasterCount())
709 : {
710 2 : ReportError(CE_Failure, CPLE_IllegalArg,
711 : "More %s values "
712 : "specified than bands in the dataset",
713 : argName);
714 2 : return false;
715 : }
716 22 : const double dfScale = CPLAtof(token.c_str());
717 22 : if ((poDS->GetRasterBand(nBandIter)->*Setter)(
718 22 : dfScale) != CE_None)
719 : {
720 0 : return false;
721 : }
722 : }
723 : }
724 16 : if (((bSyntaxExplicitBand ? 1 : 0) +
725 8 : (bSyntaxImplicitBand ? 1 : 0)) != 1)
726 : {
727 2 : ReportError(CE_Failure, CPLE_IllegalArg,
728 : "Mix of different syntaxes to specify %s",
729 : argName);
730 2 : return false;
731 : }
732 6 : if (bSyntaxImplicitBand && nBandIter != poDS->GetRasterCount())
733 : {
734 2 : ReportError(CE_Failure, CPLE_IllegalArg,
735 : "Less %s values specified "
736 : "than bands in the dataset",
737 : argName);
738 2 : return false;
739 : }
740 : }
741 :
742 6 : return true;
743 65 : };
744 :
745 65 : if (!m_scale.empty())
746 : {
747 8 : if (!ScaleOffsetSetterLambda("scale", m_scale,
748 : &GDALRasterBand::SetScale))
749 5 : return false;
750 : }
751 :
752 60 : if (!m_offset.empty())
753 : {
754 8 : if (!ScaleOffsetSetterLambda("offset", m_offset,
755 : &GDALRasterBand::SetOffset))
756 5 : return false;
757 : }
758 :
759 55 : const CPLStringList aosMD(m_metadata);
760 67 : for (const auto &[key, value] : cpl::IterateNameValue(aosMD))
761 : {
762 13 : if (poDS->SetMetadataItem(key, value) != CE_None)
763 : {
764 1 : ReportError(CE_Failure, CPLE_AppDefined,
765 : "SetMetadataItem('%s', '%s') failed", key, value);
766 1 : return false;
767 : }
768 : }
769 :
770 56 : for (const std::string &key : m_unsetMetadata)
771 : {
772 3 : if (poDS->SetMetadataItem(key.c_str(), nullptr) != CE_None)
773 : {
774 1 : ReportError(CE_Failure, CPLE_AppDefined,
775 : "SetMetadataItem('%s', NULL) failed", key.c_str());
776 1 : return false;
777 : }
778 : }
779 :
780 54 : for (const std::string &domain : m_unsetMetadataDomain)
781 : {
782 1 : if (poDS->SetMetadata(nullptr, domain.c_str()) != CE_None)
783 : {
784 0 : ReportError(CE_Failure, CPLE_AppDefined,
785 : "SetMetadata(NULL, '%s') failed", domain.c_str());
786 0 : return false;
787 : }
788 : }
789 :
790 53 : if (!m_gcps.empty())
791 : {
792 8 : const auto gcps = ParseGCPs();
793 8 : if (gcps.empty())
794 3 : return false; // error already emitted by ParseGCPs()
795 :
796 5 : OGRSpatialReference oSRS;
797 5 : if (!m_overrideCrs.empty())
798 : {
799 1 : oSRS.SetFromUserInput(m_overrideCrs.c_str());
800 1 : oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
801 : }
802 :
803 5 : if (poDS->SetGCPs(static_cast<int>(gcps.size()), gcps[0].c_ptr(),
804 10 : oSRS.IsEmpty() ? nullptr : &oSRS) != CE_None)
805 : {
806 1 : ReportError(CE_Failure, CPLE_AppDefined, "Setting GCPs failed");
807 1 : return false;
808 : }
809 : }
810 :
811 49 : const int nBands = poDS->GetRasterCount();
812 49 : int nCurProgress = 0;
813 49 : const double dfTotalProgress =
814 49 : ((m_stats || m_approxStats) ? nBands : 0) + (m_hist ? nBands : 0);
815 49 : if (m_stats || m_approxStats)
816 : {
817 4 : for (int i = 0; (i < nBands) && ret; ++i)
818 : {
819 4 : void *pScaledProgress = GDALCreateScaledProgress(
820 : nCurProgress / dfTotalProgress,
821 2 : (nCurProgress + 1) / dfTotalProgress, ctxt.m_pfnProgress,
822 : ctxt.m_pProgressData);
823 2 : ++nCurProgress;
824 2 : double dfMin = 0.0;
825 2 : double dfMax = 0.0;
826 2 : double dfMean = 0.0;
827 2 : double dfStdDev = 0.0;
828 4 : ret = poDS->GetRasterBand(i + 1)->ComputeStatistics(
829 2 : m_approxStats, &dfMin, &dfMax, &dfMean, &dfStdDev,
830 : pScaledProgress ? GDALScaledProgress : nullptr,
831 2 : pScaledProgress) == CE_None;
832 2 : GDALDestroyScaledProgress(pScaledProgress);
833 : }
834 : }
835 :
836 49 : if (m_hist)
837 : {
838 2 : for (int i = 0; (i < nBands) && ret; ++i)
839 : {
840 2 : void *pScaledProgress = GDALCreateScaledProgress(
841 : nCurProgress / dfTotalProgress,
842 1 : (nCurProgress + 1) / dfTotalProgress, ctxt.m_pfnProgress,
843 : ctxt.m_pProgressData);
844 1 : ++nCurProgress;
845 1 : double dfMin = 0.0;
846 1 : double dfMax = 0.0;
847 1 : int nBucketCount = 0;
848 1 : GUIntBig *panHistogram = nullptr;
849 2 : ret = poDS->GetRasterBand(i + 1)->GetDefaultHistogram(
850 : &dfMin, &dfMax, &nBucketCount, &panHistogram, TRUE,
851 : pScaledProgress ? GDALScaledProgress : nullptr,
852 1 : pScaledProgress) == CE_None;
853 1 : if (ret)
854 : {
855 2 : ret = poDS->GetRasterBand(i + 1)->SetDefaultHistogram(
856 1 : dfMin, dfMax, nBucketCount, panHistogram) ==
857 : CE_None;
858 : }
859 1 : CPLFree(panHistogram);
860 1 : GDALDestroyScaledProgress(pScaledProgress);
861 : }
862 : }
863 : }
864 :
865 49 : return poDS != nullptr;
866 : }
867 :
868 : GDALRasterEditAlgorithmStandalone::~GDALRasterEditAlgorithmStandalone() =
869 : default;
870 :
871 : //! @endcond
|