Coverage Report

Created: 2026-09-26 08:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/gpkg/gdalgeopackagerasterband.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GeoPackage Translator
4
 * Purpose:  Implements GDALGeoPackageRasterBand class
5
 * Author:   Even Rouault <even dot rouault at spatialys dot com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2014, Even Rouault <even dot rouault at spatialys dot com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "ogr_geopackage.h"
14
#include "memdataset.h"
15
#include "gdal_alg_priv.h"
16
#include "ogrsqlitevfs.h"
17
#include "cpl_error.h"
18
#include "cpl_float.h"
19
20
#include <algorithm>
21
#include <cmath>
22
#include <limits>
23
#include <set>
24
#include <utility>
25
26
#if !defined(DEBUG_VERBOSE) && defined(DEBUG_VERBOSE_GPKG)
27
#define DEBUG_VERBOSE
28
#endif
29
30
/************************************************************************/
31
/*                  GDALGPKGMBTilesLikePseudoDataset()                  */
32
/************************************************************************/
33
34
GDALGPKGMBTilesLikePseudoDataset::GDALGPKGMBTilesLikePseudoDataset()
35
    : m_bForceTempDBCompaction(
36
5.34k
          CPLTestBool(CPLGetConfigOption("GPKG_FORCE_TEMPDB_COMPACTION", "NO")))
37
5.34k
{
38
26.7k
    for (int i = 0; i < 4; i++)
39
21.3k
    {
40
21.3k
        m_asCachedTilesDesc[i].nRow = -1;
41
21.3k
        m_asCachedTilesDesc[i].nCol = -1;
42
21.3k
        m_asCachedTilesDesc[i].nIdxWithinTileData = -1;
43
21.3k
        m_asCachedTilesDesc[i].abBandDirty[0] = FALSE;
44
21.3k
        m_asCachedTilesDesc[i].abBandDirty[1] = FALSE;
45
21.3k
        m_asCachedTilesDesc[i].abBandDirty[2] = FALSE;
46
21.3k
        m_asCachedTilesDesc[i].abBandDirty[3] = FALSE;
47
21.3k
    }
48
5.34k
}
49
50
/************************************************************************/
51
/*                 ~GDALGPKGMBTilesLikePseudoDataset()                  */
52
/************************************************************************/
53
54
GDALGPKGMBTilesLikePseudoDataset::~GDALGPKGMBTilesLikePseudoDataset()
55
5.34k
{
56
5.34k
    if (m_poParentDS == nullptr && m_hTempDB != nullptr)
57
0
    {
58
0
        sqlite3_close(m_hTempDB);
59
0
        m_hTempDB = nullptr;
60
0
        VSIUnlink(m_osTempDBFilename);
61
0
        if (m_pMyVFS)
62
0
        {
63
0
            sqlite3_vfs_unregister(m_pMyVFS);
64
0
            CPLFree(m_pMyVFS->pAppData);
65
0
            CPLFree(m_pMyVFS);
66
0
        }
67
0
    }
68
5.34k
    CPLFree(m_pabyCachedTiles);
69
5.34k
    delete m_poCT;
70
5.34k
    CPLFree(m_pabyHugeColorArray);
71
5.34k
}
72
73
/************************************************************************/
74
/*                            SetDataType()                             */
75
/************************************************************************/
76
77
void GDALGPKGMBTilesLikePseudoDataset::SetDataType(GDALDataType eDT)
78
8
{
79
8
    CPLAssert(eDT == GDT_UInt8 || eDT == GDT_Int16 || eDT == GDT_UInt16 ||
80
8
              eDT == GDT_Float32);
81
8
    m_eDT = eDT;
82
8
    m_nDTSize = GDALGetDataTypeSizeBytes(m_eDT);
83
8
}
84
85
/************************************************************************/
86
/*                        SetGlobalOffsetScale()                        */
87
/************************************************************************/
88
89
void GDALGPKGMBTilesLikePseudoDataset::SetGlobalOffsetScale(double dfOffset,
90
                                                            double dfScale)
91
4
{
92
4
    m_dfOffset = dfOffset;
93
4
    m_dfScale = dfScale;
94
4
}
95
96
/************************************************************************/
97
/*                   GDALGPKGMBTilesLikeRasterBand()                    */
98
/************************************************************************/
99
100
// Recent GCC versions complain about null dereference of m_poTPD in
101
// the constructor body
102
#ifdef __GNUC__
103
#pragma GCC diagnostic push
104
#pragma GCC diagnostic ignored "-Wnull-dereference"
105
#endif
106
107
GDALGPKGMBTilesLikeRasterBand::GDALGPKGMBTilesLikeRasterBand(
108
    GDALGPKGMBTilesLikePseudoDataset *poTPD, int nTileWidth, int nTileHeight)
109
5.90k
    : m_poTPD(poTPD)
110
5.90k
{
111
5.90k
    eDataType = m_poTPD->m_eDT;
112
5.90k
    m_nDTSize = m_poTPD->m_nDTSize;
113
5.90k
    nBlockXSize = nTileWidth;
114
5.90k
    nBlockYSize = nTileHeight;
115
5.90k
}
116
117
#ifdef __GNUC__
118
#pragma GCC diagnostic pop
119
#endif
120
121
/************************************************************************/
122
/*                             FlushCache()                             */
123
/************************************************************************/
124
125
CPLErr GDALGPKGMBTilesLikeRasterBand::FlushCache(bool bAtClosing)
126
29.2k
{
127
29.2k
    m_poTPD->m_nLastSpaceCheckTimestamp = -1;  // disable partial flushes
128
29.2k
    CPLErr eErr = GDALPamRasterBand::FlushCache(bAtClosing);
129
29.2k
    if (eErr == CE_None)
130
29.2k
        eErr = m_poTPD->IFlushCacheWithErrCode(bAtClosing);
131
29.2k
    m_poTPD->m_nLastSpaceCheckTimestamp = 0;
132
29.2k
    return eErr;
133
29.2k
}
134
135
/************************************************************************/
136
/*                             FlushTiles()                             */
137
/************************************************************************/
138
139
CPLErr GDALGPKGMBTilesLikePseudoDataset::FlushTiles()
140
10.1k
{
141
10.1k
    CPLErr eErr = CE_None;
142
10.1k
    GDALGPKGMBTilesLikePseudoDataset *poMainDS =
143
10.1k
        m_poParentDS ? m_poParentDS : this;
144
10.1k
    if (poMainDS->m_nTileInsertionCount < 0)
145
0
        return CE_Failure;
146
147
10.1k
    if (IGetUpdate())
148
1.15k
    {
149
1.15k
        if (m_nShiftXPixelsMod || m_nShiftYPixelsMod)
150
0
        {
151
0
            eErr = FlushRemainingShiftedTiles(false /* total flush*/);
152
0
        }
153
1.15k
        else
154
1.15k
        {
155
1.15k
            eErr = WriteTile();
156
1.15k
        }
157
1.15k
    }
158
159
10.1k
    if (poMainDS->m_nTileInsertionCount > 0)
160
0
    {
161
0
        if (poMainDS->ICommitTransaction() != OGRERR_NONE)
162
0
        {
163
0
            poMainDS->m_nTileInsertionCount = -1;
164
0
            eErr = CE_Failure;
165
0
        }
166
0
        else
167
0
        {
168
0
            poMainDS->m_nTileInsertionCount = 0;
169
0
        }
170
0
    }
171
10.1k
    return eErr;
172
10.1k
}
173
174
/************************************************************************/
175
/*                           GetColorTable()                            */
176
/************************************************************************/
177
178
GDALColorTable *GDALGPKGMBTilesLikeRasterBand::GetColorTable()
179
2
{
180
2
    if (poDS->GetRasterCount() != 1)
181
0
        return nullptr;
182
183
2
    if (!m_poTPD->m_bTriedEstablishingCT)
184
2
    {
185
2
        m_poTPD->m_bTriedEstablishingCT = true;
186
2
        if (m_poTPD->m_poParentDS != nullptr)
187
0
        {
188
0
            m_poTPD->m_poCT =
189
0
                m_poTPD->m_poParentDS->IGetRasterBand(1)->GetColorTable();
190
0
            if (m_poTPD->m_poCT)
191
0
                m_poTPD->m_poCT = m_poTPD->m_poCT->Clone();
192
0
            return m_poTPD->m_poCT;
193
0
        }
194
195
6
        for (int i = 0; i < 2; i++)
196
4
        {
197
4
            bool bRetry = false;
198
4
            char *pszSQL = nullptr;
199
4
            if (i == 0)
200
2
            {
201
2
                pszSQL = sqlite3_mprintf("SELECT tile_data FROM \"%w\" "
202
2
                                         "WHERE zoom_level = %d LIMIT 1",
203
2
                                         m_poTPD->m_osRasterTable.c_str(),
204
2
                                         m_poTPD->m_nZoomLevel);
205
2
            }
206
2
            else
207
2
            {
208
                // Try a tile in the middle of the raster
209
2
                pszSQL = sqlite3_mprintf(
210
2
                    "SELECT tile_data FROM \"%w\" "
211
2
                    "WHERE zoom_level = %d AND tile_column = %d AND tile_row = "
212
2
                    "%d",
213
2
                    m_poTPD->m_osRasterTable.c_str(), m_poTPD->m_nZoomLevel,
214
2
                    m_poTPD->m_nShiftXTiles + nRasterXSize / 2 / nBlockXSize,
215
2
                    m_poTPD->GetRowFromIntoTopConvention(
216
2
                        m_poTPD->m_nShiftYTiles +
217
2
                        nRasterYSize / 2 / nBlockYSize));
218
2
            }
219
4
            sqlite3_stmt *hStmt = nullptr;
220
4
            int rc = SQLPrepareWithError(m_poTPD->IGetDB(), pszSQL, -1, &hStmt,
221
4
                                         nullptr);
222
4
            if (rc == SQLITE_OK)
223
4
            {
224
4
                rc = sqlite3_step(hStmt);
225
4
                if (rc == SQLITE_ROW &&
226
4
                    sqlite3_column_type(hStmt, 0) == SQLITE_BLOB)
227
4
                {
228
4
                    const int nBytes = sqlite3_column_bytes(hStmt, 0);
229
4
                    GByte *pabyRawData = reinterpret_cast<GByte *>(
230
4
                        const_cast<void *>(sqlite3_column_blob(hStmt, 0)));
231
4
                    const CPLString osMemFileName(
232
4
                        VSIMemGenerateHiddenFilename("gpkg_read_tile"));
233
4
                    VSILFILE *fp = VSIFileFromMemBuffer(
234
4
                        osMemFileName.c_str(), pabyRawData, nBytes, FALSE);
235
4
                    VSIFCloseL(fp);
236
237
                    // Only PNG can have color table.
238
4
                    const char *const apszDrivers[] = {"PNG", nullptr};
239
4
                    auto poDSTile = std::unique_ptr<GDALDataset>(
240
4
                        GDALDataset::Open(osMemFileName.c_str(),
241
4
                                          GDAL_OF_RASTER | GDAL_OF_INTERNAL,
242
4
                                          apszDrivers, nullptr, nullptr));
243
4
                    if (poDSTile != nullptr)
244
4
                    {
245
4
                        if (poDSTile->GetRasterCount() == 1)
246
0
                        {
247
0
                            m_poTPD->m_poCT =
248
0
                                poDSTile->GetRasterBand(1)->GetColorTable();
249
0
                            if (m_poTPD->m_poCT != nullptr)
250
0
                                m_poTPD->m_poCT = m_poTPD->m_poCT->Clone();
251
0
                        }
252
4
                        else
253
4
                        {
254
4
                            bRetry = true;
255
4
                        }
256
4
                    }
257
0
                    else
258
0
                        bRetry = true;
259
260
4
                    VSIUnlink(osMemFileName);
261
4
                }
262
4
            }
263
4
            sqlite3_free(pszSQL);
264
4
            sqlite3_finalize(hStmt);
265
4
            if (!bRetry)
266
0
                break;
267
4
        }
268
2
    }
269
270
2
    return m_poTPD->m_poCT;
271
2
}
272
273
/************************************************************************/
274
/*                           SetColorTable()                            */
275
/************************************************************************/
276
277
CPLErr GDALGPKGMBTilesLikeRasterBand::SetColorTable(GDALColorTable *poCT)
278
0
{
279
0
    if (m_poTPD->m_eDT != GDT_UInt8)
280
0
        return CE_Failure;
281
0
    if (poDS->GetRasterCount() != 1)
282
0
    {
283
0
        CPLError(CE_Failure, CPLE_NotSupported,
284
0
                 "SetColorTable() only supported for a single band dataset");
285
0
        return CE_Failure;
286
0
    }
287
0
    if (!m_poTPD->m_bNew || m_poTPD->m_bTriedEstablishingCT)
288
0
    {
289
0
        CPLError(CE_Failure, CPLE_NotSupported,
290
0
                 "SetColorTable() only supported on a newly created dataset");
291
0
        return CE_Failure;
292
0
    }
293
294
0
    AssignColorTable(poCT);
295
0
    return CE_None;
296
0
}
297
298
/************************************************************************/
299
/*                          AssignColorTable()                          */
300
/************************************************************************/
301
302
void GDALGPKGMBTilesLikeRasterBand::AssignColorTable(const GDALColorTable *poCT)
303
0
{
304
0
    m_poTPD->m_bTriedEstablishingCT = true;
305
0
    delete m_poTPD->m_poCT;
306
0
    if (poCT != nullptr)
307
0
        m_poTPD->m_poCT = poCT->Clone();
308
0
    else
309
0
        m_poTPD->m_poCT = nullptr;
310
0
}
311
312
/************************************************************************/
313
/*                       GetColorInterpretation()                       */
314
/************************************************************************/
315
316
GDALColorInterp GDALGPKGMBTilesLikeRasterBand::GetColorInterpretation()
317
36
{
318
36
    if (m_poTPD->m_eDT != GDT_UInt8)
319
0
        return GCI_Undefined;
320
36
    if (poDS->GetRasterCount() == 1)
321
0
        return GetColorTable() ? GCI_PaletteIndex : GCI_GrayIndex;
322
36
    else if (poDS->GetRasterCount() == 2)
323
0
        return (nBand == 1) ? GCI_GrayIndex : GCI_AlphaBand;
324
36
    else
325
36
        return static_cast<GDALColorInterp>(GCI_RedBand + (nBand - 1));
326
36
}
327
328
/************************************************************************/
329
/*                       SetColorInterpretation()                       */
330
/************************************************************************/
331
332
CPLErr
333
GDALGPKGMBTilesLikeRasterBand::SetColorInterpretation(GDALColorInterp eInterp)
334
0
{
335
0
    if (eInterp == GCI_Undefined)
336
0
        return CE_None;
337
0
    if (poDS->GetRasterCount() == 1 &&
338
0
        (eInterp == GCI_GrayIndex || eInterp == GCI_PaletteIndex))
339
0
        return CE_None;
340
0
    if (poDS->GetRasterCount() == 2 &&
341
0
        ((nBand == 1 && eInterp == GCI_GrayIndex) ||
342
0
         (nBand == 2 && eInterp == GCI_AlphaBand)))
343
0
        return CE_None;
344
0
    if (poDS->GetRasterCount() >= 3 && eInterp == GCI_RedBand + nBand - 1)
345
0
        return CE_None;
346
0
    CPLError(CE_Warning, CPLE_NotSupported,
347
0
             "%s color interpretation not supported. Will be ignored",
348
0
             GDALGetColorInterpretationName(eInterp));
349
0
    return CE_Warning;
350
0
}
351
352
/************************************************************************/
353
/*                         GPKGFindBestEntry()                          */
354
/************************************************************************/
355
356
static int GPKGFindBestEntry(GDALColorTable *poCT, GByte c1, GByte c2, GByte c3,
357
                             GByte c4, int nTileBandCount)
358
0
{
359
0
    const int nEntries = std::min(256, poCT->GetColorEntryCount());
360
0
    int iBestIdx = 0;
361
0
    int nBestDistance = 4 * 256 * 256;
362
0
    for (int i = 0; i < nEntries; i++)
363
0
    {
364
0
        const GDALColorEntry *psEntry = poCT->GetColorEntry(i);
365
0
        int nDistance = (psEntry->c1 - c1) * (psEntry->c1 - c1) +
366
0
                        (psEntry->c2 - c2) * (psEntry->c2 - c2) +
367
0
                        (psEntry->c3 - c3) * (psEntry->c3 - c3);
368
0
        if (nTileBandCount == 4)
369
0
            nDistance += (psEntry->c4 - c4) * (psEntry->c4 - c4);
370
0
        if (nDistance < nBestDistance)
371
0
        {
372
0
            iBestIdx = i;
373
0
            nBestDistance = nDistance;
374
0
        }
375
0
    }
376
0
    return iBestIdx;
377
0
}
378
379
/************************************************************************/
380
/*                             FillBuffer()                             */
381
/************************************************************************/
382
383
void GDALGPKGMBTilesLikePseudoDataset::FillBuffer(GByte *pabyData,
384
                                                  size_t nPixels)
385
112
{
386
112
    int bHasNoData = FALSE;
387
112
    const double dfNoDataValue = IGetRasterBand(1)->GetNoDataValue(&bHasNoData);
388
112
    if (!bHasNoData || dfNoDataValue == 0.0)
389
112
    {
390
        // cppcheck-suppress nullPointer
391
112
        memset(pabyData, 0, nPixels * m_nDTSize);
392
112
    }
393
0
    else
394
0
    {
395
0
        GDALCopyWords64(&dfNoDataValue, GDT_Float64, 0, pabyData, m_eDT,
396
0
                        m_nDTSize, nPixels);
397
0
    }
398
112
}
399
400
/************************************************************************/
401
/*                           FillEmptyTile()                            */
402
/************************************************************************/
403
404
void GDALGPKGMBTilesLikePseudoDataset::FillEmptyTile(GByte *pabyData)
405
112
{
406
112
    int nBlockXSize, nBlockYSize;
407
112
    IGetRasterBand(1)->GetBlockSize(&nBlockXSize, &nBlockYSize);
408
112
    const int nBands = IGetRasterCount();
409
112
    const size_t nPixels =
410
112
        static_cast<size_t>(nBands) * nBlockXSize * nBlockYSize;
411
112
    FillBuffer(pabyData, nPixels);
412
112
}
413
414
/************************************************************************/
415
/*                      FillEmptyTileSingleBand()                       */
416
/************************************************************************/
417
418
void GDALGPKGMBTilesLikePseudoDataset::FillEmptyTileSingleBand(GByte *pabyData)
419
0
{
420
0
    int nBlockXSize, nBlockYSize;
421
0
    IGetRasterBand(1)->GetBlockSize(&nBlockXSize, &nBlockYSize);
422
0
    const size_t nPixels = static_cast<size_t>(nBlockXSize) * nBlockYSize;
423
0
    FillBuffer(pabyData, nPixels);
424
0
}
425
426
/************************************************************************/
427
/*                              ReadTile()                              */
428
/************************************************************************/
429
430
CPLErr GDALGPKGMBTilesLikePseudoDataset::ReadTile(
431
    const CPLString &osMemFileName, GByte *pabyTileData, double dfTileOffset,
432
    double dfTileScale, bool *pbIsLossyFormat)
