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/ogrgeopackagedriver.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GeoPackage Translator
4
 * Purpose:  Implements GeoPackageDriver.
5
 * Author:   Paul Ramsey <pramsey@boundlessgeo.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2013, Paul Ramsey <pramsey@boundlessgeo.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "gdalpython.h"
14
#include "gdalsubdatasetinfo.h"
15
#include "ogr_geopackage.h"
16
17
#include "tilematrixset.hpp"
18
#include "gdalalgorithm.h"
19
20
#include <cctype>
21
#include <mutex>
22
23
using namespace GDALPy;
24
25
// g++ -g -Wall -fPIC -shared -o ogr_geopackage.so -Iport -Igcore -Iogr
26
// -Iogr/ogrsf_frmts -Iogr/ogrsf_frmts/gpkg ogr/ogrsf_frmts/gpkg/*.c* -L. -lgdal
27
28
static inline bool ENDS_WITH_CI(const char *a, const char *b)
29
40
{
30
40
    return strlen(a) >= strlen(b) && EQUAL(a + strlen(a) - strlen(b), b);
31
40
}
32
33
/************************************************************************/
34
/*                    OGRGeoPackageDriverIdentify()                     */
35
/************************************************************************/
36
37
static int OGRGeoPackageDriverIdentify(GDALOpenInfo *poOpenInfo,
38
                                       std::string &osFilenameInGpkgZip,
39
                                       bool bEmitWarning)
40
581k
{
41
581k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GPKG:"))
42
1.14k
        return TRUE;
43
44
580k
#ifdef ENABLE_SQL_GPKG_FORMAT
45
580k
    if (poOpenInfo->pabyHeader &&
46
141k
        STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
47
580k
                    "-- SQL GPKG"))
48
4.71k
    {
49
4.71k
        return TRUE;
50
4.71k
    }
51
575k
#endif
52
53
    // Try to recognize "foo.gpkg.zip"
54
575k
    const size_t nFilenameLen = strlen(poOpenInfo->pszFilename);
55
575k
    if ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) == 0 &&
56
575k
        nFilenameLen > strlen(".gpkg.zip") &&
57
555k
        !STARTS_WITH(poOpenInfo->pszFilename, "/vsizip/") &&
58
554k
        EQUAL(poOpenInfo->pszFilename + nFilenameLen - strlen(".gpkg.zip"),
59
575k
              ".gpkg.zip"))
60
1.45k
    {
61
1.45k
        int nCountGpkg = 0;
62
1.45k
        const CPLStringList aosFiles(VSIReadDirEx(
63
1.45k
            (std::string("/vsizip/") + poOpenInfo->pszFilename).c_str(), 1000));
64
1.83k
        for (int i = 0; i < aosFiles.size(); ++i)
65
377
        {
66
377
            const size_t nLen = strlen(aosFiles[i]);
67
377
            if (nLen > strlen(".gpkg") &&
68
369
                EQUAL(aosFiles[i] + nLen - strlen(".gpkg"), ".gpkg"))
69
360
            {
70
360
                osFilenameInGpkgZip = aosFiles[i];
71
360
                nCountGpkg++;
72
360
                if (nCountGpkg == 2)
73
0
                    return FALSE;
74
360
            }
75
377
        }
76
1.45k
        return nCountGpkg == 1;
77
1.45k
    }
78
79
574k
    if (poOpenInfo->nHeaderBytes < 100 || poOpenInfo->pabyHeader == nullptr ||
80
123k
        !STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
81
574k
                     "SQLite format 3"))
82
573k
    {
83
573k
        return FALSE;
84
573k
    }
85
86
    /* Requirement 3: File name has to end in "gpkg" */
87
    /* http://opengis.github.io/geopackage/#_file_extension_name */
88
    /* But be tolerant, if the GPKG application id is found, because some */
89
    /* producers don't necessarily honour that requirement (#6396) */
90
586
    const char *pszExt = poOpenInfo->osExtension.c_str();
91
586
    const bool bIsRecognizedExtension =
92
586
        EQUAL(pszExt, "GPKG") || EQUAL(pszExt, "GPKX");
93
94
    /* Requirement 2: application id */
95
    /* http://opengis.github.io/geopackage/#_file_format */
96
    /* Be tolerant since some datasets don't actually follow that requirement */
97
586
    GUInt32 nApplicationId;
98
586
    memcpy(&nApplicationId, poOpenInfo->pabyHeader + knApplicationIdPos, 4);
99
586
    nApplicationId = CPL_MSBWORD32(nApplicationId);
100
586
    GUInt32 nUserVersion;
101
586
    memcpy(&nUserVersion, poOpenInfo->pabyHeader + knUserVersionPos, 4);
102
586
    nUserVersion = CPL_MSBWORD32(nUserVersion);
103
586
    if (nApplicationId != GP10_APPLICATION_ID &&
104
576
        nApplicationId != GP11_APPLICATION_ID &&
105
576
        nApplicationId != GPKG_APPLICATION_ID)
106
501
    {
107
#ifdef DEBUG
108
        if (EQUAL(CPLGetFilename(poOpenInfo->pszFilename), ".cur_input"))
109
        {
110
            return FALSE;
111
        }
112
#endif
113
501
        if (!bIsRecognizedExtension)
114
501
            return FALSE;
115
116
0
        if (bEmitWarning)
117
0
        {
118
0
            GByte abySignature[4 + 1];
119
0
            memcpy(abySignature, poOpenInfo->pabyHeader + knApplicationIdPos,
120
0
                   4);
121
0
            abySignature[4] = '\0';
122
123
            /* Is this a GPxx version ? */
124
0
            const bool bWarn = CPLTestBool(CPLGetConfigOption(
125
0
                "GPKG_WARN_UNRECOGNIZED_APPLICATION_ID", "YES"));
126
0
            if (bWarn)
127
0
            {
128
0
                CPLError(CE_Warning, CPLE_AppDefined,
129
0
                         "GPKG: bad application_id=0x%02X%02X%02X%02X on '%s'",
130
0
                         abySignature[0], abySignature[1], abySignature[2],
131
0
                         abySignature[3], poOpenInfo->pszFilename);
132
0
            }
133
0
            else
134
0
            {
135
0
                CPLDebug("GPKG",
136
0
                         "bad application_id=0x%02X%02X%02X%02X on '%s'",
137
0
                         abySignature[0], abySignature[1], abySignature[2],
138
0
                         abySignature[3], poOpenInfo->pszFilename);
139
0
            }
140
0
        }
141
0
    }
