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