433
22
{
434
22
    const char *const apszDriversByte[] = {"JPEG", "PNG", "WEBP", nullptr};
435
22
    const char *const apszDriversInt[] = {"PNG", nullptr};
436
22
    const char *const apszDriversFloat[] = {"GTiff", nullptr};
437
22
    int nBlockXSize, nBlockYSize;
438
22
    IGetRasterBand(1)->GetBlockSize(&nBlockXSize, &nBlockYSize);
439
22
    const int nBands = IGetRasterCount();
440
22
    auto poDSTile = std::unique_ptr<GDALDataset>(GDALDataset::Open(
441
22
        osMemFileName.c_str(), GDAL_OF_RASTER | GDAL_OF_INTERNAL,
442
22
        (m_eDT == GDT_UInt8)                  ? apszDriversByte
443
22
        : (m_eTF == GPKG_TF_TIFF_32BIT_FLOAT) ? apszDriversFloat
444
4
                                              : apszDriversInt,
445
22
        nullptr, nullptr));
446
22
    if (poDSTile == nullptr)
447
5
    {
448
5
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot parse tile data");
449
5
        FillEmptyTile(pabyTileData);
450
5
        return CE_Failure;
451
5
    }
452
453
17
    const int nTileBandCount = poDSTile->GetRasterCount();
454
455
17
    if (!(poDSTile->GetRasterXSize() == nBlockXSize &&
456
17
          poDSTile->GetRasterYSize() == nBlockYSize &&
457
16
          (nTileBandCount >= 1 && nTileBandCount <= 4)) ||
458
16
        (m_eDT != GDT_UInt8 && nTileBandCount != 1))
459
1
    {
460
1
        CPLError(CE_Failure, CPLE_AppDefined,
461
1
                 "Inconsistent tiles characteristics");
462
1
        FillEmptyTile(pabyTileData);
463
1
        return CE_Failure;
464
1
    }
465
466
16
    GDALDataType eRequestDT = GDT_UInt8;
467
16
    if (m_eTF == GPKG_TF_PNG_16BIT)
468
4
    {
469
4
        CPLAssert(m_eDT == GDT_Int16 || m_eDT == GDT_UInt16 ||
470
4
                  m_eDT == GDT_Float32);
471
4
        eRequestDT = GDT_UInt16;
472
4
    }
473
12
    else if (m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
474
0
    {
475
0
        CPLAssert(m_eDT == GDT_Float32);
476
0
        eRequestDT = GDT_Float32;
477
0
    }
478
479
16
    if (poDSTile->RasterIO(GF_Read, 0, 0, nBlockXSize, nBlockYSize,
480
16
                           pabyTileData, nBlockXSize, nBlockYSize, eRequestDT,
481
16
                           poDSTile->GetRasterCount(), nullptr, 0, 0, 0,
482
16
                           nullptr) != CE_None)
483
7
    {
484
7
        FillEmptyTile(pabyTileData);
485
7
        return CE_Failure;
486
7
    }
487
488
9
    if (m_eDT != GDT_UInt8)
489
4
    {
490
4
        int bHasNoData = FALSE;
491
4
        const double dfNoDataValue =
492
4
            IGetRasterBand(1)->GetNoDataValue(&bHasNoData);
493
4
        if (m_eDT == GDT_Int16)
494
0
        {
495
0
            CPLAssert(eRequestDT == GDT_UInt16);
496
0
            for (size_t i = 0;
497
0
                 i < static_cast<size_t>(nBlockXSize) * nBlockYSize; i++)
498
0
            {
499
0
                GUInt16 usVal;
500
0
                memcpy(&usVal, pabyTileData + i * sizeof(GUInt16),
501
0
                       sizeof(usVal));
502
0
                double dfVal =
503
0
                    floor((usVal * dfTileScale + dfTileOffset) * m_dfScale +
504
0
                          m_dfOffset + 0.5);
505
0
                if (bHasNoData && usVal == m_usGPKGNull)
506
0
                    dfVal = dfNoDataValue;
507
0
                if (dfVal > 32767)
508
0
                    dfVal = 32767;
509
0
                else if (dfVal < -32768)
510
0
                    dfVal = -32768;
511
0
                GInt16 sVal = static_cast<GInt16>(dfVal);
512
0
                memcpy(pabyTileData + i * sizeof(GUInt16), &sVal, sizeof(sVal));
513
0
            }
514
0
        }
515
4
        else if (m_eDT == GDT_UInt16 &&
516
4
                 (m_dfOffset != 0.0 || m_dfScale != 1.0 ||
517
4
                  dfTileOffset != 0.0 || dfTileScale != 1.0))
518
0
        {
519
0
            CPLAssert(eRequestDT == GDT_UInt16);
520
0
            GUInt16 *psVal = reinterpret_cast<GUInt16 *>(pabyTileData);
521
0
            for (size_t i = 0;
522
0
                 i < static_cast<size_t>(nBlockXSize) * nBlockYSize; i++)
523
0
            {
524
0
                const GUInt16 nVal = psVal[i];
525
0
                double dfVal =
526
0
                    floor((nVal * dfTileScale + dfTileOffset) * m_dfScale +
527
0
                          m_dfOffset + 0.5);
528
0
                if (bHasNoData && nVal == m_usGPKGNull)
529
0
                    dfVal = dfNoDataValue;
530
0
                if (dfVal > 65535)
531
0
                    dfVal = 65535;
532
0
                else if (dfVal < 0)
533
0
                    dfVal = 0;
534
0
                psVal[i] = static_cast<GUInt16>(dfVal);
535
0
            }
536
0
        }
537
4
        else if (m_eDT == GDT_Float32 && eRequestDT == GDT_UInt16)
538
0
        {
539
            // Due to non identical data type size, we need to start from the
540
            // end of the buffer.
541
0
            for (GPtrDiff_t i =
542
0
                     static_cast<GPtrDiff_t>(nBlockXSize) * nBlockYSize - 1;
543
0
                 i >= 0; i--)
544
0
            {
545
                // Use memcpy() and not reinterpret_cast<GUInt16*> and
546
                // reinterpret_cast<float*>, otherwise compilers such as ICC
547
                // may (ab)use rules about aliasing to generate wrong code!
548
0
                GUInt16 usVal;
549
0
                memcpy(&usVal, pabyTileData + i * sizeof(GUInt16),
550
0
                       sizeof(usVal));
551
0
                double dfVal =
552
0
                    (usVal * dfTileScale + dfTileOffset) * m_dfScale +
553
0
                    m_dfOffset;
554
0
                if (m_dfPrecision == 1.0)
555
0
                    dfVal = floor(dfVal + 0.5);
556
0
                if (bHasNoData && usVal == m_usGPKGNull)
557
0
                    dfVal = dfNoDataValue;
558
0
                const float fVal = static_cast<float>(dfVal);
559
0
                memcpy(pabyTileData + i * sizeof(float), &fVal, sizeof(fVal));
560
0
            }
561
0
        }
562
563
4
        return CE_None;
564
4
    }
565
566
5
    GDALColorTable *poCT = nullptr;
567
5
    if (nBands == 1 || nTileBandCount == 1)
568
2
    {
569
2
        poCT = poDSTile->GetRasterBand(1)->GetColorTable();
570
2
        IGetRasterBand(1)->GetColorTable();
571
2
    }
572
573
5
    if (pbIsLossyFormat)
574
0
        *pbIsLossyFormat =
575
0
            !EQUAL(poDSTile->GetDriver()->GetDescription(), "PNG") ||
576
0
            (poCT != nullptr && poCT->GetColorEntryCount() == 256) /* PNG8 */;
577
578
    /* Map RGB(A) tile to single-band color indexed */
579
5
    const GPtrDiff_t nBlockPixels =
580
5
        static_cast<GPtrDiff_t>(nBlockXSize) * nBlockYSize;
581
5
    if (nBands == 1 && m_poCT != nullptr && nTileBandCount != 1)
582
0
    {
583
0
        std::map<GUInt32, int> oMapEntryToIndex;
584
0
        const int nEntries = std::min(256, m_poCT->GetColorEntryCount());
585
0
        for (int i = 0; i < nEntries; i++)
586
0
        {
587
0
            const GDALColorEntry *psEntry = m_poCT->GetColorEntry(i);
588
0
            GByte c1 = static_cast<GByte>(psEntry->c1);
589
0
            GByte c2 = static_cast<GByte>(psEntry->c2);
590
0
            GByte c3 = static_cast<GByte>(psEntry->c3);
591
0
            GUInt32 nVal = c1 + (c2 << 8) + (c3 << 16);
592
0
            if (nTileBandCount == 4)
593
0
                nVal += (static_cast<GUInt32>(psEntry->c4) << 24);
594
0
            oMapEntryToIndex[nVal] = i;
595
0
        }
596
0
        int iBestEntryFor0 =
597
0
            GPKGFindBestEntry(m_poCT, 0, 0, 0, 0, nTileBandCount);
598
0
        for (GPtrDiff_t i = 0; i < nBlockPixels; i++)
599
0
        {
600
0
            const GByte c1 = pabyTileData[i];
601
0
            const GByte c2 = pabyTileData[i + nBlockPixels];
602
0
            const GByte c3 = pabyTileData[i + 2 * nBlockPixels];
603
0
            const GByte c4 = pabyTileData[i + 3 * nBlockPixels];
604
0
            GUInt32 nVal = c1 + (c2 << 8) + (c3 << 16);
605
0
            if (nTileBandCount == 4)
606
0
                nVal += (c4 << 24);
607
0
            if (nVal == 0)
608
                // In most cases we will reach that point at partial tiles.
609
0
                pabyTileData[i] = static_cast<GByte>(iBestEntryFor0);
610
0
            else
611
0
            {
612
0
                std::map<GUInt32, int>::iterator oMapEntryToIndexIter =
613
0
                    oMapEntryToIndex.find(nVal);
614
0
                if (oMapEntryToIndexIter == oMapEntryToIndex.end())
615
                    /* Could happen with JPEG tiles */
616
0
                    pabyTileData[i] = static_cast<GByte>(GPKGFindBestEntry(
617
0
                        m_poCT, c1, c2, c3, c4, nTileBandCount));
618
0
                else
619
0
                    pabyTileData[i] =
620
0
                        static_cast<GByte>(oMapEntryToIndexIter->second);
621
0
            }
622
0
        }
623
0
        return CE_None;
624
0
    }
625
626
5
    if (nBands == 1 && nTileBandCount == 1 && poCT != nullptr &&
627
0
        m_poCT != nullptr && !poCT->IsSame(m_poCT))
628
0
    {
629
0
        CPLError(CE_Warning, CPLE_NotSupported,
630
0
                 "Different color tables. Unhandled for now");
631
0
    }
632
5
    else if ((nBands == 1 && nTileBandCount >= 3) ||
633
5
             (nBands == 1 && nTileBandCount == 1 && m_poCT != nullptr &&
634
0
              poCT == nullptr) ||
635
5
             ((nBands == 1 || nBands == 2) && nTileBandCount == 1 &&
636
0
              m_poCT == nullptr && poCT != nullptr))
637
0
    {
638
0
        CPLError(CE_Failure, CPLE_AppDefined,
639
0
                 "Inconsistent dataset and tiles band characteristics");
640
0
    }
641
642
5
    if (nBands == 2)
643
0
    {
644
        // assuming that the RGB is Grey,Grey,Grey
645
0
        if (nTileBandCount == 1 || nTileBandCount == 3)
646
0
        {
647
            /* Create fully opaque alpha */
648
0
            memset(pabyTileData + 1 * nBlockPixels, 255, nBlockPixels);
649
0
        }
650
0
        else if (nTileBandCount == 4)
651
0
        {
652
            /* Transfer alpha band */
653
0
            memcpy(pabyTileData + 1 * nBlockPixels,
654
0
                   pabyTileData + 3 * nBlockPixels, nBlockPixels);
655
0
        }
656
0
    }
657
5
    else if (nTileBandCount == 2)
658
5
    {
659
        /* Do Grey+Alpha -> RGBA */
660
5
        memcpy(pabyTileData + 3 * nBlockPixels, pabyTileData + 1 * nBlockPixels,
661
5
               nBlockPixels);
662
5
        memcpy(pabyTileData + 1 * nBlockPixels, pabyTileData, nBlockPixels);
663
5
        memcpy(pabyTileData + 2 * nBlockPixels, pabyTileData, nBlockPixels);
664
5
    }
665
0
    else if (nTileBandCount == 1 && !(nBands == 1 && m_poCT != nullptr))
666
0
    {
667
        /* Expand color indexed to RGB(A) */
668
0
        if (poCT != nullptr)
669
0
        {
670
0
            GByte abyCT[4 * 256];
671
0
            const int nEntries = std::min(256, poCT->GetColorEntryCount());
672
0
            for (int i = 0; i < nEntries; i++)
673
0
            {
674
0
                const GDALColorEntry *psEntry = poCT->GetColorEntry(i);
675
0
                abyCT[4 * i] = static_cast<GByte>(psEntry->c1);
676
0
                abyCT[4 * i + 1] = static_cast<GByte>(psEntry->c2);
677
0
                abyCT[4 * i + 2] = static_cast<GByte>(psEntry->c3);
678
0
                abyCT[4 * i + 3] = static_cast<GByte>(psEntry->c4);
679
0
            }
680
0
            for (int i = nEntries; i < 256; i++)
681
0
            {
682
0
                abyCT[4 * i] = 0;
683
0
                abyCT[4 * i + 1] = 0;
684
0
                abyCT[4 * i + 2] = 0;
685
0
                abyCT[4 * i + 3] = 0;
686
0
            }
687
0
            for (GPtrDiff_t i = 0; i < nBlockPixels; i++)
688
0
            {
689
0
                const GByte byVal = pabyTileData[i];
690
0
                pabyTileData[i] = abyCT[4 * byVal];
691
0
                pabyTileData[i + 1 * nBlockPixels] = abyCT[4 * byVal + 1];
692
0
                pabyTileData[i + 2 * nBlockPixels] = abyCT[4 * byVal + 2];
693
0
                pabyTileData[i + 3 * nBlockPixels] = abyCT[4 * byVal + 3];
694
0
            }
695
0
        }
696
0
        else
697
0
        {
698
0
            memcpy(pabyTileData + 1 * nBlockPixels, pabyTileData, nBlockPixels);
699
0
            memcpy(pabyTileData + 2 * nBlockPixels, pabyTileData, nBlockPixels);
700
0
            if (nBands == 4)
701
0
            {
702
0
                memset(pabyTileData + 3 * nBlockPixels, 255, nBlockPixels);
703
0
            }
704
0
        }
705
0
    }
706
0
    else if (nTileBandCount == 3 && nBands == 4)
707
0
    {
708
        /* Create fully opaque alpha */
709
0
        memset(pabyTileData + 3 * nBlockPixels, 255, nBlockPixels);
710
0
    }
711
712
5
    return CE_None;
713
5
}
714
715
/************************************************************************/
716
/*                              ReadTile()                              */
717
/************************************************************************/
718
719
GByte *GDALGPKGMBTilesLikePseudoDataset::ReadTile(int nRow, int nCol)
720
142
{
721
142
    int nBlockXSize, nBlockYSize;
722
142
    IGetRasterBand(1)->GetBlockSize(&nBlockXSize, &nBlockYSize);
723
142
    const int nBands = IGetRasterCount();
724
142
    const size_t nBandBlockSize =
725
142
        static_cast<size_t>(nBlockXSize) * nBlockYSize * m_nDTSize;
726
142
    const int nTileBands = m_eDT == GDT_UInt8 ? 4 : 1;
727
142
    if (m_nShiftXPixelsMod || m_nShiftYPixelsMod)
728
76
    {
729
76
        GByte *pabyData = nullptr;
730
76
        int i = 0;
731
182
        for (; i < 4; i++)
732
182
        {
733
182
            if (m_asCachedTilesDesc[i].nRow == nRow &&
734
110
                m_asCachedTilesDesc[i].nCol == nCol)
735
76
            {
736
76
                if (m_asCachedTilesDesc[i].nIdxWithinTileData >= 0)
737
16
                {
738
16
                    return m_pabyCachedTiles +
739
16
                           nBandBlockSize *
740
16
                               m_asCachedTilesDesc[i].nIdxWithinTileData *
741
16
                               nTileBands;
742
16
                }
743
60
                else
744
60
                {
745
60
                    if (i == 0)
746
15
                        m_asCachedTilesDesc[i].nIdxWithinTileData =
747
15
                            (m_asCachedTilesDesc[1].nIdxWithinTileData == 0)
748
15
                                ? 1
749
15
                                : 0;
750
45
                    else if (i == 1)
751
17
                        m_asCachedTilesDesc[i].nIdxWithinTileData =
752
17
                            (m_asCachedTilesDesc[0].nIdxWithinTileData == 0)
753
17
                                ? 1
754
17
                                : 0;
755
28
                    else if (i == 2)
756
11
                        m_asCachedTilesDesc[i].nIdxWithinTileData =
757
11
                            (m_asCachedTilesDesc[3].nIdxWithinTileData == 2)
758
11
                                ? 3
759
11
                                : 2;
760
17
                    else
761
17
                        m_asCachedTilesDesc[i].nIdxWithinTileData =
762
17
                            (m_asCachedTilesDesc[2].nIdxWithinTileData == 2)
763
17
                                ? 3
764
17
                                : 2;
765
60
                    pabyData = m_pabyCachedTiles +
766
60
                               nBandBlockSize *
767
60
                                   m_asCachedTilesDesc[i].nIdxWithinTileData *
768
60
                                   nTileBands;
769
60
                    break;
770
60
                }
771
76
            }
772
182
        }
773
60
        CPLAssert(i < 4);
774
60
        return ReadTile(nRow, nCol, pabyData);
775
76
    }
776
66
    else
777
66
    {
778
66
        GByte *pabyDest = m_pabyCachedTiles + 2 * nTileBands * nBandBlockSize;
779
66
        bool bAllNonDirty = true;
780
312
        for (int i = 0; i < nBands; i++)
781
246
        {
782
246
            if (m_asCachedTilesDesc[0].abBandDirty[i])
783
0
                bAllNonDirty = false;
784
246
        }
785
66
        if (bAllNonDirty)
786
66
        {
787
66
            return ReadTile(nRow, nCol, pabyDest);
788
66
        }
789
790
        /* If some bands of the blocks are dirty/written we need to fetch */
791
        /* the tile in a temporary buffer in order not to override dirty bands*/
792
0
        GByte *pabyTemp = m_pabyCachedTiles + 3 * nTileBands * nBandBlockSize;
793
0
        if (ReadTile(nRow, nCol, pabyTemp) != nullptr)
794
0
        {
795
0
            for (int i = 0; i < nBands; i++)
796
0
            {
797
0
                if (!m_asCachedTilesDesc[0].abBandDirty[i])
798
0
                {
799
0
                    memcpy(pabyDest + i * nBandBlockSize,
800
0
                           pabyTemp + i * nBandBlockSize, nBandBlockSize);
801
0
                }
802
0
            }
803
0
        }
804
0
        return pabyDest;
805
66
    }
806
142
}
807
808
/************************************************************************/
809
/*                       GetTileOffsetAndScale()                        */
810
/************************************************************************/
811
812
void GDALGPKGMBTilesLikePseudoDataset::GetTileOffsetAndScale(
813
    GIntBig nTileId, double &dfTileOffset, double &dfTileScale)
814
22
{
815
22
    dfTileOffset = 0.0;
816
22
    dfTileScale = 1.0;
817
818
22
    if (m_eTF == GPKG_TF_PNG_16BIT)
819
4
    {
820
4
        char *pszSQL = sqlite3_mprintf(
821
4
            "SELECT offset, scale FROM gpkg_2d_gridded_tile_ancillary WHERE "
822
4
            "tpudt_name = '%q' AND tpudt_id = ?",
823
4
            m_osRasterTable.c_str());
824
4
        sqlite3_stmt *hStmt = nullptr;
825
4
        int rc = SQLPrepareWithError(IGetDB(), pszSQL, -1, &hStmt, nullptr);
826
4
        if (rc == SQLITE_OK)
827
4
        {
828
4
            sqlite3_bind_int64(hStmt, 1, nTileId);
829
4
            rc = sqlite3_step(hStmt);
830
4
            if (rc == SQLITE_ROW)
831
3
            {
832
3
                if (sqlite3_column_type(hStmt, 0) == SQLITE_FLOAT)
833
3
                    dfTileOffset = sqlite3_column_double(hStmt, 0);
834
3
                if (sqlite3_column_type(hStmt, 1) == SQLITE_FLOAT)
835
3
                    dfTileScale = sqlite3_column_double(hStmt, 1);
836
3
            }
837
4
            sqlite3_finalize(hStmt);
838
4
        }
839
4
        sqlite3_free(pszSQL);
840
4
    }
841
22
}
842
843
/************************************************************************/
844
/*                              ReadTile()                              */
845
/************************************************************************/
846
847
GByte *GDALGPKGMBTilesLikePseudoDataset::ReadTile(int nRow, int nCol,
848
                                                  GByte *pabyData,
849
                                                  bool *pbIsLossyFormat)
850
126
{
851
126
    int nBlockXSize = 0;
852
126
    int nBlockYSize = 0;
853
126
    IGetRasterBand(1)->GetBlockSize(&nBlockXSize, &nBlockYSize);
854
126
    const int nBands = IGetRasterCount();
855
856
126
    if (pbIsLossyFormat)
857
0
        *pbIsLossyFormat = false;
858
859
126
    const size_t nBandBlockSize =
860
126
        static_cast<size_t>(nBlockXSize) * nBlockYSize * m_nDTSize;
861
126
    if (nRow < 0 || nCol < 0 || nRow >= m_nTileMatrixHeight ||
862
110
        nCol >= m_nTileMatrixWidth)
863
16
    {
864
16
        FillEmptyTile(pabyData);
865
16
        return pabyData;
866
16
    }
867
868
#ifdef DEBUG_VERBOSE
869
    CPLDebug("GPKG", "ReadTile(row=%d, col=%d)", nRow, nCol);
870
#endif
871
872
110
    char *pszSQL = sqlite3_mprintf(
873
110
        "SELECT tile_data%s FROM \"%w\" "
874
110
        "WHERE zoom_level = %d AND tile_row = %d AND tile_column = %d%s",
875
110
        m_eDT != GDT_UInt8 ? ", id" : "",  // MBTiles do not have an id
876
110
        m_osRasterTable.c_str(), m_nZoomLevel,
877
110
        GetRowFromIntoTopConvention(nRow), nCol,
878
110
        !m_osWHERE.empty() ? CPLSPrintf(" AND (%s)", m_osWHERE.c_str()) : "");
879
880
#ifdef DEBUG_VERBOSE
881
    CPLDebug("GPKG", "%s", pszSQL);
882
#endif
883
884
110
    sqlite3_stmt *hStmt = nullptr;
885
110
    int rc = SQLPrepareWithError(IGetDB(), pszSQL, -1, &hStmt, nullptr);
886
110
    sqlite3_free(pszSQL);
887
110
    if (rc != SQLITE_OK)
888
5
    {
889
5
        return nullptr;
890
5
    }
891
105
    rc = sqlite3_step(hStmt);
892
893
105
    if (rc == SQLITE_ROW && sqlite3_column_type(hStmt, 0) == SQLITE_BLOB)
894
22
    {
895
22
        const int nBytes = sqlite3_column_bytes(hStmt, 0);
896
22
        GIntBig nTileId =
897
22
            (m_eDT == GDT_UInt8) ? 0 : sqlite3_column_int64(hStmt, 1);
898
22
        GByte *pabyRawData = static_cast<GByte *>(
899
22
            const_cast<void *>(sqlite3_column_blob(hStmt, 0)));
900
22
        const CPLString osMemFileName(
901
22
            VSIMemGenerateHiddenFilename("gpkg_read_tile"));
902
22
        VSILFILE *fp = VSIFileFromMemBuffer(osMemFileName.c_str(), pabyRawData,
903
22
                                            nBytes, FALSE);
904
22
        VSIFCloseL(fp);
905
906
22
        double dfTileOffset = 0.0;
907
22
        double dfTileScale = 1.0;
908
22
        GetTileOffsetAndScale(nTileId, dfTileOffset, dfTileScale);
909
22
        ReadTile(osMemFileName, pabyData, dfTileOffset, dfTileScale,
910
22
                 pbIsLossyFormat);
911
22
        VSIUnlink(osMemFileName);
912
22
        sqlite3_finalize(hStmt);
913
22
    }
914
83
    else if (rc == SQLITE_BUSY)
915
0
    {
916
0
        FillEmptyTile(pabyData);
917
0
        CPLError(CE_Failure, CPLE_AppDefined,
918
0
                 "sqlite3_step(%s) failed (SQLITE_BUSY): %s",
919
0
                 sqlite3_sql(hStmt), sqlite3_errmsg(IGetDB()));
920
0
        sqlite3_finalize(hStmt);
921
0
        return pabyData;
922
0
    }
923
83
    else
924
83
    {
925
83
        sqlite3_finalize(hStmt);
926
83
        hStmt = nullptr;
927
928
83
        if (m_hTempDB && (m_nShiftXPixelsMod || m_nShiftYPixelsMod))
929
0
        {
930
0
            const char *pszSQLNew = CPLSPrintf(
931
0
                "SELECT partial_flag, tile_data_band_1, tile_data_band_2, "
932
0
                "tile_data_band_3, tile_data_band_4 FROM partial_tiles WHERE "
933
0
                "zoom_level = %d AND tile_row = %d AND tile_column = %d",
934
0
                m_nZoomLevel, nRow, nCol);
935
936
#ifdef DEBUG_VERBOSE
937
            CPLDebug("GPKG", "%s", pszSQLNew);
938
#endif
939
940
0
            rc = SQLPrepareWithError(m_hTempDB, pszSQLNew, -1, &hStmt, nullptr);
941
0
            if (rc != SQLITE_OK)
942
0
            {
943
0
                FillEmptyTile(pabyData);
944
0
                return pabyData;
945
0
            }
946
947
0
            rc = sqlite3_step(hStmt);
948
0
            if (rc == SQLITE_ROW)
949
0
            {
950
0
                const int nPartialFlag = sqlite3_column_int(hStmt, 0);
951
0
                for (int iBand = 1; iBand <= nBands; iBand++)
952
0
                {
953
0
                    GByte *pabyDestBand =
954
0
                        pabyData + (iBand - 1) * nBandBlockSize;
955
0
                    if (nPartialFlag & (((1 << 4) - 1) << (4 * (iBand - 1))))
956
0
                    {
957
0
                        CPLAssert(sqlite3_column_bytes(hStmt, iBand) ==
958
0
                                  static_cast<int>(nBandBlockSize));
959
0
                        memcpy(pabyDestBand, sqlite3_column_blob(hStmt, iBand),
960
0
                               nBandBlockSize);
961
0
                    }
962
0
                    else
963
0
                    {
964
0
                        FillEmptyTileSingleBand(pabyDestBand);
965
0
                    }
966
0
                }
967
0
            }
968
0
            else
969
0
            {
970
0
                FillEmptyTile(pabyData);
971
0
            }
972
0
            sqlite3_finalize(hStmt);
973
0
        }
974
83
        else
975
83
        {
976
83
            FillEmptyTile(pabyData);
977
83
        }
978
83
    }
979
980
105
    return pabyData;
981
105
}
982
983
/************************************************************************/
984
/*                             IReadBlock()                             */
985
/************************************************************************/
986
987
CPLErr GDALGPKGMBTilesLikeRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
988
                                                 void *pData)