142
85
    else if (nApplicationId == GPKG_APPLICATION_ID &&
143
             // Accept any 102XX version
144
75
             !((nUserVersion >= GPKG_1_2_VERSION &&
145
74
                nUserVersion < GPKG_1_2_VERSION + 99) ||
146
               // Accept any 103XX version
147
33
               (nUserVersion >= GPKG_1_3_VERSION &&
148
32
                nUserVersion < GPKG_1_3_VERSION + 99) ||
149
               // Accept any 104XX version
150
33
               (nUserVersion >= GPKG_1_4_VERSION &&
151
32
                nUserVersion < GPKG_1_4_VERSION + 99)))
152
1
    {
153
#ifdef DEBUG
154
        if (EQUAL(CPLGetFilename(poOpenInfo->pszFilename), ".cur_input"))
155
        {
156
            return FALSE;
157
        }
158
#endif
159
1
        if (!bIsRecognizedExtension)
160
1
            return FALSE;
161
162
0
        if (bEmitWarning)
163
0
        {
164
0
            GByte abySignature[4 + 1];
165
0
            memcpy(abySignature, poOpenInfo->pabyHeader + knUserVersionPos, 4);
166
0
            abySignature[4] = '\0';
167
168
0
            const bool bWarn = CPLTestBool(CPLGetConfigOption(
169
0
                "GPKG_WARN_UNRECOGNIZED_APPLICATION_ID", "YES"));
170
0
            if (bWarn)
171
0
            {
172
0
                if (nUserVersion > GPKG_1_4_VERSION)
173
0
                {
174
0
                    CPLError(CE_Warning, CPLE_AppDefined,
175
0
                             "This version of GeoPackage "
176
0
                             "user_version=0x%02X%02X%02X%02X "
177
0
                             "(%u, v%d.%d.%d) on '%s' may only be "
178
0
                             "partially supported",
179
0
                             abySignature[0], abySignature[1], abySignature[2],
180
0
                             abySignature[3], nUserVersion,
181
0
                             nUserVersion / 10000, (nUserVersion % 10000) / 100,
182
0
                             nUserVersion % 100, poOpenInfo->pszFilename);
183
0
                }
184
0
                else
185
0
                {
186
0
                    CPLError(CE_Warning, CPLE_AppDefined,
187
0
                             "GPKG: unrecognized user_version="
188
0
                             "0x%02X%02X%02X%02X (%u) on '%s'",
189
0
                             abySignature[0], abySignature[1], abySignature[2],
190
0
                             abySignature[3], nUserVersion,
191
0
                             poOpenInfo->pszFilename);
192
0
                }
193
0
            }
194
0
            else
195
0
            {
196
0
                if (nUserVersion > GPKG_1_4_VERSION)
197
0
                {
198
0
                    CPLDebug("GPKG",
199
0
                             "This version of GeoPackage "
200
0
                             "user_version=0x%02X%02X%02X%02X "
201
0
                             "(%u, v%d.%d.%d) on '%s' may only be "
202
0
                             "partially supported",
203
0
                             abySignature[0], abySignature[1], abySignature[2],
204
0
                             abySignature[3], nUserVersion,
205
0
                             nUserVersion / 10000, (nUserVersion % 10000) / 100,
206
0
                             nUserVersion % 100, poOpenInfo->pszFilename);
207
0
                }
208
0
                else
209
0
                {
210
0
                    CPLDebug("GPKG",
211
0
                             "unrecognized user_version=0x%02X%02X%02X%02X"
212
0
                             "(%u) on '%s'",
213
0
                             abySignature[0], abySignature[1], abySignature[2],
214
0
                             abySignature[3], nUserVersion,
215
0
                             poOpenInfo->pszFilename);
216
0
                }
217
0
            }
218
0
        }
219
0
    }
220
84
    else if (!bIsRecognizedExtension
221
#ifdef DEBUG
222
             && !EQUAL(CPLGetFilename(poOpenInfo->pszFilename), ".cur_input")
223
#endif
224
76
             && !(STARTS_WITH(poOpenInfo->pszFilename, "/vsizip/") &&
225
0
                  poOpenInfo->IsExtensionEqualToCI("zip")) &&
226
76
             !STARTS_WITH(poOpenInfo->pszFilename, "/vsigzip/"))
227
76
    {
228
76
        if (bEmitWarning)
229
38
        {
230
38
            CPLError(CE_Warning, CPLE_AppDefined,
231
38
                     "File %s has GPKG application_id, but non conformant file "
232
38
                     "extension",
233
38
                     poOpenInfo->pszFilename);
234
38
        }
235
76
    }
236
237
84
    if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
238
40
        ENDS_WITH_CI(poOpenInfo->pszFilename, ".gti.gpkg"))
239
0
    {
240
        // Most likely handled by GTI driver, but we can't be sure
241
0
        return GDAL_IDENTIFY_UNKNOWN;
242
0
    }
243
244
84
    return TRUE;
