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

Generated by: LCOV version 1.14