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

Generated by: LCOV version 1.14