245
84
}
246
247
static int OGRGeoPackageDriverIdentify(GDALOpenInfo *poOpenInfo)
248
578k
{
249
578k
    std::string osIgnored;
250
578k
    return OGRGeoPackageDriverIdentify(poOpenInfo, osIgnored, false);
251
578k
}
252
253
/************************************************************************/
254
/*                OGRGeoPackageDriverGetSubdatasetInfo()                */
255
/************************************************************************/
256
257
struct OGRGeoPackageDriverSubdatasetInfo final : public GDALSubdatasetInfo
258
{
259
  public:
260
    explicit OGRGeoPackageDriverSubdatasetInfo(const std::string &fileName)
261
0
        : GDALSubdatasetInfo(fileName)
262
0
    {
263
0
    }
264
265
    // GDALSubdatasetInfo interface
266
  private:
267
    void parseFileName() override;
268
};
269
270
void OGRGeoPackageDriverSubdatasetInfo::parseFileName()
271
0
{
272
0
    if (!STARTS_WITH_CI(m_fileName.c_str(), "GPKG:"))
273
0
    {
274
0
        return;
275
0
    }
276
277
0
    CPLStringList aosParts{CSLTokenizeString2(m_fileName.c_str(), ":", 0)};
278
0
    const int iPartsCount{CSLCount(aosParts)};
279
280
0
    if (iPartsCount == 3 || iPartsCount == 4)
281
0
    {
282
283
0
        m_driverPrefixComponent = aosParts[0];
284
285
0
        int subdatasetIndex{2};
286
0
        const bool hasDriveLetter{
287
0
            strlen(aosParts[1]) == 1 &&
288
0
            std::isalpha(static_cast<unsigned char>(aosParts[1][0]))};
289
290
        // Check for drive letter
291
0
        if (iPartsCount == 4)
292
0
        {
293
            // Invalid
294
0
            if (!hasDriveLetter)
295
0
            {
296
0
                return;
297
0
            }
298
0
            m_pathComponent = aosParts[1];
299
0
            m_pathComponent.append(":");
300
0
            m_pathComponent.append(aosParts[2]);
301
0
            subdatasetIndex++;
302
0
        }
303
0
        else  // count is 3
304
0
        {
305
0
            if (hasDriveLetter)
306
0
            {
307
0
                return;
308
0
            }
309
0
            m_pathComponent = aosParts[1];
310
0
        }
311
312
0
        m_subdatasetComponent = aosParts[subdatasetIndex];
313
0
    }
314
0
}
315
316
static GDALSubdatasetInfo *
317
OGRGeoPackageDriverGetSubdatasetInfo(const char *pszFileName)
318
5.53k
{
319
5.53k
    if (STARTS_WITH_CI(pszFileName, "GPKG:"))
320
0
    {
321
0
        std::unique_ptr<GDALSubdatasetInfo> info =
322
0
            std::make_unique<OGRGeoPackageDriverSubdatasetInfo>(pszFileName);
323
0
        if (!info->GetSubdatasetComponent().empty() &&
324
0
            !info->GetPathComponent().empty())
325
0
        {
326
0
            return info.release();
327
0
        }
328
0
    }
329
5.53k
    return nullptr;
330
5.53k
}
331
332
/************************************************************************/
333
/*                                Open()                                */
334
/************************************************************************/
335
336
static GDALDataset *OGRGeoPackageDriverOpen(GDALOpenInfo *poOpenInfo)
337
3.15k
{
338
3.15k
    std::string osFilenameInGpkgZip;
339
3.15k
    if (OGRGeoPackageDriverIdentify(poOpenInfo, osFilenameInGpkgZip, true) ==
340
3.15k
        GDAL_IDENTIFY_FALSE)
341
0
        return nullptr;
342
343
3.15k
    GDALGeoPackageDataset *poDS = new GDALGeoPackageDataset();
344
345
3.15k
    if (!poDS->Open(poOpenInfo, osFilenameInGpkgZip))
346
2.33k
    {
347
2.33k
        delete poDS;
348
2.33k
        poDS = nullptr;
349
2.33k
    }
350
351
3.15k
    return poDS;
352
3.15k
}
353
354
/************************************************************************/
355
/*                               Create()                               */
356
/************************************************************************/
357
358
static GDALDataset *OGRGeoPackageDriverCreate(const char *pszFilename,
359
                                              int nXSize, int nYSize,
360
                                              int nBands, GDALDataType eDT,
361
                                              CSLConstList papszOptions)
362
722
{
363
722
    if (strcmp(pszFilename, ":memory:") != 0)
364
722
    {
365
722
        const size_t nFilenameLen = strlen(pszFilename);
366
722
        if (nFilenameLen > strlen(".gpkg.zip") &&
367
722
            !STARTS_WITH(pszFilename, "/vsizip/") &&
368
722
            EQUAL(pszFilename + nFilenameLen - strlen(".gpkg.zip"),
369
722
                  ".gpkg.zip"))
370
432
        {
371
            // do nothing
372
432
        }
373
290
        else
374
290
        {
375
290
            const std::string osExt = CPLGetExtensionSafe(pszFilename);
376
290
            const bool bIsRecognizedExtension =
377
290
                EQUAL(osExt.c_str(), "GPKG") || EQUAL(osExt.c_str(), "GPKX");
378
290
            if (!bIsRecognizedExtension)
379
0
            {
380
0
                CPLError(
381
0
                    CE_Warning, CPLE_AppDefined,
382
0
                    "The filename extension should be 'gpkg' instead of '%s' "
383
0
                    "to conform to the GPKG specification.",
384
0
                    osExt.c_str());
385
0
            }
386
290
        }
387
722
    }
388
389
722
    GDALGeoPackageDataset *poDS = new GDALGeoPackageDataset();
390
391
722
    if (!poDS->Create(pszFilename, nXSize, nYSize, nBands, eDT, papszOptions))
392
4
    {
393
4
        delete poDS;
394
4
        poDS = nullptr;
395
4
    }
396
397
722
    return poDS;
398
722
}
399
400
/************************************************************************/
401
/*                               Delete()                               */
402
/************************************************************************/
403
404
static CPLErr OGRGeoPackageDriverDelete(const char *pszFilename)
405
406
404
{
407
404
    std::string osAuxXml(pszFilename);
408
404
    osAuxXml += ".aux.xml";
409
404
    VSIStatBufL sStat;
410
404
    if (VSIStatL(osAuxXml.c_str(), &sStat) == 0)
411
0
        CPL_IGNORE_RET_VAL(VSIUnlink(osAuxXml.c_str()));
412
413
404
    if (VSIUnlink(pszFilename) == 0)
414
185
        return CE_None;
415
219
    else
416
219
        return CE_Failure;
417
404
}
418
419
/************************************************************************/
420
/*                            GDALGPKGDriver                            */
421
/************************************************************************/
422
423
class GDALGPKGDriver final : public GDALDriver
424
{
425
    std::recursive_mutex m_oMutex{};
426
    bool m_bInitialized = false;
427
428
    void InitializeCreationOptionList();
429
430
  public:
431
24
    GDALGPKGDriver() = default;
432
433
    const char *GetMetadataItem(const char *pszName,
434
                                const char *pszDomain) override;
435
436
    CSLConstList GetMetadata(const char *pszDomain) override
437
437
    {
438
437
        std::lock_guard oLock(m_oMutex);
439
437
        InitializeCreationOptionList();
440
437
        return GDALDriver::GetMetadata(pszDomain);
441
437
    }
442
};
443
444
const char *GDALGPKGDriver::GetMetadataItem(const char *pszName,
445
                                            const char *pszDomain)
