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