LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_edit.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 204 204 100.0 %
Date: 2025-06-19 12:30:01 Functions: 6 6 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             : //! @cond Doxygen_Suppress
      20             : 
      21             : #ifndef _
      22             : #define _(x) (x)
      23             : #endif
      24             : 
      25             : /************************************************************************/
      26             : /*                          GetGCPFilename()                            */
      27             : /************************************************************************/
      28             : 
      29          20 : static std::string GetGCPFilename(const std::vector<std::string> &gcps)
      30             : {
      31          20 :     if (gcps.size() == 1 && !gcps[0].empty() && gcps[0][0] == '@')
      32             :     {
      33           8 :         return gcps[0].substr(1);
      34             :     }
      35          12 :     return std::string();
      36             : }
      37             : 
      38             : /************************************************************************/
      39             : /*          GDALRasterEditAlgorithm::GDALRasterEditAlgorithm()          */
      40             : /************************************************************************/
      41             : 
      42          64 : GDALRasterEditAlgorithm::GDALRasterEditAlgorithm(bool standaloneStep)
      43             :     : GDALRasterPipelineStepAlgorithm(
      44             :           NAME, DESCRIPTION, HELP_URL,
      45          64 :           ConstructorOptions().SetAddDefaultArguments(false))
      46             : {
      47          64 :     if (standaloneStep)
      48             :     {
      49          31 :         AddProgressArg();
      50             : 
      51             :         AddArg("dataset", 0,
      52             :                _("Dataset (to be updated in-place, unless --auxiliary)"),
      53          62 :                &m_dataset, GDAL_OF_RASTER | GDAL_OF_UPDATE)
      54          31 :             .SetPositional()
      55          31 :             .SetRequired();
      56             :         AddArg("auxiliary", 0,
      57             :                _("Ask for an auxiliary .aux.xml file to be edited"),
      58          62 :                &m_readOnly)
      59          62 :             .AddHiddenAlias("ro")
      60          31 :             .AddHiddenAlias(GDAL_ARG_NAME_READ_ONLY);
      61             :     }
      62             : 
      63         128 :     AddArg("crs", 0, _("Override CRS (without reprojection)"), &m_overrideCrs)
      64         128 :         .AddHiddenAlias("a_srs")
      65         128 :         .AddHiddenAlias("srs")
      66          64 :         .SetIsCRSArg(/*noneAllowed=*/true);
      67             : 
      68          64 :     AddBBOXArg(&m_bbox);
      69             : 
      70          64 :     AddNodataArg(&m_nodata, /* noneAllowed = */ true);
      71             : 
      72             :     {
      73             :         auto &arg = AddArg("metadata", 0, _("Add/update dataset metadata item"),
      74         128 :                            &m_metadata)
      75         128 :                         .SetMetaVar("<KEY>=<VALUE>")
      76          64 :                         .SetPackedValuesAllowed(false);
      77           3 :         arg.AddValidationAction([this, &arg]()
      78          67 :                                 { return ParseAndValidateKeyValue(arg); });
      79          64 :         arg.AddHiddenAlias("mo");
      80             :     }
      81             : 
      82             :     AddArg("unset-metadata", 0, _("Remove dataset metadata item"),
      83         128 :            &m_unsetMetadata)
      84          64 :         .SetMetaVar("<KEY>");
      85             : 
      86             :     AddArg("gcp", 0,
      87             :            _("Add ground control point, formatted as "
      88             :              "pixel,line,easting,northing[,elevation], or @filename"),
      89         128 :            &m_gcps)
      90          64 :         .SetPackedValuesAllowed(false)
      91             :         .AddValidationAction(
      92          22 :             [this]()
      93             :             {
      94          12 :                 if (GetGCPFilename(m_gcps).empty())
      95             :                 {
      96          16 :                     for (const std::string &gcp : m_gcps)
      97             :                     {
      98             :                         const CPLStringList aosTokens(
      99          10 :                             CSLTokenizeString2(gcp.c_str(), ",", 0));
     100          10 :                         if (aosTokens.size() != 4 && aosTokens.size() != 5)
     101             :                         {
     102           1 :                             ReportError(CE_Failure, CPLE_IllegalArg,
     103             :                                         "Bad format for %s", gcp.c_str());
     104           1 :                             return false;
     105             :                         }
     106          47 :                         for (int i = 0; i < aosTokens.size(); ++i)
     107             :                         {
     108          39 :                             if (CPLGetValueType(aosTokens[i]) ==
     109             :                                 CPL_VALUE_STRING)
     110             :                             {
     111           1 :                                 ReportError(CE_Failure, CPLE_IllegalArg,
     112             :                                             "Bad format for %s", gcp.c_str());
     113           1 :                                 return false;
     114             :                             }
     115             :                         }
     116             :                     }
     117             :                 }
     118          10 :                 return true;
     119          64 :             });
     120             : 
     121          64 :     if (standaloneStep)
     122             :     {
     123          62 :         AddArg("stats", 0, _("Compute statistics, using all pixels"), &m_stats)
     124          31 :             .SetMutualExclusionGroup("stats");
     125             :         AddArg("approx-stats", 0,
     126             :                _("Compute statistics, using a subset of pixels"),
     127          62 :                &m_approxStats)
     128          31 :             .SetMutualExclusionGroup("stats");
     129          31 :         AddArg("hist", 0, _("Compute histogram"), &m_hist);
     130             :     }
     131          64 : }
     132             : 
     133             : /************************************************************************/
     134             : /*           GDALRasterEditAlgorithm::~GDALRasterEditAlgorithm()        */
     135             : /************************************************************************/
     136             : 
     137             : GDALRasterEditAlgorithm::~GDALRasterEditAlgorithm() = default;
     138             : 
     139             : /************************************************************************/
     140             : /*                              ParseGCPs()                             */
     141             : /************************************************************************/
     142             : 
     143           8 : std::vector<gdal::GCP> GDALRasterEditAlgorithm::ParseGCPs() const
     144             : {
     145           8 :     std::vector<gdal::GCP> ret;
     146          16 :     const std::string osGCPFilename = GetGCPFilename(m_gcps);
     147           8 :     if (!osGCPFilename.empty())
     148             :     {
     149             :         auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
     150           4 :             osGCPFilename.c_str(), GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR));
     151           4 :         if (!poDS)
     152           1 :             return ret;
     153           3 :         if (poDS->GetLayerCount() != 1)
     154             :         {
     155           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     156             :                         "GCPs can only be specified for single-layer datasets");
     157           1 :             return ret;
     158             :         }
     159           2 :         auto poLayer = poDS->GetLayer(0);
     160           2 :         const auto poLayerDefn = poLayer->GetLayerDefn();
     161           2 :         int nIdIdx = -1, nInfoIdx = -1, nColIdx = -1, nLineIdx = -1, nXIdx = -1,
     162           2 :             nYIdx = -1, nZIdx = -1;
     163             : 
     164             :         const struct
     165             :         {
     166             :             int &idx;
     167             :             const char *name;
     168             :             bool required;
     169           2 :         } aFields[] = {
     170             :             {nIdIdx, "id", false},     {nInfoIdx, "info", false},
     171             :             {nColIdx, "column", true}, {nLineIdx, "line", true},
     172             :             {nXIdx, "x", true},        {nYIdx, "y", true},
     173             :             {nZIdx, "z", false},
     174           2 :         };
     175             : 
     176          11 :         for (auto &field : aFields)
     177             :         {
     178          10 :             field.idx = poLayerDefn->GetFieldIndex(field.name);
     179          10 :             if (field.idx < 0 && field.required)
     180             :             {
     181           3 :                 ReportError(CE_Failure, CPLE_AppDefined,
     182           1 :                             "Field '%s' cannot be found in '%s'", field.name,
     183           1 :                             poDS->GetDescription());
     184           1 :                 return ret;
     185             :             }
     186             :         }
     187           2 :         for (auto &&poFeature : poLayer)
     188             :         {
     189           2 :             gdal::GCP gcp;
     190           1 :             if (nIdIdx >= 0)
     191           1 :                 gcp.SetId(poFeature->GetFieldAsString(nIdIdx));
     192           1 :             if (nInfoIdx >= 0)
     193           1 :                 gcp.SetInfo(poFeature->GetFieldAsString(nInfoIdx));
     194           1 :             gcp.Pixel() = poFeature->GetFieldAsDouble(nColIdx);
     195           1 :             gcp.Line() = poFeature->GetFieldAsDouble(nLineIdx);
     196           1 :             gcp.X() = poFeature->GetFieldAsDouble(nXIdx);
     197           1 :             gcp.Y() = poFeature->GetFieldAsDouble(nYIdx);
     198           1 :             if (nZIdx >= 0 && poFeature->IsFieldSetAndNotNull(nZIdx))
     199           1 :                 gcp.Z() = poFeature->GetFieldAsDouble(nZIdx);
     200           1 :             ret.push_back(std::move(gcp));
     201             :         }
     202             :     }
     203             :     else
     204             :     {
     205          10 :         for (const std::string &gcpStr : m_gcps)
     206             :         {
     207             :             const CPLStringList aosTokens(
     208          12 :                 CSLTokenizeString2(gcpStr.c_str(), ",", 0));
     209             :             // Verified by validation action
     210           6 :             CPLAssert(aosTokens.size() == 4 || aosTokens.size() == 5);
     211          12 :             gdal::GCP gcp;
     212           6 :             gcp.Pixel() = CPLAtof(aosTokens[0]);
     213           6 :             gcp.Line() = CPLAtof(aosTokens[1]);
     214           6 :             gcp.X() = CPLAtof(aosTokens[2]);
     215           6 :             gcp.Y() = CPLAtof(aosTokens[3]);
     216           6 :             if (aosTokens.size() == 5)
     217           3 :                 gcp.Z() = CPLAtof(aosTokens[4]);
     218           6 :             ret.push_back(std::move(gcp));
     219             :         }
     220             :     }
     221           5 :     return ret;
     222             : }
     223             : 
     224             : /************************************************************************/
     225             : /*                GDALRasterEditAlgorithm::RunStep()                    */
     226             : /************************************************************************/
     227             : 
     228          33 : bool GDALRasterEditAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
     229             : {
     230          33 :     GDALDataset *poDS = m_dataset.GetDatasetRef();
     231          33 :     if (poDS)
     232             :     {
     233          24 :         if (poDS->GetAccess() != GA_Update && !m_readOnly)
     234             :         {
     235           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     236             :                         "Dataset should be opened in update mode unless "
     237             :                         "--auxiliary is set");
     238           1 :             return false;
     239             :         }
     240             :     }
     241             :     else
     242             :     {
     243           9 :         const auto poSrcDS = m_inputDataset[0].GetDatasetRef();
     244           9 :         CPLAssert(poSrcDS);
     245           9 :         CPLAssert(m_outputDataset.GetName().empty());
     246           9 :         CPLAssert(!m_outputDataset.GetDatasetRef());
     247             : 
     248          18 :         CPLStringList aosOptions;
     249           9 :         aosOptions.push_back("-of");
     250           9 :         aosOptions.push_back("VRT");
     251             :         GDALTranslateOptions *psOptions =
     252           9 :             GDALTranslateOptionsNew(aosOptions.List(), nullptr);
     253           9 :         GDALDatasetH hSrcDS = GDALDataset::ToHandle(poSrcDS);
     254           9 :         auto poRetDS = GDALDataset::FromHandle(
     255             :             GDALTranslate("", hSrcDS, psOptions, nullptr));
     256           9 :         GDALTranslateOptionsFree(psOptions);
     257           9 :         m_outputDataset.Set(std::unique_ptr<GDALDataset>(poRetDS));
     258           9 :         poDS = m_outputDataset.GetDatasetRef();
     259             :     }
     260             : 
     261          32 :     bool ret = poDS != nullptr;
     262             : 
     263          32 :     if (poDS)
     264             :     {
     265          32 :         if (m_overrideCrs == "null" || m_overrideCrs == "none")
     266             :         {
     267           3 :             if (poDS->SetSpatialRef(nullptr) != CE_None)
     268             :             {
     269           1 :                 ReportError(CE_Failure, CPLE_AppDefined,
     270             :                             "SetSpatialRef(%s) failed", m_overrideCrs.c_str());
     271          10 :                 return false;
     272             :             }
     273             :         }
     274          29 :         else if (!m_overrideCrs.empty() && m_gcps.empty())
     275             :         {
     276           3 :             OGRSpatialReference oSRS;
     277           3 :             oSRS.SetFromUserInput(m_overrideCrs.c_str());
     278           3 :             oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     279           3 :             if (poDS->SetSpatialRef(&oSRS) != CE_None)
     280             :             {
     281           1 :                 ReportError(CE_Failure, CPLE_AppDefined,
     282             :                             "SetSpatialRef(%s) failed", m_overrideCrs.c_str());
     283           1 :                 return false;
     284             :             }
     285             :         }
     286             : 
     287          30 :         if (!m_bbox.empty())
     288             :         {
     289           4 :             if (poDS->GetRasterXSize() == 0 || poDS->GetRasterYSize() == 0)
     290             :             {
     291           1 :                 ReportError(CE_Failure, CPLE_AppDefined,
     292             :                             "Cannot set extent because one of dataset height "
     293             :                             "or width is null");
     294           2 :                 return false;
     295             :             }
     296             :             double adfGT[6];
     297           3 :             adfGT[0] = m_bbox[0];
     298           3 :             adfGT[1] = (m_bbox[2] - m_bbox[0]) / poDS->GetRasterXSize();
     299           3 :             adfGT[2] = 0;
     300           3 :             adfGT[3] = m_bbox[3];
     301           3 :             adfGT[4] = 0;
     302           3 :             adfGT[5] = -(m_bbox[3] - m_bbox[1]) / poDS->GetRasterYSize();
     303           3 :             if (poDS->SetGeoTransform(adfGT) != CE_None)
     304             :             {
     305           1 :                 ReportError(CE_Failure, CPLE_AppDefined,
     306             :                             "Setting extent failed");
     307           1 :                 return false;
     308             :             }
     309             :         }
     310             : 
     311          28 :         if (!m_nodata.empty())
     312             :         {
     313           8 :             for (int i = 0; i < poDS->GetRasterCount(); ++i)
     314             :             {
     315           4 :                 if (EQUAL(m_nodata.c_str(), "none"))
     316           2 :                     poDS->GetRasterBand(i + 1)->DeleteNoDataValue();
     317             :                 else
     318           4 :                     poDS->GetRasterBand(i + 1)->SetNoDataValue(
     319           2 :                         CPLAtof(m_nodata.c_str()));
     320             :             }
     321             :         }
     322             : 
     323          28 :         const CPLStringList aosMD(m_metadata);
     324          32 :         for (const auto &[key, value] : cpl::IterateNameValue(aosMD))
     325             :         {
     326           5 :             if (poDS->SetMetadataItem(key, value) != CE_None)
     327             :             {
     328           1 :                 ReportError(CE_Failure, CPLE_AppDefined,
     329             :                             "SetMetadataItem('%s', '%s') failed", key, value);
     330           1 :                 return false;
     331             :             }
     332             :         }
     333             : 
     334          29 :         for (const std::string &key : m_unsetMetadata)
     335             :         {
     336           3 :             if (poDS->SetMetadataItem(key.c_str(), nullptr) != CE_None)
     337             :             {
     338           1 :                 ReportError(CE_Failure, CPLE_AppDefined,
     339             :                             "SetMetadataItem('%s', NULL) failed", key.c_str());
     340           1 :                 return false;
     341             :             }
     342             :         }
     343             : 
     344          26 :         if (!m_gcps.empty())
     345             :         {
     346           8 :             const auto gcps = ParseGCPs();
     347           8 :             if (gcps.empty())
     348           3 :                 return false;  // error already emitted by ParseGCPs()
     349             : 
     350           5 :             OGRSpatialReference oSRS;
     351           5 :             if (!m_overrideCrs.empty())
     352             :             {
     353           1 :                 oSRS.SetFromUserInput(m_overrideCrs.c_str());
     354           1 :                 oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     355             :             }
     356             : 
     357           5 :             if (poDS->SetGCPs(static_cast<int>(gcps.size()), gcps[0].c_ptr(),
     358          10 :                               oSRS.IsEmpty() ? nullptr : &oSRS) != CE_None)
     359             :             {
     360           1 :                 ReportError(CE_Failure, CPLE_AppDefined, "Setting GCPs failed");
     361           1 :                 return false;
     362             :             }
     363             :         }
     364             : 
     365          22 :         const int nBands = poDS->GetRasterCount();
     366          22 :         int nCurProgress = 0;
     367          22 :         const double dfTotalProgress =
     368          22 :             ((m_stats || m_approxStats) ? nBands : 0) + (m_hist ? nBands : 0);
     369          22 :         if (m_stats || m_approxStats)
     370             :         {
     371           4 :             for (int i = 0; (i < nBands) && ret; ++i)
     372             :             {
     373           4 :                 void *pScaledProgress = GDALCreateScaledProgress(
     374             :                     nCurProgress / dfTotalProgress,
     375           2 :                     (nCurProgress + 1) / dfTotalProgress, ctxt.m_pfnProgress,
     376             :                     ctxt.m_pProgressData);
     377           2 :                 ++nCurProgress;
     378           2 :                 double dfMin = 0.0;
     379           2 :                 double dfMax = 0.0;
     380           2 :                 double dfMean = 0.0;
     381           2 :                 double dfStdDev = 0.0;
     382           4 :                 ret = poDS->GetRasterBand(i + 1)->ComputeStatistics(
     383           2 :                           m_approxStats, &dfMin, &dfMax, &dfMean, &dfStdDev,
     384             :                           pScaledProgress ? GDALScaledProgress : nullptr,
     385           2 :                           pScaledProgress) == CE_None;
     386           2 :                 GDALDestroyScaledProgress(pScaledProgress);
     387             :             }
     388             :         }
     389             : 
     390          22 :         if (m_hist)
     391             :         {
     392           2 :             for (int i = 0; (i < nBands) && ret; ++i)
     393             :             {
     394           2 :                 void *pScaledProgress = GDALCreateScaledProgress(
     395             :                     nCurProgress / dfTotalProgress,
     396           1 :                     (nCurProgress + 1) / dfTotalProgress, ctxt.m_pfnProgress,
     397             :                     ctxt.m_pProgressData);
     398           1 :                 ++nCurProgress;
     399           1 :                 double dfMin = 0.0;
     400           1 :                 double dfMax = 0.0;
     401           1 :                 int nBucketCount = 0;
     402           1 :                 GUIntBig *panHistogram = nullptr;
     403           2 :                 ret = poDS->GetRasterBand(i + 1)->GetDefaultHistogram(
     404             :                           &dfMin, &dfMax, &nBucketCount, &panHistogram, TRUE,
     405             :                           pScaledProgress ? GDALScaledProgress : nullptr,
     406           1 :                           pScaledProgress) == CE_None;
     407           1 :                 if (ret)
     408             :                 {
     409           2 :                     ret = poDS->GetRasterBand(i + 1)->SetDefaultHistogram(
     410           1 :                               dfMin, dfMax, nBucketCount, panHistogram) ==
     411             :                           CE_None;
     412             :                 }
     413           1 :                 CPLFree(panHistogram);
     414           1 :                 GDALDestroyScaledProgress(pScaledProgress);
     415             :             }
     416             :         }
     417             :     }
     418             : 
     419          22 :     return poDS != nullptr;
     420             : }
     421             : 
     422             : GDALRasterEditAlgorithmStandalone::~GDALRasterEditAlgorithmStandalone() =
     423             :     default;
     424             : 
     425             : //! @endcond

Generated by: LCOV version 1.14