446
1.26M
{
447
1.26M
    std::lock_guard oLock(m_oMutex);
448
1.26M
    if (EQUAL(pszName, GDAL_DMD_CREATIONOPTIONLIST))
449
1.15k
    {
450
1.15k
        InitializeCreationOptionList();
451
1.15k
    }
452
1.26M
    return GDALDriver::GetMetadataItem(pszName, pszDomain);
453
1.26M
}
454
455
#define COMPRESSION_OPTIONS                                                    \
456
    "  <Option name='TILE_FORMAT' type='string-select' scope='raster' "        \
457
    "description='Format to use to create tiles' default='AUTO'>"              \
458
    "    <Value>AUTO</Value>"                                                  \
459
    "    <Value>PNG_JPEG</Value>"                                              \
460
    "    <Value>PNG</Value>"                                                   \
461
    "    <Value>PNG8</Value>"                                                  \
462
    "    <Value>JPEG</Value>"                                                  \
463
    "    <Value>WEBP</Value>"                                                  \
464
    "    <Value>TIFF</Value>"                                                  \
465
    "  </Option>"                                                              \
466
    "  <Option name='QUALITY' type='int' min='1' max='100' scope='raster' "    \
467
    "description='Quality for JPEG and WEBP tiles' default='75'/>"             \
468
    "  <Option name='ZLEVEL' type='int' min='1' max='9' scope='raster' "       \
469
    "description='DEFLATE compression level for PNG tiles' default='6'/>"      \
470
    "  <Option name='DITHER' type='boolean' scope='raster' "                   \
471
    "description='Whether to apply Floyd-Steinberg dithering (for "            \
472
    "TILE_FORMAT=PNG8)' default='NO'/>"
473
474
void GDALGPKGDriver::InitializeCreationOptionList()
475
1.59k
{
476
1.59k
    if (m_bInitialized)
477
1.59k
        return;
478
2
    m_bInitialized = true;
479
480
2
    const char *pszCOBegin =
481
2
        "<CreationOptionList>"
482
2
        "  <Option name='RASTER_TABLE' type='string' scope='raster' "
483
2
        "description='Name of tile user table'/>"
484
2
        "  <Option name='APPEND_SUBDATASET' type='boolean' scope='raster' "
485
2
        "description='Set to YES to add a new tile user table to an existing "
486
2
        "GeoPackage instead of replacing it' default='NO'/>"
487
2
        "  <Option name='RASTER_IDENTIFIER' type='string' scope='raster' "
488
2
        "description='Human-readable identifier (e.g. short name)'/>"
489
2
        "  <Option name='RASTER_DESCRIPTION' type='string' scope='raster' "
490
2
        "description='Human-readable description'/>"
491
2
        "  <Option name='BLOCKSIZE' type='int' scope='raster' "
492
2
        "description='Block size in pixels' default='256' max='4096'/>"
493
2
        "  <Option name='BLOCKXSIZE' type='int' scope='raster' "
494
2
        "description='Block width in pixels' default='256' max='4096'/>"
495
2
        "  <Option name='BLOCKYSIZE' type='int' scope='raster' "
496
2
        "description='Block height in pixels' default='256' "
497
2
        "max='4096'/>" COMPRESSION_OPTIONS
498
2
        "  <Option name='TILING_SCHEME' type='string' scope='raster' "
499
2
        "description='Which tiling scheme to use: pre-defined value or custom "
500
2
        "inline/outline JSON definition' default='CUSTOM'>"
501
2
        "    <Value>CUSTOM</Value>"
502
2
        "    <Value>GoogleCRS84Quad</Value>"
503
2
        "    <Value>PseudoTMS_GlobalGeodetic</Value>"
504
2
        "    <Value>PseudoTMS_GlobalMercator</Value>";
505
506
2
    const char *pszCOEnd =
507
2
        "  </Option>"
508
2
        "  <Option name='ZOOM_LEVEL' type='integer' scope='raster' "
509
2
        "description='Zoom level of full resolution. Only "
510
2
        "used for TILING_SCHEME != CUSTOM' min='0' max='30'/>"
511
2
        "  <Option name='ZOOM_LEVEL_STRATEGY' type='string-select' "
512
2
        "scope='raster' description='Strategy to determine zoom level. Only "
513
2
        "used for TILING_SCHEME != CUSTOM' default='AUTO'>"
514
2
        "    <Value>AUTO</Value>"
515
2
        "    <Value>LOWER</Value>"
516
2
        "    <Value>UPPER</Value>"
517
2
        "  </Option>"
518
2
        "  <Option name='RESAMPLING' type='string-select' scope='raster' "
519
2
        "description='Resampling algorithm. Only used for TILING_SCHEME != "
520
2
        "CUSTOM' default='BILINEAR'>"
521
2
        "    <Value>NEAREST</Value>"
522
2
        "    <Value>BILINEAR</Value>"
523
2
        "    <Value>CUBIC</Value>"
524
2
        "    <Value>CUBICSPLINE</Value>"
525
2
        "    <Value>LANCZOS</Value>"
526
2
        "    <Value>MODE</Value>"
527
2
        "    <Value>AVERAGE</Value>"
528
2
        "  </Option>"
529
2
        "  <Option name='PRECISION' type='float' scope='raster' "
530
2
        "description='Smallest significant value. Only used for tiled gridded "
531
2
        "coverage datasets' default='1'/>"
532
2
        "  <Option name='UOM' type='string' scope='raster' description='Unit "
533
2
        "of Measurement. Only used for tiled gridded coverage datasets' />"
534
2
        "  <Option name='FIELD_NAME' type='string' scope='raster' "
535
2
        "description='Field name. Only used for tiled gridded coverage "
536
2
        "datasets' default='Height'/>"
537
2
        "  <Option name='QUANTITY_DEFINITION' type='string' scope='raster' "
538
2
        "description='Description of the field. Only used for tiled gridded "
539
2
        "coverage datasets' default='Height'/>"
540
2
        "  <Option name='GRID_CELL_ENCODING' type='string-select' "
541
2
        "scope='raster' description='Grid cell encoding. Only used for tiled "
542
2
        "gridded coverage datasets' default='grid-value-is-center'>"
543
2
        "     <Value>grid-value-is-center</Value>"
544
2
        "     <Value>grid-value-is-area</Value>"
545
2
        "     <Value>grid-value-is-corner</Value>"
546
2
        "  </Option>"
547
2
        "  <Option name='VERSION' type='string-select' description='Set "
548
2
        "GeoPackage version (for application_id and user_version fields)' "
549
2
        "default='AUTO'>"
550
2
        "     <Value>AUTO</Value>"
551
2
        "     <Value>1.0</Value>"
552
2
        "     <Value>1.1</Value>"
553
2
        "     <Value>1.2</Value>"
554
2
        "     <Value>1.3</Value>"
555
2
        "     <Value>1.4</Value>"
556
2
        "  </Option>"
557
2
        "  <Option name='DATETIME_FORMAT' type='string-select' "
558
2
        "description='How to encode DateTime not in UTC' default='WITH_TZ'>"
559
2
        "     <Value>WITH_TZ</Value>"
560
2
        "     <Value>UTC</Value>"
561
2
        "  </Option>"
562
2
#ifdef ENABLE_GPKG_OGR_CONTENTS
563
2
        "  <Option name='ADD_GPKG_OGR_CONTENTS' type='boolean' "
564
2
        "description='Whether to add a gpkg_ogr_contents table to keep feature "
565
2
        "count' default='YES'/>"
566
2
#endif
567
2
        "  <Option name='CRS_WKT_EXTENSION' type='boolean' "
568
2
        "description='Whether to create the database with the crs_wkt "
569
2
        "extension'/>"
570
2
        "  <Option name='METADATA_TABLES' type='boolean' "
571
2
        "description='Whether to create the metadata related system tables'/>"
572
2
        "</CreationOptionList>";
573
574
2
    std::string osOptions(pszCOBegin);
575
2
    const auto tmsList = gdal::TileMatrixSet::listPredefinedTileMatrixSets();
576
2
    for (const auto &tmsName : tmsList)
577
10
    {
578
10
        const auto poTM = gdal::TileMatrixSet::parse(tmsName.c_str());
579
10
        if (poTM && poTM->haveAllLevelsSameTopLeft() &&
580
10
            poTM->haveAllLevelsSameTileSize() &&
581
10
            poTM->hasOnlyPowerOfTwoVaryingScales() &&
582
10
            !poTM->hasVariableMatrixWidth())
583
10
        {
584
10
            osOptions += "    <Value>";
585
10
            osOptions += tmsName;
586
10
            osOptions += "</Value>";
587
10
        }
588
10
    }
589
2
    osOptions += pszCOEnd;
590
591
2
    SetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST, osOptions.c_str());
