Coverage Report

Created: 2026-05-16 08:20

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