989
89
{
990
#ifdef DEBUG_VERBOSE
991
    CPLDebug("GPKG",
992
             "IReadBlock(nBand=%d,nBlockXOff=%d,nBlockYOff=%d,m_nZoomLevel=%d)",
993
             nBand, nBlockXOff, nBlockYOff, m_poTPD->m_nZoomLevel);
994
#endif
995
996
89
    if (m_poTPD->m_pabyCachedTiles == nullptr)
997
0
        return CE_Failure;
998
999
89
    const int nRowMin = nBlockYOff + m_poTPD->m_nShiftYTiles;
1000
89
    int nRowMax = nRowMin;
1001
89
    if (m_poTPD->m_nShiftYPixelsMod)
1002
23
        nRowMax++;
1003
1004
89
    const int nColMin = nBlockXOff + m_poTPD->m_nShiftXTiles;
1005
89
    int nColMax = nColMin;
1006
89
    if (m_poTPD->m_nShiftXPixelsMod)
1007
21
        nColMax++;
1008
1009
89
retry:
1010
    /* Optimize for left to right reading at constant row */
1011
89
    if (m_poTPD->m_nShiftXPixelsMod || m_poTPD->m_nShiftYPixelsMod)
1012
23
    {
1013
23
        if (nRowMin == m_poTPD->m_asCachedTilesDesc[0].nRow &&
1014
12
            nColMin == m_poTPD->m_asCachedTilesDesc[0].nCol + 1 &&
1015
9
            m_poTPD->m_asCachedTilesDesc[0].nIdxWithinTileData >= 0)
1016
8
        {
1017
8
            CPLAssert(nRowMin == m_poTPD->m_asCachedTilesDesc[1].nRow);
1018
8
            CPLAssert(nColMin == m_poTPD->m_asCachedTilesDesc[1].nCol);
1019
8
            CPLAssert(m_poTPD->m_asCachedTilesDesc[0].nIdxWithinTileData == 0 ||
1020
8
                      m_poTPD->m_asCachedTilesDesc[0].nIdxWithinTileData == 1);
1021
1022
            /* 0 1  --> 1 -1 */
1023
            /* 2 3      3 -1 */
1024
            /* or */
1025
            /* 1 0  --> 0 -1 */
1026
            /* 3 2      2 -1 */
1027
8
            m_poTPD->m_asCachedTilesDesc[0].nIdxWithinTileData =
1028
8
                m_poTPD->m_asCachedTilesDesc[1].nIdxWithinTileData;
1029
8
            m_poTPD->m_asCachedTilesDesc[2].nIdxWithinTileData =
1030
8
                m_poTPD->m_asCachedTilesDesc[3].nIdxWithinTileData;
1031
8
        }
1032
15
        else
1033
15
        {
1034
15
            m_poTPD->m_asCachedTilesDesc[0].nIdxWithinTileData = -1;
1035
15
            m_poTPD->m_asCachedTilesDesc[2].nIdxWithinTileData = -1;
1036
15
        }
1037
23
        m_poTPD->m_asCachedTilesDesc[0].nRow = nRowMin;
1038
23
        m_poTPD->m_asCachedTilesDesc[0].nCol = nColMin;
1039
23
        m_poTPD->m_asCachedTilesDesc[1].nRow = nRowMin;
1040
23
        m_poTPD->m_asCachedTilesDesc[1].nCol = nColMin + 1;
1041
23
        m_poTPD->m_asCachedTilesDesc[2].nRow = nRowMin + 1;
1042
23
        m_poTPD->m_asCachedTilesDesc[2].nCol = nColMin;
1043
23
        m_poTPD->m_asCachedTilesDesc[3].nRow = nRowMin + 1;
1044
23
        m_poTPD->m_asCachedTilesDesc[3].nCol = nColMin + 1;
1045
23
        m_poTPD->m_asCachedTilesDesc[1].nIdxWithinTileData = -1;
1046
23
        m_poTPD->m_asCachedTilesDesc[3].nIdxWithinTileData = -1;
1047
23
    }
1048
1049
192
    for (int nRow = nRowMin; nRow <= nRowMax; nRow++)
1050
108
    {
1051
245
        for (int nCol = nColMin; nCol <= nColMax; nCol++)
1052
142
        {
1053
142
            if (m_poTPD->m_nShiftXPixelsMod == 0 &&
1054
70
                m_poTPD->m_nShiftYPixelsMod == 0)
1055
66
            {
1056
66
                if (!(nRow == m_poTPD->m_asCachedTilesDesc[0].nRow &&
1057
0
                      nCol == m_poTPD->m_asCachedTilesDesc[0].nCol &&
1058
0
                      m_poTPD->m_asCachedTilesDesc[0].nIdxWithinTileData == 0))
1059
66
                {
1060
66
                    if (m_poTPD->WriteTile() != CE_None)
1061
0
                        return CE_Failure;
1062
66
                }
1063
66
            }
1064
1065
142
            GByte *pabyTileData = m_poTPD->ReadTile(nRow, nCol);
1066
142
            if (pabyTileData == nullptr)
1067
5
                return CE_Failure;
1068
1069
667
            for (int iBand = 1; iBand <= poDS->GetRasterCount(); iBand++)
1070
530
            {
1071
530
                GDALRasterBlock *poBlock = nullptr;
1072
530
                GByte *pabyDest = nullptr;
1073
530
                if (iBand == nBand)
1074
137
                {
1075
137
                    pabyDest = static_cast<GByte *>(pData);
1076
137
                }
1077
393
                else
1078
393
                {
1079
393
                    poBlock = poDS->GetRasterBand(iBand)->GetLockedBlockRef(
1080
393
                        nBlockXOff, nBlockYOff, TRUE);
1081
393
                    if (poBlock == nullptr)
1082
0
                        continue;
1083
393
                    if (poBlock->GetDirty())
1084
0
                    {
1085
0
                        poBlock->DropLock();
1086
0
                        continue;
1087
0
                    }
1088
                    /* if we are short of GDAL cache max and there are dirty
1089
                     * blocks */
1090
                    /* of our dataset, the above GetLockedBlockRef() might have
1091
                     * reset */
1092
                    /* (at least part of) the 4 tiles we want to cache and have
1093
                     */
1094
                    /* already read */
1095
                    // FIXME this is way too fragile.
1096
393
                    if ((m_poTPD->m_nShiftXPixelsMod != 0 ||
1097
189
                         m_poTPD->m_nShiftYPixelsMod != 0) &&
1098
213
                        (m_poTPD->m_asCachedTilesDesc[0].nRow != nRowMin ||
1099
213
                         m_poTPD->m_asCachedTilesDesc[0].nCol != nColMin))
1100
0
                    {
1101
0
                        poBlock->DropLock();
1102
0
                        goto retry;
1103
0
                    }
1104
393
                    pabyDest = static_cast<GByte *>(poBlock->GetDataRef());
1105
393
                }
1106
1107
                // Composite tile data into block data
1108
530
                if (m_poTPD->m_nShiftXPixelsMod == 0 &&
1109
258
                    m_poTPD->m_nShiftYPixelsMod == 0)
1110
246
                {
1111
246
                    const size_t nBandBlockSize =
1112
246
                        static_cast<size_t>(nBlockXSize) * nBlockYSize *
1113
246
                        m_nDTSize;
1114
246
                    memcpy(pabyDest,
1115
246
                           pabyTileData + (iBand - 1) * nBandBlockSize,
1116
246
                           nBandBlockSize);
1117
#ifdef DEBUG_VERBOSE
1118
                    if (eDataType == GDT_UInt8 &&
1119
                        (nBlockXOff + 1) * nBlockXSize <= nRasterXSize &&
1120
                        (nBlockYOff + 1) * nBlockYSize > nRasterYSize)
1121
                    {
1122
                        bool bFoundNonZero = false;
1123
                        for (int y = nRasterYSize - nBlockYOff * nBlockYSize;
1124
                             y < nBlockYSize; y++)
1125
                        {
1126
                            for (int x = 0; x < nBlockXSize; x++)
1127
                            {
1128
                                if (pabyDest[static_cast<GPtrDiff_t>(y) *
1129
                                                 nBlockXSize +
1130
                                             x] != 0 &&
1131
                                    !bFoundNonZero)
1132
                                {
1133
                                    CPLDebug("GPKG",
1134
                                             "IReadBlock(): Found non-zero "
1135
                                             "content in ghost part of "
1136
                                             "tile(nBand=%d,nBlockXOff=%d,"
1137
                                             "nBlockYOff=%d,m_nZoomLevel=%d)\n",
1138
                                             iBand, nBlockXOff, nBlockYOff,
1139
                                             m_poTPD->m_nZoomLevel);
1140
                                    bFoundNonZero = true;
1141
                                }
1142
                            }
1143
                        }
1144
                    }
1145
#endif
1146
246
                }
1147
284
                else
1148
284
                {
1149
284
                    int nSrcXOffset = 0;
1150
284
                    int nSrcXSize = 0;
1151
284
                    int nDstXOffset = 0;
1152
284
                    if (nCol == nColMin)
1153
148
                    {
1154
148
                        nSrcXOffset = m_poTPD->m_nShiftXPixelsMod;
1155
148
                        nSrcXSize = nBlockXSize - m_poTPD->m_nShiftXPixelsMod;
1156
148
                        nDstXOffset = 0;
1157
148
                    }
1158
136
                    else
1159
136
                    {
1160
136
                        nSrcXOffset = 0;
1161
136
                        nSrcXSize = m_poTPD->m_nShiftXPixelsMod;
1162
136
                        nDstXOffset = nBlockXSize - m_poTPD->m_nShiftXPixelsMod;
1163
136
                    }
1164
284
                    int nSrcYOffset = 0;
1165
284
                    int nSrcYSize = 0;
1166
284
                    int nDstYOffset = 0;
1167
284
                    if (nRow == nRowMin)
1168
144
                    {
1169
144
                        nSrcYOffset = m_poTPD->m_nShiftYPixelsMod;
1170
144
                        nSrcYSize = nBlockYSize - m_poTPD->m_nShiftYPixelsMod;
1171
144
                        nDstYOffset = 0;
1172
144
                    }
1173
140
                    else
1174
140
                    {
1175
140
                        nSrcYOffset = 0;
1176
140
                        nSrcYSize = m_poTPD->m_nShiftYPixelsMod;
1177
140
                        nDstYOffset = nBlockYSize - m_poTPD->m_nShiftYPixelsMod;
1178
140
                    }
1179
1180
#ifdef DEBUG_VERBOSE
1181
                    CPLDebug("GPKG",
1182
                             "Copy source tile x=%d,w=%d,y=%d,h=%d into "
1183
                             "buffer at x=%d,y=%d",
1184
                             nSrcXOffset, nSrcXSize, nSrcYOffset, nSrcYSize,
1185
                             nDstXOffset, nDstYOffset);
1186
#endif
1187
1188
36.1k
                    for (GPtrDiff_t y = 0; y < nSrcYSize; y++)
1189
35.8k
                    {
1190
35.8k
                        GByte *pSrc =
1191
35.8k
                            pabyTileData +
1192
35.8k
                            (static_cast<GPtrDiff_t>(iBand - 1) * nBlockXSize *
1193
35.8k
                                 nBlockYSize +
1194
35.8k
                             (y + nSrcYOffset) * nBlockXSize + nSrcXOffset) *
1195
35.8k
                                m_nDTSize;
1196
35.8k
                        GByte *pDst =
1197
35.8k
                            pabyDest +
1198
35.8k
                            ((y + nDstYOffset) * nBlockXSize + nDstXOffset) *
1199
35.8k
                                m_nDTSize;
1200
35.8k
                        GDALCopyWords(pSrc, eDataType, m_nDTSize, pDst,
1201
35.8k
                                      eDataType, m_nDTSize, nSrcXSize);
1202
35.8k
                    }
1203
284
                }
1204
1205
530
                if (poBlock)
1206
393
                    poBlock->DropLock();
1207
530
            }
1208
137
        }
1209
108
    }
1210
1211
84
    return CE_None;
1212
89
}
1213
1214
/************************************************************************/
1215
/*                       IGetDataCoverageStatus()                       */
1216
/************************************************************************/
1217
1218
int GDALGPKGMBTilesLikeRasterBand::IGetDataCoverageStatus(int nXOff, int nYOff,
1219
                                                          int nXSize,
1220
                                                          int nYSize,
1221
                                                          int nMaskFlagStop,
1222
                                                          double *pdfDataPct)
1223
0
{
1224
0
    if (eAccess == GA_Update)
1225
0
        FlushCache(false);
1226
1227
0
    const int iColMin = nXOff / nBlockXSize + m_poTPD->m_nShiftXTiles;
1228
0
    const int iColMax = (nXOff + nXSize - 1) / nBlockXSize +
1229
0
                        m_poTPD->m_nShiftXTiles +
1230
0
                        (m_poTPD->m_nShiftXPixelsMod ? 1 : 0);
1231
0
    const int iRowMin = nYOff / nBlockYSize + m_poTPD->m_nShiftYTiles;
1232
0
    const int iRowMax = (nYOff + nYSize - 1) / nBlockYSize +
1233
0
                        m_poTPD->m_nShiftYTiles +
1234
0
                        (m_poTPD->m_nShiftYPixelsMod ? 1 : 0);
1235
1236
0
    int iDBRowMin = m_poTPD->GetRowFromIntoTopConvention(iRowMin);
1237
0
    int iDBRowMax = m_poTPD->GetRowFromIntoTopConvention(iRowMax);
1238
0
    if (iDBRowMin > iDBRowMax)
1239
0
        std::swap(iDBRowMin, iDBRowMax);
1240
1241
0
    char *pszSQL = sqlite3_mprintf(
1242
0
        "SELECT tile_column, tile_row FROM \"%w\" "
1243
0
        "WHERE zoom_level = %d AND "
1244
0
        "(tile_row BETWEEN %d AND %d) AND "
1245
0
        "(tile_column BETWEEN %d AND %d)"
1246
0
        "%s",
1247
0
        m_poTPD->m_osRasterTable.c_str(), m_poTPD->m_nZoomLevel, iDBRowMin,
1248
0
        iDBRowMax, iColMin, iColMax,
1249
0
        !m_poTPD->m_osWHERE.empty()
1250
0
            ? CPLSPrintf(" AND (%s)", m_poTPD->m_osWHERE.c_str())
1251
0
            : "");
1252
1253
#ifdef DEBUG_VERBOSE
1254
    CPLDebug("GPKG", "%s", pszSQL);
1255
#endif
1256
1257
0
    sqlite3_stmt *hStmt = nullptr;
1258
0
    int rc =
1259
0
        SQLPrepareWithError(m_poTPD->IGetDB(), pszSQL, -1, &hStmt, nullptr);
1260
0
    if (rc != SQLITE_OK)
1261
0
    {
1262
0
        sqlite3_free(pszSQL);
1263
0
        return GDAL_DATA_COVERAGE_STATUS_UNIMPLEMENTED |
1264
0
               GDAL_DATA_COVERAGE_STATUS_DATA;
1265
0
    }
1266
0
    sqlite3_free(pszSQL);
1267
0
    rc = sqlite3_step(hStmt);
1268
0
    std::set<std::pair<int, int>> oSetTiles;  // (col, row)
1269
0
    while (rc == SQLITE_ROW)
1270
0
    {
1271
0
        const int iCol = sqlite3_column_int(hStmt, 0);
1272
0
        const int iRow =
1273
0
            m_poTPD->GetRowFromIntoTopConvention(sqlite3_column_int(hStmt, 1));
1274
0
        oSetTiles.insert(std::pair(iCol, iRow));
1275
0
        rc = sqlite3_step(hStmt);
1276
0
    }
1277
0
    sqlite3_finalize(hStmt);
1278
0
    if (rc != SQLITE_DONE)
1279
0
    {
1280
0
        return GDAL_DATA_COVERAGE_STATUS_UNIMPLEMENTED |
1281
0
               GDAL_DATA_COVERAGE_STATUS_DATA;
1282
0
    }
1283
0
    if (oSetTiles.empty())
1284
0
    {
1285
0
        if (pdfDataPct)
1286
0
            *pdfDataPct = 0.0;
1287
0
        return GDAL_DATA_COVERAGE_STATUS_EMPTY;
1288
0
    }
1289
1290
0
    if (m_poTPD->m_nShiftXPixelsMod == 0 && m_poTPD->m_nShiftYPixelsMod == 0 &&
1291
0
        oSetTiles.size() == static_cast<size_t>(iRowMax - iRowMin + 1) *
1292
0
                                (iColMax - iColMin + 1))
1293
0
    {
1294
0
        if (pdfDataPct)
1295
0
            *pdfDataPct = 100.0;
1296
0
        return GDAL_DATA_COVERAGE_STATUS_DATA;
1297
0
    }
1298
1299
0
    if (m_poTPD->m_nShiftXPixelsMod == 0 && m_poTPD->m_nShiftYPixelsMod == 0)
1300
0
    {
1301
0
        int nStatus = 0;
1302
0
        GIntBig nPixelsData = 0;
1303
0
        for (int iY = iRowMin; iY <= iRowMax; ++iY)
1304
0
        {
1305
0
            for (int iX = iColMin; iX <= iColMax; ++iX)
1306
0
            {
1307
0
                if (oSetTiles.find(std::pair(iX, iY)) == oSetTiles.end())
1308
0
                {
1309
0
                    nStatus |= GDAL_DATA_COVERAGE_STATUS_EMPTY;
1310
0
                }
1311
0
                else
1312
0
                {
1313
0
                    const int iXGDAL = iX - m_poTPD->m_nShiftXTiles;
1314
0
                    const int iYGDAL = iY - m_poTPD->m_nShiftYTiles;
1315
0
                    const int nXBlockRight =
1316
0
                        (iXGDAL * nBlockXSize > INT_MAX - nBlockXSize)
1317
0
                            ? INT_MAX
1318
0
                            : (iXGDAL + 1) * nBlockXSize;
1319
0
                    const int nYBlockBottom =
1320
0
                        (iYGDAL * nBlockYSize > INT_MAX - nBlockYSize)
1321
0
                            ? INT_MAX
1322
0
                            : (iYGDAL + 1) * nBlockYSize;
1323
1324
0
                    nPixelsData += (static_cast<GIntBig>(std::min(
1325
0
                                        nXBlockRight, nXOff + nXSize)) -
1326
0
                                    std::max(iXGDAL * nBlockXSize, nXOff)) *
1327
0
                                   (std::min(nYBlockBottom, nYOff + nYSize) -
1328
0
                                    std::max(iYGDAL * nBlockYSize, nYOff));
1329
0
                    nStatus |= GDAL_DATA_COVERAGE_STATUS_DATA;
1330
0
                }
1331
0
                if (nMaskFlagStop != 0 && (nMaskFlagStop & nStatus) != 0)
1332
0
                {
1333
0
                    if (pdfDataPct)
1334
0
                        *pdfDataPct = -1.0;
1335
0
                    return nStatus;
1336
0
                }
1337
0
            }
1338
0
        }
1339
1340
0
        if (pdfDataPct)
1341
0
        {
1342
0
            *pdfDataPct =
1343
0
                100.0 * nPixelsData / (static_cast<GIntBig>(nXSize) * nYSize);
1344
0
        }
1345
0
        return nStatus;
1346
0
    }
1347
1348
0
    return GDAL_DATA_COVERAGE_STATUS_UNIMPLEMENTED |
1349
0
           GDAL_DATA_COVERAGE_STATUS_DATA;
1350
0
}
1351
1352
/************************************************************************/
1353
/*                         WEBPSupports4Bands()                         */
1354
/************************************************************************/
1355
1356
static bool WEBPSupports4Bands()
1357
0
{
1358
0
    static int bRes = -1;
1359
0
    if (bRes < 0)
1360
0
    {
1361
0
        GDALDriver *poDrv = GDALDriver::FromHandle(GDALGetDriverByName("WEBP"));
1362
0
        if (poDrv == nullptr ||
1363
0
            CPLTestBool(CPLGetConfigOption("GPKG_SIMUL_WEBP_3BAND", "FALSE")))
1364
0
            bRes = false;
1365
0
        else
1366
0
        {
1367
            // LOSSLESS and RGBA support appeared in the same version
1368
0
            bRes = strstr(poDrv->GetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST),
1369
0
                          "LOSSLESS") != nullptr;
1370
0
        }
1371
0
        if (poDrv != nullptr && !bRes)
1372
0
        {
1373
0
            CPLError(
1374
0
                CE_Warning, CPLE_AppDefined,
1375
0
                "The version of WEBP available does not support 4-band RGBA");
1376
0
        }
1377
0
    }
1378
0
    return CPL_TO_BOOL(bRes);
1379
0
}
1380
1381
/************************************************************************/
1382
/*                             GetTileId()                              */
1383
/************************************************************************/
1384
1385
GIntBig GDALGPKGMBTilesLikePseudoDataset::GetTileId(int nRow, int nCol)
1386
0
{
1387
0
    char *pszSQL =
1388
0
        sqlite3_mprintf("SELECT id FROM \"%w\" WHERE zoom_level = %d AND "
1389
0
                        "tile_row = %d AND tile_column = %d",
1390
0
                        m_osRasterTable.c_str(), m_nZoomLevel,
1391
0
                        GetRowFromIntoTopConvention(nRow), nCol);
1392
0
    GIntBig nRes = SQLGetInteger64(IGetDB(), pszSQL, nullptr);
1393
0
    sqlite3_free(pszSQL);
1394
0
    return nRes;
1395
0
}
1396
1397
/************************************************************************/
1398
/*                             DeleteTile()                             */
1399
/************************************************************************/
1400
1401
bool GDALGPKGMBTilesLikePseudoDataset::DeleteTile(int nRow, int nCol)
1402
0
{
1403
0
    char *pszSQL =
1404
0
        sqlite3_mprintf("DELETE FROM \"%w\" "
1405
0
                        "WHERE zoom_level = %d AND tile_row = %d AND "
1406
0
                        "tile_column = %d",
1407
0
                        m_osRasterTable.c_str(), m_nZoomLevel,
1408
0
                        GetRowFromIntoTopConvention(nRow), nCol);
1409
#ifdef DEBUG_VERBOSE
1410
    CPLDebug("GPKG", "%s", pszSQL);
1411
#endif
1412
0
    char *pszErrMsg = nullptr;
1413
0
    int rc = sqlite3_exec(IGetDB(), pszSQL, nullptr, nullptr, &pszErrMsg);
1414
0
    if (rc != SQLITE_OK)
1415
0
    {
1416
0
        CPLError(CE_Failure, CPLE_AppDefined,
1417
0
                 "Failure when deleting tile (row=%d,col=%d) "
1418
0
                 "at zoom_level=%d : %s",
1419
0
                 GetRowFromIntoTopConvention(nRow), nCol, m_nZoomLevel,
1420
0
                 pszErrMsg ? pszErrMsg : "");
1421
0
    }
1422
0
    sqlite3_free(pszSQL);
1423
0
    sqlite3_free(pszErrMsg);
1424
0
    return rc == SQLITE_OK;
1425
0
}
1426
1427
/************************************************************************/
1428
/*                   DeleteFromGriddedTileAncillary()                   */
1429
/************************************************************************/
1430
1431
bool GDALGPKGMBTilesLikePseudoDataset::DeleteFromGriddedTileAncillary(
1432
    GIntBig nTileId)
1433
0
{
1434
0
    char *pszSQL =
1435
0
        sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_tile_ancillary WHERE "
1436
0
                        "tpudt_name = '%q' AND tpudt_id = ?",
1437
0
                        m_osRasterTable.c_str());
1438
0
    sqlite3_stmt *hStmt = nullptr;
1439
0
    int rc = SQLPrepareWithError(IGetDB(), pszSQL, -1, &hStmt, nullptr);
1440
0
    if (rc == SQLITE_OK)
1441
0
    {
1442
0
        sqlite3_bind_int64(hStmt, 1, nTileId);
1443
0
        rc = sqlite3_step(hStmt);
1444
0
        sqlite3_finalize(hStmt);
1445
0
    }
1446
0
    sqlite3_free(pszSQL);
1447
0
    return rc == SQLITE_OK;
1448
0
}
1449
1450
/************************************************************************/
1451
/*                       ProcessInt16UInt16Tile()                       */
1452
/************************************************************************/
1453
1454
template <class T>
1455
static void ProcessInt16UInt16Tile(
1456
    const void *pabyData, GPtrDiff_t nPixels, bool bIsInt16, bool bHasNoData,
1457
    double dfNoDataValue, GUInt16 usGPKGNull, double m_dfOffset,
1458
    double m_dfScale, GUInt16 *pTempTileBuffer, double &dfTileOffset,
1459
    double &dfTileScale, double &dfTileMin, double &dfTileMax,
1460
    double &dfTileMean, double &dfTileStdDev, GPtrDiff_t &nValidPixels)
