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

Generated by: LCOV version 1.14