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