592
2
}
593
594
/************************************************************************/
595
/*                     OGRGeoPackageRepackAlgorithm                     */
596
/************************************************************************/
597
598
#ifndef _
599
0
#define _(x) x
600
#endif
601
602
class OGRGeoPackageRepackAlgorithm final : public GDALAlgorithm
603
{
604
  public:
605
    OGRGeoPackageRepackAlgorithm()
606
0
        : GDALAlgorithm("repack", "Repack/vacuum in-place a GeoPackage dataset",
607
0
                        "/programs/gdal_driver_gpkg_repack.html")
608
0
    {
609
0
        AddProgressArg(/* hidden = */ true);
610
611
0
        constexpr int type = GDAL_OF_RASTER | GDAL_OF_VECTOR | GDAL_OF_UPDATE;
612
0
        auto &arg =
613
0
            AddArg("dataset", 0, _("GeoPackage dataset"), &m_dataset, type)
614
0
                .SetPositional()
615
0
                .SetRequired();
616
0
        SetAutoCompleteFunctionForFilename(arg, type);
617
0
    }
618
619
  protected:
620
    bool RunImpl(GDALProgressFunc, void *) override;
621
622
  private:
623
    GDALArgDatasetValue m_dataset{};
624
};
625
626
bool OGRGeoPackageRepackAlgorithm::RunImpl(GDALProgressFunc, void *)
627
0
{
628
0
    auto poDS =
629
0
        dynamic_cast<GDALGeoPackageDataset *>(m_dataset.GetDatasetRef());
630
0
    if (!poDS)
631
0
    {
632
0
        ReportError(CE_Failure, CPLE_AppDefined, "%s is not a GeoPackage",
633
0
                    m_dataset.GetName().c_str());
634
0
        return false;
635
0
    }
636
0
    CPLErrorReset();
637
0
    delete poDS->ExecuteSQL("VACUUM", nullptr, nullptr);
638
0
    return CPLGetLastErrorType() == CE_None;
639
0
}
640
641
/************************************************************************/
642
/*                    OGRGeoPackageValidateAlgorithm                    */
643
/************************************************************************/
644
645
class OGRGeoPackageValidateAlgorithm final : public GDALAlgorithm
646
{
647
  public:
648
    OGRGeoPackageValidateAlgorithm()
649
0
        : GDALAlgorithm("validate",
650
0
                        "Validate conformance of a GeoPackage dataset against "
651
0
                        "the GeoPackage specification",
652
0
                        "/programs/gdal_driver_gpkg_validate.html")
653
0
    {
654
0
        constexpr int type = GDAL_OF_RASTER | GDAL_OF_VECTOR;
655
0
        auto &arg =
656
0
            AddArg("dataset", 0, _("GeoPackage dataset"), &m_dataset, type)
657
0
                .SetPositional()
658
0
                .SetRequired();
659
0
        SetAutoCompleteFunctionForFilename(arg, type);
660
661
0
        AddArg("full-check", 0, _("Whether to perform full check"),
662
0
               &m_fullCheck)
663
0
            .SetDefault(m_fullCheck);
664
665
0
        AddOutputStringArg(&m_output);
666
0
        AddArg("verbose", 'v', _("Turn on verbose mode"), &m_verbose);
667
0
        AddProgressArg();
668
0
    }
669
670
  protected:
671
    bool RunImpl(GDALProgressFunc, void *) override;
672
673
  private:
674
    GDALArgDatasetValue m_dataset{};
675
    bool m_fullCheck = false;
676
    bool m_verbose = false;
677
    std::string m_output{};
678
};
679
680
/************************************************************************/
681
/*                              RunImpl()                               */
682
/************************************************************************/
683
684
bool OGRGeoPackageValidateAlgorithm::RunImpl(GDALProgressFunc, void *)
685
0
{
686
0
    auto poDS =
687
0
        dynamic_cast<GDALGeoPackageDataset *>(m_dataset.GetDatasetRef());
688
0
    if (!poDS)
689
0
    {
690
0
        ReportError(CE_Failure, CPLE_AppDefined, "%s is not a GeoPackage",
691
0
                    m_dataset.GetName().c_str());
692
0
        return false;
693
0
    }
694
695
0
    if (!GDALPythonInitialize())
696
0
        return false;
697
698
0
    GIL_Holder oHolder(false);
699
700
0
    const CPLString osModuleName(CPLSPrintf("gpkg_validate_module_%p", this));
701
0
    PyObject *poCompiledString =
702
0
        Py_CompileString("from osgeo_utils.samples.validate_gpkg import "
703
0
                         "main, get_output_string\n",
704
0
                         osModuleName, Py_file_input);
705
0
    if (poCompiledString == nullptr || PyErr_Occurred())
706
0
    {
707
0
        CPLError(CE_Failure, CPLE_AppDefined, "Couldn't compile code:\n%s",
708
0
                 GetPyExceptionString().c_str());
709
0
        return false;
710
0
    }
711
0
    PyObject *poModule =
712
0
        PyImport_ExecCodeModule(osModuleName, poCompiledString);
713
0
    Py_DecRef(poCompiledString);
714
715
0
    if (poModule == nullptr || PyErr_Occurred())
716
0
    {
717
0
        CPLError(CE_Failure, CPLE_AppDefined, "%s",
718
0
                 GetPyExceptionString().c_str());
719
0
        return false;
720
0
    }
721
722
0
    PyObject *poMain = PyObject_GetAttrString(poModule, "main");
723
0
    CPLAssert(poMain);
724
0
    PyObject *poGetOutput =
725
0
        PyObject_GetAttrString(poModule, "get_output_string");
726
0
    CPLAssert(poGetOutput);
727
728
0
    Py_DecRef(poModule);
729
730
0
    std::vector<std::string> args{"dummy", "-k", m_dataset.GetName()};
731
0
    if (m_quiet)
732
0
        args.push_back("-q");
733
0
    else if (m_verbose)
734
0
        args.push_back("-v");
735
0
    if (m_fullCheck)
736
0
        args.push_back("--extra");
737
738
0
    PyObject *pyArgv = PyTuple_New(args.size());
739
0
    for (size_t i = 0; i < args.size(); ++i)
740
0
    {
741
0
        PyTuple_SetItem(pyArgv, i, PyUnicode_FromString(args[i].c_str()));
742
0
    }
743
0
    PyObject *pyKwargs = PyDict_New();
744
0
    PyDict_SetItemString(pyKwargs, "argv", pyArgv);
745
0
    PyDict_SetItemString(pyKwargs, "output_in_string", PyBool_FromLong(true));
746
0
    PyObject *pyArgs = PyTuple_New(0);
747
0
    PyObject *pRetValue = PyObject_Call(poMain, pyArgs, pyKwargs);
748
0
    const auto nRetValue = pRetValue ? PyLong_AsLong(pRetValue) : -1;
749
0
    Py_DecRef(pyArgs);
750
0
    Py_DecRef(pyKwargs);
751
0
    Py_DecRef(pRetValue);
752
753
0
    if (!m_quiet || nRetValue)
754
0
    {
755
0
        pyArgs = PyTuple_New(0);
756
0
        pyKwargs = PyDict_New();
757
0
        PyObject *poMsg = PyObject_Call(poGetOutput, pyArgs, pyKwargs);
758
0
        Py_DecRef(pyArgs);
759
0
        Py_DecRef(pyKwargs);
760
0
        if (poMsg)
761
0
            m_output = GetString(poMsg);
762
0
        Py_DecRef(poMsg);
763
0
    }
764
765
0
    Py_DecRef(poMain);
766
0
    Py_DecRef(poGetOutput);
767
768
0
    if (nRetValue)
769
0
        m_output += "\nValidation failed";
770
0
    else if (!m_quiet)
771
0
    {
772
0
        if (!m_output.empty())
773
0
            m_output += '\n';
774
0
        m_output += "Validation succeeded";
775
0
    }
776
777
0
    if (nRetValue && !IsCalledFromCommandLine())
778
0
    {
779
0
        ReportError(CE_Failure, CPLE_AppDefined, "%s", m_output.c_str());
780
0
    }
781
782
0
    return nRetValue == 0;
783
0
}
784
785
/************************************************************************/
786
/*              OGRGeoPackageDriverInstantiateAlgorithm()               */
787
/************************************************************************/
788
789
static GDALAlgorithm *
790
OGRGeoPackageDriverInstantiateAlgorithm(const std::vector<std::string> &aosPath)
791
0
{
792
0
    if (aosPath.size() == 1 && aosPath[0] == "repack")
793
0
    {
794
0
        return std::make_unique<OGRGeoPackageRepackAlgorithm>().release();
795
0
    }
796
0
    else if (aosPath.size() == 1 && aosPath[0] == "validate")
797
0
    {
798
0
        return std::make_unique<OGRGeoPackageValidateAlgorithm>().release();
799
0
    }
800
0
    else
801
0
    {
802
0
        return nullptr;
803
0
    }
804
0
}
805
806
/************************************************************************/
807
/*                       RegisterOGRGeoPackage()                        */
808
/************************************************************************/
809
810
void RegisterOGRGeoPackage()
811
24
{
812
24
    if (GDALGetDriverByName("GPKG") != nullptr)
813
0
        return;
814
815
24
    GDALDriver *poDriver = new GDALGPKGDriver();
816
817
24
    poDriver->SetDescription("GPKG");
818
24
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
819
24
    poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
820
24
    poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
821
24
    poDriver->SetMetadataItem(GDAL_DCAP_DELETE_LAYER, "YES");
822
24
    poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
823
24
    poDriver->SetMetadataItem(GDAL_DCAP_DELETE_FIELD, "YES");
824
24
    poDriver->SetMetadataItem(GDAL_DCAP_REORDER_FIELDS, "YES");
825
24
    poDriver->SetMetadataItem(GDAL_DCAP_CURVE_GEOMETRIES, "YES");
826
24
    poDriver->SetMetadataItem(GDAL_DCAP_MEASURED_GEOMETRIES, "YES");
827
24
    poDriver->SetMetadataItem(GDAL_DCAP_Z_GEOMETRIES, "YES");
828
24
    poDriver->SetMetadataItem(GDAL_DCAP_CAN_READ_AFTER_DELETE, "YES");
829
24
    poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
830
24
    poDriver->SetMetadataItem(GDAL_DCAP_CREATE_SUBDATASETS, "YES");
831
24
    poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS,
832
24
                              "NATIVE OGRSQL SQLITE");