1461
0
{
1462
0
    const T *pSrc = reinterpret_cast<const T *>(pabyData);
1463
0
    T nMin = 0;
1464
0
    T nMax = 0;
1465
0
    double dfM2 = 0.0;
1466
0
    for (int i = 0; i < nPixels; i++)
1467
0
    {
1468
0
        const T nVal = pSrc[i];
1469
0
        if (bHasNoData && nVal == dfNoDataValue)
1470
0
            continue;
1471
1472
0
        if (nValidPixels == 0)
1473
0
        {
1474
0
            nMin = nVal;
1475
0
            nMax = nVal;
1476
0
        }
1477
0
        else
1478
0
        {
1479
0
            nMin = std::min(nMin, nVal);
1480
0
            nMax = std::max(nMax, nVal);
1481
0
        }
1482
0
        nValidPixels++;
1483
0
        const double dfDelta = nVal - dfTileMean;
1484
0
        dfTileMean += dfDelta / nValidPixels;
1485
0
        dfM2 += dfDelta * (nVal - dfTileMean);
1486
0
    }
1487
0
    dfTileMin = nMin;
1488
0
    dfTileMax = nMax;
1489
0
    if (nValidPixels)
1490
0
        dfTileStdDev = sqrt(dfM2 / nValidPixels);
1491
1492
0
    double dfGlobalMin = (nMin - m_dfOffset) / m_dfScale;
1493
0
    double dfGlobalMax = (nMax - m_dfOffset) / m_dfScale;
1494
0
    double dfRange = 65535.0;
1495
0
    if (bHasNoData && usGPKGNull == 65535 &&
1496
0
        dfGlobalMax - dfGlobalMin >= dfRange)
1497
0
    {
1498
0
        dfRange = 65534.0;
1499
0
    }
1500
1501
0
    if (dfGlobalMax - dfGlobalMin > dfRange)
1502
0
    {
1503
0
        dfTileScale = (dfGlobalMax - dfGlobalMin) / dfRange;
1504
0
    }
1505
0
    if (dfGlobalMin < 0.0)
1506
0
    {
1507
0
        dfTileOffset = -dfGlobalMin;
1508
0
    }
1509
0
    else if (dfGlobalMax / dfTileScale > dfRange)
1510
0
    {
1511
0
        dfTileOffset = dfGlobalMax - dfRange * dfTileScale;
1512
0
    }
1513
1514
0
    if (bHasNoData && std::numeric_limits<T>::min() == 0 && m_dfOffset == 0.0 &&
1515
0
        m_dfScale == 1.0)
1516
0
    {
1517
0
        dfTileOffset = 0.0;
1518
0
        dfTileScale = 1.0;
1519
0
    }
1520
0
    else if (bHasNoData && bIsInt16 && dfNoDataValue == -32768.0 &&
1521
0
             usGPKGNull == 65535 && m_dfOffset == -32768.0 && m_dfScale == 1.0)
1522
0
    {
1523
0
        dfTileOffset = 1.0;
1524
0
        dfTileScale = 1.0;
1525
0
    }
1526
1527
0
    for (GPtrDiff_t i = 0; i < nPixels; i++)
1528
0
    {
1529
0
        const T nVal = pSrc[i];
1530
0
        if (bHasNoData && nVal == dfNoDataValue)
1531
0
            pTempTileBuffer[i] = usGPKGNull;
1532
0
        else
1533
0
        {
1534
0
            double dfVal =
1535
0
                ((nVal - m_dfOffset) / m_dfScale - dfTileOffset) / dfTileScale;
1536
0
            CPLAssert(dfVal >= 0.0 && dfVal < 65535.5);
1537
0
            pTempTileBuffer[i] = static_cast<GUInt16>(dfVal + 0.5);
1538
0
            if (bHasNoData && pTempTileBuffer[i] == usGPKGNull)
1539
0
            {
1540
0
                if (usGPKGNull > 0)
1541
0
                    pTempTileBuffer[i]--;
1542
0
                else
1543
0
                    pTempTileBuffer[i]++;
1544
0
                ;
1545
0
            }
1546
0
        }
1547
0
    }
1548
0
}
Unexecuted instantiation: gdalgeopackagerasterband.cpp:void ProcessInt16UInt16Tile<short>(void const*, long long, bool, bool, double, unsigned short, double, double, unsigned short*, double&, double&, double&, double&, double&, double&, long long&)
Unexecuted instantiation: gdalgeopackagerasterband.cpp:void ProcessInt16UInt16Tile<unsigned short>(void const*, long long, bool, bool, double, unsigned short, double, double, unsigned short*, double&, double&, double&, double&, double&, double&, long long&)
1549
1550
/************************************************************************/
1551
/*                             WriteTile()                              */
1552
/************************************************************************/
1553
1554
CPLErr GDALGPKGMBTilesLikePseudoDataset::WriteTile()
1555
1.21k
{
1556
1.21k
    GDALGPKGMBTilesLikePseudoDataset *poMainDS =
1557
1.21k
        m_poParentDS ? m_poParentDS : this;
1558
1.21k
    if (poMainDS->m_nTileInsertionCount < 0)
1559
0
        return CE_Failure;
1560
1561
1.21k
    if (m_bInWriteTile)
1562
0
    {
1563
        // Shouldn't happen in practice, but #7022 shows that the unexpected
1564
        // can happen sometimes.
1565
0
        CPLError(
1566
0
            CE_Failure, CPLE_AppDefined,
1567
0
            "Recursive call to GDALGPKGMBTilesLikePseudoDataset::WriteTile()");
1568
0
        return CE_Failure;
1569
0
    }
1570
1.21k
    GDALRasterBlock::EnterDisableDirtyBlockFlush();
1571
1.21k
    m_bInWriteTile = true;
1572
1.21k
    CPLErr eErr = WriteTileInternal();
1573
1.21k
    m_bInWriteTile = false;
1574
1.21k
    GDALRasterBlock::LeaveDisableDirtyBlockFlush();
1575
1.21k
    return eErr;
1576
1.21k
}
1577
1578
/* should only be called by WriteTile() */
1579
CPLErr GDALGPKGMBTilesLikePseudoDataset::WriteTileInternal()
1580
1.21k
{
1581
1.21k
    if (!(IGetUpdate() && m_asCachedTilesDesc[0].nRow >= 0 &&
1582
0
          m_asCachedTilesDesc[0].nCol >= 0 &&
1583
0
          m_asCachedTilesDesc[0].nIdxWithinTileData == 0))
1584
1.21k
        return CE_None;
1585
1586
0
    int nRow = m_asCachedTilesDesc[0].nRow;
1587
0
    int nCol = m_asCachedTilesDesc[0].nCol;
1588
1589
0
    bool bAllDirty = true;
1590
0
    bool bAllNonDirty = true;
1591
0
    const int nBands = IGetRasterCount();
1592
0
    for (int i = 0; i < nBands; i++)
1593
0
    {
1594
0
        if (m_asCachedTilesDesc[0].abBandDirty[i])
1595
0
            bAllNonDirty = false;
1596
0
        else
1597
0
            bAllDirty = false;
1598
0
    }
1599
0
    if (bAllNonDirty)
1600
0
        return CE_None;
1601
1602
0
    int nBlockXSize, nBlockYSize;
1603
0
    IGetRasterBand(1)->GetBlockSize(&nBlockXSize, &nBlockYSize);
1604
1605
    /* If all bands for that block are not dirty/written, we need to */
1606
    /* fetch the missing ones if the tile exists */
1607
0
    bool bIsLossyFormat = false;
1608
0
    const size_t nBandBlockSize =
1609
0
        static_cast<size_t>(nBlockXSize) * nBlockYSize * m_nDTSize;
1610
0
    if (!bAllDirty)
1611
0
    {
1612
0
        for (int i = 1; i <= 3; i++)
1613
0
        {
1614
0
            m_asCachedTilesDesc[i].nRow = -1;
1615
0
            m_asCachedTilesDesc[i].nCol = -1;
1616
0
            m_asCachedTilesDesc[i].nIdxWithinTileData = -1;
1617
0
        }
1618
0
        const int nTileBands = m_eDT == GDT_UInt8 ? 4 : 1;
1619
0
        GByte *pabyTemp = m_pabyCachedTiles + nTileBands * nBandBlockSize;
1620
0
        ReadTile(nRow, nCol, pabyTemp, &bIsLossyFormat);
1621
0
        for (int i = 0; i < nBands; i++)
1622
0
        {
1623
0
            if (!m_asCachedTilesDesc[0].abBandDirty[i])
1624
0
            {
1625
0
                memcpy(m_pabyCachedTiles + i * nBandBlockSize,
1626
0
                       pabyTemp + i * nBandBlockSize, nBandBlockSize);
1627
0
            }
1628
0
        }
1629
0
    }
1630
1631
    /* Compute origin of tile in GDAL raster space */
1632
0
    int nXOff = (nCol - m_nShiftXTiles) * nBlockXSize - m_nShiftXPixelsMod;
1633
0
    int nYOff = (nRow - m_nShiftYTiles) * nBlockYSize - m_nShiftYPixelsMod;
1634
1635
    /* Assert that the tile at least intersects some of the GDAL raster space */
1636
0
    CPLAssert(nXOff > -nBlockXSize);
1637
0
    CPLAssert(nYOff > -nBlockYSize);
1638
    /* Can happen if the tile of the raster is less than the block size */
1639
0
    const int nRasterXSize = IGetRasterBand(1)->GetXSize();
1640
0
    const int nRasterYSize = IGetRasterBand(1)->GetYSize();
1641
0
    if (nXOff >= nRasterXSize || nYOff >= nRasterYSize)
1642
0
        return CE_None;
1643
1644
#ifdef DEBUG_VERBOSE
1645
    if (m_nShiftXPixelsMod == 0 && m_nShiftYPixelsMod == 0 &&
1646
        m_eDT == GDT_UInt8)
1647
    {
1648
        int nBlockXOff = nCol;
1649
        int nBlockYOff = nRow;
1650
        if (nBlockXOff * nBlockXSize <= nRasterXSize - nBlockXSize &&
1651
            nBlockYOff * nBlockYSize > nRasterYSize - nBlockYSize)
1652
        {
1653
            for (int i = 0; i < nBands; i++)
1654
            {
1655
                bool bFoundNonZero = false;
1656
                for (GPtrDiff_t y = nRasterYSize - nBlockYOff * nBlockYSize;
1657
                     y < nBlockYSize; y++)
1658
                {
1659
                    for (int x = 0; x < nBlockXSize; x++)
1660
                    {
1661
                        if (m_pabyCachedTiles[y * nBlockXSize + x +
1662
                                              i * nBandBlockSize] != 0 &&
1663
                            !bFoundNonZero)
1664
                        {
1665
                            CPLDebug("GPKG",
1666
                                     "WriteTileInternal(): Found non-zero "
1667
                                     "content in ghost part of "
1668
                                     "tile(band=%d,nBlockXOff=%d,nBlockYOff=%d,"
1669
                                     "m_nZoomLevel=%d)\n",
1670
                                     i + 1, nBlockXOff, nBlockYOff,
1671
                                     m_nZoomLevel);
1672
                            bFoundNonZero = true;
1673
                        }
1674
                    }
1675
                }
1676
            }
1677
        }
1678
    }
1679
#endif
1680
1681
    /* Validity area of tile data in intra-tile coordinate space */
1682
0
    int iXOff = 0;
1683
0
    GPtrDiff_t iYOff = 0;
1684
0
    int iXCount = nBlockXSize;
1685
0
    int iYCount = nBlockYSize;
1686
1687
0
    bool bPartialTile = false;
1688
0
    int nAlphaBand = (nBands == 2) ? 2 : (nBands == 4) ? 4 : 0;
1689
0
    if (nAlphaBand == 0)
1690
0
    {
1691
0
        if (nXOff < 0)
1692
0
        {
1693
0
            bPartialTile = true;
1694
0
            iXOff = -nXOff;
1695
0
            iXCount += nXOff;
1696
0
        }
1697
0
        if (nXOff > nRasterXSize - nBlockXSize)
1698
0
        {
1699
0
            bPartialTile = true;
1700
0
            iXCount -= static_cast<int>(static_cast<GIntBig>(nXOff) +
1701
0
                                        nBlockXSize - nRasterXSize);
1702
0
        }
1703
0
        if (nYOff < 0)
1704
0
        {
1705
0
            bPartialTile = true;
1706
0
            iYOff = -nYOff;
1707
0
            iYCount += nYOff;
1708
0
        }
1709
0
        if (nYOff > nRasterYSize - nBlockYSize)
1710
0
        {
1711
0
            bPartialTile = true;
1712
0
            iYCount -= static_cast<int>(static_cast<GIntBig>(nYOff) +
1713
0
                                        nBlockYSize - nRasterYSize);
1714
0
        }
1715
0
        CPLAssert(iXOff >= 0);
1716
0
        CPLAssert(iYOff >= 0);
1717
0
        CPLAssert(iXCount > 0);
1718
0
        CPLAssert(iYCount > 0);
1719
0
        CPLAssert(iXOff + iXCount <= nBlockXSize);
1720
0
        CPLAssert(iYOff + iYCount <= nBlockYSize);
1721
0
    }
1722
1723
0
    m_asCachedTilesDesc[0].nRow = -1;
1724
0
    m_asCachedTilesDesc[0].nCol = -1;
1725
0
    m_asCachedTilesDesc[0].nIdxWithinTileData = -1;
1726
0
    m_asCachedTilesDesc[0].abBandDirty[0] = false;
1727
0
    m_asCachedTilesDesc[0].abBandDirty[1] = false;
1728
0
    m_asCachedTilesDesc[0].abBandDirty[2] = false;
1729
0
    m_asCachedTilesDesc[0].abBandDirty[3] = false;
1730
1731
0
    CPLErr eErr = CE_Failure;
1732
1733
0
    int bHasNoData = FALSE;
1734
0
    double dfNoDataValue = IGetRasterBand(1)->GetNoDataValue(&bHasNoData);
1735
0
    const bool bHasNanNoData = bHasNoData && std::isnan(dfNoDataValue);
1736
1737
0
    bool bAllOpaque = true;
1738
    // Detect fully transparent tiles, but only if all bands are dirty (that is
1739
    // the user wrote content into them) !
1740
0
    if (bAllDirty && m_eDT == GDT_UInt8 && m_poCT == nullptr && nAlphaBand != 0)
1741
0
    {
1742
0
        GByte byFirstAlphaVal =
1743
0
            m_pabyCachedTiles[(nAlphaBand - 1) * nBlockXSize * nBlockYSize];
1744
0
        GPtrDiff_t i = 1;
1745
0
        for (; i < static_cast<GPtrDiff_t>(nBlockXSize) * nBlockYSize; i++)
1746
0
        {
1747
0
            if (m_pabyCachedTiles[static_cast<GPtrDiff_t>(nAlphaBand - 1) *
1748
0
                                      nBlockXSize * nBlockYSize +
1749
0
                                  i] != byFirstAlphaVal)
1750
0
                break;
1751
0
        }
1752
0
        if (i == static_cast<GPtrDiff_t>(nBlockXSize) * nBlockYSize)
1753
0
        {
1754
            // If tile is fully transparent, don't serialize it and remove it if
1755
            // it exists
1756
0
            if (byFirstAlphaVal == 0)
1757
0
            {
1758
0
                DeleteTile(nRow, nCol);
1759
1760
0
                return CE_None;
1761
0
            }
1762
0
            bAllOpaque = (byFirstAlphaVal == 255);
1763
0
        }
1764
0
        else
1765
0
            bAllOpaque = false;
1766
0
    }
1767
0
    else if (bAllDirty && m_eDT == GDT_UInt8 && m_poCT == nullptr &&
1768
0
             (!bHasNoData || dfNoDataValue == 0.0))
1769
0
    {
1770
0
        bool bAllEmpty = true;
1771
0
        const auto nPixels =
1772
0
            static_cast<GPtrDiff_t>(nBlockXSize) * nBlockYSize * nBands;
1773
0
        for (GPtrDiff_t i = 0; i < nPixels; i++)
1774
0
        {
1775
0
            if (m_pabyCachedTiles[i] != 0)
1776
0
            {
1777
0
                bAllEmpty = false;
1778
0
                break;
1779
0
            }
1780
0
        }
1781
0
        if (bAllEmpty)
1782
0
        {
1783
            // If tile is fully transparent, don't serialize it and remove it if
1784
            // it exists
1785
0
            DeleteTile(nRow, nCol);
1786
1787
0
            return CE_None;
1788
0
        }
1789
0
    }
1790
0
    else if (bAllDirty && m_eDT == GDT_Float32)
1791
0
    {
1792
0
        const float *pSrc = reinterpret_cast<float *>(m_pabyCachedTiles);
1793
0
        GPtrDiff_t i;
1794
0
        const float fNoDataValueOrZero =
1795
0
            bHasNoData ? static_cast<float>(dfNoDataValue) : 0.0f;
1796
0
        for (i = 0; i < static_cast<GPtrDiff_t>(nBlockXSize) * nBlockYSize; i++)
1797
0
        {
1798
0
            const float fVal = pSrc[i];
1799
0
            if (bHasNanNoData)
1800
0
            {
1801
0
                if (std::isnan(fVal))
1802
0
                    continue;
1803
0
            }
1804
0
            else if (fVal == fNoDataValueOrZero)
1805
0
            {
1806
0
                continue;
1807
0
            }
1808
0
            break;
1809
0
        }
1810
0
        if (i == static_cast<GPtrDiff_t>(nBlockXSize) * nBlockYSize)
1811
0
        {
1812
            // If tile is fully transparent, don't serialize it and remove it if
1813
            // it exists
1814
0
            DeleteTile(nRow, nCol);
1815
1816
0
            return CE_None;
1817
0
        }
1818
0
    }
1819
1820
0
    if (bIsLossyFormat)
1821
0
    {
1822
0
        CPLDebug("GPKG",
1823
0
                 "Had to read tile (row=%d,col=%d) at zoom_level=%d, "
1824
0
                 "stored in a lossy format, before rewriting it, causing "
1825
0
                 "potential extra quality loss",
1826
0
                 nRow, nCol, m_nZoomLevel);
1827
0
    }
1828
1829
0
    const CPLString osMemFileName(
1830
0
        VSIMemGenerateHiddenFilename("gpkg_write_tile"));
1831
0
    const char *pszDriverName = "PNG";
1832
0
    CPL_IGNORE_RET_VAL(pszDriverName);  // Make CSA happy
1833
0
    bool bTileDriverSupports1Band = false;
1834
0
    bool bTileDriverSupports2Bands = false;
1835
0
    bool bTileDriverSupports4Bands = false;
1836
0
    bool bTileDriverSupportsCT = false;
1837
1838
0
    if (nBands == 1 && m_eDT == GDT_UInt8)
1839
0
        IGetRasterBand(1)->GetColorTable();
1840
1841
0
    GDALDataType eTileDT = GDT_UInt8;
1842
    // If not all bands are dirty, then (temporarily) use a lossless format
1843
0
    if (m_eTF == GPKG_TF_PNG_JPEG || (!bAllDirty && m_eTF == GPKG_TF_JPEG))
1844
0
    {
1845
0
        bTileDriverSupports1Band = true;
1846
0
        if (bPartialTile || !bAllDirty || (nBands == 2 && !bAllOpaque) ||
1847
0
            (nBands == 4 && !bAllOpaque) || m_poCT != nullptr)
1848
0
        {
1849
0
            pszDriverName = "PNG";
1850
0
            bTileDriverSupports2Bands = m_bPNGSupports2Bands;
1851
0
            bTileDriverSupports4Bands = true;
1852
0
            bTileDriverSupportsCT = m_bPNGSupportsCT;
1853
0
        }
1854
0
        else
1855
0
            pszDriverName = "JPEG";
1856
0
    }
1857
0
    else if (m_eTF == GPKG_TF_PNG || m_eTF == GPKG_TF_PNG8)
1858
0
    {
1859
0
        pszDriverName = "PNG";
1860
0
        bTileDriverSupports1Band = true;
1861
0
        bTileDriverSupports2Bands = m_bPNGSupports2Bands;
1862
0
        bTileDriverSupports4Bands = true;
1863
0
        bTileDriverSupportsCT = m_bPNGSupportsCT;
1864
0
    }
1865
0
    else if (m_eTF == GPKG_TF_JPEG)
1866
0
    {
1867
0
        pszDriverName = "JPEG";
1868
0
        bTileDriverSupports1Band = true;
1869
0
    }
1870
0
    else if (m_eTF == GPKG_TF_WEBP)
1871
0
    {
1872
0
        pszDriverName = "WEBP";
1873
0
        bTileDriverSupports4Bands = WEBPSupports4Bands();
1874
0
    }
1875
0
    else if (m_eTF == GPKG_TF_PNG_16BIT)
1876
0
    {
1877
0
        pszDriverName = "PNG";
1878
0
        eTileDT = GDT_UInt16;
1879
0
        bTileDriverSupports1Band = true;
1880
0
    }
1881
0
    else if (m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
1882
0
    {
1883
0
        pszDriverName = "GTiff";
1884
0
        eTileDT = GDT_Float32;
1885
0
        bTileDriverSupports1Band = true;
1886
0
    }
1887
0
    else
1888
0
    {
1889
0
        CPLAssert(false);
1890
0
    }
1891
1892
0
    GDALDriver *l_poDriver =
1893
0
        GDALDriver::FromHandle(GDALGetDriverByName(pszDriverName));
1894
0
    if (l_poDriver != nullptr)
1895
0
    {
1896
0
        auto poMEMDS = MEMDataset::Create("", nBlockXSize, nBlockYSize, 0,
1897
0
                                          eTileDT, nullptr);
1898
0
        int nTileBands = nBands;
1899
0
        if (bPartialTile && nBands == 1 && m_poCT == nullptr &&
1900
0
            bTileDriverSupports2Bands)
1901
0
            nTileBands = 2;
1902
0
        else if (bPartialTile && bTileDriverSupports4Bands)
1903
0
            nTileBands = 4;
1904
        // only use (somewhat lossy) PNG8 if all bands are dirty
1905
0
        else if (bAllDirty && m_eTF == GPKG_TF_PNG8 && nBands >= 3 &&
1906
0
                 bAllOpaque && !bPartialTile)
1907
0
            nTileBands = 1;
1908
0
        else if (nBands == 2)
1909
0
        {
1910
0
            if (bAllOpaque)
1911
0
            {
1912
0
                if (bTileDriverSupports2Bands)
1913
0
                    nTileBands = 1;
1914
0
                else
1915
0
                    nTileBands = 3;
1916
0
            }
1917
0
            else if (!bTileDriverSupports2Bands)
1918
0
            {
1919
0
                if (bTileDriverSupports4Bands)
1920
0
                    nTileBands = 4;
1921
0
                else
1922
0
                    nTileBands = 3;
1923
0
            }
1924
0
        }
1925
0
        else if (nBands == 4 && (bAllOpaque || !bTileDriverSupports4Bands))
1926
0
            nTileBands = 3;
1927
0
        else if (nBands == 1 && m_poCT != nullptr && !bTileDriverSupportsCT)
1928
0
        {
1929
0
            nTileBands = 3;
1930
0
            if (bTileDriverSupports4Bands)
1931
0
            {
1932
0
                for (int i = 0; i < m_poCT->GetColorEntryCount(); i++)
1933
0
                {
1934
0
                    const GDALColorEntry *psEntry = m_poCT->GetColorEntry(i);
1935
0
                    if (psEntry->c4 == 0)
1936
0
                    {
1937
0
                        nTileBands = 4;
1938
0
                        break;
1939
0
                    }
1940
0
                }
1941
0
            }
1942
0
        }
1943
0
        else if (nBands == 1 && m_poCT == nullptr && !bTileDriverSupports1Band)
1944
0
            nTileBands = 3;
1945
1946
0
        if (bPartialTile && (nTileBands == 2 || nTileBands == 4))
1947
0
        {
1948
0
            int nTargetAlphaBand = nTileBands;
1949
0
            memset(m_pabyCachedTiles + (nTargetAlphaBand - 1) * nBandBlockSize,
1950
0
                   0, nBandBlockSize);
1951
0
            for (GPtrDiff_t iY = iYOff; iY < iYOff + iYCount; iY++)
1952
0
            {
1953
0
                memset(m_pabyCachedTiles +
1954
0
                           (static_cast<size_t>(nTargetAlphaBand - 1) *
1955
0
                                nBlockYSize +
1956
0
                            iY) *
1957
0
                               nBlockXSize +
1958
0
                           iXOff,
1959
0
                       255, iXCount);
1960
0
            }
1961
0
        }
1962
1963
0
        GUInt16 *pTempTileBuffer = nullptr;
1964
0
        GPtrDiff_t nValidPixels = 0;
1965
0
        double dfTileMin = 0.0;
1966
0
        double dfTileMax = 0.0;
1967
0
        double dfTileMean = 0.0;
1968
0
        double dfTileStdDev = 0.0;
1969
0
        double dfTileOffset = 0.0;
1970
0
        double dfTileScale = 1.0;
1971
0
        if (m_eTF == GPKG_TF_PNG_16BIT)
1972
0
        {
1973
0
            pTempTileBuffer = static_cast<GUInt16 *>(
1974
0
                VSI_MALLOC3_VERBOSE(2, nBlockXSize, nBlockYSize));
1975
1976
0
            if (m_eDT == GDT_Int16)
1977
0
            {
1978
0
                ProcessInt16UInt16Tile<GInt16>(
1979
0
                    m_pabyCachedTiles,
1980
0
                    static_cast<GPtrDiff_t>(nBlockXSize) * nBlockYSize, true,
1981
0
                    CPL_TO_BOOL(bHasNoData), dfNoDataValue, m_usGPKGNull,
1982
0
                    m_dfOffset, m_dfScale, pTempTileBuffer, dfTileOffset,
1983
0
                    dfTileScale, dfTileMin, dfTileMax, dfTileMean, dfTileStdDev,
1984
0
                    nValidPixels);
1985
0
            }
1986
0
            else if (m_eDT == GDT_UInt16)
1987
0
            {
1988
0
                ProcessInt16UInt16Tile<GUInt16>(
1989
0
                    m_pabyCachedTiles,
1990
0
                    static_cast<GPtrDiff_t>(nBlockXSize) * nBlockYSize, false,
1991
0
                    CPL_TO_BOOL(bHasNoData), dfNoDataValue, m_usGPKGNull,
1992
0
                    m_dfOffset, m_dfScale, pTempTileBuffer, dfTileOffset,
1993
0
                    dfTileScale, dfTileMin, dfTileMax, dfTileMean, dfTileStdDev,
1994
0
                    nValidPixels);
1995
0
            }
1996
0
            else if (m_eDT == GDT_Float32)
1997
0
            {
1998
0
                const float *pSrc =
1999
0
                    reinterpret_cast<float *>(m_pabyCachedTiles);
2000
0
                float fMin = 0.0f;
2001
0
                float fMax = 0.0f;
2002
0
                double dfM2 = 0.0;
2003
0
                for (GPtrDiff_t i = 0;
2004
0
                     i < static_cast<GPtrDiff_t>(nBlockXSize) * nBlockYSize;
2005
0
                     i++)
2006
0
                {
2007
0
                    const float fVal = pSrc[i];
2008
0
                    if (bHasNanNoData)
2009
0
                    {
2010
0
                        if (std::isnan(fVal))
2011
0
                            continue;
2012
0
                    }
2013
0
                    else if (bHasNoData &&
2014
0
                             fVal == static_cast<float>(dfNoDataValue))
2015
0
                    {
2016
0
                        continue;
2017
0
                    }
2018
0
                    if (std::isinf(fVal))
2019
0
                        continue;
2020
2021
0
                    if (nValidPixels == 0)
2022
0
                    {
2023
0
                        fMin = fVal;
2024
0
                        fMax = fVal;
2025
0
                    }
2026
0
                    else
2027
0
                    {
2028
0
                        fMin = std::min(fMin, fVal);
2029
0
                        fMax = std::max(fMax, fVal);
2030
0
                    }
2031
0
                    nValidPixels++;
2032
0
                    const double dfDelta = fVal - dfTileMean;
2033
0
                    dfTileMean += dfDelta / nValidPixels;
2034
0
                    dfM2 += dfDelta * (fVal - dfTileMean);
2035
0
                }
2036
0
                dfTileMin = fMin;
2037
0
                dfTileMax = fMax;
2038
0
                if (nValidPixels)
2039
0
                    dfTileStdDev = sqrt(dfM2 / nValidPixels);
2040
2041
0
                double dfGlobalMin = (fMin - m_dfOffset) / m_dfScale;
2042
0
                double dfGlobalMax = (fMax - m_dfOffset) / m_dfScale;
2043
0
                if (dfGlobalMax > dfGlobalMin)
2044
0
                {
2045
0
                    if (bHasNoData && m_usGPKGNull == 65535 &&
2046
0
                        dfGlobalMax - dfGlobalMin >= 65534.0)
2047
0
                    {
2048
0
                        dfTileOffset = dfGlobalMin;
2049
0
                        dfTileScale = (dfGlobalMax - dfGlobalMin) / 65534.0;
2050
0
                    }
2051
0
                    else if (bHasNoData && m_usGPKGNull == 0 &&
2052
0
                             (dfNoDataValue - m_dfOffset) / m_dfScale != 0)
2053
0
                    {
2054
0
                        dfTileOffset =
2055
0
                            (65535.0 * dfGlobalMin - dfGlobalMax) / 65534.0;
2056
0
                        dfTileScale = dfGlobalMin - dfTileOffset;
2057
0
                    }
2058
0
                    else
2059
0
                    {
2060
0
                        dfTileOffset = dfGlobalMin;
2061
0
                        dfTileScale = (dfGlobalMax - dfGlobalMin) / 65535.0;
2062
0
                    }
2063
0
                }
2064
0
                else
2065
0
                {
2066
0
                    dfTileOffset = dfGlobalMin;
2067
0
                }
2068
2069
0
                for (GPtrDiff_t i = 0;
2070
0
                     i < static_cast<GPtrDiff_t>(nBlockXSize) * nBlockYSize;
2071
0
                     i++)
2072
0
                {
2073
0
                    const float fVal = pSrc[i];
2074
0
                    if (bHasNanNoData)
2075
0
                    {
2076
0
                        if (std::isnan(fVal))
2077
0
                        {
2078
0
                            pTempTileBuffer[i] = m_usGPKGNull;
2079
0
                            continue;
2080
0
                        }
2081
0
                    }
2082
0
                    else if (bHasNoData)
2083
0
                    {
2084
0
                        if (fVal == static_cast<float>(dfNoDataValue))
2085
0
                        {
2086
0
                            pTempTileBuffer[i] = m_usGPKGNull;
2087
0
                            continue;
2088
0
                        }
2089
0
                    }
2090
0
                    double dfVal =
2091
0
                        std::isfinite(fVal)
2092
0
                            ? ((fVal - m_dfOffset) / m_dfScale - dfTileOffset) /
2093
0
                                  dfTileScale
2094
0
                        : (fVal > 0) ? 65535
2095
0
                                     : 0;
2096
0
                    CPLAssert(dfVal >= 0.0 && dfVal < 65535.5);
2097
0
                    pTempTileBuffer[i] = static_cast<GUInt16>(dfVal + 0.5);
2098
0
                    if (bHasNoData && pTempTileBuffer[i] == m_usGPKGNull)
2099
0
                    {
2100
0
                        if (m_usGPKGNull > 0)
2101
0
                            pTempTileBuffer[i]--;
2102
0
                        else
2103
0
                            pTempTileBuffer[i]++;
2104
0
                    }
2105
0
                }
2106
0
            }
2107
2108
0
            auto hBand = MEMCreateRasterBandEx(
2109
0
                poMEMDS, 1, reinterpret_cast<GByte *>(pTempTileBuffer),
2110
0
                GDT_UInt16, 0, 0, false);
2111
0
            poMEMDS->AddMEMBand(hBand);
2112
0
        }
2113
0
        else if (m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
2114
0
        {
2115
0
            const float *pSrc = reinterpret_cast<float *>(m_pabyCachedTiles);
2116
0
            float fMin = 0.0f;
2117
0
            float fMax = 0.0f;
2118
0
            double dfM2 = 0.0;
2119
0
            for (GPtrDiff_t i = 0;
2120
0
                 i < static_cast<GPtrDiff_t>(nBlockXSize) * nBlockYSize; i++)
2121
0
            {
2122
0
                const float fVal = pSrc[i];
2123
0
                if (bHasNanNoData)
2124
0
                {
2125
0
                    if (std::isnan(fVal))
2126
0
                        continue;
2127
0
                }
2128
0
                else if (bHasNoData &&
2129
0
                         fVal == static_cast<float>(dfNoDataValue))
2130
0
                {
2131
0
                    continue;
2132
0
                }
2133
2134
0
                if (nValidPixels == 0)
2135
0
                {
2136
0
                    fMin = fVal;
2137
0
                    fMax = fVal;
2138
0
                }
2139
0
                else
2140
0
                {
2141
0
                    fMin = std::min(fMin, fVal);
2142
0
                    fMax = std::max(fMax, fVal);
2143
0
                }
2144
0
                nValidPixels++;
2145
0
                const double dfDelta = fVal - dfTileMean;
2146
0
                dfTileMean += dfDelta / nValidPixels;
2147
0
                dfM2 += dfDelta * (fVal - dfTileMean);
2148
0
            }
2149
0
            dfTileMin = fMin;
2150
0
            dfTileMax = fMax;
2151
0
            if (nValidPixels)
2152
0
                dfTileStdDev = sqrt(dfM2 / nValidPixels);
2153
2154
0
            auto hBand = MEMCreateRasterBandEx(poMEMDS, 1, m_pabyCachedTiles,
2155
0
                                               GDT_Float32, 0, 0, false);
2156
0
            poMEMDS->AddMEMBand(hBand);
2157
0
        }
2158
0
        else
2159
0
        {
2160
0
            CPLAssert(m_eDT == GDT_UInt8);
2161
0
            for (int i = 0; i < nTileBands; i++)
2162
0
            {
2163
0
                int iSrc = i;
2164
0
                if (nBands == 1 && m_poCT == nullptr && nTileBands == 3)
2165
0
                    iSrc = 0;
2166
0
                else if (nBands == 1 && m_poCT == nullptr && bPartialTile &&
2167
0
                         nTileBands == 4)
2168
0
                    iSrc = (i < 3) ? 0 : 3;
2169
0
                else if (nBands == 2 && nTileBands >= 3)
2170
0
                    iSrc = (i < 3) ? 0 : 1;
2171
2172
0
                auto hBand = MEMCreateRasterBandEx(
2173
0
                    poMEMDS, i + 1,
2174
0
                    m_pabyCachedTiles + iSrc * nBlockXSize * nBlockYSize,
2175
0
                    GDT_UInt8, 0, 0, false);
2176
0
                poMEMDS->AddMEMBand(hBand);
2177
2178
0
                if (i == 0 && nTileBands == 1 && m_poCT != nullptr)
2179
0
                    poMEMDS->GetRasterBand(1)->SetColorTable(m_poCT);
2180
0
            }
2181
0
        }
2182
2183
0
        if ((m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT) &&
2184
0
            nValidPixels == 0)
2185
0
        {
2186
            // If tile is fully transparent, don't serialize it and remove
2187
            // it if it exists.
2188
0
            GIntBig nId = GetTileId(nRow, nCol);
2189
0
            if (nId > 0)
2190
0
            {
2191
0
                DeleteTile(nRow, nCol);
2192
2193
0
                DeleteFromGriddedTileAncillary(nId);
2194
0
            }
2195
2196
0
            CPLFree(pTempTileBuffer);
2197
0
            delete poMEMDS;
2198
0
            return CE_None;
2199
0
        }
2200
2201
0
        if (m_eTF == GPKG_TF_PNG8 && nTileBands == 1 && nBands >= 3)
2202
0
        {
2203
0
            CPLAssert(bAllDirty);
2204
2205
0
            auto poMEM_RGB_DS = MEMDataset::Create("", nBlockXSize, nBlockYSize,
2206
0
                                                   0, GDT_UInt8, nullptr);
2207
0
            for (int i = 0; i < 3; i++)
2208
0
            {
2209
0
                auto hBand = MEMCreateRasterBandEx(
2210
0
                    poMEMDS, i + 1, m_pabyCachedTiles + i * nBandBlockSize,
2211
0
                    GDT_UInt8, 0, 0, false);
2212
0
                poMEM_RGB_DS->AddMEMBand(hBand);
2213
0
            }
2214
2215
0
            if (m_pabyHugeColorArray == nullptr)
2216
0
            {
2217
0
                if (nBlockXSize <= 65536 / nBlockYSize)
2218
0
                    m_pabyHugeColorArray =
2219
0
                        VSIMalloc(MEDIAN_CUT_AND_DITHER_BUFFER_SIZE_65536);
2220
0
                else
2221
0
                    m_pabyHugeColorArray =
2222
0
                        VSIMalloc2(256 * 256 * 256, sizeof(GUInt32));
2223
0
            }
2224
2225
0
            GDALColorTable *poCT = new GDALColorTable();
2226
0
            GDALComputeMedianCutPCTInternal(
2227
0
                poMEM_RGB_DS->GetRasterBand(1), poMEM_RGB_DS->GetRasterBand(2),
2228
0
                poMEM_RGB_DS->GetRasterBand(3),
2229
                /*NULL, NULL, NULL,*/
2230
0
                m_pabyCachedTiles, m_pabyCachedTiles + nBandBlockSize,
2231
0
                m_pabyCachedTiles + 2 * nBandBlockSize, nullptr,
2232
0
                256, /* max colors */
2233
0
                8,   /* bit depth */
2234
0
                static_cast<GUInt32 *>(
2235
0
                    m_pabyHugeColorArray), /* preallocated histogram */
2236
0
                poCT, nullptr, nullptr);
2237
2238
0
            GDALDitherRGB2PCTInternal(
2239
0
                poMEM_RGB_DS->GetRasterBand(1), poMEM_RGB_DS->GetRasterBand(2),
2240
0
                poMEM_RGB_DS->GetRasterBand(3), poMEMDS->GetRasterBand(1), poCT,
2241
0
                8, /* bit depth */
2242
0
                static_cast<GInt16 *>(
2243
0
                    m_pabyHugeColorArray), /* pasDynamicColorMap */
2244
0
                m_bDither, nullptr, nullptr);
2245
0
            poMEMDS->GetRasterBand(1)->SetColorTable(poCT);
2246
0
            delete poCT;
2247
0
            GDALClose(poMEM_RGB_DS);
2248
0
        }
2249
0
        else if (nBands == 1 && m_poCT != nullptr && nTileBands > 1)
2250
0
        {
2251
0
            GByte abyCT[4 * 256];
2252
0
            const int nEntries = std::min(256, m_poCT->GetColorEntryCount());
2253
0
            for (int i = 0; i < nEntries; i++)
2254
0
            {
2255
0
                const GDALColorEntry *psEntry = m_poCT->GetColorEntry(i);
2256
0
                abyCT[4 * i] = static_cast<GByte>(psEntry->c1);
2257
0
                abyCT[4 * i + 1] = static_cast<GByte>(psEntry->c2);
2258
0
                abyCT[4 * i + 2] = static_cast<GByte>(psEntry->c3);
2259
0
                abyCT[4 * i + 3] = static_cast<GByte>(psEntry->c4);
2260
0
            }
2261
0
            for (int i = nEntries; i < 256; i++)
2262
0
            {
2263
0
                abyCT[4 * i] = 0;
2264
0
                abyCT[4 * i + 1] = 0;
2265
0
                abyCT[4 * i + 2] = 0;
2266
0
                abyCT[4 * i + 3] = 0;
2267
0
            }
2268
0
            if (iYOff > 0)
2269
0
            {
2270
0
                memset(m_pabyCachedTiles + 0 * nBandBlockSize, 0,
2271
0
                       nBlockXSize * iYOff);
2272
0
                memset(m_pabyCachedTiles + 1 * nBandBlockSize, 0,
2273
0
                       nBlockXSize * iYOff);
2274
0
                memset(m_pabyCachedTiles + 2 * nBandBlockSize, 0,
2275
0
                       nBlockXSize * iYOff);
2276
0
                memset(m_pabyCachedTiles + 3 * nBandBlockSize, 0,
2277
0
                       nBlockXSize * iYOff);
2278
0
            }
2279
0
            for (GPtrDiff_t iY = iYOff; iY < iYOff + iYCount; iY++)
2280
0
            {
2281
0
                if (iXOff > 0)
2282
0
                {
2283
0
                    const GPtrDiff_t i = iY * nBlockXSize;
2284
0
                    memset(m_pabyCachedTiles + 0 * nBandBlockSize + i, 0,
2285
0
                           iXOff);
2286
0
                    memset(m_pabyCachedTiles + 1 * nBandBlockSize + i, 0,
2287
0
                           iXOff);
2288
0
                    memset(m_pabyCachedTiles + 2 * nBandBlockSize + i, 0,
2289
0
                           iXOff);
2290
0
                    memset(m_pabyCachedTiles + 3 * nBandBlockSize + i, 0,
2291
0
                           iXOff);
2292
0
                }
2293
0
                for (int iX = iXOff; iX < iXOff + iXCount; iX++)
2294
0
                {
2295
0
                    const GPtrDiff_t i = iY * nBlockXSize + iX;
2296
0
                    GByte byVal = m_pabyCachedTiles[i];
2297
0
                    m_pabyCachedTiles[i] = abyCT[4 * byVal];
2298
0
                    m_pabyCachedTiles[i + 1 * nBandBlockSize] =
2299
0
                        abyCT[4 * byVal + 1];
2300
0
                    m_pabyCachedTiles[i + 2 * nBandBlockSize] =
2301
0
                        abyCT[4 * byVal + 2];
2302
0
                    m_pabyCachedTiles[i + 3 * nBandBlockSize] =
2303
0
                        abyCT[4 * byVal + 3];
2304
0
                }
2305
0
                if (iXOff + iXCount < nBlockXSize)
2306
0
                {
2307
0
                    const GPtrDiff_t i = iY * nBlockXSize + iXOff + iXCount;
2308
0
                    memset(m_pabyCachedTiles + 0 * nBandBlockSize + i, 0,
2309
0
                           nBlockXSize - (iXOff + iXCount));
2310
0
                    memset(m_pabyCachedTiles + 1 * nBandBlockSize + i, 0,
2311
0
                           nBlockXSize - (iXOff + iXCount));
2312
0
                    memset(m_pabyCachedTiles + 2 * nBandBlockSize + i, 0,
2313
0
                           nBlockXSize - (iXOff + iXCount));
2314
0
                    memset(m_pabyCachedTiles + 3 * nBandBlockSize + i, 0,
2315
0
                           nBlockXSize - (iXOff + iXCount));
2316
0
                }
2317
0
            }
2318
0
            if (iYOff + iYCount < nBlockYSize)
2319
0
            {
2320
0
                const GPtrDiff_t i = (iYOff + iYCount) * nBlockXSize;
2321
0
                memset(m_pabyCachedTiles + 0 * nBandBlockSize + i, 0,
2322
0
                       nBlockXSize * (nBlockYSize - (iYOff + iYCount)));
2323
0
                memset(m_pabyCachedTiles + 1 * nBandBlockSize + i, 0,
2324
0
                       nBlockXSize * (nBlockYSize - (iYOff + iYCount)));
2325
0
                memset(m_pabyCachedTiles + 2 * nBandBlockSize + i, 0,
2326
0
                       nBlockXSize * (nBlockYSize - (iYOff + iYCount)));
2327
0
                memset(m_pabyCachedTiles + 3 * nBandBlockSize + i, 0,
2328
0
                       nBlockXSize * (nBlockYSize - (iYOff + iYCount)));
2329
0
            }
2330
0
        }
2331
2332
0
        char **papszDriverOptions =
2333
0
            CSLSetNameValue(nullptr, "_INTERNAL_DATASET", "YES");
2334
0
        if (EQUAL(pszDriverName, "JPEG") || EQUAL(pszDriverName, "WEBP"))
2335
0
        {
2336
            // If not all bands are dirty, then use lossless WEBP
2337
0
            if (!bAllDirty && EQUAL(pszDriverName, "WEBP"))
2338
0
            {
2339
0
                papszDriverOptions =
2340
0
                    CSLSetNameValue(papszDriverOptions, "LOSSLESS", "YES");
2341
0
            }
2342
0
            else
2343
0
            {
2344
0
                papszDriverOptions =
2345
0
                    CSLSetNameValue(papszDriverOptions, "QUALITY",
2346
0
                                    CPLSPrintf("%d", m_nQuality));
2347
0
            }
2348
0
        }
2349
0
        else if (EQUAL(pszDriverName, "PNG"))
2350
0
        {
2351
0
            papszDriverOptions = CSLSetNameValue(papszDriverOptions, "ZLEVEL",
2352
0
                                                 CPLSPrintf("%d", m_nZLevel));
2353
0
        }
2354
0
        else if (EQUAL(pszDriverName, "GTiff"))
2355
0
        {
2356
0
            papszDriverOptions =
2357
0
                CSLSetNameValue(papszDriverOptions, "COMPRESS", "LZW");
2358
0
            if (nBlockXSize * nBlockYSize <= 512 * 512)
2359
0
            {
2360
                // If tile is not too big, create it as single-strip TIFF
2361
0
                papszDriverOptions =
2362
0
                    CSLSetNameValue(papszDriverOptions, "BLOCKYSIZE",
2363
0
                                    CPLSPrintf("%d", nBlockYSize));
2364
0
            }
2365
0
        }
2366
#ifdef DEBUG
2367
        VSIStatBufL sStat;
2368
        CPLAssert(VSIStatL(osMemFileName, &sStat) != 0);
2369
#endif
2370
0
        GDALDataset *poOutDS =
2371
0
            l_poDriver->CreateCopy(osMemFileName, poMEMDS, FALSE,
2372
0
                                   papszDriverOptions, nullptr, nullptr);
2373
0
        CSLDestroy(papszDriverOptions);
2374
0
        CPLFree(pTempTileBuffer);
2375
2376
0
        if (poOutDS)
2377
0
        {
2378
0
            GDALClose(poOutDS);
2379
0
            vsi_l_offset nBlobSize = 0;
2380
0
            GByte *pabyBlob =
2381
0
                VSIGetMemFileBuffer(osMemFileName, &nBlobSize, TRUE);
2382
2383
            /* Create or commit and recreate transaction */
2384
0
            GDALGPKGMBTilesLikePseudoDataset *poMainDS =
2385
0
                m_poParentDS ? m_poParentDS : this;
2386
0
            if (poMainDS->m_nTileInsertionCount == 0)
2387
0
            {
2388
0
                poMainDS->IStartTransaction();
2389
0
            }
2390
0
            else if (poMainDS->m_nTileInsertionCount == 1000)
2391
0
            {
2392
0
                if (poMainDS->ICommitTransaction() != OGRERR_NONE)
2393
0
                {
2394
0
                    poMainDS->m_nTileInsertionCount = -1;
2395
0
                    CPLFree(pabyBlob);
2396
0
                    VSIUnlink(osMemFileName);
2397
0
                    delete poMEMDS;
2398
0
                    return CE_Failure;
2399
0
                }
2400
0
                poMainDS->IStartTransaction();
2401
0
                poMainDS->m_nTileInsertionCount = 0;
2402
0
            }
2403
0
            poMainDS->m_nTileInsertionCount++;
2404
2405
0
            char *pszSQL =
2406
0
                sqlite3_mprintf("INSERT OR REPLACE INTO \"%w\" "
2407
0
                                "(zoom_level, tile_row, tile_column, "
2408
0
                                "tile_data) VALUES (%d, %d, %d, ?)",
2409
0
                                m_osRasterTable.c_str(), m_nZoomLevel,
2410
0
                                GetRowFromIntoTopConvention(nRow), nCol);
2411
#ifdef DEBUG_VERBOSE
2412
            CPLDebug("GPKG", "%s", pszSQL);
2413
#endif
2414
0
            sqlite3_stmt *hStmt = nullptr;
2415
0
            int rc = SQLPrepareWithError(IGetDB(), pszSQL, -1, &hStmt, nullptr);
2416
0
            if (rc != SQLITE_OK)
2417
0
            {
2418
0
                CPLFree(pabyBlob);
2419
0
            }
2420
0
            else
2421
0
            {
2422
0
                sqlite3_bind_blob(hStmt, 1, pabyBlob,
2423
0
                                  static_cast<int>(nBlobSize), CPLFree);
2424
0
                rc = sqlite3_step(hStmt);
2425
0
                if (rc == SQLITE_DONE)
2426
0
                    eErr = CE_None;
2427
0
                else
2428
0
                {
2429
0
                    CPLError(CE_Failure, CPLE_AppDefined,
2430
0
                             "Failure when inserting tile (row=%d,col=%d) at "
2431
0
                             "zoom_level=%d : %s",
2432
0
                             GetRowFromIntoTopConvention(nRow), nCol,
2433
0
                             m_nZoomLevel, sqlite3_errmsg(IGetDB()));
2434
0
                }
2435
0
            }
2436
0
            sqlite3_finalize(hStmt);
2437
0
            sqlite3_free(pszSQL);
2438
2439
0
            if (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
2440
0
            {
2441
0
                GIntBig nTileId = GetTileId(nRow, nCol);
2442
0
                if (nTileId == 0)
2443
0
                    eErr = CE_Failure;
2444
0
                else
2445
0
                {
2446
0
                    DeleteFromGriddedTileAncillary(nTileId);
2447
2448
0
                    pszSQL = sqlite3_mprintf(
2449
0
                        "INSERT INTO gpkg_2d_gridded_tile_ancillary "
2450
0
                        "(tpudt_name, tpudt_id, scale, offset, min, max, "
2451
0
                        "mean, std_dev) VALUES "
2452
0
                        "('%q', ?, %.17g, %.17g, ?, ?, ?, ?)",
2453
0
                        m_osRasterTable.c_str(), dfTileScale, dfTileOffset);
2454
#ifdef DEBUG_VERBOSE
2455
                    CPLDebug("GPKG", "%s", pszSQL);
2456
#endif
2457
0
                    hStmt = nullptr;
2458
0
                    rc = SQLPrepareWithError(IGetDB(), pszSQL, -1, &hStmt,
2459
0
                                             nullptr);
2460
0
                    if (rc != SQLITE_OK)
2461
0
                    {
2462
0
                        eErr = CE_Failure;
2463
0
                    }
2464
0
                    else
2465
0
                    {
2466
0
                        sqlite3_bind_int64(hStmt, 1, nTileId);
2467
0
                        sqlite3_bind_double(hStmt, 2, dfTileMin);
2468
0
                        sqlite3_bind_double(hStmt, 3, dfTileMax);
2469
0
                        sqlite3_bind_double(hStmt, 4, dfTileMean);
2470
0
                        sqlite3_bind_double(hStmt, 5, dfTileStdDev);
2471
0
                        rc = sqlite3_step(hStmt);
2472
0
                        if (rc == SQLITE_DONE)
2473
0
                        {
2474
0
                            eErr = CE_None;
2475
0
                        }
2476
0
                        else
2477
0
                        {
2478
0
                            CPLError(CE_Failure, CPLE_AppDefined,
2479
0
                                     "Cannot insert into "
2480
0
                                     "gpkg_2d_gridded_tile_ancillary");
2481
0
                            eErr = CE_Failure;
2482
0
                        }
2483
0
                    }
2484
0
                    sqlite3_finalize(hStmt);
2485
0
                    sqlite3_free(pszSQL);
2486
0
                }
2487
0
            }
2488
0
        }
2489
2490
0
        VSIUnlink(osMemFileName);
2491
0
        delete poMEMDS;
2492
0
    }
2493
0
    else
2494
0
    {
2495
0
        CPLError(CE_Failure, CPLE_NotSupported, "Cannot find driver %s",
2496
0
                 pszDriverName);
2497
0
    }
2498
2499
0
    return eErr;
2500
0
}
2501
2502
/************************************************************************/
2503
/*                     FlushRemainingShiftedTiles()                     */
2504
/************************************************************************/
2505
2506
CPLErr
2507
GDALGPKGMBTilesLikePseudoDataset::FlushRemainingShiftedTiles(bool bPartialFlush)
2508
0
{
2509
0
    if (m_hTempDB == nullptr)
2510
0
        return CE_None;
2511
2512
0
    for (int i = 0; i <= 3; i++)
2513
0
    {
2514
0
        m_asCachedTilesDesc[i].nRow = -1;
2515
0
        m_asCachedTilesDesc[i].nCol = -1;
2516
0
        m_asCachedTilesDesc[i].nIdxWithinTileData = -1;
2517
0
    }
2518
2519
0
    int nBlockXSize, nBlockYSize;
2520
0
    IGetRasterBand(1)->GetBlockSize(&nBlockXSize, &nBlockYSize);
2521
0
    const int nBands = IGetRasterCount();
2522
0
    const int nRasterXSize = IGetRasterBand(1)->GetXSize();
2523
0
    const int nRasterYSize = IGetRasterBand(1)->GetYSize();
2524
0
    const int nXBlocks = DIV_ROUND_UP(nRasterXSize, nBlockXSize);
2525
0
    const int nYBlocks = DIV_ROUND_UP(nRasterYSize, nBlockYSize);
2526
2527
0
    int nPartialActiveTiles = 0;
2528
0
    if (bPartialFlush)
2529
0
    {
2530
0
        sqlite3_stmt *hStmt = nullptr;
2531
0
        CPLString osSQL;
2532
0
        osSQL.Printf("SELECT COUNT(*) FROM partial_tiles WHERE zoom_level = %d "
2533
0
                     "AND partial_flag != 0",
2534
0
                     m_nZoomLevel);
2535
0
        if (SQLPrepareWithError(m_hTempDB, osSQL.c_str(), -1, &hStmt,
2536
0
                                nullptr) == SQLITE_OK)
2537
0
        {
2538
0
            if (sqlite3_step(hStmt) == SQLITE_ROW)
2539
0
            {
2540
0
                nPartialActiveTiles = sqlite3_column_int(hStmt, 0);
2541
0
                CPLDebug("GPKG", "Active partial tiles before flush: %d",
2542
0
                         nPartialActiveTiles);
2543
0
            }
2544
0
            sqlite3_finalize(hStmt);
2545
0
        }
2546
0
    }
2547
2548
0
    CPLString osSQL = "SELECT tile_row, tile_column, partial_flag";
2549
0
    for (int nBand = 1; nBand <= nBands; nBand++)
2550
0
    {
2551
0
        osSQL += CPLSPrintf(", tile_data_band_%d", nBand);
2552
0
    }
2553
0
    osSQL += CPLSPrintf(" FROM partial_tiles WHERE "
2554
0
                        "zoom_level = %d AND partial_flag != 0",
2555
0
                        m_nZoomLevel);
2556
0
    if (bPartialFlush)
2557
0
    {
2558
0
        osSQL += " ORDER BY age";
2559
0
    }
2560
0
    const char *pszSQL = osSQL.c_str();
2561
2562
#ifdef DEBUG_VERBOSE
2563
    CPLDebug("GPKG", "%s", pszSQL);
2564
#endif
2565
0
    sqlite3_stmt *hStmt = nullptr;
2566
0
    int rc = SQLPrepareWithError(m_hTempDB, pszSQL, -1, &hStmt, nullptr);
2567
0
    if (rc != SQLITE_OK)
2568
0
    {
2569
0
        return CE_Failure;
2570
0
    }
2571
2572
0
    CPLErr eErr = CE_None;
2573
0
    bool bGotPartialTiles = false;
2574
0
    int nCountFlushedTiles = 0;
2575
0
    const size_t nBandBlockSize =
2576
0
        static_cast<size_t>(nBlockXSize) * nBlockYSize * m_nDTSize;
2577
0
    do
2578
0
    {
2579
0
        rc = sqlite3_step(hStmt);
2580
0
        if (rc == SQLITE_ROW)
2581
0
        {
2582
0
            bGotPartialTiles = true;
2583
2584
0
            int nRow = sqlite3_column_int(hStmt, 0);
2585
0
            int nCol = sqlite3_column_int(hStmt, 1);
2586
0
            int nPartialFlags = sqlite3_column_int(hStmt, 2);
2587
2588
0
            if (bPartialFlush)
2589
0
            {
2590
                // This method assumes that there are no dirty blocks alive
2591
                // so check this assumption.
2592
                // When called with bPartialFlush = false, FlushCache() has
2593
                // already been called, so no need to check.
2594
0
                bool bFoundDirtyBlock = false;
2595
0
                int nBlockXOff = nCol - m_nShiftXTiles;
2596
0
                int nBlockYOff = nRow - m_nShiftYTiles;
2597
0
                for (int iX = 0; !bFoundDirtyBlock &&
2598
0
                                 iX < ((m_nShiftXPixelsMod != 0) ? 2 : 1);
2599
0
                     iX++)
2600
0
                {
2601
0
                    if (nBlockXOff + iX < 0 || nBlockXOff + iX >= nXBlocks)
2602
0
                        continue;
2603
0
                    for (int iY = 0; !bFoundDirtyBlock &&
2604
0
                                     iY < ((m_nShiftYPixelsMod != 0) ? 2 : 1);
2605
0
                         iY++)
2606
0
                    {
2607
0
                        if (nBlockYOff + iY < 0 || nBlockYOff + iY >= nYBlocks)
2608
0
                            continue;
2609
0
                        for (int iBand = 1;
2610
0
                             !bFoundDirtyBlock && iBand <= nBands; iBand++)
2611
0
                        {
2612
0
                            GDALRasterBlock *poBlock =
2613
0
                                cpl::down_cast<GDALGPKGMBTilesLikeRasterBand *>(
2614
0
                                    IGetRasterBand(iBand))
2615
0
                                    ->AccessibleTryGetLockedBlockRef(
2616
0
                                        nBlockXOff + iX, nBlockYOff + iY);
2617
0
                            if (poBlock)
2618
0
                            {
2619
0
                                if (poBlock->GetDirty())
2620
0
                                    bFoundDirtyBlock = true;
2621
0
                                poBlock->DropLock();
2622
0
                            }
2623
0
                        }
2624
0
                    }
2625
0
                }
2626
0
                if (bFoundDirtyBlock)
2627
0
                {
2628
#ifdef DEBUG_VERBOSE
2629
                    CPLDebug("GPKG",
2630
                             "Skipped flushing tile row = %d, column = %d "
2631
                             "because it has dirty block(s) in GDAL cache",
2632
                             nRow, nCol);
2633
#endif
2634
0
                    continue;
2635
0
                }
2636
0
            }
2637
2638
0
            nCountFlushedTiles++;
2639
0
            if (bPartialFlush && nCountFlushedTiles >= nPartialActiveTiles / 2)
2640
0
            {
2641
0
                CPLDebug("GPKG", "Flushed %d tiles", nCountFlushedTiles);
2642
0
                break;
2643
0
            }
2644
2645
0
            for (int nBand = 1; nBand <= nBands; nBand++)
2646
0
            {
2647
0
                if (nPartialFlags & (((1 << 4) - 1) << (4 * (nBand - 1))))
2648
0
                {
2649
0
                    CPLAssert(sqlite3_column_bytes(hStmt, 2 + nBand) ==
2650
0
                              static_cast<int>(nBandBlockSize));
2651
0
                    memcpy(m_pabyCachedTiles + (nBand - 1) * nBandBlockSize,
2652
0
                           sqlite3_column_blob(hStmt, 2 + nBand),
2653
0
                           nBandBlockSize);
2654
0
                }
2655
0
                else
2656
0
                {
2657
0
                    FillEmptyTileSingleBand(m_pabyCachedTiles +
2658
0
                                            (nBand - 1) * nBandBlockSize);
2659
0
                }
2660
0
            }
2661
2662
0
            int nFullFlags = (1 << (4 * nBands)) - 1;
2663
2664
            // In case the partial flags indicate that there's some quadrant
2665
            // missing, check in the main database if there is already a tile
2666
            // In which case, use the parts of that tile that aren't in the
2667
            // temporary database
2668
0
            if (nPartialFlags != nFullFlags)
2669
0
            {
2670
0
                char *pszNewSQL = sqlite3_mprintf(
2671
0
                    "SELECT tile_data%s FROM \"%w\" "
2672
0
                    "WHERE zoom_level = %d AND tile_row = %d AND tile_column = "
2673
0
                    "%d%s",
2674
0
                    m_eDT != GDT_UInt8 ? ", id"
2675
0
                                       : "",  // MBTiles do not have an id
2676
0
                    m_osRasterTable.c_str(), m_nZoomLevel,
2677
0
                    GetRowFromIntoTopConvention(nRow), nCol,
2678
0
                    !m_osWHERE.empty()
2679
0
                        ? CPLSPrintf(" AND (%s)", m_osWHERE.c_str())
2680
0
                        : "");
2681
#ifdef DEBUG_VERBOSE
2682
                CPLDebug("GPKG", "%s", pszNewSQL);
2683
#endif
2684
0
                sqlite3_stmt *hNewStmt = nullptr;
2685
0
                rc = SQLPrepareWithError(IGetDB(), pszNewSQL, -1, &hNewStmt,
2686
0
                                         nullptr);
2687
0
                if (rc == SQLITE_OK)
2688
0
                {
2689
0
                    rc = sqlite3_step(hNewStmt);
2690
0
                    if (rc == SQLITE_ROW &&
2691
0
                        sqlite3_column_type(hNewStmt, 0) == SQLITE_BLOB)
2692
0
                    {
2693
0
                        const int nBytes = sqlite3_column_bytes(hNewStmt, 0);
2694
0
                        GIntBig nTileId =
2695
0
                            (m_eDT == GDT_UInt8)
2696
0
                                ? 0
2697
0
                                : sqlite3_column_int64(hNewStmt, 1);
2698
0
                        GByte *pabyRawData =
2699
0
                            const_cast<GByte *>(static_cast<const GByte *>(
2700
0
                                sqlite3_column_blob(hNewStmt, 0)));
2701
0
                        const CPLString osMemFileName(
2702
0
                            VSIMemGenerateHiddenFilename("gpkg_read_tile"));
2703
0
                        VSILFILE *fp = VSIFileFromMemBuffer(
2704
0
                            osMemFileName.c_str(), pabyRawData, nBytes, FALSE);
2705
0
                        VSIFCloseL(fp);
2706
2707
0
                        double dfTileOffset = 0.0;
2708
0
                        double dfTileScale = 1.0;
2709
0
                        GetTileOffsetAndScale(nTileId, dfTileOffset,
2710
0
                                              dfTileScale);
2711
0
                        const int nTileBands = m_eDT == GDT_UInt8 ? 4 : 1;
2712
0
                        GByte *pabyTemp =
2713
0
                            m_pabyCachedTiles + nTileBands * nBandBlockSize;
2714
0
                        ReadTile(osMemFileName, pabyTemp, dfTileOffset,
2715
0
                                 dfTileScale);
2716
0
                        VSIUnlink(osMemFileName);
2717
2718
0
                        int iYQuadrantMax = (m_nShiftYPixelsMod) ? 1 : 0;
2719
0
                        int iXQuadrantMax = (m_nShiftXPixelsMod) ? 1 : 0;
2720
0
                        for (int iYQuadrant = 0; iYQuadrant <= iYQuadrantMax;
2721
0
                             iYQuadrant++)
2722
0
                        {
2723
0
                            for (int iXQuadrant = 0;
2724
0
                                 iXQuadrant <= iXQuadrantMax; iXQuadrant++)
2725
0
                            {
2726
0
                                for (int nBand = 1; nBand <= nBands; nBand++)
2727
0
                                {
2728
0
                                    int iQuadrantFlag = 0;
2729
0
                                    if (iXQuadrant == 0 && iYQuadrant == 0)
2730
0
                                        iQuadrantFlag |= (1 << 0);
2731
0
                                    if (iXQuadrant == iXQuadrantMax &&
2732
0
                                        iYQuadrant == 0)
2733
0
                                        iQuadrantFlag |= (1 << 1);
2734
0
                                    if (iXQuadrant == 0 &&
2735
0
                                        iYQuadrant == iYQuadrantMax)
2736
0
                                        iQuadrantFlag |= (1 << 2);
2737
0
                                    if (iXQuadrant == iXQuadrantMax &&
2738
0
                                        iYQuadrant == iYQuadrantMax)
2739
0
                                        iQuadrantFlag |= (1 << 3);
2740
0
                                    int nLocalFlag = iQuadrantFlag
2741
0
                                                     << (4 * (nBand - 1));
2742
0
                                    if (!(nPartialFlags & nLocalFlag))
2743
0
                                    {
2744
0
                                        int nXOff, nYOff, nXSize, nYSize;
2745
0
                                        if (iXQuadrant == 0 &&
2746
0
                                            m_nShiftXPixelsMod != 0)
2747
0
                                        {
2748
0
                                            nXOff = 0;
2749
0
                                            nXSize = m_nShiftXPixelsMod;
2750
0
                                        }
2751
0
                                        else
2752
0
                                        {
2753
0
                                            nXOff = m_nShiftXPixelsMod;
2754
0
                                            nXSize = nBlockXSize -
2755
0
                                                     m_nShiftXPixelsMod;
2756
0
                                        }
2757
0
                                        if (iYQuadrant == 0 &&
2758
0
                                            m_nShiftYPixelsMod != 0)
2759
0
                                        {
2760
0
                                            nYOff = 0;
2761
0
                                            nYSize = m_nShiftYPixelsMod;
2762
0
                                        }
2763
0
                                        else
2764
0
                                        {
2765
0
                                            nYOff = m_nShiftYPixelsMod;
2766
0
                                            nYSize = nBlockYSize -
2767
0
                                                     m_nShiftYPixelsMod;
2768
0
                                        }
2769
0
                                        for (int iY = nYOff;
2770
0
                                             iY < nYOff + nYSize; iY++)
2771
0
                                        {
2772
0
                                            memcpy(m_pabyCachedTiles +
2773
0
                                                       ((static_cast<size_t>(
2774
0
                                                             nBand - 1) *
2775
0
                                                             nBlockYSize +
2776
0
                                                         iY) *
2777
0
                                                            nBlockXSize +
2778
0
                                                        nXOff) *
2779
0
                                                           m_nDTSize,
2780
0
                                                   pabyTemp +
2781
0
                                                       ((static_cast<size_t>(
2782
0
                                                             nBand - 1) *
2783
0
                                                             nBlockYSize +
2784
0
                                                         iY) *
2785
0
                                                            nBlockXSize +
2786
0
                                                        nXOff) *
2787
0
                                                           m_nDTSize,
2788
0
                                                   static_cast<size_t>(nXSize) *
2789
0
                                                       m_nDTSize);
2790
0
                                        }
2791
0
                                    }
2792
0
                                }
2793
0
                            }
2794
0
                        }
2795
0
                    }
2796
0
                    else if (rc != SQLITE_DONE)
2797
0
                    {
2798
0
                        CPLError(CE_Failure, CPLE_AppDefined,
2799
0
                                 "sqlite3_step(%s) failed: %s", pszNewSQL,
2800
0
                                 sqlite3_errmsg(m_hTempDB));
2801
0
                    }
2802
0
                    sqlite3_finalize(hNewStmt);
2803
0
                }
2804
0
                sqlite3_free(pszNewSQL);
2805
0
            }
2806
2807
0
            m_asCachedTilesDesc[0].nRow = nRow;
2808
0
            m_asCachedTilesDesc[0].nCol = nCol;
2809
0
            m_asCachedTilesDesc[0].nIdxWithinTileData = 0;
2810
0
            m_asCachedTilesDesc[0].abBandDirty[0] = true;
2811
0
            m_asCachedTilesDesc[0].abBandDirty[1] = true;
2812
0
            m_asCachedTilesDesc[0].abBandDirty[2] = true;
2813
0
            m_asCachedTilesDesc[0].abBandDirty[3] = true;
2814
2815
0
            eErr = WriteTile();
2816
2817
0
            if (eErr == CE_None && bPartialFlush)
2818
0
            {
2819
0
                pszSQL =
2820
0
                    CPLSPrintf("DELETE FROM partial_tiles WHERE zoom_level = "
2821
0
                               "%d AND tile_row = %d AND tile_column = %d",
2822
0
                               m_nZoomLevel, nRow, nCol);
2823
#ifdef DEBUG_VERBOSE
2824
                CPLDebug("GPKG", "%s", pszSQL);
2825
#endif
2826
0
                if (SQLCommand(m_hTempDB, pszSQL) != OGRERR_NONE)
2827
0
                    eErr = CE_None;
2828
0
            }
2829
0
        }
2830
0
        else
2831
0
        {
2832
0
            if (rc != SQLITE_DONE)
2833
0
            {
2834
0
                CPLError(CE_Failure, CPLE_AppDefined,
2835
0
                         "sqlite3_step(%s) failed: %s", pszSQL,
2836
0
                         sqlite3_errmsg(m_hTempDB));
2837
0
            }
2838
0
            break;
2839
0
        }
2840
0
    } while (eErr == CE_None);
2841
2842
0
    sqlite3_finalize(hStmt);
2843
2844
0
    if (bPartialFlush && nCountFlushedTiles < nPartialActiveTiles / 2)
2845
0
    {
2846
0
        CPLDebug("GPKG", "Flushed %d tiles. Target was %d", nCountFlushedTiles,
2847
0
                 nPartialActiveTiles / 2);
2848
0
    }
2849
2850
0
    if (bGotPartialTiles && !bPartialFlush)
2851
0
    {
2852
#ifdef DEBUG_VERBOSE
2853
        pszSQL = CPLSPrintf("SELECT p1.id, p1.tile_row, p1.tile_column FROM "
2854
                            "partial_tiles p1, partial_tiles p2 "
2855
                            "WHERE p1.zoom_level = %d AND p2.zoom_level = %d "
2856
                            "AND p1.tile_row = p2.tile_row AND p1.tile_column "
2857
                            "= p2.tile_column AND p2.partial_flag != 0",
2858
                            -1 - m_nZoomLevel, m_nZoomLevel);
2859
        rc = SQLPrepareWithError(m_hTempDB, pszSQL, -1, &hStmt, nullptr);
2860
        CPLAssert(rc == SQLITE_OK);
2861
        while ((rc = sqlite3_step(hStmt)) == SQLITE_ROW)
2862
        {
2863
            CPLDebug("GPKG",
2864
                     "Conflict: existing id = %d, tile_row = %d, tile_column = "
2865
                     "%d, zoom_level = %d",
2866
                     sqlite3_column_int(hStmt, 0), sqlite3_column_int(hStmt, 1),
2867
                     sqlite3_column_int(hStmt, 2), m_nZoomLevel);
2868
        }
2869
        sqlite3_finalize(hStmt);
2870
#endif
2871
2872
0
        pszSQL = CPLSPrintf("UPDATE partial_tiles SET zoom_level = %d, "
2873
0
                            "partial_flag = 0, age = -1 WHERE zoom_level = %d "
2874
0
                            "AND partial_flag != 0",
2875
0
                            -1 - m_nZoomLevel, m_nZoomLevel);
2876
#ifdef DEBUG_VERBOSE
2877
        CPLDebug("GPKG", "%s", pszSQL);
2878
#endif
2879
0
        SQLCommand(m_hTempDB, pszSQL);
2880
0
    }
2881
2882
0
    return eErr;
2883
0
}
2884
2885
/************************************************************************/
2886
/*              DoPartialFlushOfPartialTilesIfNecessary()               */
2887
/************************************************************************/
2888
2889
CPLErr
2890
GDALGPKGMBTilesLikePseudoDataset::DoPartialFlushOfPartialTilesIfNecessary()
2891
0
{
2892
0
    time_t nCurTimeStamp = time(nullptr);
2893
0
    if (m_nLastSpaceCheckTimestamp == 0)
2894
0
        m_nLastSpaceCheckTimestamp = nCurTimeStamp;
2895
0
    if (m_nLastSpaceCheckTimestamp > 0 &&
2896
0
        (m_bForceTempDBCompaction ||
2897
0
         nCurTimeStamp - m_nLastSpaceCheckTimestamp > 10))
2898
0
    {
2899
0
        m_nLastSpaceCheckTimestamp = nCurTimeStamp;
2900
0
        GIntBig nFreeSpace =
2901
0
            VSIGetDiskFreeSpace(CPLGetDirnameSafe(m_osTempDBFilename).c_str());
2902
0
        bool bTryFreeing = false;
2903
0
        if (nFreeSpace >= 0 && nFreeSpace < 1024 * 1024 * 1024)
2904
0
        {
2905
0
            CPLDebug("GPKG",
2906
0
                     "Free space below 1GB. Flushing part of partial tiles");
2907
0
            bTryFreeing = true;
2908
0
        }
2909
0
        else
2910
0
        {
2911
0
            VSIStatBufL sStat;
2912
0
            if (VSIStatL(m_osTempDBFilename, &sStat) == 0)
2913
0
            {
2914
0
                GIntBig nTempSpace = sStat.st_size;
2915
0
                if (VSIStatL((m_osTempDBFilename + "-journal").c_str(),
2916
0
                             &sStat) == 0)
2917
0
                    nTempSpace += sStat.st_size;
2918
0
                else if (VSIStatL((m_osTempDBFilename + "-wal").c_str(),
2919
0
                                  &sStat) == 0)
2920
0
                    nTempSpace += sStat.st_size;
2921
2922
0
                int nBlockXSize, nBlockYSize;
2923
0
                IGetRasterBand(1)->GetBlockSize(&nBlockXSize, &nBlockYSize);
2924
0
                const int nBands = IGetRasterCount();
2925
2926
0
                if (nTempSpace >
2927
0
                    4 * static_cast<GIntBig>(IGetRasterBand(1)->GetXSize()) *
2928
0
                        nBlockYSize * nBands * m_nDTSize)
2929
0
                {
2930
0
                    CPLDebug("GPKG",
2931
0
                             "Partial tiles DB is " CPL_FRMT_GIB
2932
0
                             " bytes. Flushing part of partial tiles",
2933
0
                             nTempSpace);
2934
0
                    bTryFreeing = true;
2935
0
                }
2936
0
            }
2937
0
        }
2938
0
        if (bTryFreeing)
2939
0
        {
2940
0
            if (FlushRemainingShiftedTiles(true /* partial flush*/) != CE_None)
2941
0
            {
2942
0
                return CE_Failure;
2943
0
            }
2944
0
            SQLCommand(m_hTempDB,
2945
0
                       "DELETE FROM partial_tiles WHERE zoom_level < 0");
2946
0
            SQLCommand(m_hTempDB, "VACUUM");
2947
0
        }
2948
0
    }
2949
0
    return CE_None;
2950
0
}
2951
2952
/************************************************************************/
2953
/*                          WriteShiftedTile()                          */
2954
/************************************************************************/
2955
2956
CPLErr GDALGPKGMBTilesLikePseudoDataset::WriteShiftedTile(
2957
    int nRow, int nCol, int nBand, int nDstXOffset, int nDstYOffset,
2958
    int nDstXSize, int nDstYSize)
2959
0
{
2960
0
    CPLAssert(m_nShiftXPixelsMod || m_nShiftYPixelsMod);
2961
0
    CPLAssert(nRow >= 0);
2962
0
    CPLAssert(nCol >= 0);
2963
0
    CPLAssert(nRow < m_nTileMatrixHeight);
2964
0
    CPLAssert(nCol < m_nTileMatrixWidth);
2965
2966
0
    if (m_hTempDB == nullptr &&
2967
0
        (m_poParentDS == nullptr || m_poParentDS->m_hTempDB == nullptr))
2968
0
    {
2969
0
        const char *pszBaseFilename =
2970
0
            m_poParentDS ? m_poParentDS->IGetFilename() : IGetFilename();
2971
0
        m_osTempDBFilename =
2972
0
            CPLResetExtensionSafe(pszBaseFilename, "partial_tiles.db");
2973
0
        CPLPushErrorHandler(CPLQuietErrorHandler);
2974
0
        VSIUnlink(m_osTempDBFilename);
2975
0
        CPLPopErrorHandler();
2976
0
        m_hTempDB = nullptr;
2977
0
        int rc = 0;
2978
0
        if (STARTS_WITH(m_osTempDBFilename, "/vsi"))
2979
0
        {
2980
0
            m_pMyVFS = OGRSQLiteCreateVFS(nullptr, nullptr);
2981
0
            sqlite3_vfs_register(m_pMyVFS, 0);
2982
0
            rc = sqlite3_open_v2(m_osTempDBFilename, &m_hTempDB,
2983
0
                                 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE |
2984
0
                                     SQLITE_OPEN_NOMUTEX,
2985
0
                                 m_pMyVFS->zName);
2986
0
        }
2987
0
        else
2988
0
        {
2989
0
            rc = sqlite3_open(m_osTempDBFilename, &m_hTempDB);
2990
0
        }
2991
0
        if (rc != SQLITE_OK || m_hTempDB == nullptr)
2992
0
        {
2993
0
            CPLError(CE_Failure, CPLE_AppDefined,
2994
0
                     "Cannot create temporary database %s",
2995
0
                     m_osTempDBFilename.c_str());
2996
0
            return CE_Failure;
2997
0
        }
2998
0
        SQLCommand(m_hTempDB, "PRAGMA synchronous = OFF");
2999
0
        SQLCommand(m_hTempDB, "PRAGMA journal_mode = OFF");
3000
0
        SQLCommand(m_hTempDB, "CREATE TABLE partial_tiles("
3001
0
                              "id INTEGER PRIMARY KEY AUTOINCREMENT,"
3002
0
                              "zoom_level INTEGER NOT NULL,"
3003
0
                              "tile_column INTEGER NOT NULL,"
3004
0
                              "tile_row INTEGER NOT NULL,"
3005
0
                              "tile_data_band_1 BLOB,"
3006
0
                              "tile_data_band_2 BLOB,"
3007
0
                              "tile_data_band_3 BLOB,"
3008
0
                              "tile_data_band_4 BLOB,"
3009
0
                              "partial_flag INTEGER NOT NULL,"
3010
0
                              "age INTEGER NOT NULL,"
3011
0
                              "UNIQUE (zoom_level, tile_column, tile_row))");
3012
0
        SQLCommand(m_hTempDB, "CREATE INDEX partial_tiles_partial_flag_idx "
3013
0
                              "ON partial_tiles(partial_flag)");
3014
0
        SQLCommand(m_hTempDB, "CREATE INDEX partial_tiles_age_idx "
3015
0
                              "ON partial_tiles(age)");
3016
3017
0
        if (m_poParentDS != nullptr)
3018
0
        {
3019
0
            m_poParentDS->m_osTempDBFilename = m_osTempDBFilename;
3020
0
            m_poParentDS->m_hTempDB = m_hTempDB;
3021
0
        }
3022
0
    }
3023
3024
0
    if (m_poParentDS != nullptr)
3025
0
        m_hTempDB = m_poParentDS->m_hTempDB;
3026
3027
0
    int nBlockXSize, nBlockYSize;
3028
0
    IGetRasterBand(1)->GetBlockSize(&nBlockXSize, &nBlockYSize);
3029
0
    const int nBands = IGetRasterCount();
3030
0
    const size_t nBandBlockSize =
3031
0
        static_cast<size_t>(nBlockXSize) * nBlockYSize * m_nDTSize;
3032
3033
0
    int iQuadrantFlag = 0;
3034
0
    if (nDstXOffset == 0 && nDstYOffset == 0)
3035
0
        iQuadrantFlag |= (1 << 0);
3036
0
    if ((nDstXOffset != 0 || nDstXOffset + nDstXSize == nBlockXSize) &&
3037
0
        nDstYOffset == 0)
3038
0
        iQuadrantFlag |= (1 << 1);
3039
0
    if (nDstXOffset == 0 &&
3040
0
        (nDstYOffset != 0 || nDstYOffset + nDstYSize == nBlockYSize))
3041
0
        iQuadrantFlag |= (1 << 2);
3042
0
    if ((nDstXOffset != 0 || nDstXOffset + nDstXSize == nBlockXSize) &&
3043
0
        (nDstYOffset != 0 || nDstYOffset + nDstYSize == nBlockYSize))
3044
0
        iQuadrantFlag |= (1 << 3);
3045
0
    int l_nFlags = iQuadrantFlag << (4 * (nBand - 1));
3046
0
    int nFullFlags = (1 << (4 * nBands)) - 1;
3047
0
    int nOldFlags = 0;
3048
3049
0
    for (int i = 1; i <= 3; i++)
3050
0
    {
3051
0
        m_asCachedTilesDesc[i].nRow = -1;
3052
0
        m_asCachedTilesDesc[i].nCol = -1;
3053
0
        m_asCachedTilesDesc[i].nIdxWithinTileData = -1;
3054
0
    }
3055
3056
0
    int nExistingId = 0;
3057
0
    const char *pszSQL = CPLSPrintf(
3058
0
        "SELECT id, partial_flag, tile_data_band_%d FROM partial_tiles WHERE "
3059
0
        "zoom_level = %d AND tile_row = %d AND tile_column = %d",
3060
0
        nBand, m_nZoomLevel, nRow, nCol);
3061
#ifdef DEBUG_VERBOSE
3062
    CPLDebug("GPKG", "%s", pszSQL);
3063
#endif
3064
0
    sqlite3_stmt *hStmt = nullptr;
3065
0
    int rc = SQLPrepareWithError(m_hTempDB, pszSQL, -1, &hStmt, nullptr);
3066
0
    if (rc != SQLITE_OK)
3067
0
    {
3068
0
        return CE_Failure;
3069
0
    }
3070
3071
0
    rc = sqlite3_step(hStmt);
3072
0
    const int nTileBands = m_eDT == GDT_UInt8 ? 4 : 1;
3073
0
    GByte *pabyTemp = m_pabyCachedTiles + nTileBands * nBandBlockSize;
3074
0
    if (rc == SQLITE_ROW)
3075
0
    {
3076
0
        nExistingId = sqlite3_column_int(hStmt, 0);
3077
#ifdef DEBUG_VERBOSE
3078
        CPLDebug("GPKG", "Using partial_tile id=%d", nExistingId);
3079
#endif
3080
0
        nOldFlags = sqlite3_column_int(hStmt, 1);
3081
0
        CPLAssert(nOldFlags != 0);
3082
0
        if ((nOldFlags & (((1 << 4) - 1) << (4 * (nBand - 1)))) == 0)
3083
0
        {
3084
0
            FillEmptyTileSingleBand(pabyTemp + (nBand - 1) * nBandBlockSize);
3085
0
        }
3086
0
        else
3087
0
        {
3088
0
            CPLAssert(sqlite3_column_bytes(hStmt, 2) ==
3089
0
                      static_cast<int>(nBandBlockSize));
3090
0
            memcpy(pabyTemp + (nBand - 1) * nBandBlockSize,
3091
0
                   sqlite3_column_blob(hStmt, 2), nBandBlockSize);
3092
0
        }
3093
0
    }
3094
0
    else
3095
0
    {
3096
0
        FillEmptyTileSingleBand(pabyTemp + (nBand - 1) * nBandBlockSize);
3097
0
    }
3098
0
    sqlite3_finalize(hStmt);
3099
0
    hStmt = nullptr;
3100
3101
    /* Copy the updated rectangle into the full tile */
3102
0
    for (int iY = nDstYOffset; iY < nDstYOffset + nDstYSize; iY++)
3103
0
    {
3104
0
        memcpy(pabyTemp +
3105
0
                   (static_cast<size_t>(nBand - 1) * nBlockXSize * nBlockYSize +
3106
0
                    static_cast<size_t>(iY) * nBlockXSize + nDstXOffset) *
3107
0
                       m_nDTSize,
3108
0
               m_pabyCachedTiles +
3109
0
                   (static_cast<size_t>(nBand - 1) * nBlockXSize * nBlockYSize +
3110
0
                    static_cast<size_t>(iY) * nBlockXSize + nDstXOffset) *
3111
0
                       m_nDTSize,
3112
0
               static_cast<size_t>(nDstXSize) * m_nDTSize);
3113
0
    }
3114
3115
#ifdef notdef
3116
    static int nCounter = 1;
3117
    GDALDataset *poLogDS =
3118
        ((GDALDriver *)GDALGetDriverByName("GTiff"))
3119
            ->Create(CPLSPrintf("/tmp/partial_band_%d_%d.tif", 1, nCounter++),
3120
                     nBlockXSize, nBlockYSize, nBands, m_eDT, NULL);
3121
    poLogDS->RasterIO(GF_Write, 0, 0, nBlockXSize, nBlockYSize,
3122
                      pabyTemp + (nBand - 1) * nBandBlockSize, nBlockXSize,
3123
                      nBlockYSize, m_eDT, 1, NULL, 0, 0, 0, NULL);
3124
    GDALClose(poLogDS);
3125
#endif
3126
3127
0
    if ((nOldFlags & l_nFlags) != 0)
3128
0
    {
3129
0
        CPLDebug("GPKG",
3130
0
                 "Rewriting quadrant %d of band %d of tile (row=%d,col=%d)",
3131
0
                 iQuadrantFlag, nBand, nRow, nCol);
3132
0
    }
3133
3134
0
    l_nFlags |= nOldFlags;
3135
0
    if (l_nFlags == nFullFlags)
3136
0
    {
3137
#ifdef DEBUG_VERBOSE
3138
        CPLDebug("GPKG", "Got all quadrants for that tile");
3139
#endif
3140
0
        for (int iBand = 1; iBand <= nBands; iBand++)
3141
0
        {
3142
0
            if (iBand != nBand)
3143
0
            {
3144
0
                pszSQL = CPLSPrintf(
3145
0
                    "SELECT tile_data_band_%d FROM partial_tiles WHERE "
3146
0
                    "id = %d",
3147
0
                    iBand, nExistingId);
3148
#ifdef DEBUG_VERBOSE
3149
                CPLDebug("GPKG", "%s", pszSQL);
3150
#endif
3151
0
                hStmt = nullptr;
3152
0
                rc =
3153
0
                    SQLPrepareWithError(m_hTempDB, pszSQL, -1, &hStmt, nullptr);
3154
0
                if (rc != SQLITE_OK)
3155
0
                {
3156
0
                    return CE_Failure;
3157
0
                }
3158
3159
0
                rc = sqlite3_step(hStmt);
3160
0
                if (rc == SQLITE_ROW)
3161
0
                {
3162
0
                    CPLAssert(sqlite3_column_bytes(hStmt, 0) ==
3163
0
                              static_cast<int>(nBandBlockSize));
3164
0
                    memcpy(m_pabyCachedTiles + (iBand - 1) * nBandBlockSize,
3165
0
                           sqlite3_column_blob(hStmt, 0), nBandBlockSize);
3166
0
                }
3167
0
                sqlite3_finalize(hStmt);
3168
0
                hStmt = nullptr;
3169
0
            }
3170
0
            else
3171
0
            {
3172
0
                memcpy(m_pabyCachedTiles + (iBand - 1) * nBandBlockSize,
3173
0
                       pabyTemp + (iBand - 1) * nBandBlockSize, nBandBlockSize);
3174
0
            }
3175
0
        }
3176
3177
0
        m_asCachedTilesDesc[0].nRow = nRow;
3178
0
        m_asCachedTilesDesc[0].nCol = nCol;
3179
0
        m_asCachedTilesDesc[0].nIdxWithinTileData = 0;
3180
0
        m_asCachedTilesDesc[0].abBandDirty[0] = true;
3181
0
        m_asCachedTilesDesc[0].abBandDirty[1] = true;
3182
0
        m_asCachedTilesDesc[0].abBandDirty[2] = true;
3183
0
        m_asCachedTilesDesc[0].abBandDirty[3] = true;
3184
3185
0
        pszSQL = CPLSPrintf("UPDATE partial_tiles SET zoom_level = %d, "
3186
0
                            "partial_flag = 0, age = -1 WHERE id = %d",
3187
0
                            -1 - m_nZoomLevel, nExistingId);
3188
0
        SQLCommand(m_hTempDB, pszSQL);
3189
#ifdef DEBUG_VERBOSE
3190
        CPLDebug("GPKG", "%s", pszSQL);
3191
#endif
3192
0
        CPLErr eErr = WriteTile();
3193
3194
        // Call DoPartialFlushOfPartialTilesIfNecessary() after using
3195
        // m_pabyCachedTiles as it is going to mess with it.
3196
0
        if (DoPartialFlushOfPartialTilesIfNecessary() != CE_None)
3197
0
            eErr = CE_None;
3198
3199
0
        return eErr;
3200
0
    }
3201
3202
0
    if (nExistingId == 0)
3203
0
    {
3204
0
        OGRErr err;
3205
0
        pszSQL = CPLSPrintf("SELECT id FROM partial_tiles WHERE "
3206
0
                            "partial_flag = 0 AND zoom_level = %d "
3207
0
                            "AND tile_row = %d AND tile_column = %d",
3208
0
                            -1 - m_nZoomLevel, nRow, nCol);
3209
#ifdef DEBUG_VERBOSE
3210
        CPLDebug("GPKG", "%s", pszSQL);
3211
#endif
3212
0
        nExistingId = SQLGetInteger(m_hTempDB, pszSQL, &err);
3213
0
        if (nExistingId == 0)
3214
0
        {
3215
0
            pszSQL =
3216
0
                "SELECT id FROM partial_tiles WHERE partial_flag = 0 LIMIT 1";
3217
#ifdef DEBUG_VERBOSE
3218
            CPLDebug("GPKG", "%s", pszSQL);
3219
#endif
3220
0
            nExistingId = SQLGetInteger(m_hTempDB, pszSQL, &err);
3221
0
        }
3222
0
    }
3223
3224
0
    const GIntBig nAge = (m_poParentDS) ? m_poParentDS->m_nAge : m_nAge;
3225
0
    if (nExistingId == 0)
3226
0
    {
3227
0
        pszSQL = CPLSPrintf(
3228
0
            "INSERT INTO partial_tiles "
3229
0
            "(zoom_level, tile_row, tile_column, tile_data_band_%d, "
3230
0
            "partial_flag, age) VALUES (%d, %d, %d, ?, %d, " CPL_FRMT_GIB ")",
3231
0
            nBand, m_nZoomLevel, nRow, nCol, l_nFlags, nAge);
3232
0
    }
3233
0
    else
3234
0
    {
3235
0
        pszSQL = CPLSPrintf(
3236
0
            "UPDATE partial_tiles SET zoom_level = %d, "
3237
0
            "tile_row = %d, tile_column = %d, "
3238
0
            "tile_data_band_%d = ?, partial_flag = %d, age = " CPL_FRMT_GIB
3239
0
            " WHERE id = %d",
3240
0
            m_nZoomLevel, nRow, nCol, nBand, l_nFlags, nAge, nExistingId);
3241
0
    }
3242
0
    if (m_poParentDS)
3243
0
        m_poParentDS->m_nAge++;
3244
0
    else
3245
0
        m_nAge++;
3246
3247
#ifdef DEBUG_VERBOSE
3248
    CPLDebug("GPKG", "%s", pszSQL);
3249
#endif
3250
3251
0
    hStmt = nullptr;
3252
0
    rc = SQLPrepareWithError(m_hTempDB, pszSQL, -1, &hStmt, nullptr);
3253
0
    if (rc != SQLITE_OK)
3254
0
    {
3255
0
        return CE_Failure;
3256
0
    }
3257
3258
0
    sqlite3_bind_blob(hStmt, 1, pabyTemp + (nBand - 1) * nBandBlockSize,
3259
0
                      static_cast<int>(nBandBlockSize), SQLITE_TRANSIENT);
3260
0
    rc = sqlite3_step(hStmt);
3261
0
    CPLErr eErr = CE_Failure;
3262
0
    if (rc == SQLITE_DONE)
3263
0
        eErr = CE_None;
3264
0
    else
3265
0
    {
3266
0
        CPLError(CE_Failure, CPLE_AppDefined,
3267
0
                 "Failure when inserting partial tile (row=%d,col=%d) at "
3268
0
                 "zoom_level=%d : %s",
3269
0
                 nRow, nCol, m_nZoomLevel, sqlite3_errmsg(m_hTempDB));
3270
0
    }
3271
3272
0
    sqlite3_finalize(hStmt);
3273
3274
    // Call DoPartialFlushOfPartialTilesIfNecessary() after using
3275
    // m_pabyCachedTiles as it is going to mess with it.
3276
0
    if (DoPartialFlushOfPartialTilesIfNecessary() != CE_None)
3277
0
        eErr = CE_None;
3278
3279
0
    return eErr;
3280
0
}
3281
3282
/************************************************************************/
3283
/*                            IWriteBlock()                             */
3284
/************************************************************************/
3285
3286
CPLErr GDALGPKGMBTilesLikeRasterBand::IWriteBlock(int nBlockXOff,
3287
                                                  int nBlockYOff, void *pData)
3288
0
{
3289
#ifdef DEBUG_VERBOSE
3290
    CPLDebug(
3291
        "GPKG",
3292
        "IWriteBlock(nBand=%d,nBlockXOff=%d,nBlockYOff=%d,m_nZoomLevel=%d)",
3293
        nBand, nBlockXOff, nBlockYOff, m_poTPD->m_nZoomLevel);
3294
#endif
3295
0
    if (!m_poTPD->ICanIWriteBlock())
3296
0
    {
3297
0
        return CE_Failure;
3298
0
    }
3299
0
    if (m_poTPD->m_poParentDS)
3300
0
        m_poTPD->m_poParentDS->m_bHasModifiedTiles = true;
3301
0
    else
3302
0
        m_poTPD->m_bHasModifiedTiles = true;
3303
3304
0
    int nRow = nBlockYOff + m_poTPD->m_nShiftYTiles;
3305
0
    int nCol = nBlockXOff + m_poTPD->m_nShiftXTiles;
3306
3307
0
    int nRowMin = nRow;
3308
0
    int nRowMax = nRowMin;
3309
0
    if (m_poTPD->m_nShiftYPixelsMod)
3310
0
        nRowMax++;
3311
3312
0
    int nColMin = nCol;
3313
0
    int nColMax = nColMin;
3314
0
    if (m_poTPD->m_nShiftXPixelsMod)
3315
0
        nColMax++;
3316
3317
0
    CPLErr eErr = CE_None;
3318
3319
0
    for (nRow = nRowMin; eErr == CE_None && nRow <= nRowMax; nRow++)
3320
0
    {
3321
0
        for (nCol = nColMin; eErr == CE_None && nCol <= nColMax; nCol++)
3322
0
        {
3323
0
            if (nRow < 0 || nCol < 0 || nRow >= m_poTPD->m_nTileMatrixHeight ||
3324
0
                nCol >= m_poTPD->m_nTileMatrixWidth)
3325
0
            {
3326
0
                continue;
3327
0
            }
3328
3329
0
            if (m_poTPD->m_nShiftXPixelsMod == 0 &&
3330
0
                m_poTPD->m_nShiftYPixelsMod == 0)
3331
0
            {
3332
0
                if (!(nRow == m_poTPD->m_asCachedTilesDesc[0].nRow &&
3333
0
                      nCol == m_poTPD->m_asCachedTilesDesc[0].nCol &&
3334
0
                      m_poTPD->m_asCachedTilesDesc[0].nIdxWithinTileData == 0))
3335
0
                {
3336
0
                    eErr = m_poTPD->WriteTile();
3337
3338
0
                    m_poTPD->m_asCachedTilesDesc[0].nRow = nRow;
3339
0
                    m_poTPD->m_asCachedTilesDesc[0].nCol = nCol;
3340
0
                    m_poTPD->m_asCachedTilesDesc[0].nIdxWithinTileData = 0;
3341
0
                }
3342
0
            }
3343
3344
            // Composite block data into tile, and check if all bands for this
3345
            // block are dirty, and if so write the tile
3346
0
            bool bAllDirty = true;
3347
0
            for (int iBand = 1; iBand <= poDS->GetRasterCount(); iBand++)
3348
0
            {
3349
0
                GDALRasterBlock *poBlock = nullptr;
3350
0
                GByte *pabySrc = nullptr;
3351
0
                if (iBand == nBand)
3352
0
                {
3353
0
                    pabySrc = static_cast<GByte *>(pData);
3354
0
                }
3355
0
                else
3356
0
                {
3357
0
                    if (!(m_poTPD->m_nShiftXPixelsMod == 0 &&
3358
0
                          m_poTPD->m_nShiftYPixelsMod == 0))
3359
0
                        continue;
3360
3361
                    // If the block for this band is not dirty, it might be
3362
                    // dirty in cache
3363
0
                    if (m_poTPD->m_asCachedTilesDesc[0].abBandDirty[iBand - 1])
3364
0
                        continue;
3365
0
                    else
3366
0
                    {
3367
0
                        poBlock =
3368
0
                            cpl::down_cast<GDALGPKGMBTilesLikeRasterBand *>(
3369
0
                                poDS->GetRasterBand(iBand))
3370
0
                                ->TryGetLockedBlockRef(nBlockXOff, nBlockYOff);
3371
0
                        if (poBlock && poBlock->GetDirty())
3372
0
                        {
3373
0
                            pabySrc =
3374
0
                                static_cast<GByte *>(poBlock->GetDataRef());
3375
0
                            poBlock->MarkClean();
3376
0
                        }
3377
0
                        else
3378
0
                        {
3379
0
                            if (poBlock)
3380
0
                                poBlock->DropLock();
3381
0
                            bAllDirty = false;
3382
0
                            continue;
3383
0
                        }
3384
0
                    }
3385
0
                }
3386
3387
0
                if (m_poTPD->m_nShiftXPixelsMod == 0 &&
3388
0
                    m_poTPD->m_nShiftYPixelsMod == 0)
3389
0
                    m_poTPD->m_asCachedTilesDesc[0].abBandDirty[iBand - 1] =
3390
0
                        true;
3391
3392
0
                int nDstXOffset = 0;
3393
0
                int nDstXSize = nBlockXSize;
3394
0
                int nDstYOffset = 0;
3395
0
                int nDstYSize = nBlockYSize;
3396
                // Composite block data into tile data
3397
0
                if (m_poTPD->m_nShiftXPixelsMod == 0 &&
3398
0
                    m_poTPD->m_nShiftYPixelsMod == 0)
3399
0
                {
3400
3401
#ifdef DEBUG_VERBOSE
3402
                    if (eDataType == GDT_UInt8 &&
3403
                        nBlockXOff * nBlockXSize <=
3404
                            nRasterXSize - nBlockXSize &&
3405
                        nBlockYOff * nBlockYSize > nRasterYSize - nBlockYSize)
3406
                    {
3407
                        bool bFoundNonZero = false;
3408
                        for (int y = nRasterYSize - nBlockYOff * nBlockYSize;
3409
                             y < nBlockYSize; y++)
3410
                        {
3411
                            for (int x = 0; x < nBlockXSize; x++)
3412
                            {
3413
                                if (pabySrc[static_cast<GPtrDiff_t>(y) *
3414
                                                nBlockXSize +
3415
                                            x] != 0 &&
3416
                                    !bFoundNonZero)
3417
                                {
3418
                                    CPLDebug("GPKG",
3419
                                             "IWriteBlock(): Found non-zero "
3420
                                             "content in ghost part of "
3421
                                             "tile(nBand=%d,nBlockXOff=%d,"
3422
                                             "nBlockYOff=%d,m_nZoomLevel=%d)\n",
3423
                                             iBand, nBlockXOff, nBlockYOff,
3424
                                             m_poTPD->m_nZoomLevel);
3425
                                    bFoundNonZero = true;
3426
                                }
3427
                            }
3428
                        }
3429
                    }
3430
#endif
3431
3432
0
                    const size_t nBandBlockSize =
3433
0
                        static_cast<size_t>(nBlockXSize) * nBlockYSize *
3434
0
                        m_nDTSize;
3435
0
                    memcpy(m_poTPD->m_pabyCachedTiles +
3436
0
                               (iBand - 1) * nBandBlockSize,
3437
0
                           pabySrc, nBandBlockSize);
3438
3439
                    // Make sure partial blocks are zero'ed outside of the
3440
                    // validity area but do that only when know that JPEG will
3441
                    // not be used so as to avoid edge effects (although we
3442
                    // should probably repeat last pixels if we really want to
3443
                    // do that, but that only makes sense if readers only clip
3444
                    // to the gpkg_contents extent). Well, ere on the safe side
3445
                    // for now
3446
0
                    if (m_poTPD->m_eTF != GPKG_TF_JPEG &&
3447
0
                        (nBlockXOff * nBlockXSize >=
3448
0
                             nRasterXSize - nBlockXSize ||
3449
0
                         nBlockYOff * nBlockYSize >=
3450
0
                             nRasterYSize - nBlockYSize))
3451
0
                    {
3452
0
                        int nXEndValidity =
3453
0
                            nRasterXSize - nBlockXOff * nBlockXSize;
3454
0
                        if (nXEndValidity > nBlockXSize)
3455
0
                            nXEndValidity = nBlockXSize;
3456
0
                        int nYEndValidity =
3457
0
                            nRasterYSize - nBlockYOff * nBlockYSize;
3458
0
                        if (nYEndValidity > nBlockYSize)
3459
0
                            nYEndValidity = nBlockYSize;
3460
0
                        if (nXEndValidity < nBlockXSize)
3461
0
                        {
3462
0
                            for (int iY = 0; iY < nYEndValidity; iY++)
3463
0
                            {
3464
0
                                m_poTPD->FillBuffer(
3465
0
                                    m_poTPD->m_pabyCachedTiles +
3466
0
                                        ((static_cast<size_t>(iBand - 1) *
3467
0
                                              nBlockYSize +
3468
0
                                          iY) *
3469
0
                                             nBlockXSize +
3470
0
                                         nXEndValidity) *
3471
0
                                            m_nDTSize,
3472
0
                                    nBlockXSize - nXEndValidity);
3473
0
                            }
3474
0
                        }
3475
0
                        if (nYEndValidity < nBlockYSize)
3476
0
                        {
3477
0
                            m_poTPD->FillBuffer(
3478
0
                                m_poTPD->m_pabyCachedTiles +
3479
0
                                    (static_cast<size_t>(iBand - 1) *
3480
0
                                         nBlockYSize +
3481
0
                                     nYEndValidity) *
3482
0
                                        nBlockXSize * m_nDTSize,
3483
0
                                static_cast<size_t>(nBlockYSize -
3484
0
                                                    nYEndValidity) *
3485
0
                                    nBlockXSize);
3486
0
                        }
3487
0
                    }
3488
0
                }
3489
0
                else
3490
0
                {
3491
0
                    const int nXValid =
3492
0
                        (nBlockXOff * nBlockXSize > nRasterXSize - nBlockXSize)
3493
0
                            ? (nRasterXSize - nBlockXOff * nBlockXSize)
3494
0
                            : nBlockXSize;
3495
0
                    const int nYValid =
3496
0
                        (nBlockYOff * nBlockYSize > nRasterYSize - nBlockYSize)
3497
0
                            ? (nRasterYSize - nBlockYOff * nBlockYSize)
3498
0
                            : nBlockYSize;
3499
3500
0
                    int nSrcXOffset = 0;
3501
0
                    if (nCol == nColMin)
3502
0
                    {
3503
0
                        nDstXOffset = m_poTPD->m_nShiftXPixelsMod;
3504
0
                        nDstXSize = std::min(
3505
0
                            nXValid, nBlockXSize - m_poTPD->m_nShiftXPixelsMod);
3506
0
                    }
3507
0
                    else
3508
0
                    {
3509
0
                        nDstXOffset = 0;
3510
0
                        if (nXValid > nBlockXSize - m_poTPD->m_nShiftXPixelsMod)
3511
0
                        {
3512
0
                            nDstXSize = nXValid - (nBlockXSize -
3513
0
                                                   m_poTPD->m_nShiftXPixelsMod);
3514
0
                        }
3515
0
                        else
3516
0
                            nDstXSize = 0;
3517
0
                        nSrcXOffset = nBlockXSize - m_poTPD->m_nShiftXPixelsMod;
3518
0
                    }
3519
3520
0
                    int nSrcYOffset = 0;
3521
0
                    if (nRow == nRowMin)
3522
0
                    {
3523
0
                        nDstYOffset = m_poTPD->m_nShiftYPixelsMod;
3524
0
                        nDstYSize = std::min(
3525
0
                            nYValid, nBlockYSize - m_poTPD->m_nShiftYPixelsMod);
3526
0
                    }
3527
0
                    else
3528
0
                    {
3529
0
                        nDstYOffset = 0;
3530
0
                        if (nYValid > nBlockYSize - m_poTPD->m_nShiftYPixelsMod)
3531
0
                        {
3532
0
                            nDstYSize = nYValid - (nBlockYSize -
3533
0
                                                   m_poTPD->m_nShiftYPixelsMod);
3534
0
                        }
3535
0
                        else
3536
0
                            nDstYSize = 0;
3537
0
                        nSrcYOffset = nBlockYSize - m_poTPD->m_nShiftYPixelsMod;
3538
0
                    }
3539
3540
#ifdef DEBUG_VERBOSE
3541
                    CPLDebug("GPKG",
3542
                             "Copy source tile x=%d,w=%d,y=%d,h=%d into "
3543
                             "buffer at x=%d,y=%d",
3544
                             nDstXOffset, nDstXSize, nDstYOffset, nDstYSize,
3545
                             nSrcXOffset, nSrcYOffset);
3546
#endif
3547
3548
0
                    if (nDstXSize > 0 && nDstYSize > 0)
3549
0
                    {
3550
0
                        for (GPtrDiff_t y = 0; y < nDstYSize; y++)
3551
0
                        {
3552
0
                            GByte *pDst = m_poTPD->m_pabyCachedTiles +
3553
0
                                          (static_cast<size_t>(iBand - 1) *
3554
0
                                               nBlockXSize * nBlockYSize +
3555
0
                                           (y + nDstYOffset) * nBlockXSize +
3556
0
                                           nDstXOffset) *
3557
0
                                              m_nDTSize;
3558
0
                            GByte *pSrc =
3559
0
                                pabySrc + ((y + nSrcYOffset) * nBlockXSize +
3560
0
                                           nSrcXOffset) *
3561
0
                                              m_nDTSize;
3562
0
                            GDALCopyWords(pSrc, eDataType, m_nDTSize, pDst,
3563
0
                                          eDataType, m_nDTSize, nDstXSize);
3564
0
                        }
3565
0
                    }
3566
0
                }
3567
3568
0
                if (poBlock)
3569
0
                    poBlock->DropLock();
3570
3571
0
                if (!(m_poTPD->m_nShiftXPixelsMod == 0 &&
3572
0
                      m_poTPD->m_nShiftYPixelsMod == 0))
3573
0
                {
3574
0
                    m_poTPD->m_asCachedTilesDesc[0].nRow = -1;
3575
0
                    m_poTPD->m_asCachedTilesDesc[0].nCol = -1;
3576
0
                    m_poTPD->m_asCachedTilesDesc[0].nIdxWithinTileData = -1;
3577
0
                    if (nDstXSize > 0 && nDstYSize > 0)
3578
0
                    {
3579
0
                        eErr = m_poTPD->WriteShiftedTile(
3580
0
                            nRow, nCol, iBand, nDstXOffset, nDstYOffset,
3581
0
                            nDstXSize, nDstYSize);
3582
0
                    }
3583
0
                }
3584
0
            }
3585
3586
0
            if (m_poTPD->m_nShiftXPixelsMod == 0 &&
3587
0
                m_poTPD->m_nShiftYPixelsMod == 0)
3588
0
            {
3589
0
                if (bAllDirty)
3590
0
                {
3591
0
                    eErr = m_poTPD->WriteTile();
3592
0
                }
3593
0
            }
3594
0
        }
3595
0
    }
3596
3597
0
    return eErr;
3598
0
}
3599
3600
/************************************************************************/
3601
/*                           GetNoDataValue()                           */
3602
/************************************************************************/
3603
3604
double GDALGPKGMBTilesLikeRasterBand::GetNoDataValue(int *pbSuccess)
3605
210
{
3606
210
    if (m_bHasNoData)
3607
0
    {
3608
0
        if (pbSuccess)
3609
0
            *pbSuccess = TRUE;
3610
0
        return m_dfNoDataValue;
3611
0
    }
3612
210
    return GDALPamRasterBand::GetNoDataValue(pbSuccess);
3613
210
}
3614
3615
/************************************************************************/
3616
/*                       SetNoDataValueInternal()                       */
3617
/************************************************************************/
3618
3619
void GDALGPKGMBTilesLikeRasterBand::SetNoDataValueInternal(double dfNoDataValue)
3620
0
{
3621
0
    m_bHasNoData = true;
3622
0
    m_dfNoDataValue = dfNoDataValue;
3623
0
}
3624
3625
/************************************************************************/
3626
/*                      GDALGeoPackageRasterBand()                      */
3627
/************************************************************************/
3628
3629
GDALGeoPackageRasterBand::GDALGeoPackageRasterBand(
3630
    GDALGeoPackageDataset *poDSIn, int nTileWidth, int nTileHeight)
3631
62
    : GDALGPKGMBTilesLikeRasterBand(poDSIn, nTileWidth, nTileHeight)
3632
62
{
3633
62
    poDS = poDSIn;
3634
62
}
3635
3636
/************************************************************************/
3637
/*                          GetOverviewCount()                          */
3638
/************************************************************************/
3639
3640
int GDALGeoPackageRasterBand::GetOverviewCount()
3641
19
{
3642
19
    GDALGeoPackageDataset *poGDS =
3643
19
        cpl::down_cast<GDALGeoPackageDataset *>(poDS);
3644
19
    return static_cast<int>(poGDS->m_apoOverviewDS.size());
3645
19
}
3646
3647
/************************************************************************/
3648
/*                          GetOverviewCount()                          */
3649
/************************************************************************/
3650
3651
GDALRasterBand *GDALGeoPackageRasterBand::GetOverview(int nIdx)
3652
1
{
3653
1
    GDALGeoPackageDataset *poGDS =
3654
1
        cpl::down_cast<GDALGeoPackageDataset *>(poDS);
3655
1
    if (nIdx < 0 || nIdx >= static_cast<int>(poGDS->m_apoOverviewDS.size()))
3656
0
        return nullptr;
3657
1
    return poGDS->m_apoOverviewDS[nIdx]->GetRasterBand(nBand);
3658
1
}
3659
3660
/************************************************************************/
3661
/*                           SetNoDataValue()                           */
3662
/************************************************************************/
3663
3664
CPLErr GDALGeoPackageRasterBand::SetNoDataValue(double dfNoDataValue)
3665
0
{
3666
0
    GDALGeoPackageDataset *poGDS =
3667
0
        cpl::down_cast<GDALGeoPackageDataset *>(poDS);
3668
3669
0
    if (eDataType == GDT_UInt8)
3670
0
    {
3671
0
        if (!(dfNoDataValue >= 0 && dfNoDataValue <= 255 &&
3672
0
              static_cast<int>(dfNoDataValue) == dfNoDataValue))
3673
0
        {
3674
0
            CPLError(CE_Failure, CPLE_NotSupported,
3675
0
                     "Invalid nodata value for a Byte band: %.17g",
3676
0
                     dfNoDataValue);
3677
0
            return CE_Failure;
3678
0
        }
3679
3680
0
        for (int i = 1; i <= poGDS->nBands; ++i)
3681
0
        {
3682
0
            if (i != nBand)
3683
0
            {
3684
0
                int bHasNoData = FALSE;
3685
0
                double dfOtherNoDataValue =
3686
0
                    poGDS->GetRasterBand(i)->GetNoDataValue(&bHasNoData);
3687
0
                if (bHasNoData && dfOtherNoDataValue != dfNoDataValue)
3688
0
                {
3689
0
                    CPLError(
3690
0
                        CE_Failure, CPLE_NotSupported,
3691
0
                        "Only the same nodata value can be set on all bands");
3692
0
                    return CE_Failure;
3693
0
                }
3694
0
            }
3695
0
        }
3696
3697
0
        SetNoDataValueInternal(dfNoDataValue);
3698
0
        poGDS->m_bMetadataDirty = true;
3699
0
        return CE_None;
3700
0
    }
3701
3702
0
    if (std::isnan(dfNoDataValue))
3703
0
    {
3704
0
        CPLError(CE_Warning, CPLE_NotSupported,
3705
0
                 "A NaN nodata value cannot be recorded in "
3706
0
                 "gpkg_2d_gridded_coverage_ancillary table");
3707
0
    }
3708
3709
0
    SetNoDataValueInternal(dfNoDataValue);
3710
3711
0
    char *pszSQL = sqlite3_mprintf(
3712
0
        "UPDATE gpkg_2d_gridded_coverage_ancillary SET data_null = ? "
3713
0
        "WHERE tile_matrix_set_name = '%q'",
3714
0
        poGDS->m_osRasterTable.c_str());
3715
0
    sqlite3_stmt *hStmt = nullptr;
3716
0
    int rc = SQLPrepareWithError(poGDS->IGetDB(), pszSQL, -1, &hStmt, nullptr);
3717
0
    if (rc == SQLITE_OK)
3718
0
    {
3719
0
        if (poGDS->m_eTF == GPKG_TF_PNG_16BIT)
3720
0
        {
3721
0
            if (eDataType == GDT_UInt16 && poGDS->m_dfOffset == 0.0 &&
3722
0
                poGDS->m_dfScale == 1.0 && dfNoDataValue >= 0 &&
3723
0
                dfNoDataValue <= 65535 &&
3724
0
                static_cast<GUInt16>(dfNoDataValue) == dfNoDataValue)
3725
0
            {
3726
0
                poGDS->m_usGPKGNull = static_cast<GUInt16>(dfNoDataValue);
3727
0
            }
3728
0
            else
3729
0
            {
3730
0
                poGDS->m_usGPKGNull = 65535;
3731
0
            }
3732
0
            sqlite3_bind_double(hStmt, 1, poGDS->m_usGPKGNull);
3733
0
        }
3734
0
        else
3735
0
        {
3736
0
            sqlite3_bind_double(hStmt, 1, static_cast<float>(dfNoDataValue));
3737
0
        }
3738
0
        rc = sqlite3_step(hStmt);
3739
0
        sqlite3_finalize(hStmt);
3740
0
    }
3741
0
    sqlite3_free(pszSQL);
3742
3743
0
    return (rc == SQLITE_OK) ? CE_None : CE_Failure;
3744
0
}
3745
3746
/************************************************************************/
3747
/*                          LoadBandMetadata()                          */
3748
/************************************************************************/
3749
3750
void GDALGeoPackageRasterBand::LoadBandMetadata()
3751
38
{
3752
38
    GDALGeoPackageDataset *poGDS =
3753
38
        cpl::down_cast<GDALGeoPackageDataset *>(poDS);
3754
3755
38
    if (m_bHasReadMetadataFromStorage)
3756
19
        return;
3757
3758
19
    m_bHasReadMetadataFromStorage = true;
3759
3760
19
    poGDS->TryLoadXML();
3761
3762
19
    if (!poGDS->HasMetadataTables())
3763
17
        return;
3764
3765
2
    char *pszSQL = sqlite3_mprintf(
3766
2
        "SELECT md.metadata, md.md_standard_uri, md.mime_type "
3767
2
        "FROM gpkg_metadata md "
3768
2
        "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
3769
2
        "WHERE "
3770
2
        "mdr.reference_scope = 'table' AND lower(mdr.table_name) = "
3771
2
        "lower('%q') ORDER BY md.id "
3772
2
        "LIMIT 1000",  // to avoid denial of service
3773
2
        poGDS->m_osRasterTable.c_str());
3774
3775
2
    auto oResult = SQLQuery(poGDS->hDB, pszSQL);
3776
2
    sqlite3_free(pszSQL);
3777
2
    if (!oResult)
3778
0
    {
3779
0
        return;
3780
0
    }
3781
3782
    /* GDAL metadata */
3783
4
    for (int i = 0; i < oResult->RowCount(); i++)
3784
2
    {
3785
2
        const char *pszMetadata = oResult->GetValue(0, i);
3786
2
        const char *pszMDStandardURI = oResult->GetValue(1, i);
3787
2
        const char *pszMimeType = oResult->GetValue(2, i);
3788
2
        if (pszMetadata && pszMDStandardURI && pszMimeType &&
3789
2
            EQUAL(pszMDStandardURI, "http://gdal.org") &&
3790
2
            EQUAL(pszMimeType, "text/xml"))
3791
2
        {
3792
2
            CPLXMLNode *psXMLNode = CPLParseXMLString(pszMetadata);
3793
2
            if (psXMLNode)
3794
2
            {
3795
2
                GDALMultiDomainMetadata oLocalMDMD;
3796
2
                oLocalMDMD.XMLInit(psXMLNode, FALSE);
3797
3798
2
                CSLConstList papszDomainList = oLocalMDMD.GetDomainList();
3799
2
                for (CSLConstList papszIter = papszDomainList;
3800
6
                     papszIter && *papszIter; ++papszIter)
3801
4
                {
3802
4
                    if (STARTS_WITH(*papszIter, "BAND_"))
3803
0
                    {
3804
0
                        int l_nBand = atoi(*papszIter + strlen("BAND_"));
3805
0
                        if (l_nBand >= 1 && l_nBand <= poGDS->GetRasterCount())
3806
0
                        {
3807
0
                            auto l_poBand =
3808
0
                                cpl::down_cast<GDALGeoPackageRasterBand *>(
3809
0
                                    poGDS->GetRasterBand(l_nBand));
3810
0
                            l_poBand->m_bHasReadMetadataFromStorage = true;
3811
3812
0
                            char **papszMD = CSLDuplicate(
3813
0
                                oLocalMDMD.GetMetadata(*papszIter));
3814
0
                            papszMD = CSLMerge(
3815
0
                                papszMD,
3816
0
                                GDALGPKGMBTilesLikeRasterBand::GetMetadata(""));
3817
0
                            l_poBand->GDALPamRasterBand::SetMetadata(papszMD);
3818
0
                            CSLDestroy(papszMD);
3819
0
                        }
3820
0
                    }
3821
4
                }
3822
3823
2
                CPLDestroyXMLNode(psXMLNode);
3824
2
            }
3825
2
        }
3826
2
    }
3827
2
}
3828
3829
/************************************************************************/
3830
/*                            GetMetadata()                             */
3831
/************************************************************************/
3832
3833
CSLConstList GDALGeoPackageRasterBand::GetMetadata(const char *pszDomain)
3834
19
{
3835
19
    GDALGeoPackageDataset *poGDS =
3836
19
        cpl::down_cast<GDALGeoPackageDataset *>(poDS);
3837
19
    LoadBandMetadata(); /* force loading from storage if needed */
3838
3839
19
    if (poGDS->eAccess == GA_ReadOnly && eDataType != GDT_UInt8 &&
3840
4
        (pszDomain == nullptr || EQUAL(pszDomain, "")) &&
3841
4
        !m_bMinMaxComputedFromTileAncillary &&
3842
4
        !GDALGPKGMBTilesLikeRasterBand::GetMetadataItem("STATISTICS_MINIMUM") &&
3843
4
        !GDALGPKGMBTilesLikeRasterBand::GetMetadataItem("STATISTICS_MAXIMUM"))
3844
4
    {
3845
4
        m_bMinMaxComputedFromTileAncillary = true;
3846
3847
4
        const int nColMin = poGDS->m_nShiftXTiles;
3848
4
        const int nColMax =
3849
4
            (nRasterXSize - 1 + poGDS->m_nShiftXPixelsMod) / nBlockXSize +
3850
4
            poGDS->m_nShiftXTiles;
3851
4
        const int nRowMin = poGDS->m_nShiftYTiles;
3852
4
        const int nRowMax =
3853
4
            (nRasterYSize - 1 + poGDS->m_nShiftYPixelsMod) / nBlockYSize +
3854
4
            poGDS->m_nShiftYTiles;
3855
3856
4
        bool bOK = false;
3857
4
        if (poGDS->m_nShiftXPixelsMod == 0 && poGDS->m_nShiftYPixelsMod == 0 &&
3858
4
            (nRasterXSize % nBlockXSize) == 0 &&
3859
4
            (nRasterYSize % nBlockYSize) == 0)
3860
4
        {
3861
            // If the area of interest matches entire tiles, then we can
3862
            // use tile statistics
3863
4
            bOK = true;
3864
4
        }
3865
0
        else if (m_bHasNoData)
3866
0
        {
3867
            // Otherwise, in the case where we have nodata, we assume that
3868
            // if the area of interest is at least larger than the existing
3869
            // tiles, the tile statistics will be reliable.
3870
0
            char *pszSQL = sqlite3_mprintf(
3871
0
                "SELECT MIN(tile_column), MAX(tile_column), "
3872
0
                "MIN(tile_row), MAX(tile_row) FROM \"%w\" "
3873
0
                "WHERE zoom_level = %d",
3874
0
                poGDS->m_osRasterTable.c_str(), poGDS->m_nZoomLevel);
3875
0
            auto sResult = SQLQuery(poGDS->IGetDB(), pszSQL);
3876
0
            if (sResult && sResult->RowCount() == 1)
3877
0
            {
3878
0
                const char *pszMinX = sResult->GetValue(0, 0);
3879
0
                const char *pszMaxX = sResult->GetValue(1, 0);
3880
0
                const char *pszMinY = sResult->GetValue(2, 0);
3881
0
                const char *pszMaxY = sResult->GetValue(3, 0);
3882
0
                if (pszMinX && pszMaxX && pszMinY && pszMaxY)
3883
0
                {
3884
0
                    bOK = atoi(pszMinX) >= nColMin &&
3885
0
                          atoi(pszMaxX) <= nColMax &&
3886
0
                          atoi(pszMinY) >= nRowMin && atoi(pszMaxY) <= nRowMax;
3887
0
                }
3888
0
            }
3889
0
            sqlite3_free(pszSQL);
3890
0
        }
3891
3892
4
        if (bOK)
3893
4
        {
3894
4
            char *pszSQL = sqlite3_mprintf(
3895
4
                "SELECT MIN(min), MAX(max) FROM "
3896
4
                "gpkg_2d_gridded_tile_ancillary WHERE tpudt_id "
3897
4
                "IN (SELECT id FROM \"%w\" WHERE "
3898
4
                "zoom_level = %d AND "
3899
4
                "tile_column >= %d AND tile_column <= %d AND "
3900
4
                "tile_row >= %d AND tile_row <= %d)",
3901
4
                poGDS->m_osRasterTable.c_str(), poGDS->m_nZoomLevel, nColMin,
3902
4
                nColMax, nRowMin, nRowMax);
3903
4
            auto sResult = SQLQuery(poGDS->IGetDB(), pszSQL);
3904
4
            CPLDebug("GPKG", "%s", pszSQL);
3905
4
            if (sResult && sResult->RowCount() == 1)
3906
3
            {
3907
3
                const char *pszMin = sResult->GetValue(0, 0);
3908
3
                const char *pszMax = sResult->GetValue(1, 0);
3909
3
                if (pszMin)
3910
3
                {
3911
3
                    m_dfStatsMinFromTileAncillary = CPLAtof(pszMin);
3912
3
                }
3913
3
                if (pszMax)
3914
3
                {
3915
3
                    m_dfStatsMaxFromTileAncillary = CPLAtof(pszMax);
3916
3
                }
3917
3
            }
3918
4
            sqlite3_free(pszSQL);
3919
4
        }
3920
4
    }
3921
3922
19
    if (m_bAddImplicitStatistics && m_bMinMaxComputedFromTileAncillary &&
3923
4
        (pszDomain == nullptr || EQUAL(pszDomain, "")) &&
3924
4
        !GDALGPKGMBTilesLikeRasterBand::GetMetadataItem("STATISTICS_MINIMUM") &&
3925
4
        !GDALGPKGMBTilesLikeRasterBand::GetMetadataItem("STATISTICS_MAXIMUM"))
3926
4
    {
3927
4
        m_aosMD.Assign(CSLDuplicate(
3928
4
            GDALGPKGMBTilesLikeRasterBand::GetMetadata(pszDomain)));
3929
4
        if (!std::isnan(m_dfStatsMinFromTileAncillary))
3930
3
        {
3931
3
            m_aosMD.SetNameValue(
3932
3
                "STATISTICS_MINIMUM",
3933
3
                CPLSPrintf("%.14g", m_dfStatsMinFromTileAncillary));
3934
3
        }
3935
4
        if (!std::isnan(m_dfStatsMaxFromTileAncillary))
3936
3
        {
3937
3
            m_aosMD.SetNameValue(
3938
3
                "STATISTICS_MAXIMUM",
3939
3
                CPLSPrintf("%.14g", m_dfStatsMaxFromTileAncillary));
3940
3
        }
3941
4
        return m_aosMD.List();
3942
4
    }
3943
3944
15
    return GDALGPKGMBTilesLikeRasterBand::GetMetadata(pszDomain);
3945
19
}
3946
3947
/************************************************************************/
3948
/*                          GetMetadataItem()                           */
3949
/************************************************************************/
3950
3951
const char *GDALGeoPackageRasterBand::GetMetadataItem(const char *pszName,
3952
                                                      const char *pszDomain)
3953
19
{
3954
19
    LoadBandMetadata(); /* force loading from storage if needed */
3955
3956
19
    if (m_bAddImplicitStatistics && eDataType != GDT_UInt8 &&
3957
4
        (pszDomain == nullptr || EQUAL(pszDomain, "")) &&
3958
4
        (EQUAL(pszName, "STATISTICS_MINIMUM") ||
3959
4
         EQUAL(pszName, "STATISTICS_MAXIMUM")))
3960
0
    {
3961
0
        return CSLFetchNameValue(GetMetadata(), pszName);
3962
0
    }
3963
3964
19
    return GDALGPKGMBTilesLikeRasterBand::GetMetadataItem(pszName, pszDomain);
3965
19
}
3966
3967
/************************************************************************/
3968
/*                            SetMetadata()                             */
3969
/************************************************************************/
3970
3971
CPLErr GDALGeoPackageRasterBand::SetMetadata(CSLConstList papszMetadata,
3972
                                             const char *pszDomain)
3973
0
{
3974
0
    GDALGeoPackageDataset *poGDS =
3975
0
        cpl::down_cast<GDALGeoPackageDataset *>(poDS);
3976
0
    LoadBandMetadata(); /* force loading from storage if needed */
3977
0
    poGDS->m_bMetadataDirty = true;
3978
0
    for (CSLConstList papszIter = papszMetadata; papszIter && *papszIter;
3979
0
         ++papszIter)
3980
0
    {
3981
0
        if (STARTS_WITH(*papszIter, "STATISTICS_"))
3982
0
            m_bStatsMetadataSetInThisSession = true;
3983
0
    }
3984
0
    return GDALPamRasterBand::SetMetadata(papszMetadata, pszDomain);
3985
0
}
3986
3987
/************************************************************************/
3988
/*                          SetMetadataItem()                           */
3989
/************************************************************************/
3990
3991
CPLErr GDALGeoPackageRasterBand::SetMetadataItem(const char *pszName,
3992
                                                 const char *pszValue,
3993
                                                 const char *pszDomain)
3994
0
{
3995
0
    GDALGeoPackageDataset *poGDS =
3996
0
        cpl::down_cast<GDALGeoPackageDataset *>(poDS);
3997
0
    LoadBandMetadata(); /* force loading from storage if needed */
3998
0
    poGDS->m_bMetadataDirty = true;
3999
0
    if (STARTS_WITH(pszName, "STATISTICS_"))
4000
0
        m_bStatsMetadataSetInThisSession = true;
4001
0
    return GDALPamRasterBand::SetMetadataItem(pszName, pszValue, pszDomain);
4002
0
}
4003
4004
/************************************************************************/
4005
/*                        InvalidateStatistics()                        */
4006
/************************************************************************/
4007
4008
void GDALGeoPackageRasterBand::InvalidateStatistics()
4009
0
{
4010
0
    bool bModified = false;
4011
0
    CPLStringList aosMD(CSLDuplicate(GetMetadata()));
4012
0
    for (CSLConstList papszIter = GetMetadata(); papszIter && *papszIter;
4013
0
         ++papszIter)
4014
0
    {
4015
0
        if (STARTS_WITH(*papszIter, "STATISTICS_"))
4016
0
        {
4017
0
            char *pszKey = nullptr;
4018
0
            CPLParseNameValue(*papszIter, &pszKey);
4019
0
            CPLAssert(pszKey);
4020
0
            aosMD.SetNameValue(pszKey, nullptr);
4021
0
            CPLFree(pszKey);
4022
0
            bModified = true;
4023
0
        }
4024
0
    }
4025
0
    if (bModified)
4026
0
        SetMetadata(aosMD.List());
4027
0
}