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

Generated by: LCOV version 1.14