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