LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_reclassify.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 95 98 96.9 %
Date: 2026-07-09 08:35:43 Functions: 4 4 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  "reclassify" step of "raster pipeline"
       5             :  * Author:   Daniel Baston
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2025, ISciences LLC
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "gdalalg_raster_reclassify.h"
      14             : 
      15             : #include "cpl_vsi_virtual.h"
      16             : #include "gdal_priv.h"
      17             : #include "gdal_utils.h"
      18             : #include "../frmts/vrt/vrtdataset.h"
      19             : #include "../frmts/vrt/vrtreclassifier.h"
      20             : 
      21             : #include <array>
      22             : 
      23             : //! @cond Doxygen_Suppress
      24             : 
      25             : #ifndef _
      26             : #define _(x) (x)
      27             : #endif
      28             : 
      29             : /************************************************************************/
      30             : /*    GDALRasterReclassifyAlgorithm::GDALRasterReclassifyAlgorithm()    */
      31             : /************************************************************************/
      32             : 
      33          97 : GDALRasterReclassifyAlgorithm::GDALRasterReclassifyAlgorithm(
      34          97 :     bool standaloneStep)
      35             :     : GDALRasterPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
      36          97 :                                       standaloneStep)
      37             : {
      38             :     AddArg("mapping", 'm',
      39             :            _("Reclassification mappings (or specify a @<filename> to point to "
      40             :              "a file containing mappings"),
      41         194 :            &m_mapping)
      42          97 :         .SetRequired();
      43             :     AddArg("keep-color-table", 0, _("Preserve the input color table"),
      44          97 :            &m_keepColorTable);
      45          97 :     AddOutputDataTypeArg(&m_type);
      46          97 : }
      47             : 
      48             : /************************************************************************/
      49             : /*                 GDALRasterReclassifyValidateMappings                 */
      50             : /************************************************************************/
      51             : 
      52          12 : static bool GDALReclassifyValidateMappings(GDALDataset &input,
      53             :                                            const std::string &mappings,
      54             :                                            GDALDataType eDstType)
      55             : {
      56             :     int hasNoData;
      57             :     std::optional<double> noData =
      58          12 :         input.GetRasterBand(1)->GetNoDataValue(&hasNoData);
      59          12 :     if (!hasNoData)
      60             :     {
      61           8 :         noData.reset();
      62             :     }
      63             : 
      64          12 :     gdal::Reclassifier reclassifier;
      65          24 :     return reclassifier.Init(mappings.c_str(), noData, eDstType) == CE_None;
      66             : }
      67             : 
      68             : /************************************************************************/
      69             : /*                 GDALRasterReclassifyCreateVRTDerived                 */
      70             : /************************************************************************/
      71             : 
      72             : static std::unique_ptr<GDALDataset>
      73          11 : GDALReclassifyCreateVRTDerived(GDALDataset &input, const std::string &mappings,
      74             :                                GDALDataType eDstType, bool keepColorTable)
      75             : {
      76          22 :     CPLXMLTreeCloser root(CPLCreateXMLNode(nullptr, CXT_Element, "VRTDataset"));
      77             : 
      78          11 :     const auto nX = input.GetRasterXSize();
      79          11 :     const auto nY = input.GetRasterYSize();
      80             : 
      81          24 :     for (int iBand = 1; iBand <= input.GetRasterCount(); ++iBand)
      82             :     {
      83          13 :         GDALRasterBand *poSrcBand = input.GetRasterBand(iBand);
      84          13 :         const GDALDataType srcType = poSrcBand->GetRasterDataType();
      85          13 :         const GDALDataType bandType =
      86          13 :             eDstType == GDT_Unknown ? srcType : eDstType;
      87          13 :         const GDALDataType xferType = GDALDataTypeUnion(srcType, bandType);
      88             : 
      89             :         CPLXMLNode *band =
      90          13 :             CPLCreateXMLNode(root.get(), CXT_Element, "VRTRasterBand");
      91          13 :         CPLAddXMLAttributeAndValue(band, "subClass", "VRTDerivedRasterBand");
      92          13 :         CPLAddXMLAttributeAndValue(band, "dataType",
      93             :                                    GDALGetDataTypeName(bandType));
      94             : 
      95             :         CPLXMLNode *pixelFunctionType =
      96          13 :             CPLCreateXMLNode(band, CXT_Element, "PixelFunctionType");
      97          13 :         CPLCreateXMLNode(pixelFunctionType, CXT_Text, "reclassify");
      98             :         CPLXMLNode *arguments =
      99          13 :             CPLCreateXMLNode(band, CXT_Element, "PixelFunctionArguments");
     100          13 :         CPLAddXMLAttributeAndValue(arguments, "mapping", mappings.c_str());
     101             : 
     102             :         CPLXMLNode *sourceTransferType =
     103          13 :             CPLCreateXMLNode(band, CXT_Element, "SourceTransferType");
     104          13 :         CPLCreateXMLNode(sourceTransferType, CXT_Text,
     105             :                          GDALGetDataTypeName(xferType));
     106             : 
     107          13 :         if (const GDALColorTable *poTable =
     108          13 :                 keepColorTable ? poSrcBand->GetColorTable() : nullptr)
     109             :         {
     110             :             CPLXMLNode *colorTable =
     111           1 :                 CPLCreateXMLNode(band, CXT_Element, "ColorTable");
     112           5 :             for (int i = 0; i < poTable->GetColorEntryCount(); ++i)
     113             :             {
     114           4 :                 const GDALColorEntry *poSrcEntry = poTable->GetColorEntry(i);
     115             : 
     116             :                 CPLXMLNode *entry =
     117           4 :                     CPLCreateXMLNode(colorTable, CXT_Element, "Entry");
     118           4 :                 CPLAddXMLAttributeAndValue(
     119           8 :                     entry, "c1", std::to_string(poSrcEntry->c1).c_str());
     120           4 :                 CPLAddXMLAttributeAndValue(
     121           8 :                     entry, "c2", std::to_string(poSrcEntry->c2).c_str());
     122           4 :                 CPLAddXMLAttributeAndValue(
     123           8 :                     entry, "c3", std::to_string(poSrcEntry->c3).c_str());
     124           4 :                 CPLAddXMLAttributeAndValue(
     125           8 :                     entry, "c4", std::to_string(poSrcEntry->c4).c_str());
     126             :             }
     127             :         }
     128             :     }
     129             : 
     130          22 :     auto ds = std::make_unique<VRTDataset>(nX, nY);
     131          11 :     if (ds->XMLInit(root.get(), "") != CE_None)
     132             :     {
     133           0 :         return nullptr;
     134             :     };
     135             : 
     136          24 :     for (int iBand = 1; iBand <= input.GetRasterCount(); ++iBand)
     137             :     {
     138          13 :         auto poSrcBand = input.GetRasterBand(iBand);
     139             :         auto poDstBand =
     140          13 :             cpl::down_cast<VRTDerivedRasterBand *>(ds->GetRasterBand(iBand));
     141          13 :         GDALCopyNoDataValue(poDstBand, poSrcBand);
     142          13 :         poDstBand->AddSimpleSource(poSrcBand);
     143             :     }
     144             : 
     145          11 :     GDALGeoTransform gt;
     146          11 :     if (input.GetGeoTransform(gt) == CE_None)
     147           7 :         ds->SetGeoTransform(gt);
     148          11 :     ds->SetSpatialRef(input.GetSpatialRef());
     149             : 
     150          11 :     return ds;
     151             : }
     152             : 
     153             : /************************************************************************/
     154             : /*               GDALRasterReclassifyAlgorithm::RunStep()               */
     155             : /************************************************************************/
     156             : 
     157          15 : bool GDALRasterReclassifyAlgorithm::RunStep(GDALPipelineStepRunContext &)
     158             : {
     159          15 :     const auto poSrcDS = m_inputDataset[0].GetDatasetRef();
     160          15 :     CPLAssert(poSrcDS);
     161          15 :     CPLAssert(m_outputDataset.GetName().empty());
     162          15 :     CPLAssert(!m_outputDataset.GetDatasetRef());
     163             : 
     164             :     // Already validated by argument parser
     165             :     const GDALDataType eDstType =
     166          15 :         m_type.empty() ? GDT_Unknown : GDALGetDataTypeByName(m_type.c_str());
     167             : 
     168          15 :     const auto nErrorCount = CPLGetErrorCounter();
     169          15 :     if (!m_mapping.empty() && m_mapping[0] == '@')
     170             :     {
     171             :         auto f =
     172           6 :             VSIVirtualHandleUniquePtr(VSIFOpenL(m_mapping.c_str() + 1, "r"));
     173           6 :         if (!f)
     174             :         {
     175           1 :             ReportError(CE_Failure, CPLE_FileIO, "Cannot open %s",
     176           1 :                         m_mapping.c_str() + 1);
     177           1 :             return false;
     178             :         }
     179             : 
     180           5 :         m_mapping.clear();
     181             :         try
     182             :         {
     183           5 :             constexpr int MAX_CHARS_PER_LINE = 1000 * 1000;
     184           5 :             constexpr size_t MAX_MAPPING_SIZE = 10 * 1000 * 1000;
     185             :             while (const char *line =
     186        1034 :                        CPLReadLine2L(f.get(), MAX_CHARS_PER_LINE, nullptr))
     187             :             {
     188        1200 :                 while (isspace(*line))
     189             :                 {
     190         170 :                     line++;
     191             :                 }
     192             : 
     193        1030 :                 if (line[0])
     194             :                 {
     195        1024 :                     if (!m_mapping.empty())
     196             :                     {
     197        1018 :                         m_mapping.append(";");
     198             :                     }
     199             : 
     200        1024 :                     const char *comment = strchr(line, '#');
     201        1024 :                     if (!comment)
     202             :                     {
     203        1018 :                         m_mapping.append(line);
     204             :                     }
     205             :                     else
     206             :                     {
     207             :                         m_mapping.append(line,
     208           6 :                                          static_cast<size_t>(comment - line));
     209             :                     }
     210        1024 :                     if (m_mapping.size() > MAX_MAPPING_SIZE)
     211             :                     {
     212           1 :                         ReportError(CE_Failure, CPLE_AppDefined,
     213             :                                     "Too large mapping size");
     214           1 :                         return false;
     215             :                     }
     216             :                 }
     217        1029 :             }
     218             :         }
     219           0 :         catch (const std::exception &)
     220             :         {
     221           0 :             ReportError(CE_Failure, CPLE_OutOfMemory,
     222             :                         "Out of memory while ingesting mapping file");
     223             :         }
     224             :     }
     225          13 :     if (nErrorCount == CPLGetErrorCounter())
     226             :     {
     227          12 :         if (!GDALReclassifyValidateMappings(*poSrcDS, m_mapping, eDstType))
     228             :         {
     229           1 :             return false;
     230             :         }
     231             : 
     232          11 :         m_outputDataset.Set(GDALReclassifyCreateVRTDerived(
     233          11 :             *poSrcDS, m_mapping, eDstType, m_keepColorTable));
     234             :     }
     235          12 :     return m_outputDataset.GetDatasetRef() != nullptr;
     236             : }
     237             : 
     238             : GDALRasterReclassifyAlgorithmStandalone::
     239             :     ~GDALRasterReclassifyAlgorithmStandalone() = default;
     240             : 
     241             : //! @endcond

Generated by: LCOV version 1.14