LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_as_features.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 198 219 90.4 %
Date: 2025-09-10 17:48:50 Functions: 11 15 73.3 %

          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             : 
      15             : #include "cpl_conv.h"
      16             : #include "gdal_priv.h"
      17             : #include "gdal_alg.h"
      18             : #include "ogrsf_frmts.h"
      19             : 
      20             : #include <cmath>
      21             : #include <optional>
      22             : 
      23             : //! @cond Doxygen_Suppress
      24             : 
      25             : #ifndef _
      26             : #define _(x) (x)
      27             : #endif
      28             : 
      29          18 : GDALRasterAsFeaturesAlgorithm::GDALRasterAsFeaturesAlgorithm(
      30          18 :     bool standaloneStep)
      31             :     : GDALPipelineStepAlgorithm(
      32             :           NAME, DESCRIPTION, HELP_URL,
      33           0 :           ConstructorOptions()
      34          18 :               .SetStandaloneStep(standaloneStep)
      35          36 :               .SetOutputFormatCreateCapability(GDAL_DCAP_CREATE)),
      36             :       m_bands{}, m_geomTypeName("none"), m_skipNoData(false),
      37          54 :       m_includeXY(false), m_includeRowCol(false)
      38             : {
      39          18 :     m_outputLayerName = "pixels";
      40             : 
      41             :     // TODO: This block is copied from gdalalg_raster_polygonize. Avoid duplication?
      42          18 :     if (standaloneStep)
      43             :     {
      44          18 :         AddOutputFormatArg(&m_format).AddMetadataItem(
      45          54 :             GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_VECTOR, GDAL_DCAP_CREATE});
      46          18 :         AddOpenOptionsArg(&m_openOptions);
      47          18 :         AddInputFormatsArg(&m_inputFormats)
      48          36 :             .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_RASTER});
      49             : 
      50          18 :         AddInputDatasetArg(&m_inputDataset, GDAL_OF_RASTER);
      51          18 :         AddOutputDatasetArg(&m_outputDataset, GDAL_OF_VECTOR)
      52          18 :             .SetDatasetInputFlags(GADV_NAME | GADV_OBJECT);
      53          18 :         AddCreationOptionsArg(&m_creationOptions);
      54          18 :         AddLayerCreationOptionsArg(&m_layerCreationOptions);
      55          18 :         AddOverwriteArg(&m_overwrite);
      56          18 :         AddUpdateArg(&m_update);
      57          18 :         AddOverwriteLayerArg(&m_overwriteLayer);
      58          18 :         AddAppendLayerArg(&m_appendLayer);
      59          18 :         AddLayerNameArg(&m_outputLayerName)
      60          36 :             .AddAlias("nln")
      61          18 :             .SetDefault(m_outputLayerName);
      62             :     }
      63             : 
      64          18 :     AddBandArg(&m_bands);
      65          36 :     AddArg("geometry-type", 0, _("Geometry type"), &m_geomTypeName)
      66          18 :         .SetChoices("none", "point", "polygon")
      67          18 :         .SetDefault("none");
      68             :     AddArg("skip-nodata", 0, _("Omit NoData pixels from the result"),
      69          18 :            &m_skipNoData);
      70             :     AddArg("include-xy", 0, _("Include fields for cell center coordinates"),
      71          18 :            &m_includeXY);
      72             :     AddArg("include-row-col", 0, _("Include columns for row and column"),
      73          18 :            &m_includeRowCol);
      74          18 : }
      75             : 
      76             : GDALRasterAsFeaturesAlgorithm::~GDALRasterAsFeaturesAlgorithm() = default;
      77             : 
      78          14 : bool GDALRasterAsFeaturesAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
      79             :                                             void *pProgressData)
      80             : {
      81          14 :     GDALPipelineStepRunContext stepCtxt;
      82          14 :     stepCtxt.m_pfnProgress = pfnProgress;
      83          14 :     stepCtxt.m_pProgressData = pProgressData;
      84          14 :     return RunPreStepPipelineValidations() && RunStep(stepCtxt);
      85             : }
      86             : 
      87             : GDALRasterAsFeaturesAlgorithmStandalone::
      88             :     ~GDALRasterAsFeaturesAlgorithmStandalone() = default;
      89             : 
      90             : struct RasterAsFeaturesOptions
      91             : {
      92             :     OGRwkbGeometryType geomType{wkbNone};
      93             :     bool includeXY{false};
      94             :     bool includeRowCol{false};
      95             :     bool skipNoData{false};
      96             :     std::vector<int> bands{};
      97             : };
      98             : 
      99             : class GDALRasterToVectorPipelineOutputDataset final : public GDALDataset
     100             : {
     101             : 
     102             :   public:
     103           0 :     int GetLayerCount() const override
     104             :     {
     105           0 :         return static_cast<int>(m_layers.size());
     106             :     }
     107             : 
     108           0 :     const OGRLayer *GetLayer(int idx) const override
     109             :     {
     110           0 :         return m_layers[idx].get();
     111             :     }
     112             : 
     113             :     int TestCapability(const char *) const override;
     114             : 
     115             :     void AddLayer(std::unique_ptr<OGRLayer> layer)
     116             :     {
     117             :         m_layers.emplace_back(std::move(layer));
     118             :     }
     119             : 
     120             :   private:
     121             :     std::vector<std::unique_ptr<OGRLayer>> m_layers{};
     122             : };
     123             : 
     124           0 : int GDALRasterToVectorPipelineOutputDataset::TestCapability(const char *) const
     125             : {
     126           0 :     return 0;
     127             : }
     128             : 
     129             : class GDALRasterAsFeaturesLayer final : public OGRLayer
     130             : {
     131             :   public:
     132             :     static constexpr const char *ROW_FIELD = "ROW";
     133             :     static constexpr const char *COL_FIELD = "COL";
     134             :     static constexpr const char *X_FIELD = "CENTER_X";
     135             :     static constexpr const char *Y_FIELD = "CENTER_Y";
     136             : 
     137          14 :     GDALRasterAsFeaturesLayer(GDALDataset &ds, RasterAsFeaturesOptions options)
     138          14 :         : m_ds(ds), m_bufType(GDT_Float64),
     139             :           m_it(GDALRasterBand::WindowIterator(
     140          28 :               m_ds.GetRasterXSize(), m_ds.GetRasterYSize(),
     141          28 :               m_ds.GetRasterXSize(), m_ds.GetRasterYSize(), 0, 0)),
     142             :           m_end(GDALRasterBand::WindowIterator(
     143          28 :               m_ds.GetRasterXSize(), m_ds.GetRasterYSize(),
     144          28 :               m_ds.GetRasterXSize(), m_ds.GetRasterYSize(), 1, 0)),
     145          14 :           m_includeXY(options.includeXY),
     146          14 :           m_includeRowCol(options.includeRowCol),
     147          14 :           m_excludeNoDataPixels(options.skipNoData)
     148             :     {
     149             :         // TODO: Handle Int64, UInt64
     150          14 :         m_ds.GetGeoTransform(m_gt);
     151             : 
     152          14 :         int nBands = m_ds.GetRasterCount();
     153          14 :         m_bands.resize(nBands);
     154          31 :         for (int i = 1; i <= nBands; i++)
     155             :         {
     156          17 :             m_bands[i - 1] = i;
     157             :         }
     158             : 
     159             :         // TODO: Handle per-band NoData values
     160          14 :         if (nBands > 0)
     161             :         {
     162             :             int hasNoData;
     163          13 :             double noData = m_ds.GetRasterBand(1)->GetNoDataValue(&hasNoData);
     164          13 :             if (hasNoData)
     165             :             {
     166           1 :                 m_noData = noData;
     167             :             }
     168             :         }
     169             : 
     170          14 :         m_defn = new OGRFeatureDefn();
     171          14 :         m_defn->GetGeomFieldDefn(0)->SetType(options.geomType);
     172          14 :         m_defn->GetGeomFieldDefn(0)->SetSpatialRef(ds.GetSpatialRef());
     173          14 :         m_defn->Reference();
     174             : 
     175          14 :         if (m_includeXY)
     176             :         {
     177           2 :             auto xField = std::make_unique<OGRFieldDefn>(X_FIELD, OFTReal);
     178           2 :             auto yField = std::make_unique<OGRFieldDefn>(Y_FIELD, OFTReal);
     179           1 :             m_defn->AddFieldDefn(std::move(xField));
     180           1 :             m_defn->AddFieldDefn(std::move(yField));
     181             :         }
     182          14 :         if (m_includeRowCol)
     183             :         {
     184             :             auto rowField =
     185           8 :                 std::make_unique<OGRFieldDefn>(ROW_FIELD, OFTInteger);
     186             :             auto colField =
     187           8 :                 std::make_unique<OGRFieldDefn>(COL_FIELD, OFTInteger);
     188           4 :             m_defn->AddFieldDefn(std::move(rowField));
     189           4 :             m_defn->AddFieldDefn(std::move(colField));
     190             :         }
     191          31 :         for (int band : m_bands)
     192             :         {
     193          34 :             CPLString fieldName = CPLSPrintf("BAND_%d", band);
     194             :             auto bandField =
     195          17 :                 std::make_unique<OGRFieldDefn>(fieldName.c_str(), OFTReal);
     196          17 :             m_defn->AddFieldDefn(std::move(bandField));
     197          17 :             m_bandFields.push_back(m_defn->GetFieldIndex(fieldName));
     198             :         }
     199             : 
     200          14 :         GDALRasterAsFeaturesLayer::ResetReading();
     201          14 :     }
     202             : 
     203             :     ~GDALRasterAsFeaturesLayer() override;
     204             : 
     205          28 :     void ResetReading() override
     206             :     {
     207          28 :         if (m_ds.GetRasterCount() > 0)
     208             :         {
     209          26 :             GDALRasterBand *poFirstBand = m_ds.GetRasterBand(1);
     210          26 :             CPLAssert(poFirstBand);  // appease clang scan-build
     211          26 :             m_it = poFirstBand->IterateWindows().begin();
     212          26 :             m_end = poFirstBand->IterateWindows().end();
     213             :         }
     214          28 :     }
     215             : 
     216           0 :     int TestCapability(const char *) const override
     217             :     {
     218           0 :         return 0;
     219             :     }
     220             : 
     221          28 :     OGRFeatureDefn *GetLayerDefn() const override
     222             :     {
     223          28 :         return m_defn;
     224             :     }
     225             : 
     226        8598 :     OGRFeature *GetNextFeature() override
     227             :     {
     228        8598 :         if (m_row >= m_window.nYSize && !NextWindow())
     229             :         {
     230          14 :             return nullptr;
     231             :         }
     232             : 
     233       17168 :         std::unique_ptr<OGRFeature> feature;
     234             : 
     235        8604 :         while (m_row < m_window.nYSize)
     236             :         {
     237       17208 :             const double *pSrcVal = static_cast<double *>(m_buf) +
     238        8604 :                                     (m_bands.size() * m_row * m_window.nXSize +
     239        8604 :                                      m_col * m_bands.size());
     240             : 
     241             :             const bool emitFeature =
     242        8604 :                 !m_excludeNoDataPixels || !IsNoData(*pSrcVal);
     243             : 
     244        8604 :             if (emitFeature)
     245             :             {
     246        8584 :                 feature.reset(OGRFeature::CreateFeature(m_defn));
     247             : 
     248       27164 :                 for (int fieldPos : m_bandFields)
     249             :                 {
     250       18580 :                     feature->SetField(fieldPos, *pSrcVal);
     251       18580 :                     pSrcVal++;
     252             :                 }
     253             : 
     254        8584 :                 const double line = m_window.nYOff + m_row;
     255        8584 :                 const double pixel = m_window.nXOff + m_col;
     256             : 
     257        8584 :                 if (m_includeRowCol)
     258             :                 {
     259         404 :                     feature->SetField(ROW_FIELD, static_cast<GIntBig>(line));
     260         404 :                     feature->SetField(COL_FIELD, static_cast<GIntBig>(pixel));
     261             :                 }
     262        8584 :                 if (m_includeXY)
     263             :                 {
     264             :                     double x, y;
     265         400 :                     m_gt.Apply(pixel + 0.5, line + 0.5, &x, &y);
     266         400 :                     feature->SetField(X_FIELD, x);
     267         400 :                     feature->SetField(Y_FIELD, y);
     268             :                 }
     269             : 
     270           0 :                 std::unique_ptr<OGRGeometry> geom;
     271        8584 :                 const auto geomType = m_defn->GetGeomFieldDefn(0)->GetType();
     272        8584 :                 if (geomType == wkbPoint)
     273             :                 {
     274             :                     double x, y;
     275        2900 :                     m_gt.Apply(pixel + 0.5, line + 0.5, &x, &y);
     276             : 
     277        2900 :                     geom = std::make_unique<OGRPoint>(x, y);
     278             :                 }
     279        5684 :                 else if (geomType == wkbPolygon)
     280             :                 {
     281             :                     double x, y;
     282             : 
     283         800 :                     auto lr = std::make_unique<OGRLinearRing>();
     284             : 
     285         400 :                     m_gt.Apply(pixel, line, &x, &y);
     286         400 :                     lr->addPoint(x, y);
     287         400 :                     m_gt.Apply(pixel, line + 1, &x, &y);
     288         400 :                     lr->addPoint(x, y);
     289         400 :                     m_gt.Apply(pixel + 1, line + 1, &x, &y);
     290         400 :                     lr->addPoint(x, y);
     291         400 :                     m_gt.Apply(pixel + 1, line, &x, &y);
     292         400 :                     lr->addPoint(x, y);
     293         400 :                     m_gt.Apply(pixel, line, &x, &y);
     294         400 :                     lr->addPoint(x, y);
     295             : 
     296         800 :                     auto poly = std::make_unique<OGRPolygon>();
     297         400 :                     poly->addRing(std::move(lr));
     298         400 :                     geom = std::move(poly);
     299             :                 }
     300             : 
     301        8584 :                 feature->SetGeometry(std::move(geom));
     302             :             }
     303             : 
     304        8604 :             m_col += 1;
     305        8604 :             if (m_col >= m_window.nXSize)
     306             :             {
     307         282 :                 m_col = 0;
     308         282 :                 m_row++;
     309             :             }
     310             : 
     311        8604 :             if (feature)
     312             :             {
     313        8584 :                 return feature.release();
     314             :             }
     315             :         }
     316             : 
     317           0 :         return nullptr;
     318             :     }
     319             : 
     320             :     CPL_DISALLOW_COPY_ASSIGN(GDALRasterAsFeaturesLayer)
     321             : 
     322             :   private:
     323         400 :     bool IsNoData(double x) const
     324             :     {
     325         400 :         if (!m_noData.has_value())
     326             :         {
     327           0 :             return false;
     328             :         }
     329             : 
     330         780 :         return m_noData.value() == x ||
     331         780 :                (std::isnan(m_noData.value()) && std::isnan(x));
     332             :     }
     333             : 
     334          26 :     bool NextWindow()
     335             :     {
     336          26 :         int nBandCount = static_cast<int>(m_bands.size());
     337             : 
     338          26 :         if (m_it == m_end)
     339             :         {
     340          13 :             return false;
     341             :         }
     342             : 
     343          13 :         if (m_ds.GetRasterXSize() == 0 || m_ds.GetRasterYSize() == 0)
     344             :         {
     345           1 :             return false;
     346             :         }
     347             : 
     348          12 :         m_window = *m_it;
     349          12 :         ++m_it;
     350             : 
     351          12 :         if (m_buf == nullptr && !m_bands.empty())
     352             :         {
     353          11 :             m_buf =
     354          11 :                 VSI_MALLOC3_VERBOSE(m_window.nXSize, m_window.nYSize,
     355             :                                     static_cast<size_t>(nBandCount) *
     356             :                                         GDALGetDataTypeSizeBytes(m_bufType));
     357          11 :             if (m_buf == nullptr)
     358             :             {
     359           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     360             :                          "Failed to allocate buffer");
     361           0 :                 return false;
     362             :             }
     363             :         }
     364             : 
     365          27 :         for (size_t i = 0; i < m_bands.size(); i++)
     366             :         {
     367             :             auto eErr =
     368          15 :                 m_ds.GetRasterBand(m_bands[i])
     369          15 :                     ->RasterIO(GF_Read, m_window.nXOff, m_window.nYOff,
     370             :                                m_window.nXSize, m_window.nYSize,
     371          15 :                                static_cast<GByte *>(m_buf) +
     372          15 :                                    i * GDALGetDataTypeSizeBytes(m_bufType),
     373             :                                m_window.nXSize, m_window.nYSize, m_bufType,
     374          15 :                                static_cast<GSpacing>(nBandCount) *
     375          15 :                                    GDALGetDataTypeSizeBytes(m_bufType),
     376          30 :                                static_cast<GSpacing>(nBandCount) *
     377          15 :                                    GDALGetDataTypeSizeBytes(m_bufType) *
     378          15 :                                    m_window.nXSize,
     379             :                                nullptr);
     380             : 
     381          15 :             if (eErr != CE_None)
     382             :             {
     383           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     384             :                          "Failed to read raster data");
     385           0 :                 return false;
     386             :             }
     387             :         }
     388             : 
     389          12 :         m_row = 0;
     390          12 :         m_col = 0;
     391             : 
     392          12 :         return true;
     393             :     }
     394             : 
     395             :     GDALDataset &m_ds;
     396             :     void *m_buf{nullptr};
     397             :     GDALDataType m_bufType;
     398             :     GDALGeoTransform m_gt{};
     399             :     std::optional<double> m_noData{std::nullopt};
     400             : 
     401             :     std::vector<int> m_bands{};
     402             :     std::vector<int> m_bandFields{};
     403             : 
     404             :     GDALRasterBand::WindowIterator m_it;
     405             :     GDALRasterBand::WindowIterator m_end;
     406             :     GDALRasterWindow m_window{};
     407             : 
     408             :     int m_row{0};
     409             :     int m_col{0};
     410             : 
     411             :     OGRFeatureDefn *m_defn{nullptr};
     412             :     bool m_includeXY;
     413             :     bool m_includeRowCol;
     414             :     bool m_excludeNoDataPixels;
     415             : };
     416             : 
     417          28 : GDALRasterAsFeaturesLayer::~GDALRasterAsFeaturesLayer()
     418             : {
     419          14 :     m_defn->Release();
     420          14 :     if (m_buf != nullptr)
     421             :     {
     422          11 :         CPLFree(m_buf);
     423             :     }
     424          28 : }
     425             : 
     426          14 : bool GDALRasterAsFeaturesAlgorithm::RunStep(GDALPipelineStepRunContext &)
     427             : {
     428          14 :     auto poSrcDS = m_inputDataset[0].GetDatasetRef();
     429             : 
     430          14 :     GDALDataset *poDstDS = m_outputDataset.GetDatasetRef();
     431          28 :     std::string outputFilename = m_outputDataset.GetName();
     432             : 
     433          14 :     std::unique_ptr<GDALDataset> poRetDS;
     434          14 :     if (!poDstDS)
     435             :     {
     436          14 :         if (m_standaloneStep && m_format.empty())
     437             :         {
     438             :             const auto aosFormats =
     439             :                 CPLStringList(GDALGetOutputDriversForDatasetName(
     440          14 :                     m_outputDataset.GetName().c_str(), GDAL_OF_VECTOR,
     441             :                     /* bSingleMatch = */ true,
     442          14 :                     /* bWarn = */ true));
     443          14 :             if (aosFormats.size() != 1)
     444             :             {
     445           0 :                 ReportError(CE_Failure, CPLE_AppDefined,
     446             :                             "Cannot guess driver for %s",
     447           0 :                             m_outputDataset.GetName().c_str());
     448           0 :                 return false;
     449             :             }
     450          14 :             m_format = aosFormats[0];
     451             : 
     452             :             auto poDriver =
     453          14 :                 GetGDALDriverManager()->GetDriverByName(m_format.c_str());
     454             : 
     455          14 :             poRetDS.reset(
     456             :                 poDriver->Create(outputFilename.c_str(), 0, 0, 0, GDT_Unknown,
     457          28 :                                  CPLStringList(m_creationOptions).List()));
     458             :         }
     459             :         else
     460             :         {
     461             :             poRetDS =
     462           0 :                 std::make_unique<GDALRasterToVectorPipelineOutputDataset>();
     463             :         }
     464             : 
     465          14 :         if (!poRetDS)
     466           0 :             return false;
     467             : 
     468          14 :         poDstDS = poRetDS.get();
     469             :     }
     470             : 
     471          28 :     RasterAsFeaturesOptions options;
     472          26 :     options.geomType = m_geomTypeName == "point"     ? wkbPoint
     473          12 :                        : m_geomTypeName == "polygon" ? wkbPolygon
     474             :                                                      : wkbNone;
     475          14 :     options.includeRowCol = m_includeRowCol;
     476          14 :     options.includeXY = m_includeXY;
     477          14 :     options.skipNoData = m_skipNoData;
     478             : 
     479          14 :     if (!m_bands.empty())
     480             :     {
     481           1 :         options.bands = std::move(m_bands);
     482             :     }
     483             : 
     484             :     auto poLayer =
     485          14 :         std::make_unique<GDALRasterAsFeaturesLayer>(*poSrcDS, options);
     486             : 
     487          14 :     poDstDS->CopyLayer(poLayer.get(), m_outputLayerName.c_str());
     488             : 
     489          14 :     if (poRetDS)
     490             :     {
     491          14 :         m_outputDataset.Set(std::move(poRetDS));
     492             :     }
     493             : 
     494          14 :     return true;
     495             : }
     496             : 
     497             : //! @endcond

Generated by: LCOV version 1.14