Coverage Report

Created: 2026-07-25 06:50

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