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