LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_edit.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 418 431 97.0 %
Date: 2026-05-13 23:47:50 Functions: 12 12 100.0 %

          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

Generated by: LCOV version 1.14