833
834
24
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "GeoPackage");
835
24
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "gpkg gpkg.zip");
836
24
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/vector/gpkg.html");
837
24
    poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES,
838
24
                              "Byte Int16 UInt16 Float32");
839
840
24
    poDriver->SetMetadataItem(
841
24
        GDAL_DMD_OPENOPTIONLIST,
842
24
        "<OpenOptionList>"
843
24
        "  <Option name='LIST_ALL_TABLES' type='string-select' scope='vector' "
844
24
        "description='Whether all tables, including those non listed in "
845
24
        "gpkg_contents, should be listed' default='AUTO'>"
846
24
        "    <Value>AUTO</Value>"
847
24
        "    <Value>YES</Value>"
848
24
        "    <Value>NO</Value>"
849
24
        "  </Option>"
850
24
        "  <Option name='TABLE' type='string' scope='raster' description='Name "
851
24
        "of tile user-table'/>"
852
24
        "  <Option name='ZOOM_LEVEL' type='integer' scope='raster' "
853
24
        "description='Zoom level of full resolution. If not specified, maximum "
854
24
        "non-empty zoom level'/>"
855
24
        "  <Option name='BAND_COUNT' type='string-select' scope='raster' "
856
24
        "description='Number of raster bands (only for Byte data type)' "
857
24
        "default='AUTO'>"
858
24
        "    <Value>AUTO</Value>"
859
24
        "    <Value>1</Value>"
860
24
        "    <Value>2</Value>"
861
24
        "    <Value>3</Value>"
862
24
        "    <Value>4</Value>"
863
24
        "  </Option>"
864
24
        "  <Option name='MINX' type='float' scope='raster' "
865
24
        "description='Minimum X of area of interest'/>"
866
24
        "  <Option name='MINY' type='float' scope='raster' "
867
24
        "description='Minimum Y of area of interest'/>"
868
24
        "  <Option name='MAXX' type='float' scope='raster' "
869
24
        "description='Maximum X of area of interest'/>"
870
24
        "  <Option name='MAXY' type='float' scope='raster' "
871
24
        "description='Maximum Y of area of interest'/>"
872
24
        "  <Option name='USE_TILE_EXTENT' type='boolean' scope='raster' "
873
24
        "description='Use tile extent of content to determine area of "
874
24
        "interest' default='NO'/>"
875
24
        "  <Option name='WHERE' type='string' scope='raster' description='SQL "
876
24
        "WHERE clause to be appended to tile requests'/>" COMPRESSION_OPTIONS
877
24
        "  <Option name='PRELUDE_STATEMENTS' type='string' "
878
24
        "scope='raster,vector' description='SQL statement(s) to send on the "
879
24
        "SQLite connection before any other ones'/>"
880
24
        "  <Option name='NOLOCK' type='boolean' description='Whether the "
881
24
        "database should be opened in nolock mode'/>"
882
24
        "  <Option name='IMMUTABLE' type='boolean' description='Whether the "
883
24
        "database should be opened in immutable mode'/>"
884
24
        "</OpenOptionList>");
