LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_as_features.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 187 201 93.0 %
Date: 2026-02-21 16:21:44 Functions: 13 13 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             : *
       3             :  * Project:  GDAL
       4             :  * Purpose:  "as-features" step of "gdal 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_as_features.h"
      14             : #include "gdalalg_vector_pipeline.h"
      15             : 
      16             : #include "cpl_conv.h"
      17             : #include "gdal_priv.h"
      18             : #include "gdal_alg.h"
      19             : #include "ogrsf_frmts.h"
      20             : 
      21             : #include <cmath>
      22             : #include <limits>
      23             : #include <optional>
      24             : 
      25             : //! @cond Doxygen_Suppress
      26             : 
      27             : #ifndef _
      28             : #define _(x) (x)
      29             : #endif
      30             : 
      31          40 : GDALRasterAsFeaturesAlgorithm::GDALRasterAsFeaturesAlgorithm(
      32          40 :     bool standaloneStep)
      33             :     : GDALPipelineStepAlgorithm(
      34             :           NAME, DESCRIPTION, HELP_URL,
      35           0 :           ConstructorOptions()
      36          40 :               .SetStandaloneStep(standaloneStep)
      37          40 :               .SetAddUpsertArgument(false)
      38          40 :               .SetAddSkipErrorsArgument(false)
      39          80 :               .SetOutputFormatCreateCapability(GDAL_DCAP_CREATE))
      40             : {
      41          40 :     m_outputLayerName = "pixels";
      42             : 
      43          40 :     if (standaloneStep)
      44             :     {
      45          25 :         AddRasterInputArgs(false, false);
      46          25 :         AddVectorOutputArgs(false, false);
      47             :     }
      48             :     else
      49             :     {
      50          15 :         AddRasterHiddenInputDatasetArg();
      51          15 :         AddOutputLayerNameArg(/* hiddenForCLI = */ false,
      52             :                               /* shortNameOutputLayerAllowed = */ false);
      53             :     }
      54             : 
      55          40 :     AddBandArg(&m_bands);
      56          80 :     AddArg("geometry-type", 0, _("Geometry type"), &m_geomTypeName)
      57          40 :         .SetChoices("none", "point", "polygon")
      58          40 :         .SetDefault(m_geomTypeName);
      59             :     AddArg("skip-nodata", 0, _("Omit NoData pixels from the result"),
      60          40 :            &m_skipNoData);
      61             :     AddArg("include-xy", 0, _("Include fields for cell center coordinates"),
      62          40 :            &m_includeXY);
      63             :     AddArg("include-row-col", 0, _("Include columns for row and column"),
      64          40 :            &m_includeRowCol);
      65          40 : }
      66             : 
      67             : GDALRasterAsFeaturesAlgorithm::~GDALRasterAsFeaturesAlgorithm() = default;
      68             : 
      69             : GDALRasterAsFeaturesAlgorithmStandalone::
      70             :     ~GDALRasterAsFeaturesAlgorithmStandalone() = default;
      71             : 
      72             : struct RasterAsFeaturesOptions
      73             : {
      74             :     OGRwkbGeometryType geomType{wkbNone};
      75             :     bool includeXY{false};
      76             :     bool includeRowCol{false};
      77             :     bool skipNoData{false};
      78             :     std::vector<int> bands{};
      79             :     std::string outputLayerName{};
      80             : };
      81             : 
      82             : class GDALRasterAsFeaturesLayer final
      83             :     : public OGRLayer,
      84             :       public OGRGetNextFeatureThroughRaw<GDALRasterAsFeaturesLayer>
      85             : {
      86             :   public:
      87             :     static constexpr const char *ROW_FIELD = "ROW";
      88             :     static constexpr const char *COL_FIELD = "COL";
      89             :     static constexpr const char *X_FIELD = "CENTER_X";
      90             :     static constexpr const char *Y_FIELD = "CENTER_Y";
      91             : 
      92        8197 :     DEFINE_GET_NEXT_FEATURE_THROUGH_RAW(GDALRasterAsFeaturesLayer)
      93             : 
      94          15 :     GDALRasterAsFeaturesLayer(GDALDataset &ds, RasterAsFeaturesOptions options)
      95          15 :         : m_ds(ds), m_it(GDALRasterBand::WindowIterator(
      96          30 :                         m_ds.GetRasterXSize(), m_ds.GetRasterYSize(),
      97          30 :                         m_ds.GetRasterXSize(), m_ds.GetRasterYSize(), 0, 0)),
      98             :           m_end(GDALRasterBand::WindowIterator(
      99          30 :               m_ds.GetRasterXSize(), m_ds.GetRasterYSize(),
     100          30 :               m_ds.GetRasterXSize(), m_ds.GetRasterYSize(), 1, 0)),
     101          15 :           m_includeXY(options.includeXY),
     102          15 :           m_includeRowCol(options.includeRowCol),
     103          15 :           m_excludeNoDataPixels(options.skipNoData)
     104             :     {
     105             :         // TODO: Handle Int64, UInt64
     106          15 :         m_ds.GetGeoTransform(m_gt);
     107             : 
     108          15 :         int nBands = m_ds.GetRasterCount();
     109          15 :         m_bands.resize(nBands);
     110          33 :         for (int i = 1; i <= nBands; i++)
     111             :         {
     112          18 :             m_bands[i - 1] = i;
     113             :         }
     114             : 
     115             :         // TODO: Handle per-band NoData values
     116          15 :         if (nBands > 0)
     117             :         {
     118             :             int hasNoData;
     119          14 :             double noData = m_ds.GetRasterBand(1)->GetNoDataValue(&hasNoData);
     120          14 :             if (hasNoData)
     121             :             {
     122           1 :                 m_noData = noData;
     123             :             }
     124             :         }
     125             : 
     126          15 :         SetDescription(options.outputLayerName.c_str());
     127          15 :         m_defn = new OGRFeatureDefn(options.outputLayerName.c_str());
     128          15 :         if (options.geomType == wkbNone)
     129             :         {
     130          12 :             m_defn->SetGeomType(wkbNone);
     131             :         }
     132             :         else
     133             :         {
     134           3 :             m_defn->GetGeomFieldDefn(0)->SetType(options.geomType);
     135           3 :             m_defn->GetGeomFieldDefn(0)->SetSpatialRef(ds.GetSpatialRef());
     136             :         }
     137          15 :         m_defn->Reference();
     138             : 
     139          15 :         if (m_includeXY)
     140             :         {
     141           2 :             auto xField = std::make_unique<OGRFieldDefn>(X_FIELD, OFTReal);
     142           2 :             auto yField = std::make_unique<OGRFieldDefn>(Y_FIELD, OFTReal);
     143           1 :             m_defn->AddFieldDefn(std::move(xField));
     144           1 :             m_defn->AddFieldDefn(std::move(yField));
     145             :         }
     146          15 :         if (m_includeRowCol)
     147             :         {
     148             :             auto rowField =
     149           8 :                 std::make_unique<OGRFieldDefn>(ROW_FIELD, OFTInteger);
     150             :             auto colField =
     151           8 :                 std::make_unique<OGRFieldDefn>(COL_FIELD, OFTInteger);
     152           4 :             m_defn->AddFieldDefn(std::move(rowField));
     153           4 :             m_defn->AddFieldDefn(std::move(colField));
     154             :         }
     155          33 :         for (int band : m_bands)
     156             :         {
     157          36 :             CPLString fieldName = CPLSPrintf("BAND_%d", band);
     158             :             auto bandField =
     159          18 :                 std::make_unique<OGRFieldDefn>(fieldName.c_str(), OFTReal);
     160          18 :             m_defn->AddFieldDefn(std::move(bandField));
     161          18 :             m_bandFields.push_back(m_defn->GetFieldIndex(fieldName));
     162             :         }
     163             : 
     164          15 :         GDALRasterAsFeaturesLayer::ResetReading();
     165          15 :     }
     166             : 
     167             :     ~GDALRasterAsFeaturesLayer() override;
     168             : 
     169          28 :     void ResetReading() override
     170             :     {
     171          28 :         if (m_ds.GetRasterCount() > 0)
     172             :         {
     173          26 :             GDALRasterBand *poFirstBand = m_ds.GetRasterBand(1);
     174          26 :             CPLAssert(poFirstBand);  // appease clang scan-build
     175          26 :             m_it = poFirstBand->IterateWindows().begin();
     176          26 :             m_end = poFirstBand->IterateWindows().end();
     177             :         }
     178          28 :     }
     179             : 
     180          14 :     int TestCapability(const char *pszCap) const override
     181             :     {
     182          15 :         return EQUAL(pszCap, OLCFastFeatureCount) &&
     183          15 :                m_poFilterGeom == nullptr && m_poAttrQuery == nullptr &&
     184          15 :                !m_excludeNoDataPixels;
     185             :     }
     186             : 
     187           1 :     GIntBig GetFeatureCount(int bForce) override
     188             :     {
     189           1 :         if (m_poFilterGeom == nullptr && m_poAttrQuery == nullptr &&
     190           1 :             !m_excludeNoDataPixels)
     191             :         {
     192           1 :             return static_cast<GIntBig>(m_ds.GetRasterXSize()) *
     193           1 :                    m_ds.GetRasterYSize();
     194             :         }
     195           0 :         return OGRLayer::GetFeatureCount(bForce);
     196             :     }
     197             : 
     198          74 :     OGRFeatureDefn *GetLayerDefn() const override
     199             :     {
     200          74 :         return m_defn;
     201             :     }
     202             : 
     203        8197 :     OGRFeature *GetNextRawFeature()
     204             :     {
     205        8197 :         if (m_row >= m_window.nYSize && !NextWindow())
     206             :         {
     207          13 :             return nullptr;
     208             :         }
     209             : 
     210       16368 :         std::unique_ptr<OGRFeature> feature;
     211             : 
     212        8204 :         while (m_row < m_window.nYSize)
     213             :         {
     214        8204 :             const double *pSrcVal = reinterpret_cast<double *>(m_buf.data()) +
     215        8204 :                                     (m_bands.size() * m_row * m_window.nXSize +
     216        8204 :                                      m_col * m_bands.size());
     217             : 
     218             :             const bool emitFeature =
     219        8204 :                 !m_excludeNoDataPixels || !IsNoData(*pSrcVal);
     220             : 
     221        8204 :             if (emitFeature)
     222             :             {
     223        8184 :                 feature.reset(OGRFeature::CreateFeature(m_defn));
     224             : 
     225       26364 :                 for (int fieldPos : m_bandFields)
     226             :                 {
     227       18180 :                     feature->SetField(fieldPos, *pSrcVal);
     228       18180 :                     pSrcVal++;
     229             :                 }
     230             : 
     231        8184 :                 const double line = m_window.nYOff + m_row;
     232        8184 :                 const double pixel = m_window.nXOff + m_col;
     233             : 
     234        8184 :                 if (m_includeRowCol)
     235             :                 {
     236         404 :                     feature->SetField(ROW_FIELD, static_cast<GIntBig>(line));
     237         404 :                     feature->SetField(COL_FIELD, static_cast<GIntBig>(pixel));
     238             :                 }
     239        8184 :                 if (m_includeXY)
     240             :                 {
     241             :                     double x, y;
     242         400 :                     m_gt.Apply(pixel + 0.5, line + 0.5, &x, &y);
     243         400 :                     feature->SetField(X_FIELD, x);
     244         400 :                     feature->SetField(Y_FIELD, y);
     245             :                 }
     246             : 
     247           0 :                 std::unique_ptr<OGRGeometry> geom;
     248        8184 :                 const auto geomType = m_defn->GetGeomType();
     249        8184 :                 if (geomType == wkbPoint)
     250             :                 {
     251             :                     double x, y;
     252        2900 :                     m_gt.Apply(pixel + 0.5, line + 0.5, &x, &y);
     253             : 
     254        2900 :                     geom = std::make_unique<OGRPoint>(x, y);
     255        5800 :                     geom->assignSpatialReference(
     256        2900 :                         m_defn->GetGeomFieldDefn(0)->GetSpatialRef());
     257             :                 }
     258        5284 :                 else if (geomType == wkbPolygon)
     259             :                 {
     260             :                     double x, y;
     261             : 
     262         800 :                     auto lr = std::make_unique<OGRLinearRing>();
     263             : 
     264         400 :                     m_gt.Apply(pixel, line, &x, &y);
     265         400 :                     lr->addPoint(x, y);
     266         400 :                     m_gt.Apply(pixel, line + 1, &x, &y);
     267         400 :                     lr->addPoint(x, y);
     268         400 :                     m_gt.Apply(pixel + 1, line + 1, &x, &y);
     269         400 :                     lr->addPoint(x, y);
     270         400 :                     m_gt.Apply(pixel + 1, line, &x, &y);
     271         400 :                     lr->addPoint(x, y);
     272         400 :                     m_gt.Apply(pixel, line, &x, &y);
     273         400 :                     lr->addPoint(x, y);
     274             : 
     275         800 :                     auto poly = std::make_unique<OGRPolygon>();
     276         400 :                     poly->addRing(std::move(lr));
     277         400 :                     geom = std::move(poly);
     278         800 :                     geom->assignSpatialReference(
     279         400 :                         m_defn->GetGeomFieldDefn(0)->GetSpatialRef());
     280             :                 }
     281             : 
     282        8184 :                 feature->SetGeometry(std::move(geom));
     283             :             }
     284             : 
     285        8204 :             m_col += 1;
     286        8204 :             if (m_col >= m_window.nXSize)
     287             :             {
     288         262 :                 m_col = 0;
     289         262 :                 m_row++;
     290             :             }
     291             : 
     292        8204 :             if (feature)
     293             :             {
     294        8184 :                 return feature.release();
     295             :             }
     296             :         }
     297             : 
     298           0 :         return nullptr;
     299             :     }
     300             : 
     301             :     CPL_DISALLOW_COPY_ASSIGN(GDALRasterAsFeaturesLayer)
     302             : 
     303             :   private:
     304         400 :     bool IsNoData(double x) const
     305             :     {
     306         400 :         if (!m_noData.has_value())
     307             :         {
     308           0 :             return false;
     309             :         }
     310             : 
     311         780 :         return m_noData.value() == x ||
     312         780 :                (std::isnan(m_noData.value()) && std::isnan(x));
     313             :     }
     314             : 
     315          24 :     bool NextWindow()
     316             :     {
     317          24 :         int nBandCount = static_cast<int>(m_bands.size());
     318             : 
     319          24 :         if (m_it == m_end)
     320             :         {
     321          12 :             return false;
     322             :         }
     323             : 
     324          12 :         if (m_ds.GetRasterXSize() == 0 || m_ds.GetRasterYSize() == 0)
     325             :         {
     326           1 :             return false;
     327             :         }
     328             : 
     329          11 :         m_window = *m_it;
     330          11 :         ++m_it;
     331             : 
     332          11 :         if (!m_bands.empty())
     333             :         {
     334          10 :             const auto nBufTypeSize = GDALGetDataTypeSizeBytes(m_bufType);
     335             :             if constexpr (sizeof(int) < sizeof(size_t))
     336             :             {
     337          20 :                 if (m_window.nYSize > 0 &&
     338          10 :                     static_cast<size_t>(m_window.nXSize) >
     339          10 :                         std::numeric_limits<size_t>::max() / m_window.nYSize)
     340             :                 {
     341           0 :                     CPLError(CE_Failure, CPLE_OutOfMemory,
     342             :                              "Failed to allocate buffer");
     343           0 :                     return false;
     344             :                 }
     345             :             }
     346          10 :             const size_t nPixelCount =
     347          10 :                 static_cast<size_t>(m_window.nXSize) * m_window.nYSize;
     348          20 :             if (static_cast<size_t>(nBandCount) * nBufTypeSize >
     349          10 :                 std::numeric_limits<size_t>::max() / nPixelCount)
     350             :             {
     351           0 :                 CPLError(CE_Failure, CPLE_OutOfMemory,
     352             :                          "Failed to allocate buffer");
     353           0 :                 return false;
     354             :             }
     355          10 :             const size_t nBufSize = nPixelCount * nBandCount * nBufTypeSize;
     356          10 :             if (m_buf.size() < nBufSize)
     357             :             {
     358             :                 try
     359             :                 {
     360          10 :                     m_buf.resize(nBufSize);
     361             :                 }
     362           0 :                 catch (const std::exception &)
     363             :                 {
     364           0 :                     CPLError(CE_Failure, CPLE_OutOfMemory,
     365             :                              "Failed to allocate buffer");
     366           0 :                     return false;
     367             :                 }
     368             :             }
     369             : 
     370          10 :             const auto nPixelSpace =
     371          10 :                 static_cast<GSpacing>(nBandCount) * nBufTypeSize;
     372          10 :             const auto eErr = m_ds.RasterIO(
     373             :                 GF_Read, m_window.nXOff, m_window.nYOff, m_window.nXSize,
     374          10 :                 m_window.nYSize, m_buf.data(), m_window.nXSize, m_window.nYSize,
     375          10 :                 m_bufType, static_cast<int>(m_bands.size()), m_bands.data(),
     376          10 :                 nPixelSpace, nPixelSpace * m_window.nXSize, nBufTypeSize,
     377             :                 nullptr);
     378             : 
     379          10 :             if (eErr != CE_None)
     380             :             {
     381           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     382             :                          "Failed to read raster data");
     383           0 :                 return false;
     384             :             }
     385             :         }
     386             : 
     387          11 :         m_row = 0;
     388          11 :         m_col = 0;
     389             : 
     390          11 :         return true;
     391             :     }
     392             : 
     393             :     GDALDataset &m_ds;
     394             :     std::vector<GByte> m_buf{};
     395             :     const GDALDataType m_bufType = GDT_Float64;
     396             :     GDALGeoTransform m_gt{};
     397             :     std::optional<double> m_noData{std::nullopt};
     398             : 
     399             :     std::vector<int> m_bands{};
     400             :     std::vector<int> m_bandFields{};
     401             : 
     402             :     GDALRasterBand::WindowIterator m_it;
     403             :     GDALRasterBand::WindowIterator m_end;
     404             :     GDALRasterWindow m_window{};
     405             : 
     406             :     int m_row{0};
     407             :     int m_col{0};
     408             : 
     409             :     OGRFeatureDefn *m_defn{nullptr};
     410             :     bool m_includeXY;
     411             :     bool m_includeRowCol;
     412             :     bool m_excludeNoDataPixels;
     413             : };
     414             : 
     415          30 : GDALRasterAsFeaturesLayer::~GDALRasterAsFeaturesLayer()
     416             : {
     417          15 :     m_defn->Release();
     418          30 : }
     419             : 
     420          15 : bool GDALRasterAsFeaturesAlgorithm::RunStep(GDALPipelineStepRunContext &)
     421             : {
     422          15 :     auto poSrcDS = m_inputDataset[0].GetDatasetRef();
     423             : 
     424          30 :     RasterAsFeaturesOptions options;
     425          28 :     options.geomType = m_geomTypeName == "point"     ? wkbPoint
     426          13 :                        : m_geomTypeName == "polygon" ? wkbPolygon
     427             :                                                      : wkbNone;
     428          15 :     options.includeRowCol = m_includeRowCol;
     429          15 :     options.includeXY = m_includeXY;
     430          15 :     options.skipNoData = m_skipNoData;
     431          15 :     options.outputLayerName = m_outputLayerName;
     432             : 
     433          15 :     if (!m_bands.empty())
     434             :     {
     435           1 :         options.bands = std::move(m_bands);
     436             :     }
     437             : 
     438             :     auto poLayer =
     439          30 :         std::make_unique<GDALRasterAsFeaturesLayer>(*poSrcDS, options);
     440          15 :     auto poRetDS = std::make_unique<GDALVectorOutputDataset>();
     441          15 :     poRetDS->AddLayer(std::move(poLayer));
     442             : 
     443          15 :     m_outputDataset.Set(std::move(poRetDS));
     444             : 
     445          30 :     return true;
     446             : }
     447             : 
     448             : //! @endcond

Generated by: LCOV version 1.14