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  * Permission is hereby granted, free of charge, to any person obtaining a
11  * copy of this software and associated documentation files (the "Software"),
12  * to deal in the Software without restriction, including without limitation
13  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14  * and/or sell copies of the Software, and to permit persons to whom the
15  * Software is furnished to do so, subject to the following conditions:
16  *
17  * The above copyright notice and this permission notice shall be included
18  * in all copies or substantial portions of the Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26  * DEALINGS IN THE SOFTWARE.
27  ****************************************************************************/
28 
29 #include "ogr_geopackage.h"
30 
31 #include "tilematrixset.hpp"
32 
33 CPL_CVSID("$Id: ogrgeopackagedriver.cpp 652f826f3549f0359e3ccd637631725ee7708fc4 2021-04-08 15:37:24 +0200 Even Rouault $")
34 
35 // g++ -g -Wall -fPIC -shared -o ogr_geopackage.so -Iport -Igcore -Iogr -Iogr/ogrsf_frmts -Iogr/ogrsf_frmts/gpkg ogr/ogrsf_frmts/gpkg/*.c* -L. -lgdal
36 
37 /************************************************************************/
38 /*                       OGRGeoPackageDriverIdentify()                  */
39 /************************************************************************/
40 
OGRGeoPackageDriverIdentify(GDALOpenInfo * poOpenInfo,bool bEmitWarning)41 static int OGRGeoPackageDriverIdentify( GDALOpenInfo* poOpenInfo, bool bEmitWarning )
42 {
43     if( STARTS_WITH_CI(poOpenInfo->pszFilename, "GPKG:") )
44         return TRUE;
45 
46 #ifdef ENABLE_SQL_GPKG_FORMAT
47     if( poOpenInfo->pabyHeader &&
48         STARTS_WITH((const char*)poOpenInfo->pabyHeader, "-- SQL GPKG") )
49     {
50         return TRUE;
51     }
52 #endif
53 
54     if ( poOpenInfo->nHeaderBytes < 100 ||
55          poOpenInfo->pabyHeader == nullptr ||
56          !STARTS_WITH((const char*)poOpenInfo->pabyHeader, "SQLite format 3") )
57     {
58         return FALSE;
59     }
60 
61     /* Requirement 3: File name has to end in "gpkg" */
62     /* http://opengis.github.io/geopackage/#_file_extension_name */
63     /* But be tolerant, if the GPKG application id is found, because some */
64     /* producers don't necessarily honour that requirement (#6396) */
65     const char* pszExt = CPLGetExtension(poOpenInfo->pszFilename);
66     const bool bIsRecognizedExtension = EQUAL(pszExt, "GPKG") || EQUAL(pszExt, "GPKX");
67 
68     /* Requirement 2: application id */
69     /* http://opengis.github.io/geopackage/#_file_format */
70     /* Be tolerant since some datasets don't actually follow that requirement */
71     GUInt32 nApplicationId;
72     memcpy(&nApplicationId, poOpenInfo->pabyHeader + knApplicationIdPos, 4);
73     nApplicationId = CPL_MSBWORD32(nApplicationId);
74     GUInt32 nUserVersion;
75     memcpy(&nUserVersion, poOpenInfo->pabyHeader + knUserVersionPos, 4);
76     nUserVersion = CPL_MSBWORD32(nUserVersion);
77     if( nApplicationId != GP10_APPLICATION_ID &&
78         nApplicationId != GP11_APPLICATION_ID &&
79         nApplicationId != GPKG_APPLICATION_ID )
80     {
81 #ifdef DEBUG
82         if( EQUAL(CPLGetFilename(poOpenInfo->pszFilename), ".cur_input")  )
83         {
84             return FALSE;
85         }
86 #endif
87         if( !bIsRecognizedExtension )
88             return FALSE;
89 
90         if( bEmitWarning )
91         {
92             GByte abySignature[4+1];
93             memcpy(abySignature, poOpenInfo->pabyHeader + knApplicationIdPos, 4);
94             abySignature[4] = '\0';
95 
96             /* Is this a GPxx version ? */
97             const bool bWarn = CPLTestBool(CPLGetConfigOption(
98                 "GPKG_WARN_UNRECOGNIZED_APPLICATION_ID", "YES"));
99             if( bWarn )
100             {
101                 CPLError( CE_Warning, CPLE_AppDefined,
102                           "GPKG: bad application_id=0x%02X%02X%02X%02X on '%s'",
103                           abySignature[0], abySignature[1],
104                           abySignature[2], abySignature[3],
105                           poOpenInfo->pszFilename );
106             }
107             else
108             {
109                 CPLDebug( "GPKG",
110                           "bad application_id=0x%02X%02X%02X%02X on '%s'",
111                           abySignature[0], abySignature[1],
112                           abySignature[2], abySignature[3],
113                           poOpenInfo->pszFilename );
114             }
115         }
116     }
117     else if(nApplicationId == GPKG_APPLICATION_ID &&
118             // Accept any 102XX version
119             !((nUserVersion >= GPKG_1_2_VERSION &&
120                nUserVersion < GPKG_1_2_VERSION + 99) ||
121             // Accept any 103XX version
122               (nUserVersion >= GPKG_1_3_VERSION &&
123                nUserVersion < GPKG_1_3_VERSION + 99)
124               ))
125     {
126 #ifdef DEBUG
127         if( EQUAL(CPLGetFilename(poOpenInfo->pszFilename), ".cur_input")  )
128         {
129             return FALSE;
130         }
131 #endif
132         if( !bIsRecognizedExtension )
133             return FALSE;
134 
135         if( bEmitWarning )
136         {
137             GByte abySignature[4+1];
138             memcpy(abySignature, poOpenInfo->pabyHeader + knUserVersionPos, 4);
139             abySignature[4] = '\0';
140 
141             const bool bWarn = CPLTestBool(CPLGetConfigOption(
142                             "GPKG_WARN_UNRECOGNIZED_APPLICATION_ID", "YES"));
143             if( bWarn )
144             {
145                 if( nUserVersion > GPKG_1_3_VERSION )
146                 {
147                     CPLError( CE_Warning, CPLE_AppDefined,
148                               "This version of GeoPackage "
149                               "user_version=0x%02X%02X%02X%02X "
150                               "(%u, v%d.%d.%d) on '%s' may only be "
151                               "partially supported",
152                               abySignature[0], abySignature[1],
153                               abySignature[2], abySignature[3],
154                               nUserVersion,
155                               nUserVersion / 10000,
156                               (nUserVersion % 10000 ) / 100,
157                               nUserVersion % 100,
158                               poOpenInfo->pszFilename );
159                 }
160                 else
161                 {
162                     CPLError( CE_Warning, CPLE_AppDefined,
163                               "GPKG: unrecognized user_version="
164                               "0x%02X%02X%02X%02X (%u) on '%s'",
165                               abySignature[0], abySignature[1],
166                               abySignature[2], abySignature[3],
167                               nUserVersion,
168                               poOpenInfo->pszFilename );
169                 }
170             }
171             else
172             {
173                 if( nUserVersion > GPKG_1_3_VERSION )
174                 {
175                     CPLDebug( "GPKG",
176                               "This version of GeoPackage "
177                               "user_version=0x%02X%02X%02X%02X "
178                               "(%u, v%d.%d.%d) on '%s' may only be "
179                               "partially supported",
180                               abySignature[0], abySignature[1],
181                               abySignature[2], abySignature[3],
182                               nUserVersion,
183                               nUserVersion / 10000,
184                               (nUserVersion % 10000 ) / 100,
185                               nUserVersion % 100,
186                               poOpenInfo->pszFilename );
187                 }
188                 else
189                 {
190                     CPLDebug( "GPKG",
191                               "unrecognized user_version=0x%02X%02X%02X%02X"
192                               "(%u) on '%s'",
193                               abySignature[0], abySignature[1],
194                               abySignature[2], abySignature[3],
195                               nUserVersion,
196                               poOpenInfo->pszFilename );
197                 }
198             }
199         }
200     }
201     else if( !bIsRecognizedExtension
202 #ifdef DEBUG
203               && !EQUAL(CPLGetFilename(poOpenInfo->pszFilename), ".cur_input")
204 #endif
205               && !(STARTS_WITH(poOpenInfo->pszFilename, "/vsizip/") &&
206                    EQUAL(CPLGetExtension(poOpenInfo->pszFilename), "zip") )
207               && !STARTS_WITH(poOpenInfo->pszFilename, "/vsigzip/") )
208     {
209         if( bEmitWarning )
210         {
211             CPLError( CE_Warning, CPLE_AppDefined,
212                       "File %s has GPKG application_id, but non conformant file extension",
213                       poOpenInfo->pszFilename);
214         }
215     }
216 
217     return TRUE;
218 }
219 
OGRGeoPackageDriverIdentify(GDALOpenInfo * poOpenInfo)220 static int OGRGeoPackageDriverIdentify( GDALOpenInfo* poOpenInfo )
221 {
222     return OGRGeoPackageDriverIdentify(poOpenInfo, false);
223 }
224 
225 /************************************************************************/
226 /*                                Open()                                */
227 /************************************************************************/
228 
OGRGeoPackageDriverOpen(GDALOpenInfo * poOpenInfo)229 static GDALDataset *OGRGeoPackageDriverOpen( GDALOpenInfo* poOpenInfo )
230 {
231     if( !OGRGeoPackageDriverIdentify(poOpenInfo, true) )
232         return nullptr;
233 
234     GDALGeoPackageDataset   *poDS = new GDALGeoPackageDataset();
235 
236     if( !poDS->Open( poOpenInfo ) )
237     {
238         delete poDS;
239         poDS = nullptr;
240     }
241 
242     return poDS;
243 }
244 
245 /************************************************************************/
246 /*                               Create()                               */
247 /************************************************************************/
248 
OGRGeoPackageDriverCreate(const char * pszFilename,int nXSize,int nYSize,int nBands,GDALDataType eDT,char ** papszOptions)249 static GDALDataset* OGRGeoPackageDriverCreate( const char * pszFilename,
250                                             int nXSize,
251                                             int nYSize,
252                                             int nBands,
253                                             GDALDataType eDT,
254                                             char **papszOptions )
255 {
256     const char* pszExt = CPLGetExtension(pszFilename);
257     const bool bIsRecognizedExtension = EQUAL(pszExt, "GPKG") || EQUAL(pszExt, "GPKX");
258     if( !bIsRecognizedExtension )
259     {
260         CPLError(CE_Warning, CPLE_AppDefined,
261                  "The filename extension should be 'gpkg' instead of '%s' "
262                  "to conform to the GPKG specification.",
263                  pszExt);
264     }
265 
266     GDALGeoPackageDataset   *poDS = new GDALGeoPackageDataset();
267 
268     if( !poDS->Create( pszFilename, nXSize, nYSize,
269                        nBands, eDT, papszOptions ) )
270     {
271         delete poDS;
272         poDS = nullptr;
273     }
274 
275     return poDS;
276 }
277 
278 /************************************************************************/
279 /*                               Delete()                               */
280 /************************************************************************/
281 
OGRGeoPackageDriverDelete(const char * pszFilename)282 static CPLErr OGRGeoPackageDriverDelete( const char *pszFilename )
283 
284 {
285     if( VSIUnlink(pszFilename) == 0 )
286         return CE_None;
287     else
288         return CE_Failure;
289 }
290 
291 /************************************************************************/
292 /*                         RegisterOGRGeoPackage()                       */
293 /************************************************************************/
294 
295 class GDALGPKGDriver final: public GDALDriver
296 {
297         bool m_bInitialized = false;
298 
299         void InitializeCreationOptionList();
300 
301     public:
302         GDALGPKGDriver() = default;
303 
GetMetadataItem(const char * pszName,const char * pszDomain)304         const char* GetMetadataItem(const char* pszName, const char* pszDomain) override
305         {
306             if( EQUAL(pszName, GDAL_DMD_CREATIONOPTIONLIST) )
307             {
308                 InitializeCreationOptionList();
309             }
310             return GDALDriver::GetMetadataItem(pszName, pszDomain);
311         }
312 
GetMetadata(const char * pszDomain)313         char** GetMetadata(const char* pszDomain) override
314         {
315             InitializeCreationOptionList();
316             return GDALDriver::GetMetadata(pszDomain);
317         }
318 };
319 
320 #define COMPRESSION_OPTIONS \
321 "  <Option name='TILE_FORMAT' type='string-select' scope='raster' description='Format to use to create tiles' default='AUTO'>" \
322 "    <Value>AUTO</Value>" \
323 "    <Value>PNG_JPEG</Value>" \
324 "    <Value>PNG</Value>" \
325 "    <Value>PNG8</Value>" \
326 "    <Value>JPEG</Value>" \
327 "    <Value>WEBP</Value>" \
328 "    <Value>TIFF</Value>" \
329 "  </Option>" \
330 "  <Option name='QUALITY' type='int' min='1' max='100' scope='raster' description='Quality for JPEG and WEBP tiles' default='75'/>" \
331 "  <Option name='ZLEVEL' type='int' min='1' max='9' scope='raster' description='DEFLATE compression level for PNG tiles' default='6'/>" \
332 "  <Option name='DITHER' type='boolean' scope='raster' description='Whether to apply Floyd-Steinberg dithering (for TILE_FORMAT=PNG8)' default='NO'/>"
333 
InitializeCreationOptionList()334 void GDALGPKGDriver::InitializeCreationOptionList()
335 {
336     if( m_bInitialized )
337         return;
338     m_bInitialized = true;
339 
340     const char* pszCOBegin =
341 "<CreationOptionList>"
342 "  <Option name='RASTER_TABLE' type='string' scope='raster' description='Name of tile user table'/>"
343 "  <Option name='APPEND_SUBDATASET' type='boolean' scope='raster' description='Set to YES to add a new tile user table to an existing GeoPackage instead of replacing it' default='NO'/>"
344 "  <Option name='RASTER_IDENTIFIER' type='string' scope='raster' description='Human-readable identifier (e.g. short name)'/>"
345 "  <Option name='RASTER_DESCRIPTION' type='string' scope='raster' description='Human-readable description'/>"
346 "  <Option name='BLOCKSIZE' type='int' scope='raster' description='Block size in pixels' default='256' max='4096'/>"
347 "  <Option name='BLOCKXSIZE' type='int' scope='raster' description='Block width in pixels' default='256' max='4096'/>"
348 "  <Option name='BLOCKYSIZE' type='int' scope='raster' description='Block height in pixels' default='256' max='4096'/>"
349 COMPRESSION_OPTIONS
350 "  <Option name='TILING_SCHEME' type='string' scope='raster' description='Which tiling scheme to use: pre-defined value or custom inline/outline JSON definition' default='CUSTOM'>"
351 "    <Value>CUSTOM</Value>"
352 "    <Value>GoogleCRS84Quad</Value>"
353 "    <Value>PseudoTMS_GlobalGeodetic</Value>"
354 "    <Value>PseudoTMS_GlobalMercator</Value>";
355 
356             const char* pszCOEnd =
357 "  </Option>"
358 "  <Option name='ZOOM_LEVEL_STRATEGY' type='string-select' scope='raster' description='Strategy to determine zoom level. Only used for TILING_SCHEME != CUSTOM' default='AUTO'>"
359 "    <Value>AUTO</Value>"
360 "    <Value>LOWER</Value>"
361 "    <Value>UPPER</Value>"
362 "  </Option>"
363 "  <Option name='RESAMPLING' type='string-select' scope='raster' description='Resampling algorithm. Only used for TILING_SCHEME != CUSTOM' default='BILINEAR'>"
364 "    <Value>NEAREST</Value>"
365 "    <Value>BILINEAR</Value>"
366 "    <Value>CUBIC</Value>"
367 "    <Value>CUBICSPLINE</Value>"
368 "    <Value>LANCZOS</Value>"
369 "    <Value>MODE</Value>"
370 "    <Value>AVERAGE</Value>"
371 "  </Option>"
372 "  <Option name='PRECISION' type='float' scope='raster' description='Smallest significant value. Only used for tiled gridded coverage datasets' default='1'/>"
373 "  <Option name='UOM' type='string' scope='raster' description='Unit of Measurement. Only used for tiled gridded coverage datasets' />"
374 "  <Option name='FIELD_NAME' type='string' scope='raster' description='Field name. Only used for tiled gridded coverage datasets' default='Height'/>"
375 "  <Option name='QUANTITY_DEFINITION' type='string' scope='raster' description='Description of the field. Only used for tiled gridded coverage datasets' default='Height'/>"
376 "  <Option name='GRID_CELL_ENCODING' type='string-select' scope='raster' description='Grid cell encoding. Only used for tiled gridded coverage datasets' default='grid-value-is-center'>"
377 "     <Value>grid-value-is-center</Value>"
378 "     <Value>grid-value-is-area</Value>"
379 "     <Value>grid-value-is-corner</Value>"
380 "  </Option>"
381 "  <Option name='VERSION' type='string-select' description='Set GeoPackage version (for application_id and user_version fields)' default='AUTO'>"
382 "     <Value>AUTO</Value>"
383 "     <Value>1.0</Value>"
384 "     <Value>1.1</Value>"
385 "     <Value>1.2</Value>"
386 "     <Value>1.3</Value>"
387 "  </Option>"
388 "  <Option name='DATETIME_FORMAT' type='string-select' description='How to encode DateTime not in UTC' default='WITH_TZ'>"
389 "     <Value>WITH_TZ</Value>"
390 "     <Value>UTC</Value>"
391 "  </Option>"
392 #ifdef ENABLE_GPKG_OGR_CONTENTS
393 "  <Option name='ADD_GPKG_OGR_CONTENTS' type='boolean' description='Whether to add a gpkg_ogr_contents table to keep feature count' default='YES'/>"
394 #endif
395 "</CreationOptionList>";
396 
397     std::string osOptions(pszCOBegin);
398     const auto tmsList = gdal::TileMatrixSet::listPredefinedTileMatrixSets();
399     for( const auto& tmsName: tmsList )
400     {
401         const auto poTM = gdal::TileMatrixSet::parse(tmsName.c_str());
402         if( poTM &&
403             poTM->haveAllLevelsSameTopLeft() &&
404             poTM->haveAllLevelsSameTileSize() &&
405             poTM->hasOnlyPowerOfTwoVaryingScales() &&
406             !poTM->hasVariableMatrixWidth() )
407         {
408             osOptions += "    <Value>";
409             osOptions += tmsName;
410             osOptions += "</Value>";
411         }
412     }
413     osOptions += pszCOEnd;
414 
415     SetMetadataItem( GDAL_DMD_CREATIONOPTIONLIST, osOptions.c_str());
416 }
417 
418 
RegisterOGRGeoPackage()419 void RegisterOGRGeoPackage()
420 {
421     if( GDALGetDriverByName( "GPKG" ) != nullptr )
422         return;
423 
424     GDALDriver *poDriver = new GDALGPKGDriver();
425 
426     poDriver->SetDescription( "GPKG" );
427     poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
428     poDriver->SetMetadataItem( GDAL_DCAP_VECTOR, "YES" );
429     poDriver->SetMetadataItem( GDAL_DMD_SUBDATASETS, "YES" );
430 
431     poDriver->SetMetadataItem( GDAL_DMD_LONGNAME, "GeoPackage" );
432     poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "gpkg" );
433     poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, "drivers/vector/geopackage.html" );
434     poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES, "Byte Int16 UInt16 Float32" );
435 
436     poDriver->SetMetadataItem( GDAL_DMD_OPENOPTIONLIST, "<OpenOptionList>"
437 "  <Option name='LIST_ALL_TABLES' type='string-select' scope='vector' description='Whether all tables, including those non listed in gpkg_contents, should be listed' default='AUTO'>"
438 "    <Value>AUTO</Value>"
439 "    <Value>YES</Value>"
440 "    <Value>NO</Value>"
441 "  </Option>"
442 "  <Option name='TABLE' type='string' scope='raster' description='Name of tile user-table'/>"
443 "  <Option name='ZOOM_LEVEL' type='integer' scope='raster' description='Zoom level of full resolution. If not specified, maximum non-empty zoom level'/>"
444 "  <Option name='BAND_COUNT' type='int' min='1' max='4' scope='raster' description='Number of raster bands' default='4'/>"
445 "  <Option name='MINX' type='float' scope='raster' description='Minimum X of area of interest'/>"
446 "  <Option name='MINY' type='float' scope='raster' description='Minimum Y of area of interest'/>"
447 "  <Option name='MAXX' type='float' scope='raster' description='Maximum X of area of interest'/>"
448 "  <Option name='MAXY' type='float' scope='raster' description='Maximum Y of area of interest'/>"
449 "  <Option name='USE_TILE_EXTENT' type='boolean' scope='raster' description='Use tile extent of content to determine area of interest' default='NO'/>"
450 "  <Option name='WHERE' type='string' scope='raster' description='SQL WHERE clause to be appended to tile requests'/>"
451 COMPRESSION_OPTIONS
452 "  <Option name='PRELUDE_STATEMENTS' type='string' scope='raster,vector' description='SQL statement(s) to send on the SQLite connection before any other ones'/>"
453 "</OpenOptionList>");
454 
455     poDriver->SetMetadataItem( GDAL_DS_LAYER_CREATIONOPTIONLIST,
456 "<LayerCreationOptionList>"
457 "  <Option name='GEOMETRY_NAME' type='string' description='Name of geometry column.' default='geom' deprecated_alias='GEOMETRY_COLUMN'/>"
458 "  <Option name='GEOMETRY_NULLABLE' type='boolean' description='Whether the values of the geometry column can be NULL' default='YES'/>"
459 "  <Option name='FID' type='string' description='Name of the FID column to create' default='fid'/>"
460 "  <Option name='OVERWRITE' type='boolean' description='Whether to overwrite an existing table with the layer name to be created' default='NO'/>"
461 "  <Option name='PRECISION' type='boolean' description='Whether text fields created should keep the width' default='YES'/>"
462 "  <Option name='TRUNCATE_FIELDS' type='boolean' description='Whether to truncate text content that exceeds maximum width' default='NO'/>"
463 "  <Option name='SPATIAL_INDEX' type='boolean' description='Whether to create a spatial index' default='YES'/>"
464 "  <Option name='IDENTIFIER' type='string' description='Identifier of the layer, as put in the contents table'/>"
465 "  <Option name='DESCRIPTION' type='string' description='Description of the layer, as put in the contents table'/>"
466 "  <Option name='ASPATIAL_VARIANT' type='string-select' description='How to register non spatial tables' default='GPKG_ATTRIBUTES'>"
467 "     <Value>GPKG_ATTRIBUTES</Value>"
468 "     <Value>NOT_REGISTERED</Value>"
469 "  </Option>"
470 "</LayerCreationOptionList>");
471 
472     poDriver->SetMetadataItem( GDAL_DMD_CREATIONFIELDDATATYPES,
473                                "Integer Integer64 Real String Date DateTime "
474                                "Binary" );
475     poDriver->SetMetadataItem( GDAL_DMD_CREATIONFIELDDATASUBTYPES, "Boolean Int16 Float32" );
476     poDriver->SetMetadataItem( GDAL_DCAP_NOTNULL_FIELDS, "YES" );
477     poDriver->SetMetadataItem( GDAL_DCAP_DEFAULT_FIELDS, "YES" );
478     poDriver->SetMetadataItem( GDAL_DCAP_UNIQUE_FIELDS, "YES" );
479     poDriver->SetMetadataItem( GDAL_DCAP_NOTNULL_GEOMFIELDS, "YES" );
480 
481 #ifdef ENABLE_SQL_GPKG_FORMAT
482     poDriver->SetMetadataItem("ENABLE_SQL_GPKG_FORMAT", "YES");
483 #endif
484 #ifdef SQLITE_HAS_COLUMN_METADATA
485     poDriver->SetMetadataItem("SQLITE_HAS_COLUMN_METADATA", "YES");
486 #endif
487 
488     poDriver->pfnOpen = OGRGeoPackageDriverOpen;
489     poDriver->pfnIdentify = OGRGeoPackageDriverIdentify;
490     poDriver->pfnCreate = OGRGeoPackageDriverCreate;
491     poDriver->pfnCreateCopy = GDALGeoPackageDataset::CreateCopy;
492     poDriver->pfnDelete = OGRGeoPackageDriverDelete;
493 
494     poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );
495 
496     GetGDALDriverManager()->RegisterDriver( poDriver );
497 }
498