885
24
    poDriver->SetMetadataItem(GDAL_DMD_OVERVIEW_CREATIONOPTIONLIST,
886
24
                              "<OverviewCreationOptionList>"
887
24
                              "</OverviewCreationOptionList>");
888
889
24
    poDriver->SetMetadataItem(
890
24
        GDAL_DS_LAYER_CREATIONOPTIONLIST,
891
24
        "<LayerCreationOptionList>"
892
24
        "  <Option name='LAUNDER' type='boolean' description='Whether layer "
893
24
        "and field names will be laundered.' default='NO'/>"
894
24
        "  <Option name='GEOMETRY_NAME' type='string' description='Name of "
895
24
        "geometry column.' default='geom' deprecated_alias='GEOMETRY_COLUMN'/>"
896
24
        "  <Option name='GEOMETRY_NULLABLE' type='boolean' "
897
24
        "description='Whether the values of the geometry column can be NULL' "
898
24
        "default='YES'/>"
899
24
        "  <Option name='SRID' type='integer' description='Forced srs_id of "
900
24
        "the "
901
24
        "entry in the gpkg_spatial_ref_sys table to point to'/>"
902
24
        "  <Option name='DISCARD_COORD_LSB' type='boolean' "
903
24
        "description='Whether the geometry coordinate precision should be used "
904
24
        "to set to zero non-significant least-significant bits of geometries. "
905
24
        "Helps when further compression is used' default='NO'/>"
906
24
        "  <Option name='UNDO_DISCARD_COORD_LSB_ON_READING' type='boolean' "
907
24
        "description='Whether to ask GDAL to take into coordinate precision to "
908
24
        "undo the effects of DISCARD_COORD_LSB' default='NO'/>"
909
24
        "  <Option name='FID' type='string' description='Name of the FID "
910
24
        "column to create' default='fid'/>"
911
24
        "  <Option name='OVERWRITE' type='boolean' description='Whether to "
912
24
        "overwrite an existing table with the layer name to be created' "
913
24
        "default='NO'/>"
914
24
        "  <Option name='PRECISION' type='boolean' description='Whether text "
915
24
        "fields created should keep the width' default='YES'/>"
916
24
        "  <Option name='TRUNCATE_FIELDS' type='boolean' description='Whether "
917
24
        "to truncate text content that exceeds maximum width' default='NO'/>"
918
24
        "  <Option name='SPATIAL_INDEX' type='boolean' description='Whether to "
919
24
        "create a spatial index' default='YES'/>"
920
24
        "  <Option name='IDENTIFIER' type='string' description='Identifier of "
921
24
        "the layer, as put in the contents table'/>"
922
24
        "  <Option name='DESCRIPTION' type='string' description='Description "
923
24
        "of the layer, as put in the contents table'/>"
924
24
        "  <Option name='ASPATIAL_VARIANT' type='string-select' "
925
24
        "description='How to register non spatial tables' "
926
24
        "default='GPKG_ATTRIBUTES'>"
927
24
        "     <Value>GPKG_ATTRIBUTES</Value>"
928
24
        "     <Value>NOT_REGISTERED</Value>"
929
24
        "  </Option>"
930
24
        "  <Option name='DATETIME_PRECISION' type='string-select' "
931
24
        "description='Number of components of datetime fields' "
932
24
        "default='AUTO'>"
933
24
        "     <Value>AUTO</Value>"
934
24
        "     <Value>MILLISECOND</Value>"
935
24
        "     <Value>SECOND</Value>"
936
24
        "     <Value>MINUTE</Value>"
937
24
        "  </Option>"
938
24
        "</LayerCreationOptionList>");
