Coverage Report

Created: 2026-02-14 06:52

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