LCOV - code coverage report
Current view: top level - alg - zonal.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 869 963 90.2 %
Date: 2025-12-28 05:19:56 Functions: 32 32 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             : *
       3             :  * Project:  GDAL
       4             :  * Purpose:  GDALZonalStats implementation
       5             :  * Author:   Dan Baston
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2025, ISciences LLC
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_string.h"
      14             : #include "gdal_priv.h"
      15             : #include "gdal_alg.h"
      16             : #include "gdal_utils.h"
      17             : #include "ogrsf_frmts.h"
      18             : #include "raster_stats.h"
      19             : 
      20             : #include "../frmts/mem/memdataset.h"
      21             : #include "../frmts/vrt/vrtdataset.h"
      22             : 
      23             : #include "ogr_geos.h"
      24             : 
      25             : #include <algorithm>
      26             : #include <array>
      27             : #include <cstring>
      28             : #include <limits>
      29             : #include <variant>
      30             : #include <vector>
      31             : 
      32             : #if GEOS_VERSION_MAJOR > 3 ||                                                  \
      33             :     (GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 14)
      34             : #define GEOS_GRID_INTERSECTION_AVAILABLE 1
      35             : #endif
      36             : 
      37             : struct GDALZonalStatsOptions
      38             : {
      39         147 :     CPLErr Init(CSLConstList papszOptions)
      40             :     {
      41         928 :         for (const auto &[key, value] : cpl::IterateNameValue(papszOptions))
      42             :         {
      43         781 :             if (EQUAL(key, "BANDS"))
      44             :             {
      45             :                 const CPLStringList aosBands(CSLTokenizeString2(
      46          21 :                     value, ",", CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES));
      47          49 :                 for (const char *pszBand : aosBands)
      48             :                 {
      49          28 :                     int nBand = std::atoi(pszBand);
      50          28 :                     if (nBand <= 0)
      51             :                     {
      52           0 :                         CPLError(CE_Failure, CPLE_IllegalArg,
      53             :                                  "Invalid band: %s", pszBand);
      54           0 :                         return CE_Failure;
      55             :                     }
      56          28 :                     bands.push_back(nBand);
      57             :                 }
      58             :             }
      59         760 :             else if (EQUAL(key, "INCLUDE_FIELDS"))
      60             :             {
      61             :                 CPLStringList aosFields(CSLTokenizeString2(
      62             :                     value, ",",
      63             :                     CSLT_HONOURSTRINGS | CSLT_STRIPLEADSPACES |
      64          10 :                         CSLT_STRIPENDSPACES));
      65          12 :                 for (const char *pszField : aosFields)
      66             :                 {
      67           7 :                     include_fields.push_back(pszField);
      68             :                 }
      69             :             }
      70         755 :             else if (EQUAL(key, "PIXEL_INTERSECTION"))
      71             :             {
      72         147 :                 if (EQUAL(value, "DEFAULT"))
      73             :                 {
      74          83 :                     pixels = DEFAULT;
      75             :                 }
      76          64 :                 else if (EQUAL(value, "ALL-TOUCHED") ||
      77          56 :                          EQUAL(value, "ALL_TOUCHED"))
      78             :                 {
      79           8 :                     pixels = ALL_TOUCHED;
      80             :                 }
      81          56 :                 else if (EQUAL(value, "FRACTIONAL"))
      82             :                 {
      83          56 :                     pixels = FRACTIONAL;
      84             :                 }
      85             :                 else
      86             :                 {
      87           0 :                     CPLError(CE_Failure, CPLE_IllegalArg,
      88             :                              "Unexpected value of PIXEL_INTERSECTION: %s",
      89             :                              value);
      90           0 :                     return CE_Failure;
      91             :                 }
      92             :             }
      93         608 :             else if (EQUAL(key, "RASTER_CHUNK_SIZE_BYTES"))
      94             :             {
      95         147 :                 char *endptr = nullptr;
      96         147 :                 errno = 0;
      97         147 :                 const auto memory64 = std::strtoull(value, &endptr, 10);
      98         294 :                 bool ok = errno != ERANGE && memory64 != ULLONG_MAX &&
      99         147 :                           endptr == value + strlen(value);
     100             :                 if constexpr (sizeof(memory64) > sizeof(size_t))
     101             :                 {
     102             :                     ok = ok &&
     103             :                          memory64 <= std::numeric_limits<size_t>::max() - 1;
     104             :                 }
     105         147 :                 if (!ok)
     106             :                 {
     107           0 :                     CPLError(CE_Failure, CPLE_IllegalArg,
     108             :                              "Invalid memory size: %s", value);
     109           0 :                     return CE_Failure;
     110             :                 }
     111         147 :                 memory = static_cast<size_t>(memory64);
     112             :             }
     113         461 :             else if (EQUAL(key, "STATS"))
     114             :             {
     115         294 :                 stats = CPLStringList(CSLTokenizeString2(
     116         147 :                     value, ",", CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES));
     117             :             }
     118         314 :             else if (EQUAL(key, "STRATEGY"))
     119             :             {
     120         147 :                 if (EQUAL(value, "FEATURE_SEQUENTIAL"))
     121             :                 {
     122          98 :                     strategy = FEATURE_SEQUENTIAL;
     123             :                 }
     124          49 :                 else if (EQUAL(value, "RASTER_SEQUENTIAL"))
     125             :                 {
     126          49 :                     strategy = RASTER_SEQUENTIAL;
     127             :                 }
     128             :                 else
     129             :                 {
     130           0 :                     CPLError(CE_Failure, CPLE_IllegalArg,
     131             :                              "Unexpected value of STRATEGY: %s", value);
     132           0 :                     return CE_Failure;
     133             :                 }
     134             :             }
     135         167 :             else if (EQUAL(key, "WEIGHTS_BAND"))
     136             :             {
     137         147 :                 weights_band = std::atoi(value);
     138         147 :                 if (weights_band <= 0)
     139             :                 {
     140           0 :                     CPLError(CE_Failure, CPLE_IllegalArg,
     141             :                              "Invalid weights band: %s", value);
     142           0 :                     return CE_Failure;
     143             :                 }
     144             :             }
     145          20 :             else if (EQUAL(key, "ZONES_BAND"))
     146             :             {
     147           1 :                 zones_band = std::atoi(value);
     148           1 :                 if (zones_band <= 0)
     149             :                 {
     150           0 :                     CPLError(CE_Failure, CPLE_IllegalArg,
     151             :                              "Invalid zones band: %s", value);
     152           0 :                     return CE_Failure;
     153             :                 }
     154             :             }
     155          19 :             else if (EQUAL(key, "ZONES_LAYER"))
     156             :             {
     157           1 :                 zones_layer = value;
     158             :             }
     159          18 :             else if (STARTS_WITH(key, "LCO_"))
     160             :             {
     161          18 :                 layer_creation_options.SetNameValue(key + strlen("LCO_"),
     162          18 :                                                     value);
     163             :             }
     164             :             else
     165             :             {
     166           0 :                 CPLError(CE_Failure, CPLE_IllegalArg,
     167             :                          "Unexpected zonal stats option: %s", key);
     168             :             }
     169             :         }
     170             : 
     171         147 :         return CE_None;
     172             :     }
     173             : 
     174             :     enum PixelIntersection
     175             :     {
     176             :         DEFAULT,
     177             :         ALL_TOUCHED,
     178             :         FRACTIONAL,
     179             :     };
     180             : 
     181             :     enum Strategy
     182             :     {
     183             :         FEATURE_SEQUENTIAL,
     184             :         RASTER_SEQUENTIAL,
     185             :     };
     186             : 
     187             :     PixelIntersection pixels{DEFAULT};
     188             :     Strategy strategy{FEATURE_SEQUENTIAL};
     189             :     std::vector<std::string> stats{};
     190             :     std::vector<std::string> include_fields{};
     191             :     std::vector<int> bands{};
     192             :     std::string zones_layer{};
     193             :     std::size_t memory{0};
     194             :     int zones_band{};
     195             :     int weights_band{};
     196             :     CPLStringList layer_creation_options{};
     197             : };
     198             : 
     199          16 : template <typename T = GByte> auto CreateBuffer()
     200             : {
     201          16 :     return std::unique_ptr<T, VSIFreeReleaser>(nullptr);
     202             : }
     203             : 
     204             : template <typename T>
     205         533 : void Realloc(T &buf, size_t size1, size_t size2, bool &success)
     206             : {
     207         533 :     if (!success)
     208             :     {
     209           0 :         return;
     210             :     }
     211             :     if constexpr (sizeof(size_t) < sizeof(uint64_t))
     212             :     {
     213             :         if (size1 > std::numeric_limits<size_t>::max() / size2)
     214             :         {
     215             :             success = false;
     216             :             CPLError(CE_Failure, CPLE_OutOfMemory,
     217             :                      "Too big memory allocation attempt");
     218             :             return;
     219             :         }
     220             :     }
     221         533 :     const auto size = size1 * size2;
     222         533 :     auto oldBuf = buf.release();
     223             :     auto newBuf = static_cast<typename T::element_type *>(
     224         533 :         VSI_REALLOC_VERBOSE(oldBuf, size));
     225         533 :     if (newBuf == nullptr)
     226             :     {
     227           0 :         VSIFree(oldBuf);
     228           0 :         success = false;
     229             :     }
     230         533 :     buf.reset(newBuf);
     231             : }
     232             : 
     233          57 : static void CalculateCellCenters(const GDALRasterWindow &window,
     234             :                                  const GDALGeoTransform &gt, double *padfX,
     235             :                                  double *padfY)
     236             : {
     237             :     double dfJunk;
     238          57 :     double x0 = window.nXOff;
     239          57 :     double y0 = window.nYOff;
     240             : 
     241         765 :     for (int i = 0; i < window.nXSize; i++)
     242             :     {
     243         708 :         gt.Apply(x0 + i + 0.5, window.nYOff, padfX + i, &dfJunk);
     244             :     }
     245        1176 :     for (int i = 0; i < window.nYSize; i++)
     246             :     {
     247        1119 :         gt.Apply(x0, y0 + i + 0.5, &dfJunk, padfY + i);
     248             :     }
     249          57 : }
     250             : 
     251             : class GDALZonalStatsImpl
     252             : {
     253             :   public:
     254             :     enum Stat
     255             :     {
     256             :         CENTER_X,  // must be first value
     257             :         CENTER_Y,
     258             :         COUNT,
     259             :         COVERAGE,
     260             :         FRAC,
     261             :         MAX,
     262             :         MAX_CENTER_X,
     263             :         MAX_CENTER_Y,
     264             :         MEAN,
     265             :         MIN,
     266             :         MIN_CENTER_X,
     267             :         MIN_CENTER_Y,
     268             :         MINORITY,
     269             :         MODE,
     270             :         STDEV,
     271             :         SUM,
     272             :         UNIQUE,
     273             :         VALUES,
     274             :         VARIANCE,
     275             :         VARIETY,
     276             :         WEIGHTED_FRAC,
     277             :         WEIGHTED_MEAN,
     278             :         WEIGHTED_SUM,
     279             :         WEIGHTED_STDEV,
     280             :         WEIGHTED_VARIANCE,
     281             :         WEIGHTS,
     282             :         INVALID,  // must be last value
     283             :     };
     284             : 
     285         134 :     static constexpr bool IsWeighted(Stat eStat)
     286             :     {
     287         133 :         return eStat == WEIGHTS || eStat == WEIGHTED_FRAC ||
     288         132 :                eStat == WEIGHTED_MEAN || eStat == WEIGHTED_SUM ||
     289         267 :                eStat == WEIGHTED_VARIANCE || eStat == WEIGHTED_STDEV;
     290             :     }
     291             : 
     292             :     using BandOrLayer = std::variant<GDALRasterBand *, OGRLayer *>;
     293             : 
     294         144 :     GDALZonalStatsImpl(GDALDataset &src, GDALDataset &dst, GDALDataset *weights,
     295             :                        BandOrLayer zones, const GDALZonalStatsOptions &options)
     296         144 :         : m_src(src), m_weights(weights), m_dst(dst), m_zones(zones),
     297         144 :           m_coverageDataType(options.pixels == GDALZonalStatsOptions::FRACTIONAL
     298         144 :                                  ? GDT_Float32
     299             :                                  : GDT_UInt8),
     300             :           m_options(options),
     301         288 :           m_maxCells(options.memory /
     302         288 :                      std::max(1, GDALGetDataTypeSizeBytes(m_workingDataType)))
     303             :     {
     304             : #ifdef HAVE_GEOS
     305         144 :         m_geosContext = OGRGeometry::createGEOSContext();
     306             : #endif
     307         144 :     }
     308             : 
     309         144 :     ~GDALZonalStatsImpl()
     310         144 :     {
     311             : #ifdef HAVE_GEOS
     312         144 :         if (m_geosContext)
     313             :         {
     314         144 :             finishGEOS_r(m_geosContext);
     315             :         }
     316             : #endif
     317         144 :     }
     318             : 
     319             :   private:
     320         144 :     bool Init()
     321             :     {
     322             : #if !(GEOS_GRID_INTERSECTION_AVAILABLE)
     323             :         if (m_options.pixels == GDALZonalStatsOptions::FRACTIONAL)
     324             :         {
     325             :             CPLError(CE_Failure, CPLE_AppDefined,
     326             :                      "Fractional pixel coverage calculation requires a GDAL "
     327             :                      "build against GEOS >= 3.14");
     328             :             return false;
     329             :         }
     330             : #endif
     331             : 
     332         144 :         if (m_options.bands.empty())
     333             :         {
     334         123 :             const int nBands = m_src.GetRasterCount();
     335         123 :             if (nBands == 0)
     336             :             {
     337           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     338             :                          "GDALRasterZonalStats: input dataset has no bands");
     339           0 :                 return false;
     340             :             }
     341         123 :             m_options.bands.resize(nBands);
     342         246 :             for (int i = 0; i < nBands; i++)
     343             :             {
     344         123 :                 m_options.bands[i] = i + 1;
     345             :             }
     346             :         }
     347             :         else
     348             :         {
     349          49 :             for (int nBand : m_options.bands)
     350             :             {
     351          28 :                 if (nBand <= 0 || nBand > m_src.GetRasterCount())
     352             :                 {
     353           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
     354             :                              "GDALRasterZonalStats: Invalid band number: %d",
     355             :                              nBand);
     356           0 :                     return false;
     357             :                 }
     358             :             }
     359             :         }
     360             : 
     361             :         {
     362         144 :             const auto eSrcType = m_src.GetRasterBand(m_options.bands.front())
     363         144 :                                       ->GetRasterDataType();
     364         144 :             if (GDALDataTypeIsConversionLossy(eSrcType, m_workingDataType))
     365             :             {
     366           5 :                 CPLError(CE_Failure, CPLE_AppDefined,
     367             :                          "GDALRasterZonalStats: Source data type %s is not "
     368             :                          "supported",
     369             :                          GDALGetDataTypeName(eSrcType));
     370           5 :                 return false;
     371             :             }
     372             :         }
     373             : 
     374         139 :         if (m_weights)
     375             :         {
     376          79 :             if (m_options.weights_band > m_weights->GetRasterCount())
     377             :             {
     378           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     379             :                          "GDALRasterZonalStats: invalid weights band");
     380           1 :                 return false;
     381             :             }
     382             :             const auto eWeightsType =
     383          78 :                 m_weights->GetRasterBand(m_options.weights_band)
     384          78 :                     ->GetRasterDataType();
     385          78 :             if (GDALDataTypeIsConversionLossy(eWeightsType, GDT_Float64))
     386             :             {
     387           5 :                 CPLError(CE_Failure, CPLE_AppDefined,
     388             :                          "GDALRasterZonalStats: Weights data type %s is not "
     389             :                          "supported",
     390             :                          GDALGetDataTypeName(eWeightsType));
     391           5 :                 return false;
     392             :             }
     393             :         }
     394             : 
     395         374 :         for (const auto &stat : m_options.stats)
     396             :         {
     397         246 :             const auto eStat = GetStat(stat);
     398         246 :             switch (eStat)
     399             :             {
     400           0 :                 case INVALID:
     401             :                 {
     402           0 :                     CPLError(CE_Failure, CPLE_AppDefined, "Invalid stat: %s",
     403             :                              stat.c_str());
     404           5 :                     return false;
     405             :                 }
     406             : 
     407           2 :                 case COVERAGE:
     408           2 :                     m_stats_options.store_coverage_fraction = true;
     409           2 :                     break;
     410             : 
     411          30 :                 case VARIETY:
     412             :                 case MODE:
     413             :                 case MINORITY:
     414             :                 case UNIQUE:
     415             :                 case FRAC:
     416             :                 case WEIGHTED_FRAC:
     417          30 :                     m_stats_options.store_histogram = true;
     418          30 :                     break;
     419             : 
     420          20 :                 case VARIANCE:
     421             :                 case STDEV:
     422             :                 case WEIGHTED_VARIANCE:
     423             :                 case WEIGHTED_STDEV:
     424          20 :                     m_stats_options.calc_variance = true;
     425          20 :                     break;
     426             : 
     427          30 :                 case CENTER_X:
     428             :                 case CENTER_Y:
     429             :                 case MIN_CENTER_X:
     430             :                 case MIN_CENTER_Y:
     431             :                 case MAX_CENTER_X:
     432             :                 case MAX_CENTER_Y:
     433          30 :                     m_stats_options.store_xy = true;
     434          30 :                     break;
     435             : 
     436           2 :                 case VALUES:
     437           2 :                     m_stats_options.store_values = true;
     438           2 :                     break;
     439             : 
     440           6 :                 case WEIGHTS:
     441           6 :                     m_stats_options.store_weights = true;
     442           6 :                     break;
     443             : 
     444         156 :                 case COUNT:
     445             :                 case MIN:
     446             :                 case MAX:
     447             :                 case SUM:
     448             :                 case MEAN:
     449             :                 case WEIGHTED_SUM:
     450             :                 case WEIGHTED_MEAN:
     451         156 :                     break;
     452             :             }
     453         246 :             if (m_weights == nullptr && IsWeighted(eStat))
     454             :             {
     455           5 :                 CPLError(CE_Failure, CPLE_AppDefined,
     456             :                          "Stat %s requires weights but none were provided",
     457             :                          stat.c_str());
     458           5 :                 return false;
     459             :             }
     460             :         }
     461             : 
     462         128 :         if (m_src.GetGeoTransform(m_srcGT) != CE_None)
     463             :         {
     464           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     465             :                      "Dataset has no geotransform");
     466           1 :             return false;
     467             :         }
     468         127 :         if (!m_srcGT.GetInverse(m_srcInvGT))
     469             :         {
     470           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     471             :                      "Dataset geotransform cannot be inverted");
     472           1 :             return false;
     473             :         }
     474             : 
     475         126 :         const OGRSpatialReference *poRastSRS = m_src.GetSpatialRefRasterOnly();
     476             :         const OGRSpatialReference *poWeightsSRS =
     477         126 :             m_weights ? m_weights->GetSpatialRefRasterOnly() : nullptr;
     478         126 :         const OGRSpatialReference *poZonesSRS = nullptr;
     479             : 
     480         126 :         if (ZonesAreFeature())
     481             :         {
     482         109 :             const OGRLayer *poSrcLayer = std::get<OGRLayer *>(m_zones);
     483         109 :             const OGRFeatureDefn *poSrcDefn = poSrcLayer->GetLayerDefn();
     484         109 :             poZonesSRS = poSrcLayer->GetSpatialRef();
     485             : 
     486         113 :             for (const auto &field : m_options.include_fields)
     487             :             {
     488           6 :                 if (poSrcDefn->GetFieldIndex(field.c_str()) == -1)
     489             :                 {
     490           2 :                     CPLError(CE_Failure, CPLE_AppDefined, "Field %s not found.",
     491             :                              field.c_str());
     492           2 :                     return false;
     493             :                 }
     494             :             }
     495             :         }
     496             :         else
     497             :         {
     498          17 :             poZonesSRS = std::get<GDALRasterBand *>(m_zones)
     499          17 :                              ->GetDataset()
     500          17 :                              ->GetSpatialRefRasterOnly();
     501             : 
     502          17 :             if (!m_options.include_fields.empty())
     503             :             {
     504           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     505             :                          "Cannot include fields from raster zones");
     506           1 :                 return false;
     507             :             }
     508             :         }
     509             : 
     510         123 :         CPLStringList aosOptions;
     511         123 :         aosOptions.AddNameValue("IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING", "1");
     512             : 
     513         135 :         if (poRastSRS && poZonesSRS &&
     514          12 :             !poRastSRS->IsSame(poZonesSRS, aosOptions.List()))
     515             :         {
     516           2 :             CPLError(CE_Warning, CPLE_AppDefined,
     517             :                      "Inputs and zones do not have the same SRS");
     518             :         }
     519             : 
     520         125 :         if (poWeightsSRS && poZonesSRS &&
     521           2 :             !poWeightsSRS->IsSame(poZonesSRS, aosOptions.List()))
     522             :         {
     523           2 :             CPLError(CE_Warning, CPLE_AppDefined,
     524             :                      "Weights and zones do not have the same SRS");
     525             :         }
     526             : 
     527         127 :         if (poWeightsSRS && poRastSRS &&
     528           4 :             !poWeightsSRS->IsSame(poRastSRS, aosOptions.List()))
     529             :         {
     530           4 :             CPLError(CE_Warning, CPLE_AppDefined,
     531             :                      "Inputs and weights do not have the same SRS");
     532             :         }
     533             : 
     534         123 :         return true;
     535             :     }
     536             : 
     537       11761 :     gdal::RasterStats<double> CreateStats() const
     538             :     {
     539       11761 :         return gdal::RasterStats<double>{m_stats_options};
     540             :     }
     541             : 
     542         122 :     OGRLayer *GetOutputLayer(bool createValueField)
     543             :     {
     544         244 :         std::string osLayerName = "stats";
     545             : 
     546             :         OGRLayer *poLayer =
     547         122 :             m_dst.CreateLayer(osLayerName.c_str(), nullptr,
     548         122 :                               m_options.layer_creation_options.List());
     549         122 :         if (!poLayer)
     550           0 :             return nullptr;
     551             : 
     552         122 :         if (createValueField)
     553             :         {
     554          16 :             OGRFieldDefn oFieldDefn("value", OFTReal);
     555          16 :             if (poLayer->CreateField(&oFieldDefn) != OGRERR_NONE)
     556           0 :                 return nullptr;
     557             :         }
     558             : 
     559         122 :         if (!m_options.include_fields.empty())
     560             :         {
     561             :             const OGRFeatureDefn *poSrcDefn =
     562           2 :                 std::get<OGRLayer *>(m_zones)->GetLayerDefn();
     563             : 
     564           6 :             for (const auto &field : m_options.include_fields)
     565             :             {
     566           4 :                 const int iField = poSrcDefn->GetFieldIndex(field.c_str());
     567             :                 // Already checked field names during Init()
     568           4 :                 if (poLayer->CreateField(poSrcDefn->GetFieldDefn(iField)) !=
     569             :                     OGRERR_NONE)
     570           0 :                     return nullptr;
     571             :             }
     572             :         }
     573             : 
     574         251 :         for (int iBand : m_options.bands)
     575             :         {
     576         129 :             auto &aiStatFields = m_statFields[iBand];
     577         129 :             aiStatFields.fill(-1);
     578             : 
     579         382 :             for (const auto &stat : m_options.stats)
     580             :             {
     581         253 :                 const Stat eStat = GetStat(stat);
     582             : 
     583         253 :                 std::string osFieldName;
     584         253 :                 if (m_options.bands.size() > 1)
     585             :                 {
     586          36 :                     osFieldName = CPLSPrintf("%s_band_%d", stat.c_str(), iBand);
     587             :                 }
     588             :                 else
     589             :                 {
     590         217 :                     osFieldName = stat;
     591             :                 }
     592             : 
     593             :                 OGRFieldDefn oFieldDefn(osFieldName.c_str(),
     594         253 :                                         GetFieldType(eStat));
     595         253 :                 if (poLayer->CreateField(&oFieldDefn) != OGRERR_NONE)
     596           0 :                     return nullptr;
     597             :                 const int iNewField =
     598         253 :                     poLayer->GetLayerDefn()->GetFieldIndex(osFieldName.c_str());
     599         253 :                 aiStatFields[eStat] = iNewField;
     600             :             }
     601             :         }
     602             : 
     603         122 :         return poLayer;
     604             :     }
     605             : 
     606        6812 :     static const char *GetString(Stat s)
     607             :     {
     608        6812 :         switch (s)
     609             :         {
     610         499 :             case CENTER_X:
     611         499 :                 return "center_x";
     612         495 :             case CENTER_Y:
     613         495 :                 return "center_y";
     614         491 :             case COUNT:
     615         491 :                 return "count";
     616         447 :             case COVERAGE:
     617         447 :                 return "coverage";
     618         443 :             case FRAC:
     619         443 :                 return "frac";
     620         439 :             case MAX:
     621         439 :                 return "max";
     622         416 :             case MAX_CENTER_X:
     623         416 :                 return "max_center_x";
     624         393 :             case MAX_CENTER_Y:
     625         393 :                 return "max_center_y";
     626         370 :             case MEAN:
     627         370 :                 return "mean";
     628         305 :             case MIN:
     629         305 :                 return "min";
     630         301 :             case MIN_CENTER_X:
     631         301 :                 return "min_center_x";
     632         297 :             case MIN_CENTER_Y:
     633         297 :                 return "min_center_y";
     634         293 :             case MINORITY:
     635         293 :                 return "minority";
     636         289 :             case MODE:
     637         289 :                 return "mode";
     638         245 :             case STDEV:
     639         245 :                 return "stdev";
     640         241 :             case SUM:
     641         241 :                 return "sum";
     642         128 :             case UNIQUE:
     643         128 :                 return "unique";
     644         124 :             case VALUES:
     645         124 :                 return "values";
     646         120 :             case VARIANCE:
     647         120 :                 return "variance";
     648         116 :             case VARIETY:
     649         116 :                 return "variety";
     650         112 :             case WEIGHTED_FRAC:
     651         112 :                 return "weighted_frac";
     652         112 :             case WEIGHTED_MEAN:
     653         112 :                 return "weighted_mean";
     654          58 :             case WEIGHTED_SUM:
     655          58 :                 return "weighted_sum";
     656          41 :             case WEIGHTED_STDEV:
     657          41 :                 return "weighted_stdev";
     658          26 :             case WEIGHTED_VARIANCE:
     659          26 :                 return "weighted_variance";
     660          11 :             case WEIGHTS:
     661          11 :                 return "weights";
     662           0 :             case INVALID:
     663           0 :                 break;
     664             :         }
     665           0 :         return "invalid";
     666             :     }
     667             : 
     668         499 :     static Stat GetStat(const std::string &stat)
     669             :     {
     670        6812 :         for (Stat s = CENTER_X; s < INVALID; s = static_cast<Stat>(s + 1))
     671             :         {
     672        6812 :             if (stat == GetString(s))
     673         499 :                 return s;
     674             :         }
     675           0 :         return INVALID;
     676             :     }
     677             : 
     678         253 :     static OGRFieldType GetFieldType(Stat stat)
     679             :     {
     680         253 :         switch (stat)
     681             :         {
     682          17 :             case CENTER_X:
     683             :             case CENTER_Y:
     684             :             case COVERAGE:
     685             :             case FRAC:
     686             :             case UNIQUE:
     687             :             case VALUES:
     688             :             case WEIGHTS:
     689          17 :                 return OFTRealList;
     690           2 :             case VARIETY:
     691           2 :                 return OFTInteger;
     692         234 :             case COUNT:
     693             :             case MAX:
     694             :             case MAX_CENTER_X:
     695             :             case MAX_CENTER_Y:
     696             :             case MEAN:
     697             :             case MIN:
     698             :             case MIN_CENTER_X:
     699             :             case MIN_CENTER_Y:
     700             :             case MINORITY:
     701             :             case MODE:
     702             :             case STDEV:
     703             :             case SUM:
     704             :             case VARIANCE:
     705             :             case WEIGHTED_FRAC:
     706             :             case WEIGHTED_MEAN:
     707             :             case WEIGHTED_SUM:
     708             :             case WEIGHTED_STDEV:
     709             :             case WEIGHTED_VARIANCE:
     710             :             case INVALID:
     711         234 :                 break;
     712             :         }
     713         234 :         return OFTReal;
     714             :     }
     715             : 
     716        7182 :     int GetFieldIndex(int iBand, Stat eStat) const
     717             :     {
     718        7182 :         auto it = m_statFields.find(iBand);
     719        7182 :         if (it == m_statFields.end())
     720             :         {
     721           0 :             return -1;
     722             :         }
     723             : 
     724        7182 :         return it->second[eStat];
     725             :     }
     726             : 
     727         382 :     OGREnvelope ToEnvelope(const GDALRasterWindow &window) const
     728             :     {
     729         382 :         OGREnvelope oSnappedGeomExtent;
     730         382 :         m_srcGT.Apply(window, oSnappedGeomExtent);
     731         382 :         return oSnappedGeomExtent;
     732             :     }
     733             : 
     734         266 :     void SetStatFields(OGRFeature &feature, int iBand,
     735             :                        const gdal::RasterStats<double> &stats) const
     736             :     {
     737         266 :         if (auto iField = GetFieldIndex(iBand, CENTER_X); iField != -1)
     738             :         {
     739           2 :             const auto &center_x = stats.center_x();
     740           2 :             feature.SetField(iField, static_cast<int>(center_x.size()),
     741             :                              center_x.data());
     742             :         }
     743         266 :         if (auto iField = GetFieldIndex(iBand, CENTER_Y); iField != -1)
     744             :         {
     745           2 :             const auto &center_y = stats.center_y();
     746           2 :             feature.SetField(iField, static_cast<int>(center_y.size()),
     747             :                              center_y.data());
     748             :         }
     749         266 :         if (auto iField = GetFieldIndex(iBand, COUNT); iField != -1)
     750             :         {
     751          46 :             feature.SetField(iField, stats.count());
     752             :         }
     753         266 :         if (auto iField = GetFieldIndex(iBand, COVERAGE); iField != -1)
     754             :         {
     755           2 :             const auto &cov = stats.coverage_fractions();
     756           4 :             std::vector<double> doubleCov(cov.begin(), cov.end());
     757             :             // TODO: Add float* overload to Feature::SetField to avoid this copy
     758           2 :             feature.SetField(iField, static_cast<int>(doubleCov.size()),
     759           2 :                              doubleCov.data());
     760             :         }
     761         266 :         if (auto iField = GetFieldIndex(iBand, FRAC); iField != -1)
     762             :         {
     763           2 :             const auto count = stats.count();
     764           2 :             const auto &freq = stats.freq();
     765           4 :             std::vector<double> values;
     766           2 :             values.reserve(freq.size());
     767          18 :             for (const auto &[_, valueCount] : freq)
     768             :             {
     769          16 :                 values.push_back(valueCount.m_sum_ci / count);
     770             :             }
     771           2 :             feature.SetField(iField, static_cast<int>(values.size()),
     772           2 :                              values.data());
     773             :         }
     774         266 :         if (auto iField = GetFieldIndex(iBand, MAX); iField != -1)
     775             :         {
     776          34 :             const auto &max = stats.max();
     777          34 :             if (max.has_value())
     778          34 :                 feature.SetField(iField, max.value());
     779             :         }
     780         266 :         if (auto iField = GetFieldIndex(iBand, MAX_CENTER_X); iField != -1)
     781             :         {
     782          34 :             const auto &loc = stats.max_xy();
     783          34 :             if (loc.has_value())
     784          34 :                 feature.SetField(iField, loc.value().first);
     785             :         }
     786         266 :         if (auto iField = GetFieldIndex(iBand, MAX_CENTER_Y); iField != -1)
     787             :         {
     788          34 :             const auto &loc = stats.max_xy();
     789          34 :             if (loc.has_value())
     790          34 :                 feature.SetField(iField, loc.value().second);
     791             :         }
     792         266 :         if (auto iField = GetFieldIndex(iBand, MEAN); iField != -1)
     793             :         {
     794          94 :             feature.SetField(iField, stats.mean());
     795             :         }
     796         266 :         if (auto iField = GetFieldIndex(iBand, MIN); iField != -1)
     797             :         {
     798           2 :             const auto &min = stats.min();
     799           2 :             if (min.has_value())
     800           2 :                 feature.SetField(iField, min.value());
     801             :         }
     802         266 :         if (auto iField = GetFieldIndex(iBand, MINORITY); iField != -1)
     803             :         {
     804           2 :             const auto &minority = stats.minority();
     805           2 :             if (minority.has_value())
     806           2 :                 feature.SetField(iField, minority.value());
     807             :         }
     808         266 :         if (auto iField = GetFieldIndex(iBand, MIN_CENTER_X); iField != -1)
     809             :         {
     810           2 :             const auto &loc = stats.min_xy();
     811           2 :             if (loc.has_value())
     812           2 :                 feature.SetField(iField, loc.value().first);
     813             :         }
     814         266 :         if (auto iField = GetFieldIndex(iBand, MIN_CENTER_Y); iField != -1)
     815             :         {
     816           2 :             const auto &loc = stats.min_xy();
     817           2 :             if (loc.has_value())
     818           2 :                 feature.SetField(iField, loc.value().second);
     819             :         }
     820         266 :         if (auto iField = GetFieldIndex(iBand, MODE); iField != -1)
     821             :         {
     822          46 :             const auto &mode = stats.mode();
     823          46 :             if (mode.has_value())
     824           7 :                 feature.SetField(iField, mode.value());
     825             :         }
     826         266 :         if (auto iField = GetFieldIndex(iBand, STDEV); iField != -1)
     827             :         {
     828           2 :             feature.SetField(iField, stats.stdev());
     829             :         }
     830         266 :         if (auto iField = GetFieldIndex(iBand, SUM); iField != -1)
     831             :         {
     832         172 :             feature.SetField(iField, stats.sum());
     833             :         }
     834         266 :         if (auto iField = GetFieldIndex(iBand, UNIQUE); iField != -1)
     835             :         {
     836           2 :             const auto &freq = stats.freq();
     837           4 :             std::vector<double> values;
     838           2 :             values.reserve(freq.size());
     839          18 :             for (const auto &[value, _] : freq)
     840             :             {
     841          16 :                 values.push_back(value);
     842             :             }
     843             : 
     844           2 :             feature.SetField(iField, static_cast<int>(values.size()),
     845           2 :                              values.data());
     846             :         }
     847         266 :         if (auto iField = GetFieldIndex(iBand, VALUES); iField != -1)
     848             :         {
     849           2 :             const auto &values = stats.values();
     850           2 :             feature.SetField(iField, static_cast<int>(values.size()),
     851             :                              values.data());
     852             :         }
     853         266 :         if (auto iField = GetFieldIndex(iBand, VARIANCE); iField != -1)
     854             :         {
     855           2 :             feature.SetField(iField, stats.variance());
     856             :         }
     857         266 :         if (auto iField = GetFieldIndex(iBand, VARIETY); iField != -1)
     858             :         {
     859           2 :             feature.SetField(iField, static_cast<GIntBig>(stats.variety()));
     860             :         }
     861         266 :         if (auto iField = GetFieldIndex(iBand, WEIGHTED_FRAC); iField != -1)
     862             :         {
     863           0 :             const auto count = stats.count();
     864           0 :             const auto &freq = stats.freq();
     865           0 :             std::vector<double> values;
     866           0 :             values.reserve(freq.size());
     867           0 :             for (const auto &[_, valueCount] : freq)
     868             :             {
     869             :                 // Add std::numeric_limits<double>::min() to please Coverity Scan
     870           0 :                 values.push_back(valueCount.m_sum_ciwi /
     871           0 :                                  (count + std::numeric_limits<double>::min()));
     872             :             }
     873           0 :             feature.SetField(iField, static_cast<int>(values.size()),
     874           0 :                              values.data());
     875             :         }
     876         266 :         if (auto iField = GetFieldIndex(iBand, WEIGHTED_MEAN); iField != -1)
     877             :         {
     878          43 :             feature.SetField(iField, stats.weighted_mean());
     879             :         }
     880         266 :         if (auto iField = GetFieldIndex(iBand, WEIGHTED_STDEV); iField != -1)
     881             :         {
     882           7 :             feature.SetField(iField, stats.weighted_stdev());
     883             :         }
     884         266 :         if (auto iField = GetFieldIndex(iBand, WEIGHTED_SUM); iField != -1)
     885             :         {
     886          12 :             feature.SetField(iField, stats.weighted_sum());
     887             :         }
     888         266 :         if (auto iField = GetFieldIndex(iBand, WEIGHTED_VARIANCE); iField != -1)
     889             :         {
     890           7 :             feature.SetField(iField, stats.weighted_variance());
     891             :         }
     892         266 :         if (auto iField = GetFieldIndex(iBand, WEIGHTED_SUM); iField != -1)
     893             :         {
     894          12 :             feature.SetField(iField, stats.weighted_sum());
     895             :         }
     896         266 :         if (auto iField = GetFieldIndex(iBand, WEIGHTS); iField != -1)
     897             :         {
     898           5 :             const auto &weights = stats.weights();
     899           5 :             feature.SetField(iField, static_cast<int>(weights.size()),
     900             :                              weights.data());
     901             :         }
     902         266 :     }
     903             : 
     904             :   public:
     905         270 :     bool ZonesAreFeature() const
     906             :     {
     907         270 :         return std::holds_alternative<OGRLayer *>(m_zones);
     908             :     }
     909             : 
     910         144 :     bool Process(GDALProgressFunc pfnProgress, void *pProgressData)
     911             :     {
     912         144 :         if (ZonesAreFeature())
     913             :         {
     914         125 :             if (m_options.strategy == GDALZonalStatsOptions::RASTER_SEQUENTIAL)
     915             :             {
     916          49 :                 return ProcessVectorZonesByChunk(pfnProgress, pProgressData);
     917             :             }
     918             : 
     919          76 :             return ProcessVectorZonesByFeature(pfnProgress, pProgressData);
     920             :         }
     921             : 
     922          19 :         return ProcessRasterZones(pfnProgress, pProgressData);
     923             :     }
     924             : 
     925             :   private:
     926             :     static std::unique_ptr<GDALDataset>
     927          89 :     GetVRT(GDALDataset &src, const GDALDataset &dst, bool &resampled)
     928             :     {
     929          89 :         resampled = false;
     930             : 
     931          89 :         GDALGeoTransform srcGT, dstGT;
     932          89 :         if (src.GetGeoTransform(srcGT) != CE_None)
     933             :         {
     934           0 :             return nullptr;
     935             :         }
     936          89 :         if (dst.GetGeoTransform(dstGT) != CE_None)
     937             :         {
     938           0 :             return nullptr;
     939             :         }
     940             : 
     941         178 :         CPLStringList aosOptions;
     942          89 :         aosOptions.AddString("-of");
     943          89 :         aosOptions.AddString("VRT");
     944             : 
     945          89 :         aosOptions.AddString("-ot");
     946          89 :         aosOptions.AddString("Float64");
     947             : 
     948             :         // Prevent warning message about Computed -srcwin outside source raster extent.
     949             :         // We've already tested for this an issued a more understandable message.
     950          89 :         aosOptions.AddString("--no-warn-about-outside-window");
     951             : 
     952         123 :         if (srcGT != dstGT || src.GetRasterXSize() != dst.GetRasterXSize() ||
     953          34 :             src.GetRasterYSize() != dst.GetRasterYSize())
     954             :         {
     955             :             const double dfColOffset =
     956          55 :                 std::fmod(std::abs(srcGT.xorig - dstGT.xorig), dstGT.xscale);
     957             :             const double dfRowOffset =
     958          55 :                 std::fmod(std::abs(srcGT.yorig - dstGT.yorig), dstGT.yscale);
     959             : 
     960          55 :             OGREnvelope oDstEnv;
     961          55 :             dst.GetExtent(&oDstEnv);
     962             : 
     963          55 :             aosOptions.AddString("-projwin");
     964          55 :             aosOptions.AddString(CPLSPrintf("%.17g", oDstEnv.MinX));
     965          55 :             aosOptions.AddString(CPLSPrintf("%.17g", oDstEnv.MaxY));
     966          55 :             aosOptions.AddString(CPLSPrintf("%.17g", oDstEnv.MaxX));
     967          55 :             aosOptions.AddString(CPLSPrintf("%.17g", oDstEnv.MinY));
     968             : 
     969         106 :             if (srcGT.xscale != dstGT.xscale || srcGT.yscale != dstGT.yscale ||
     970         161 :                 std::abs(dfColOffset) > 1e-4 || std::abs(dfRowOffset) > 1e-4)
     971             :             {
     972           2 :                 resampled = true;
     973           2 :                 aosOptions.AddString("-r");
     974           2 :                 aosOptions.AddString("average");
     975             :             }
     976             : 
     977          55 :             aosOptions.AddString("-tr");
     978          55 :             aosOptions.AddString(CPLSPrintf("%.17g", dstGT.xscale));
     979          55 :             aosOptions.AddString(CPLSPrintf("%.17g", std::abs(dstGT.yscale)));
     980             :         }
     981             : 
     982          89 :         std::unique_ptr<GDALDataset> ret;
     983             : 
     984             :         GDALTranslateOptions *psOptions =
     985          89 :             GDALTranslateOptionsNew(aosOptions.List(), nullptr);
     986          89 :         ret.reset(GDALDataset::FromHandle(GDALTranslate(
     987             :             "", GDALDataset::ToHandle(&src), psOptions, nullptr)));
     988          89 :         GDALTranslateOptionsFree(psOptions);
     989             : 
     990          89 :         return ret;
     991             :     }
     992             : 
     993          16 :     void WarnIfZonesNotCovered(const GDALRasterBand *poZonesBand) const
     994             :     {
     995          16 :         OGREnvelope oZonesEnv;
     996          16 :         poZonesBand->GetDataset()->GetExtent(&oZonesEnv);
     997             : 
     998             :         {
     999          16 :             OGREnvelope oSrcEnv;
    1000          16 :             m_src.GetExtent(&oSrcEnv);
    1001             : 
    1002          16 :             if (!oZonesEnv.Intersects(oSrcEnv))
    1003             :             {
    1004             :                 // TODO: Make this an error? Or keep it as a warning but short-circuit to avoid reading pixels?
    1005           2 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1006             :                          "Source raster does not intersect zones raster");
    1007             :             }
    1008          14 :             else if (!oSrcEnv.Contains(oZonesEnv))
    1009             :             {
    1010             :                 int bHasNoData;
    1011           2 :                 m_src.GetRasterBand(m_options.bands.front())
    1012           2 :                     ->GetNoDataValue(&bHasNoData);
    1013           2 :                 if (bHasNoData)
    1014             :                 {
    1015           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1016             :                              "Source raster does not fully cover zones raster."
    1017             :                              "Pixels that do not intersect the values raster "
    1018             :                              "will be treated as having a NoData value.");
    1019             :                 }
    1020             :                 else
    1021             :                 {
    1022           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1023             :                              "Source raster does not fully cover zones raster. "
    1024             :                              "Pixels that do not intersect the value raster "
    1025             :                              "will be treated as having value of zero.");
    1026             :                 }
    1027             :             }
    1028             :         }
    1029             : 
    1030          16 :         if (!m_weights)
    1031             :         {
    1032           5 :             return;
    1033             :         }
    1034             : 
    1035          11 :         OGREnvelope oWeightsEnv;
    1036          11 :         m_weights->GetExtent(&oWeightsEnv);
    1037             : 
    1038          11 :         if (!oZonesEnv.Intersects(oWeightsEnv))
    1039             :         {
    1040             :             // TODO: Make this an error? Or keep it as a warning but short-circuit to avoid reading pixels?
    1041           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1042             :                      "Weighting raster does not intersect zones raster");
    1043             :         }
    1044          11 :         else if (!oWeightsEnv.Contains(oZonesEnv))
    1045             :         {
    1046             :             int bHasNoData;
    1047           1 :             m_src.GetRasterBand(m_options.bands.front())
    1048           1 :                 ->GetNoDataValue(&bHasNoData);
    1049           1 :             if (bHasNoData)
    1050             :             {
    1051           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1052             :                          "Weighting raster does not fully cover zones raster."
    1053             :                          "Pixels that do not intersect the weighting raster "
    1054             :                          "will be treated as having a NoData weight.");
    1055             :             }
    1056             :             else
    1057             :             {
    1058           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1059             :                          "Weighting raster does not fully cover zones raster. "
    1060             :                          "Pixels that do not intersect the weighting raster "
    1061             :                          "will be treated as having a weight of zero.");
    1062             :             }
    1063             :         }
    1064             :     }
    1065             : 
    1066          19 :     bool ProcessRasterZones(GDALProgressFunc pfnProgress, void *pProgressData)
    1067             :     {
    1068          19 :         if (!Init())
    1069             :         {
    1070           3 :             return false;
    1071             :         }
    1072             : 
    1073          16 :         GDALRasterBand *poZonesBand = std::get<GDALRasterBand *>(m_zones);
    1074          16 :         WarnIfZonesNotCovered(poZonesBand);
    1075             : 
    1076          16 :         OGRLayer *poDstLayer = GetOutputLayer(true);
    1077          16 :         if (!poDstLayer)
    1078           0 :             return false;
    1079             : 
    1080             :         // Align the src dataset to the zones.
    1081             :         bool resampled;
    1082             :         std::unique_ptr<GDALDataset> poAlignedValuesDS =
    1083          32 :             GetVRT(m_src, *poZonesBand->GetDataset(), resampled);
    1084          16 :         if (resampled)
    1085             :         {
    1086           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1087             :                      "Resampled source raster to match zones using average "
    1088             :                      "resampling.");
    1089             :         }
    1090             : 
    1091             :         // Align the weighting dataset to the zones.
    1092          16 :         std::unique_ptr<GDALDataset> poAlignedWeightsDS;
    1093          16 :         GDALRasterBand *poWeightsBand = nullptr;
    1094          16 :         if (m_weights)
    1095             :         {
    1096             :             poAlignedWeightsDS =
    1097          11 :                 GetVRT(*m_weights, *poZonesBand->GetDataset(), resampled);
    1098          11 :             if (!poAlignedWeightsDS)
    1099             :             {
    1100           0 :                 return false;
    1101             :             }
    1102          11 :             if (resampled)
    1103             :             {
    1104           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1105             :                          "Resampled weighting raster to match zones using "
    1106             :                          "average resampling.");
    1107             :             }
    1108             : 
    1109             :             poWeightsBand =
    1110          11 :                 poAlignedWeightsDS->GetRasterBand(m_options.weights_band);
    1111             :         }
    1112             : 
    1113          32 :         std::map<double, std::vector<gdal::RasterStats<double>>> stats;
    1114             : 
    1115          32 :         auto pabyZonesBuf = CreateBuffer();
    1116          16 :         size_t nBufSize = 0;
    1117             : 
    1118             :         const auto windowIteratorWrapper =
    1119          16 :             poAlignedValuesDS->GetRasterBand(1)->IterateWindows(m_maxCells);
    1120          16 :         const auto nIterCount = windowIteratorWrapper.count();
    1121          16 :         uint64_t iWindow = 0;
    1122          53 :         for (const auto &oWindow : windowIteratorWrapper)
    1123             :         {
    1124          37 :             const auto nWindowSize = static_cast<size_t>(oWindow.nXSize) *
    1125          37 :                                      static_cast<size_t>(oWindow.nYSize);
    1126             : 
    1127          37 :             if (nBufSize < nWindowSize)
    1128             :             {
    1129          16 :                 bool bAllocSuccess = true;
    1130          16 :                 Realloc(m_pabyValuesBuf, nWindowSize,
    1131          16 :                         GDALGetDataTypeSizeBytes(m_workingDataType),
    1132             :                         bAllocSuccess);
    1133          16 :                 Realloc(pabyZonesBuf, nWindowSize,
    1134          16 :                         GDALGetDataTypeSizeBytes(m_zonesDataType),
    1135             :                         bAllocSuccess);
    1136          16 :                 Realloc(m_pabyMaskBuf, nWindowSize,
    1137          16 :                         GDALGetDataTypeSizeBytes(m_maskDataType),
    1138             :                         bAllocSuccess);
    1139             : 
    1140          16 :                 if (m_stats_options.store_xy)
    1141             :                 {
    1142           3 :                     Realloc(m_padfX, oWindow.nXSize,
    1143           3 :                             GDALGetDataTypeSizeBytes(GDT_Float64),
    1144             :                             bAllocSuccess);
    1145           3 :                     Realloc(m_padfY, oWindow.nYSize,
    1146           3 :                             GDALGetDataTypeSizeBytes(GDT_Float64),
    1147             :                             bAllocSuccess);
    1148             :                 }
    1149             : 
    1150          16 :                 if (poWeightsBand)
    1151             :                 {
    1152          11 :                     Realloc(m_padfWeightsBuf, nWindowSize,
    1153          11 :                             GDALGetDataTypeSizeBytes(GDT_Float64),
    1154             :                             bAllocSuccess);
    1155          11 :                     Realloc(m_pabyWeightsMaskBuf, nWindowSize,
    1156          11 :                             GDALGetDataTypeSizeBytes(m_maskDataType),
    1157             :                             bAllocSuccess);
    1158             :                 }
    1159          16 :                 if (!bAllocSuccess)
    1160             :                 {
    1161           0 :                     return false;
    1162             :                 }
    1163             : 
    1164          16 :                 nBufSize = nWindowSize;
    1165             :             }
    1166             : 
    1167          37 :             if (m_padfX && m_padfY)
    1168             :             {
    1169          24 :                 CalculateCellCenters(oWindow, m_srcGT, m_padfX.get(),
    1170             :                                      m_padfY.get());
    1171             :             }
    1172             : 
    1173          37 :             if (!ReadWindow(*poZonesBand, oWindow, pabyZonesBuf.get(),
    1174             :                             m_zonesDataType))
    1175             :             {
    1176           0 :                 return false;
    1177             :             }
    1178             : 
    1179          37 :             if (poWeightsBand)
    1180             :             {
    1181          32 :                 if (!ReadWindow(
    1182             :                         *poWeightsBand, oWindow,
    1183          32 :                         reinterpret_cast<GByte *>(m_padfWeightsBuf.get()),
    1184             :                         GDT_Float64))
    1185             :                 {
    1186           0 :                     return false;
    1187             :                 }
    1188          32 :                 if (!ReadWindow(*poWeightsBand->GetMaskBand(), oWindow,
    1189             :                                 m_pabyWeightsMaskBuf.get(), GDT_UInt8))
    1190             :                 {
    1191           0 :                     return false;
    1192             :                 }
    1193             :             }
    1194             : 
    1195          82 :             for (size_t i = 0; i < m_options.bands.size(); i++)
    1196             :             {
    1197          45 :                 const int iBand = m_options.bands[i];
    1198             : 
    1199             :                 GDALRasterBand *poBand =
    1200          45 :                     poAlignedValuesDS->GetRasterBand(iBand);
    1201             : 
    1202          45 :                 if (!ReadWindow(*poBand, oWindow, m_pabyValuesBuf.get(),
    1203          45 :                                 m_workingDataType))
    1204             :                 {
    1205           0 :                     return false;
    1206             :                 }
    1207             : 
    1208          45 :                 if (!ReadWindow(*poBand->GetMaskBand(), oWindow,
    1209          45 :                                 m_pabyMaskBuf.get(), m_maskDataType))
    1210             :                 {
    1211           0 :                     return false;
    1212             :                 }
    1213             : 
    1214          45 :                 size_t ipx = 0;
    1215         985 :                 for (int k = 0; k < oWindow.nYSize; k++)
    1216             :                 {
    1217       12540 :                     for (int j = 0; j < oWindow.nXSize; j++)
    1218             :                     {
    1219             :                         // TODO use inner loop to search for a block of constant pixel values.
    1220             :                         double zone =
    1221       11600 :                             reinterpret_cast<double *>(pabyZonesBuf.get())[ipx];
    1222             : 
    1223       11600 :                         auto &aoStats = stats[zone];
    1224       11600 :                         aoStats.resize(m_options.bands.size(), CreateStats());
    1225             : 
    1226       68800 :                         aoStats[i].process(
    1227       11600 :                             reinterpret_cast<double *>(m_pabyValuesBuf.get()) +
    1228             :                                 ipx,
    1229       11600 :                             m_pabyMaskBuf.get() + ipx,
    1230       11600 :                             m_padfWeightsBuf.get()
    1231       10800 :                                 ? m_padfWeightsBuf.get() + ipx
    1232             :                                 : nullptr,
    1233       11600 :                             m_pabyWeightsMaskBuf.get()
    1234       10800 :                                 ? m_pabyWeightsMaskBuf.get() + ipx
    1235             :                                 : nullptr,
    1236       21600 :                             m_padfX ? m_padfX.get() + j : nullptr,
    1237       21600 :                             m_padfY ? m_padfY.get() + k : nullptr, 1, 1);
    1238             : 
    1239       11600 :                         ipx++;
    1240             :                     }
    1241             :                 }
    1242             :             }
    1243             : 
    1244          37 :             if (pfnProgress != nullptr)
    1245             :             {
    1246           0 :                 ++iWindow;
    1247           0 :                 pfnProgress(static_cast<double>(iWindow) /
    1248           0 :                                 static_cast<double>(nIterCount),
    1249             :                             "", pProgressData);
    1250             :             }
    1251             :         }
    1252             : 
    1253          81 :         for (const auto &[dfValue, zoneStats] : stats)
    1254             :         {
    1255          65 :             OGRFeature oFeature(poDstLayer->GetLayerDefn());
    1256          65 :             oFeature.SetField("value", dfValue);
    1257         135 :             for (size_t i = 0; i < m_options.bands.size(); i++)
    1258             :             {
    1259          70 :                 const auto iBand = m_options.bands[i];
    1260          70 :                 SetStatFields(oFeature, iBand, zoneStats[i]);
    1261             :             }
    1262          65 :             if (poDstLayer->CreateFeature(&oFeature) != OGRERR_NONE)
    1263             :             {
    1264           0 :                 return false;
    1265             :             }
    1266             :         }
    1267             : 
    1268          16 :         return true;
    1269             :     }
    1270             : 
    1271         717 :     static bool ReadWindow(GDALRasterBand &band,
    1272             :                            const GDALRasterWindow &oWindow, GByte *pabyBuf,
    1273             :                            GDALDataType dataType)
    1274             :     {
    1275        1434 :         return band.RasterIO(GF_Read, oWindow.nXOff, oWindow.nYOff,
    1276         717 :                              oWindow.nXSize, oWindow.nYSize, pabyBuf,
    1277         717 :                              oWindow.nXSize, oWindow.nYSize, dataType, 0, 0,
    1278         717 :                              nullptr) == CE_None;
    1279             :     }
    1280             : 
    1281             : #ifndef HAVE_GEOS
    1282             :     bool ProcessVectorZonesByChunk(GDALProgressFunc, void *)
    1283             :     {
    1284             :         CPLError(CE_Failure, CPLE_AppDefined,
    1285             :                  "The GEOS library is required to iterate over blocks of the "
    1286             :                  "input rasters. Processing can be performed by iterating over "
    1287             :                  "the input features instead.");
    1288             :         return false;
    1289             : #else
    1290          49 :     bool ProcessVectorZonesByChunk(GDALProgressFunc pfnProgress,
    1291             :                                    void *pProgressData)
    1292             :     {
    1293          49 :         if (!Init())
    1294             :         {
    1295           1 :             return false;
    1296             :         }
    1297             : 
    1298          48 :         std::unique_ptr<GDALDataset> poAlignedWeightsDS;
    1299             :         // Align the weighting dataset to the values.
    1300          48 :         if (m_weights)
    1301             :         {
    1302          27 :             bool resampled = false;
    1303          27 :             poAlignedWeightsDS = GetVRT(*m_weights, m_src, resampled);
    1304          27 :             if (!poAlignedWeightsDS)
    1305             :             {
    1306           0 :                 return false;
    1307             :             }
    1308          27 :             if (resampled)
    1309             :             {
    1310           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1311             :                          "Resampled weights to match source raster using "
    1312             :                          "average resampling.");
    1313             :             }
    1314             :         }
    1315             : 
    1316          48 :         auto TreeDeleter = [this](GEOSSTRtree *tree)
    1317          48 :         { GEOSSTRtree_destroy_r(m_geosContext, tree); };
    1318             : 
    1319             :         std::unique_ptr<GEOSSTRtree, decltype(TreeDeleter)> tree(
    1320          96 :             GEOSSTRtree_create_r(m_geosContext, 10), TreeDeleter);
    1321             : 
    1322          96 :         std::vector<std::unique_ptr<OGRFeature>> features;
    1323          96 :         std::map<int, std::vector<gdal::RasterStats<double>>> statsMap;
    1324             : 
    1325             :         // Construct spatial index of all input features, storing the index
    1326             :         // of the feature.
    1327             :         {
    1328          48 :             OGREnvelope oGeomExtent;
    1329         121 :             for (auto &poFeatureIn : *std::get<OGRLayer *>(m_zones))
    1330             :             {
    1331          74 :                 features.emplace_back(poFeatureIn.release());
    1332             : 
    1333          74 :                 const OGRGeometry *poGeom = features.back()->GetGeometryRef();
    1334             : 
    1335          74 :                 if (poGeom == nullptr || poGeom->IsEmpty())
    1336             :                 {
    1337           9 :                     continue;
    1338             :                 }
    1339             : 
    1340          65 :                 if (poGeom->getDimension() != 2)
    1341             :                 {
    1342           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1343             :                              "Non-polygonal geometry encountered.");
    1344           1 :                     return false;
    1345             :                 }
    1346             : 
    1347          64 :                 poGeom->getEnvelope(&oGeomExtent);
    1348          64 :                 GEOSGeometry *poEnv = CreateGEOSEnvelope(oGeomExtent);
    1349          64 :                 if (poEnv == nullptr)
    1350             :                 {
    1351           0 :                     return false;
    1352             :                 }
    1353             : 
    1354          64 :                 GEOSSTRtree_insert_r(
    1355             :                     m_geosContext, tree.get(), poEnv,
    1356          64 :                     reinterpret_cast<void *>(features.size() - 1));
    1357          64 :                 GEOSGeom_destroy_r(m_geosContext, poEnv);
    1358             :             }
    1359             :         }
    1360             : 
    1361          97 :         for (int iBand : m_options.bands)
    1362             :         {
    1363          50 :             statsMap[iBand].resize(features.size(), CreateStats());
    1364             :         }
    1365             : 
    1366          94 :         std::vector<void *> aiHits;
    1367          92 :         auto addHit = [](void *hit, void *hits)
    1368          92 :         { static_cast<std::vector<void *> *>(hits)->push_back(hit); };
    1369          47 :         size_t nBufSize = 0;
    1370             : 
    1371             :         const auto windowIteratorWrapper =
    1372          47 :             m_src.GetRasterBand(m_options.bands.front())
    1373          47 :                 ->IterateWindows(m_maxCells);
    1374          47 :         const auto nIterCount = windowIteratorWrapper.count();
    1375          47 :         uint64_t iWindow = 0;
    1376         157 :         for (const auto &oChunkWindow : windowIteratorWrapper)
    1377             :         {
    1378         110 :             const size_t nWindowSize =
    1379         110 :                 static_cast<size_t>(oChunkWindow.nXSize) *
    1380         110 :                 static_cast<size_t>(oChunkWindow.nYSize);
    1381         110 :             const OGREnvelope oChunkExtent = ToEnvelope(oChunkWindow);
    1382             : 
    1383         110 :             aiHits.clear();
    1384             : 
    1385             :             {
    1386         110 :                 GEOSGeometry *poEnv = CreateGEOSEnvelope(oChunkExtent);
    1387         110 :                 if (poEnv == nullptr)
    1388             :                 {
    1389           0 :                     return false;
    1390             :                 }
    1391             : 
    1392         110 :                 GEOSSTRtree_query_r(m_geosContext, tree.get(), poEnv, addHit,
    1393             :                                     &aiHits);
    1394         110 :                 GEOSGeom_destroy_r(m_geosContext, poEnv);
    1395             :             }
    1396             : 
    1397         110 :             if (!aiHits.empty())
    1398             :             {
    1399          74 :                 if (nBufSize < nWindowSize)
    1400             :                 {
    1401          38 :                     bool bAllocSuccess = true;
    1402          38 :                     Realloc(m_pabyValuesBuf, nWindowSize,
    1403          38 :                             GDALGetDataTypeSizeBytes(m_workingDataType),
    1404             :                             bAllocSuccess);
    1405          38 :                     Realloc(m_pabyCoverageBuf, nWindowSize,
    1406          38 :                             GDALGetDataTypeSizeBytes(m_coverageDataType),
    1407             :                             bAllocSuccess);
    1408          38 :                     Realloc(m_pabyMaskBuf, nWindowSize,
    1409          38 :                             GDALGetDataTypeSizeBytes(m_maskDataType),
    1410             :                             bAllocSuccess);
    1411          38 :                     if (m_stats_options.store_xy)
    1412             :                     {
    1413           9 :                         Realloc(m_padfX, oChunkWindow.nXSize,
    1414           9 :                                 GDALGetDataTypeSizeBytes(GDT_Float64),
    1415             :                                 bAllocSuccess);
    1416           9 :                         Realloc(m_padfY, oChunkWindow.nYSize,
    1417           9 :                                 GDALGetDataTypeSizeBytes(GDT_Float64),
    1418             :                                 bAllocSuccess);
    1419             :                     }
    1420          38 :                     if (m_weights != nullptr)
    1421             :                     {
    1422          27 :                         Realloc(m_padfWeightsBuf, nWindowSize,
    1423          27 :                                 GDALGetDataTypeSizeBytes(GDT_Float64),
    1424             :                                 bAllocSuccess);
    1425          27 :                         Realloc(m_pabyWeightsMaskBuf, nWindowSize,
    1426          27 :                                 GDALGetDataTypeSizeBytes(m_maskDataType),
    1427             :                                 bAllocSuccess);
    1428             :                     }
    1429          38 :                     if (!bAllocSuccess)
    1430             :                     {
    1431           0 :                         return false;
    1432             :                     }
    1433          38 :                     nBufSize = nWindowSize;
    1434             :                 }
    1435             : 
    1436          74 :                 if (m_padfX && m_padfY)
    1437             :                 {
    1438          21 :                     CalculateCellCenters(oChunkWindow, m_srcGT, m_padfX.get(),
    1439             :                                          m_padfY.get());
    1440             :                 }
    1441             : 
    1442          74 :                 if (m_weights != nullptr)
    1443             :                 {
    1444             :                     GDALRasterBand *poWeightsBand =
    1445          27 :                         poAlignedWeightsDS->GetRasterBand(
    1446             :                             m_options.weights_band);
    1447             : 
    1448          27 :                     if (!ReadWindow(
    1449             :                             *poWeightsBand, oChunkWindow,
    1450          27 :                             reinterpret_cast<GByte *>(m_padfWeightsBuf.get()),
    1451             :                             GDT_Float64))
    1452             :                     {
    1453           0 :                         return false;
    1454             :                     }
    1455          27 :                     if (!ReadWindow(*poWeightsBand->GetMaskBand(), oChunkWindow,
    1456             :                                     m_pabyWeightsMaskBuf.get(), GDT_UInt8))
    1457             :                     {
    1458           0 :                         return false;
    1459             :                     }
    1460             :                 }
    1461             : 
    1462         163 :                 for (int iBand : m_options.bands)
    1463             :                 {
    1464             : 
    1465          89 :                     GDALRasterBand *poBand = m_src.GetRasterBand(iBand);
    1466             : 
    1467         178 :                     if (!(ReadWindow(*poBand, oChunkWindow,
    1468             :                                      m_pabyValuesBuf.get(),
    1469          89 :                                      m_workingDataType) &&
    1470          89 :                           ReadWindow(*poBand->GetMaskBand(), oChunkWindow,
    1471          89 :                                      m_pabyMaskBuf.get(), m_maskDataType)))
    1472             :                     {
    1473           0 :                         return false;
    1474             :                     }
    1475             : 
    1476             :                     GDALRasterWindow oGeomWindow;
    1477          89 :                     OGREnvelope oGeomExtent;
    1478         199 :                     for (const void *hit : aiHits)
    1479             :                     {
    1480         110 :                         const size_t iHit = reinterpret_cast<size_t>(hit);
    1481         110 :                         const auto poGeom = features[iHit]->GetGeometryRef();
    1482             : 
    1483             :                         // Trim the chunk window to the portion that intersects
    1484             :                         // the geometry being processed.
    1485         110 :                         poGeom->getEnvelope(&oGeomExtent);
    1486         110 :                         oGeomExtent.Intersect(oChunkExtent);
    1487         110 :                         if (!m_srcInvGT.Apply(oGeomExtent, oGeomWindow))
    1488             :                         {
    1489           0 :                             return false;
    1490             :                         }
    1491         110 :                         oGeomWindow.nXOff =
    1492         110 :                             std::max(oGeomWindow.nXOff, oChunkWindow.nXOff);
    1493         110 :                         oGeomWindow.nYOff =
    1494         110 :                             std::max(oGeomWindow.nYOff, oChunkWindow.nYOff);
    1495         110 :                         oGeomWindow.nXSize =
    1496         110 :                             std::min(oGeomWindow.nXSize,
    1497         220 :                                      oChunkWindow.nXOff + oChunkWindow.nXSize -
    1498         110 :                                          oGeomWindow.nXOff);
    1499         110 :                         oGeomWindow.nYSize =
    1500         110 :                             std::min(oGeomWindow.nYSize,
    1501         220 :                                      oChunkWindow.nYOff + oChunkWindow.nYSize -
    1502         110 :                                          oGeomWindow.nYOff);
    1503         110 :                         if (oGeomWindow.nXSize <= 0 || oGeomWindow.nYSize <= 0)
    1504           0 :                             continue;
    1505             :                         const OGREnvelope oTrimmedEnvelope =
    1506         110 :                             ToEnvelope(oGeomWindow);
    1507             : 
    1508         110 :                         if (!CalculateCoverage(
    1509             :                                 poGeom, oTrimmedEnvelope, oGeomWindow.nXSize,
    1510             :                                 oGeomWindow.nYSize, m_pabyCoverageBuf.get()))
    1511             :                         {
    1512           0 :                             return false;
    1513             :                         }
    1514             : 
    1515             :                         // Because the window used for polygon coverage is not the
    1516             :                         // same as the window used for raster values, iterate
    1517             :                         // over partial scanlines on the raster window.
    1518         110 :                         const auto nCoverageXOff =
    1519         110 :                             oGeomWindow.nXOff - oChunkWindow.nXOff;
    1520         110 :                         const auto nCoverageYOff =
    1521         110 :                             oGeomWindow.nYOff - oChunkWindow.nYOff;
    1522         859 :                         for (int iRow = 0; iRow < oGeomWindow.nYSize; iRow++)
    1523             :                         {
    1524         749 :                             const auto nFirstPx =
    1525         749 :                                 (nCoverageYOff + iRow) * oChunkWindow.nXSize +
    1526             :                                 nCoverageXOff;
    1527        3745 :                             UpdateStats(
    1528         749 :                                 statsMap[iBand][iHit],
    1529        1498 :                                 m_pabyValuesBuf.get() +
    1530         749 :                                     nFirstPx * GDALGetDataTypeSizeBytes(
    1531         749 :                                                    m_workingDataType),
    1532         749 :                                 m_pabyMaskBuf.get() +
    1533         749 :                                     nFirstPx * GDALGetDataTypeSizeBytes(
    1534         749 :                                                    m_maskDataType),
    1535             :                                 m_padfWeightsBuf
    1536          80 :                                     ? m_padfWeightsBuf.get() + nFirstPx
    1537         749 :                                     : nullptr,
    1538             :                                 m_pabyWeightsMaskBuf
    1539          80 :                                     ? m_pabyWeightsMaskBuf.get() +
    1540          80 :                                           nFirstPx * GDALGetDataTypeSizeBytes(
    1541          80 :                                                          m_maskDataType)
    1542         749 :                                     : nullptr,
    1543         749 :                                 m_pabyCoverageBuf.get() +
    1544        1498 :                                     iRow * oGeomWindow.nXSize *
    1545         749 :                                         GDALGetDataTypeSizeBytes(
    1546         749 :                                             m_coverageDataType),
    1547         168 :                                 m_padfX ? m_padfX.get() + nCoverageXOff
    1548         749 :                                         : nullptr,
    1549         168 :                                 m_padfY ? m_padfY.get() + nCoverageYOff + iRow
    1550         749 :                                         : nullptr,
    1551         749 :                                 oGeomWindow.nXSize, 1);
    1552             :                         }
    1553             :                     }
    1554             :                 }
    1555             :             }
    1556             : 
    1557         110 :             if (pfnProgress != nullptr)
    1558             :             {
    1559           0 :                 ++iWindow;
    1560           0 :                 pfnProgress(static_cast<double>(iWindow) /
    1561           0 :                                 static_cast<double>(nIterCount),
    1562             :                             "", pProgressData);
    1563             :             }
    1564             :         }
    1565             : 
    1566          47 :         OGRLayer *poDstLayer = GetOutputLayer(false);
    1567          47 :         if (!poDstLayer)
    1568           0 :             return false;
    1569             : 
    1570         120 :         for (size_t iFeature = 0; iFeature < features.size(); iFeature++)
    1571             :         {
    1572             :             auto poDstFeature =
    1573          73 :                 std::make_unique<OGRFeature>(poDstLayer->GetLayerDefn());
    1574          73 :             poDstFeature->SetFrom(features[iFeature].get());
    1575         152 :             for (int iBand : m_options.bands)
    1576             :             {
    1577          79 :                 SetStatFields(*poDstFeature, iBand, statsMap[iBand][iFeature]);
    1578             :             }
    1579          73 :             if (poDstLayer->CreateFeature(poDstFeature.get()) != OGRERR_NONE)
    1580             :             {
    1581           0 :                 return false;
    1582             :             }
    1583             :         }
    1584             : 
    1585          47 :         return true;
    1586             : #endif
    1587             :     }
    1588             : 
    1589          76 :     bool ProcessVectorZonesByFeature(GDALProgressFunc pfnProgress,
    1590             :                                      void *pProgressData)
    1591             :     {
    1592          76 :         if (!Init())
    1593             :         {
    1594          17 :             return false;
    1595             :         }
    1596             : 
    1597          59 :         OGREnvelope oGeomExtent;
    1598             :         GDALRasterWindow oWindow;
    1599             : 
    1600          59 :         std::unique_ptr<GDALDataset> poAlignedWeightsDS;
    1601             :         // Align the weighting dataset to the values.
    1602          59 :         if (m_weights)
    1603             :         {
    1604          35 :             bool resampled = false;
    1605          35 :             poAlignedWeightsDS = GetVRT(*m_weights, m_src, resampled);
    1606          35 :             if (!poAlignedWeightsDS)
    1607             :             {
    1608           0 :                 return false;
    1609             :             }
    1610          35 :             if (resampled)
    1611             :             {
    1612           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1613             :                          "Resampled weights to match source raster using "
    1614             :                          "average resampling.");
    1615             :             }
    1616             :         }
    1617             : 
    1618          59 :         size_t nBufSize = 0;
    1619             : 
    1620          59 :         OGRLayer *poSrcLayer = std::get<OGRLayer *>(m_zones);
    1621          59 :         OGRLayer *poDstLayer = GetOutputLayer(false);
    1622          59 :         if (!poDstLayer)
    1623           0 :             return false;
    1624          59 :         size_t i = 0;
    1625          59 :         auto nFeatures = poSrcLayer->GetFeatureCount();
    1626             :         GDALRasterWindow oRasterWindow;
    1627          59 :         oRasterWindow.nXOff = 0;
    1628          59 :         oRasterWindow.nYOff = 0;
    1629          59 :         oRasterWindow.nXSize = m_src.GetRasterXSize();
    1630          59 :         oRasterWindow.nYSize = m_src.GetRasterYSize();
    1631          59 :         const OGREnvelope oRasterExtent = ToEnvelope(oRasterWindow);
    1632             : 
    1633         170 :         for (const auto &poFeature : *poSrcLayer)
    1634             :         {
    1635         112 :             const auto *poGeom = poFeature->GetGeometryRef();
    1636             : 
    1637         112 :             oWindow.nXSize = 0;
    1638         112 :             oWindow.nYSize = 0;
    1639         112 :             if (poGeom == nullptr || poGeom->IsEmpty())
    1640             :             {
    1641             :                 // do nothing
    1642             :             }
    1643         103 :             else if (poGeom->getDimension() != 2)
    1644             :             {
    1645           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1646             :                          "Non-polygonal geometry encountered.");
    1647           1 :                 return false;
    1648             :             }
    1649             :             else
    1650             :             {
    1651         102 :                 poGeom->getEnvelope(&oGeomExtent);
    1652         102 :                 if (oGeomExtent.Intersects(oRasterExtent))
    1653             :                 {
    1654          94 :                     oGeomExtent.Intersect(oRasterExtent);
    1655          94 :                     if (!m_srcInvGT.Apply(oGeomExtent, oWindow))
    1656             :                     {
    1657           0 :                         return false;
    1658             :                     }
    1659          94 :                     oWindow.nXOff =
    1660          94 :                         std::max(oWindow.nXOff, oRasterWindow.nXOff);
    1661          94 :                     oWindow.nYOff =
    1662          94 :                         std::max(oWindow.nYOff, oRasterWindow.nYOff);
    1663          94 :                     oWindow.nXSize =
    1664         188 :                         std::min(oWindow.nXSize, oRasterWindow.nXOff +
    1665         188 :                                                      oRasterWindow.nXSize -
    1666          94 :                                                      oWindow.nXOff);
    1667          94 :                     oWindow.nYSize =
    1668         188 :                         std::min(oWindow.nYSize, oRasterWindow.nYOff +
    1669          94 :                                                      oRasterWindow.nYSize -
    1670          94 :                                                      oWindow.nYOff);
    1671             :                 }
    1672             :             }
    1673             : 
    1674             :             std::unique_ptr<OGRFeature> poDstFeature(
    1675         111 :                 OGRFeature::CreateFeature(poDstLayer->GetLayerDefn()));
    1676         111 :             poDstFeature->SetFrom(poFeature.get());
    1677             : 
    1678         111 :             if (oWindow.nXSize == 0 || oWindow.nYSize == 0)
    1679             :             {
    1680          34 :                 const gdal::RasterStats<double> empty(CreateStats());
    1681          34 :                 for (int iBand : m_options.bands)
    1682             :                 {
    1683          17 :                     SetStatFields(*poDstFeature, iBand, empty);
    1684          17 :                 }
    1685             :             }
    1686             :             else
    1687             :             {
    1688             :                 // Calculate how many rows of raster data we can read in at
    1689             :                 // a time while remaining within maxCells.
    1690          94 :                 const int nRowsPerChunk = std::min(
    1691             :                     oWindow.nYSize,
    1692         188 :                     std::max(1, static_cast<int>(
    1693          94 :                                     m_maxCells /
    1694          94 :                                     static_cast<size_t>(oWindow.nXSize))));
    1695             : 
    1696          94 :                 const size_t nWindowSize = static_cast<size_t>(oWindow.nXSize) *
    1697          94 :                                            static_cast<size_t>(nRowsPerChunk);
    1698             : 
    1699          94 :                 if (nBufSize < nWindowSize)
    1700             :                 {
    1701          61 :                     bool bAllocSuccess = true;
    1702          61 :                     Realloc(m_pabyValuesBuf, nWindowSize,
    1703          61 :                             GDALGetDataTypeSizeBytes(m_workingDataType),
    1704             :                             bAllocSuccess);
    1705          61 :                     Realloc(m_pabyCoverageBuf, nWindowSize,
    1706          61 :                             GDALGetDataTypeSizeBytes(m_coverageDataType),
    1707             :                             bAllocSuccess);
    1708          61 :                     Realloc(m_pabyMaskBuf, nWindowSize,
    1709          61 :                             GDALGetDataTypeSizeBytes(m_maskDataType),
    1710             :                             bAllocSuccess);
    1711             : 
    1712          61 :                     if (m_stats_options.store_xy)
    1713             :                     {
    1714           9 :                         Realloc(m_padfX, oWindow.nXSize,
    1715           9 :                                 GDALGetDataTypeSizeBytes(GDT_Float64),
    1716             :                                 bAllocSuccess);
    1717           9 :                         Realloc(m_padfY, oWindow.nYSize,
    1718           9 :                                 GDALGetDataTypeSizeBytes(GDT_Float64),
    1719             :                                 bAllocSuccess);
    1720             :                     }
    1721             : 
    1722          61 :                     if (m_weights != nullptr)
    1723             :                     {
    1724          35 :                         Realloc(m_padfWeightsBuf, nWindowSize,
    1725          35 :                                 GDALGetDataTypeSizeBytes(GDT_Float64),
    1726             :                                 bAllocSuccess);
    1727          35 :                         Realloc(m_pabyWeightsMaskBuf, nWindowSize,
    1728          35 :                                 GDALGetDataTypeSizeBytes(m_maskDataType),
    1729             :                                 bAllocSuccess);
    1730             :                     }
    1731          61 :                     if (!bAllocSuccess)
    1732             :                     {
    1733           0 :                         return false;
    1734             :                     }
    1735             : 
    1736          61 :                     nBufSize = nWindowSize;
    1737             :                 }
    1738             : 
    1739          94 :                 if (m_padfX && m_padfY)
    1740             :                 {
    1741          12 :                     CalculateCellCenters(oWindow, m_srcGT, m_padfX.get(),
    1742             :                                          m_padfY.get());
    1743             :                 }
    1744             : 
    1745          94 :                 std::vector<gdal::RasterStats<double>> aoStats;
    1746          94 :                 aoStats.resize(m_options.bands.size(), CreateStats());
    1747             : 
    1748          94 :                 for (int nYOff = oWindow.nYOff;
    1749         197 :                      nYOff < oWindow.nYOff + oWindow.nYSize;
    1750         103 :                      nYOff += nRowsPerChunk)
    1751             :                 {
    1752             :                     GDALRasterWindow oSubWindow;
    1753         103 :                     oSubWindow.nXOff = oWindow.nXOff;
    1754         103 :                     oSubWindow.nXSize = oWindow.nXSize;
    1755         103 :                     oSubWindow.nYOff = nYOff;
    1756         103 :                     oSubWindow.nYSize = std::min(
    1757         103 :                         nRowsPerChunk, oWindow.nYOff + oWindow.nYSize - nYOff);
    1758             : 
    1759         103 :                     const auto nCoverageXOff = oSubWindow.nXOff - oWindow.nXOff;
    1760         103 :                     const auto nCoverageYOff = oSubWindow.nYOff - oWindow.nYOff;
    1761             : 
    1762             :                     const OGREnvelope oSnappedGeomExtent =
    1763         103 :                         ToEnvelope(oSubWindow);
    1764             : 
    1765         103 :                     if (!CalculateCoverage(poGeom, oSnappedGeomExtent,
    1766             :                                            oSubWindow.nXSize, oSubWindow.nYSize,
    1767             :                                            m_pabyCoverageBuf.get()))
    1768             :                     {
    1769           0 :                         return false;
    1770             :                     }
    1771             : 
    1772         103 :                     if (m_weights != nullptr)
    1773             :                     {
    1774             :                         GDALRasterBand *poWeightsBand =
    1775          35 :                             poAlignedWeightsDS->GetRasterBand(
    1776             :                                 m_options.weights_band);
    1777             : 
    1778          35 :                         if (!ReadWindow(*poWeightsBand, oSubWindow,
    1779             :                                         reinterpret_cast<GByte *>(
    1780          35 :                                             m_padfWeightsBuf.get()),
    1781             :                                         GDT_Float64))
    1782             :                         {
    1783           0 :                             return false;
    1784             :                         }
    1785          35 :                         if (!ReadWindow(*poWeightsBand->GetMaskBand(),
    1786             :                                         oSubWindow, m_pabyWeightsMaskBuf.get(),
    1787             :                                         GDT_UInt8))
    1788             :                         {
    1789           0 :                             return false;
    1790             :                         }
    1791             :                     }
    1792             : 
    1793         215 :                     for (size_t iBandInd = 0; iBandInd < m_options.bands.size();
    1794             :                          iBandInd++)
    1795             :                     {
    1796             :                         GDALRasterBand *poBand =
    1797         112 :                             m_src.GetRasterBand(m_options.bands[iBandInd]);
    1798             : 
    1799         112 :                         if (!ReadWindow(*poBand, oSubWindow,
    1800             :                                         m_pabyValuesBuf.get(),
    1801         112 :                                         m_workingDataType))
    1802             :                         {
    1803           0 :                             return false;
    1804             :                         }
    1805         112 :                         if (!ReadWindow(*poBand->GetMaskBand(), oSubWindow,
    1806         112 :                                         m_pabyMaskBuf.get(), m_maskDataType))
    1807             :                         {
    1808           0 :                             return false;
    1809             :                         }
    1810             : 
    1811         336 :                         UpdateStats(
    1812         112 :                             aoStats[iBandInd], m_pabyValuesBuf.get(),
    1813         112 :                             m_pabyMaskBuf.get(), m_padfWeightsBuf.get(),
    1814         112 :                             m_pabyWeightsMaskBuf.get(), m_pabyCoverageBuf.get(),
    1815         127 :                             m_padfX ? m_padfX.get() + nCoverageXOff : nullptr,
    1816          15 :                             m_padfY ? m_padfY.get() + nCoverageYOff : nullptr,
    1817         112 :                             oSubWindow.nXSize, oSubWindow.nYSize);
    1818             :                     }
    1819             :                 }
    1820             : 
    1821         194 :                 for (size_t iBandInd = 0; iBandInd < m_options.bands.size();
    1822             :                      iBandInd++)
    1823             :                 {
    1824         100 :                     SetStatFields(*poDstFeature, m_options.bands[iBandInd],
    1825         100 :                                   aoStats[iBandInd]);
    1826             :                 }
    1827             :             }
    1828             : 
    1829         111 :             if (poDstLayer->CreateFeature(poDstFeature.get()) != OGRERR_NONE)
    1830             :             {
    1831           0 :                 return false;
    1832             :             }
    1833             : 
    1834         111 :             if (pfnProgress)
    1835             :             {
    1836           0 :                 pfnProgress(static_cast<double>(i + 1) /
    1837           0 :                                 static_cast<double>(nFeatures),
    1838             :                             "", pProgressData);
    1839             :             }
    1840         111 :             i++;
    1841             :         }
    1842             : 
    1843          58 :         return true;
    1844             :     }
    1845             : 
    1846         861 :     void UpdateStats(gdal::RasterStats<double> &stats, const GByte *pabyValues,
    1847             :                      const GByte *pabyMask, const double *padfWeights,
    1848             :                      const GByte *pabyWeightsMask, const GByte *pabyCoverage,
    1849             :                      const double *pdfX, const double *pdfY, size_t nX,
    1850             :                      size_t nY) const
    1851             :     {
    1852         861 :         if (m_coverageDataType == GDT_Float32)
    1853             :         {
    1854         312 :             stats.process(reinterpret_cast<const double *>(pabyValues),
    1855             :                           pabyMask, padfWeights, pabyWeightsMask,
    1856             :                           reinterpret_cast<const float *>(pabyCoverage), pdfX,
    1857             :                           pdfY, nX, nY);
    1858             :         }
    1859             :         else
    1860             :         {
    1861         549 :             stats.process(reinterpret_cast<const double *>(pabyValues),
    1862             :                           pabyMask, padfWeights, pabyWeightsMask, pabyCoverage,
    1863             :                           pdfX, pdfY, nX, nY);
    1864             :         }
    1865         861 :     }
    1866             : 
    1867         213 :     bool CalculateCoverage(const OGRGeometry *poGeom,
    1868             :                            const OGREnvelope &oSnappedGeomExtent, int nXSize,
    1869             :                            int nYSize, GByte *pabyCoverageBuf) const
    1870             :     {
    1871             : #if GEOS_GRID_INTERSECTION_AVAILABLE
    1872         213 :         if (m_options.pixels == GDALZonalStatsOptions::FRACTIONAL)
    1873             :         {
    1874          83 :             std::memset(pabyCoverageBuf, 0,
    1875          83 :                         static_cast<size_t>(nXSize) * nYSize *
    1876          83 :                             GDALGetDataTypeSizeBytes(GDT_Float32));
    1877             :             GEOSGeometry *poGeosGeom =
    1878          83 :                 poGeom->exportToGEOS(m_geosContext, true);
    1879          83 :             if (!poGeosGeom)
    1880             :             {
    1881           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1882             :                          "Failed to convert geometry to GEOS.");
    1883           0 :                 return false;
    1884             :             }
    1885             : 
    1886         166 :             const bool bRet = GEOSGridIntersectionFractions_r(
    1887          83 :                 m_geosContext, poGeosGeom, oSnappedGeomExtent.MinX,
    1888          83 :                 oSnappedGeomExtent.MinY, oSnappedGeomExtent.MaxX,
    1889          83 :                 oSnappedGeomExtent.MaxY, nXSize, nYSize,
    1890          83 :                 reinterpret_cast<float *>(pabyCoverageBuf));
    1891          83 :             if (!bRet)
    1892             :             {
    1893           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1894             :                          "Failed to calculate pixel intersection fractions.");
    1895             :             }
    1896          83 :             GEOSGeom_destroy_r(m_geosContext, poGeosGeom);
    1897             : 
    1898          83 :             return bRet;
    1899             :         }
    1900             :         else
    1901             : #endif
    1902             :         {
    1903         130 :             GDALGeoTransform oCoverageGT;
    1904         130 :             oCoverageGT.xorig = oSnappedGeomExtent.MinX;
    1905         130 :             oCoverageGT.xscale = m_srcGT.xscale;
    1906         130 :             oCoverageGT.xrot = 0;
    1907             : 
    1908         130 :             oCoverageGT.yorig = m_srcGT.yscale < 0 ? oSnappedGeomExtent.MaxY
    1909             :                                                    : oSnappedGeomExtent.MinY;
    1910         130 :             oCoverageGT.yscale = m_srcGT.yscale;
    1911         130 :             oCoverageGT.yrot = 0;
    1912             : 
    1913             :             // Create a memory dataset that wraps the coverage buffer so that
    1914             :             // we can invoke GDALRasterize
    1915             :             std::unique_ptr<MEMDataset> poMemDS(MEMDataset::Create(
    1916         260 :                 "", nXSize, nYSize, 0, m_coverageDataType, nullptr));
    1917         130 :             poMemDS->SetGeoTransform(oCoverageGT);
    1918         130 :             constexpr double dfBurnValue = 255.0;
    1919         130 :             constexpr int nBand = 1;
    1920             : 
    1921             :             MEMRasterBand *poCoverageBand =
    1922         130 :                 new MEMRasterBand(poMemDS.get(), 1, pabyCoverageBuf,
    1923         130 :                                   m_coverageDataType, 0, 0, false, nullptr);
    1924         130 :             poMemDS->AddMEMBand(poCoverageBand);
    1925         130 :             poCoverageBand->Fill(0);
    1926             : 
    1927         130 :             CPLStringList aosOptions;
    1928         130 :             if (m_options.pixels == GDALZonalStatsOptions::ALL_TOUCHED)
    1929             :             {
    1930          35 :                 aosOptions.AddString("ALL_TOUCHED=1");
    1931             :             }
    1932             : 
    1933             :             OGRGeometryH hGeom =
    1934         130 :                 OGRGeometry::ToHandle(const_cast<OGRGeometry *>(poGeom));
    1935             : 
    1936         130 :             const auto eErr = GDALRasterizeGeometries(
    1937         130 :                 GDALDataset::ToHandle(poMemDS.get()), 1, &nBand, 1, &hGeom,
    1938         130 :                 nullptr, nullptr, &dfBurnValue, aosOptions.List(), nullptr,
    1939             :                 nullptr);
    1940             : 
    1941         130 :             return eErr == CE_None;
    1942             :         }
    1943             :     }
    1944             : 
    1945             : #ifdef HAVE_GEOS
    1946         174 :     GEOSGeometry *CreateGEOSEnvelope(const OGREnvelope &oEnv) const
    1947             :     {
    1948         174 :         GEOSCoordSequence *seq = GEOSCoordSeq_create_r(m_geosContext, 2, 2);
    1949         174 :         if (seq == nullptr)
    1950             :         {
    1951           0 :             return nullptr;
    1952             :         }
    1953         174 :         GEOSCoordSeq_setXY_r(m_geosContext, seq, 0, oEnv.MinX, oEnv.MinY);
    1954         174 :         GEOSCoordSeq_setXY_r(m_geosContext, seq, 1, oEnv.MaxX, oEnv.MaxY);
    1955         174 :         return GEOSGeom_createLineString_r(m_geosContext, seq);
    1956             :     }
    1957             : #endif
    1958             : 
    1959             :     CPL_DISALLOW_COPY_ASSIGN(GDALZonalStatsImpl)
    1960             : 
    1961             :     GDALDataset &m_src;
    1962             :     GDALDataset *m_weights;
    1963             :     GDALDataset &m_dst;
    1964             :     const BandOrLayer m_zones;
    1965             : 
    1966             :     const GDALDataType m_coverageDataType;
    1967             :     const GDALDataType m_workingDataType = GDT_Float64;
    1968             :     const GDALDataType m_maskDataType = GDT_UInt8;
    1969             :     static constexpr GDALDataType m_zonesDataType = GDT_Float64;
    1970             : 
    1971             :     GDALGeoTransform m_srcGT{};
    1972             :     GDALGeoTransform m_srcInvGT{};
    1973             : 
    1974             :     GDALZonalStatsOptions m_options{};
    1975             :     gdal::RasterStatsOptions m_stats_options{};
    1976             : 
    1977             :     size_t m_maxCells{0};
    1978             : 
    1979             :     static constexpr auto NUM_STATS = Stat::INVALID + 1;
    1980             :     std::map<int, std::array<int, NUM_STATS>> m_statFields{};
    1981             : 
    1982             :     std::unique_ptr<GByte, VSIFreeReleaser> m_pabyCoverageBuf{};
    1983             :     std::unique_ptr<GByte, VSIFreeReleaser> m_pabyMaskBuf{};
    1984             :     std::unique_ptr<GByte, VSIFreeReleaser> m_pabyValuesBuf{};
    1985             :     std::unique_ptr<double, VSIFreeReleaser> m_padfWeightsBuf{};
    1986             :     std::unique_ptr<GByte, VSIFreeReleaser> m_pabyWeightsMaskBuf{};
    1987             :     std::unique_ptr<double, VSIFreeReleaser> m_padfX{};
    1988             :     std::unique_ptr<double, VSIFreeReleaser> m_padfY{};
    1989             : 
    1990             : #ifdef HAVE_GEOS
    1991             :     GEOSContextHandle_t m_geosContext{nullptr};
    1992             : #endif
    1993             : };
    1994             : 
    1995         147 : static CPLErr GDALZonalStats(GDALDataset &srcDataset, GDALDataset *poWeights,
    1996             :                              GDALDataset &zonesDataset, GDALDataset &dstDataset,
    1997             :                              const GDALZonalStatsOptions &options,
    1998             :                              GDALProgressFunc pfnProgress, void *pProgressData)
    1999             : {
    2000         147 :     int nZonesBand = options.zones_band;
    2001         294 :     std::string osZonesLayer = options.zones_layer;
    2002             : 
    2003         147 :     if (nZonesBand < 1 && osZonesLayer.empty())
    2004             :     {
    2005         145 :         if (zonesDataset.GetRasterCount() + zonesDataset.GetLayerCount() > 1)
    2006             :         {
    2007           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2008             :                      "Zones dataset has more than one band or layer. Use "
    2009             :                      "the --zone-band or --zone-layer argument to specify "
    2010             :                      "which should be used.");
    2011           0 :             return CE_Failure;
    2012             :         }
    2013         145 :         if (zonesDataset.GetRasterCount() > 0)
    2014             :         {
    2015          19 :             nZonesBand = 1;
    2016             :         }
    2017         126 :         else if (zonesDataset.GetLayerCount() > 0)
    2018             :         {
    2019         125 :             osZonesLayer = zonesDataset.GetLayer(0)->GetName();
    2020             :         }
    2021             :         else
    2022             :         {
    2023           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    2024             :                      "Zones dataset has no band or layer.");
    2025           1 :             return CE_Failure;
    2026             :         }
    2027             :     }
    2028             : 
    2029         146 :     GDALZonalStatsImpl::BandOrLayer poZones;
    2030             : 
    2031         146 :     if (nZonesBand > 0)
    2032             :     {
    2033          20 :         if (nZonesBand > zonesDataset.GetRasterCount())
    2034             :         {
    2035           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid zones band: %d",
    2036             :                      nZonesBand);
    2037           1 :             return CE_Failure;
    2038             :         }
    2039          19 :         GDALRasterBand *poZonesBand = zonesDataset.GetRasterBand(nZonesBand);
    2040          19 :         if (poZonesBand == nullptr)
    2041             :         {
    2042           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2043             :                      "Specified zones band %d not found", nZonesBand);
    2044           0 :             return CE_Failure;
    2045             :         }
    2046          19 :         poZones = poZonesBand;
    2047             :     }
    2048             :     else
    2049             :     {
    2050             :         OGRLayer *poZonesLayer =
    2051         126 :             zonesDataset.GetLayerByName(osZonesLayer.c_str());
    2052         126 :         if (poZonesLayer == nullptr)
    2053             :         {
    2054           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    2055             :                      "Specified zones layer '%s' not found",
    2056             :                      options.zones_layer.c_str());
    2057           1 :             return CE_Failure;
    2058             :         }
    2059         125 :         poZones = poZonesLayer;
    2060             :     }
    2061             : 
    2062         144 :     GDALZonalStatsImpl alg(srcDataset, dstDataset, poWeights, poZones, options);
    2063         144 :     return alg.Process(pfnProgress, pProgressData) ? CE_None : CE_Failure;
    2064             : }
    2065             : 
    2066             : /** Compute statistics of raster values within defined zones
    2067             :  *
    2068             :  * @param hSrcDS raster dataset containing values to be summarized
    2069             :  * @param hWeightsDS optional raster dataset containing weights
    2070             :  * @param hZonesDS raster or vector dataset containing zones across which values will be summarized
    2071             :  * @param hOutDS dataset to which output layer will be written
    2072             :  * @param papszOptions list of options
    2073             :  *   BANDS: a comma-separated list of band indices to be processed from the
    2074             :  *          source dataset. If not present, all bands will be processed.
    2075             :  *   INCLUDE_FIELDS: a comma-separated list of field names from the zones
    2076             :  *          dataset to be included in output features.
    2077             :  *   PIXEL_INTERSECTION: controls which pixels are included in calculations:
    2078             :  *          - DEFAULT: use default options to GDALRasterize
    2079             :  *          - ALL_TOUCHED: use ALL_TOUCHED option of GDALRasterize
    2080             :  *          - FRACTIONAL: calculate fraction of each pixel that is covered
    2081             :  *              by the zone. Requires the GEOS library, version >= 3.14.
    2082             :  *   RASTER_CHUNK_SIZE_BYTES: sets a maximum amount of raster data to read
    2083             :  *              into memory at a single time (from a single source)
    2084             :  *   STATS: comma-separated list of stats. The following stats are supported:
    2085             :  *          - center_x
    2086             :  *          - center_y
    2087             :  *          - count
    2088             :  *          - coverage
    2089             :  *          - frac
    2090             :  *          - max
    2091             :  *          - max_center_x
    2092             :  *          - max_center_y
    2093             :  *          - mean
    2094             :  *          - min
    2095             :  *          - min_center_x
    2096             :  *          - min_center_y
    2097             :  *          - minority
    2098             :  *          - mode
    2099             :  *          - stdev
    2100             :  *          - sum
    2101             :  *          - unique
    2102             :  *          - values
    2103             :  *          - variance
    2104             :  *          - weighted_frac
    2105             :  *          - mean
    2106             :  *          - weighted_sum
    2107             :  *          - weighted_stdev
    2108             :  *          - weighted_variance
    2109             :  *          - weights
    2110             :  *   STRATEGY: determine how to perform processing with vector zones:
    2111             :  *           - FEATURE_SEQUENTIAL: iterate over zones, finding raster pixels
    2112             :  *             that intersect with each, calculating stats, and writing output
    2113             :  *             to hOutDS.
    2114             :  *           - RASTER_SEQUENTIAL: iterate over chunks of the raster, finding
    2115             :  *             zones that intersect with each chunk and updating stats.
    2116             :  *             Features are written to hOutDS after all processing has been
    2117             :  *             completed.
    2118             :  *   WEIGHTS_BAND: the band to read from WeightsDS
    2119             :  *   ZONES_BAND: the band to read from hZonesDS, if hZonesDS is a raster
    2120             :  *   ZONES_LAYER: the layer to read from hZonesDS, if hZonesDS is a vector
    2121             :  *   LCO_{key}: layer creation option {key}
    2122             :  *
    2123             :  * @param pfnProgress optional progress reporting callback
    2124             :  * @param pProgressArg optional data for progress callback
    2125             :  * @return CE_Failure if an error occurred, CE_None otherwise
    2126             :  */
    2127         147 : CPLErr GDALZonalStats(GDALDatasetH hSrcDS, GDALDatasetH hWeightsDS,
    2128             :                       GDALDatasetH hZonesDS, GDALDatasetH hOutDS,
    2129             :                       CSLConstList papszOptions, GDALProgressFunc pfnProgress,
    2130             :                       void *pProgressArg)
    2131             : {
    2132         147 :     VALIDATE_POINTER1(hSrcDS, __func__, CE_Failure);
    2133         147 :     VALIDATE_POINTER1(hZonesDS, __func__, CE_Failure);
    2134         147 :     VALIDATE_POINTER1(hOutDS, __func__, CE_Failure);
    2135             : 
    2136         294 :     GDALZonalStatsOptions sOptions;
    2137         147 :     if (papszOptions)
    2138             :     {
    2139         147 :         if (auto eErr = sOptions.Init(papszOptions); eErr != CE_None)
    2140             :         {
    2141           0 :             return eErr;
    2142             :         }
    2143             :     }
    2144             : 
    2145         294 :     return GDALZonalStats(
    2146         147 :         *GDALDataset::FromHandle(hSrcDS), GDALDataset::FromHandle(hWeightsDS),
    2147         147 :         *GDALDataset::FromHandle(hZonesDS), *GDALDataset::FromHandle(hOutDS),
    2148         147 :         sOptions, pfnProgress, pProgressArg);
    2149             : }

Generated by: LCOV version 1.14