939
940
24
    poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES,
941
24
                              "Integer Integer64 Real String Date DateTime "
942
24
                              "Binary");
943
24
    poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES,
944
24
                              "Boolean Int16 Float32 JSON");
945
24
    poDriver->SetMetadataItem(GDAL_DMD_CREATION_FIELD_DEFN_FLAGS,
946
24
                              "WidthPrecision Nullable Default Unique "
947
24
                              "Comment AlternativeName Domain");
948
949
24
    poDriver->SetMetadataItem(GDAL_DMD_ALTER_FIELD_DEFN_FLAGS,
950
24
                              "Name Type WidthPrecision Nullable Default "
951
24
                              "Unique Domain AlternativeName Comment");
952
953
24
    poDriver->SetMetadataItem(GDAL_DCAP_NOTNULL_FIELDS, "YES");
954
24
    poDriver->SetMetadataItem(GDAL_DCAP_DEFAULT_FIELDS, "YES");
955
24
    poDriver->SetMetadataItem(GDAL_DCAP_UNIQUE_FIELDS, "YES");
956
24
    poDriver->SetMetadataItem(GDAL_DCAP_NOTNULL_GEOMFIELDS, "YES");
957
24
    poDriver->SetMetadataItem(GDAL_DCAP_MULTIPLE_VECTOR_LAYERS, "YES");
958
24
    poDriver->SetMetadataItem(GDAL_DCAP_FIELD_DOMAINS, "YES");
959
24
    poDriver->SetMetadataItem(GDAL_DCAP_RELATIONSHIPS, "YES");
960
24
    poDriver->SetMetadataItem(GDAL_DCAP_CREATE_RELATIONSHIP, "YES");
961
24
    poDriver->SetMetadataItem(GDAL_DCAP_DELETE_RELATIONSHIP, "YES");
962
24
    poDriver->SetMetadataItem(GDAL_DCAP_UPDATE_RELATIONSHIP, "YES");
963
24
    poDriver->SetMetadataItem(GDAL_DCAP_FLUSHCACHE_CONSISTENT_STATE, "YES");
964
24
    poDriver->SetMetadataItem(GDAL_DCAP_UPSERT, "YES");
965
966
24
    poDriver->SetMetadataItem(GDAL_DMD_RELATIONSHIP_FLAGS,
967
24
                              "ManyToMany Association");
968
969
24
    poDriver->SetMetadataItem(GDAL_DCAP_RENAME_LAYERS, "YES");
970
24
    poDriver->SetMetadataItem(GDAL_DMD_CREATION_FIELD_DOMAIN_TYPES,
971
24
                              "Coded Range Glob");
972
973
24
    poDriver->SetMetadataItem(GDAL_DMD_ALTER_GEOM_FIELD_DEFN_FLAGS,
974
24
                              "Name SRS CoordinateEpoch");
975
976
24
    poDriver->SetMetadataItem(
977
24
        GDAL_DMD_RELATIONSHIP_RELATED_TABLE_TYPES,
978
24
        "features media simple_attributes attributes tiles");
979
980
24
    poDriver->SetMetadataItem(GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION, "YES");
981
982
24
#ifdef ENABLE_SQL_GPKG_FORMAT
983
24
    poDriver->SetMetadataItem("ENABLE_SQL_GPKG_FORMAT", "YES");
984
24
#endif
985
24
#ifdef SQLITE_HAS_COLUMN_METADATA
986
24
    poDriver->SetMetadataItem("SQLITE_HAS_COLUMN_METADATA", "YES");
987
24
#endif
988
989
24
    poDriver->SetMetadataItem(GDAL_DCAP_UPDATE, "YES");
990
24
    poDriver->SetMetadataItem(GDAL_DMD_UPDATE_ITEMS,
991
24
                              "DatasetMetadata BandMetadata RasterValues "
992
24
                              "LayerMetadata Features");
993
994
24
    poDriver->pfnOpen = OGRGeoPackageDriverOpen;
995
24
    poDriver->pfnIdentify = OGRGeoPackageDriverIdentify;
996
24
    poDriver->pfnCreate = OGRGeoPackageDriverCreate;
997
24
    poDriver->pfnCreateCopy = GDALGeoPackageDataset::CreateCopy;
998
24
    poDriver->pfnDelete = OGRGeoPackageDriverDelete;
999
24
    poDriver->pfnGetSubdatasetInfoFunc = OGRGeoPackageDriverGetSubdatasetInfo;
1000
1001
24
    poDriver->pfnInstantiateAlgorithm = OGRGeoPackageDriverInstantiateAlgorithm;
1002
24
    poDriver->DeclareAlgorithm({"repack"});
1003
24
    poDriver->DeclareAlgorithm({"validate"});
1004
1005
24
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1006
1007
24
    GetGDALDriverManager()->RegisterDriver(poDriver);
1008
24
}