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 803 : static inline bool ENDS_WITH_CI(const char *a, const char *b)
29 : {
30 803 : return strlen(a) >= strlen(b) && EQUAL(a + strlen(a) - strlen(b), b);
31 : }
32 :
33 : /************************************************************************/
34 : /* OGRGeoPackageDriverIdentify() */
35 : /************************************************************************/
36 :
37 65431 : static int OGRGeoPackageDriverIdentify(GDALOpenInfo *poOpenInfo,
38 : std::string &osFilenameInGpkgZip,
39 : bool bEmitWarning)
40 : {
41 65431 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GPKG:"))
42 520 : return TRUE;
43 :
44 : #ifdef ENABLE_SQL_GPKG_FORMAT
45 64911 : if (poOpenInfo->pabyHeader &&
46 7207 : 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 64901 : const size_t nFilenameLen = strlen(poOpenInfo->pszFilename);
55 64901 : if ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) == 0 &&
56 62711 : nFilenameLen > strlen(".gpkg.zip") &&
57 62711 : !STARTS_WITH(poOpenInfo->pszFilename, "/vsizip/") &&
58 62669 : 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 64893 : if (poOpenInfo->nHeaderBytes < 100 || poOpenInfo->pabyHeader == nullptr ||
80 6773 : !STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
81 : "SQLite format 3"))
82 : {
83 62362 : 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 2531 : const char *pszExt = poOpenInfo->osExtension.c_str();
91 2531 : const bool bIsRecognizedExtension =
92 2531 : 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 2531 : memcpy(&nApplicationId, poOpenInfo->pabyHeader + knApplicationIdPos, 4);
99 2531 : nApplicationId = CPL_MSBWORD32(nApplicationId);
100 : GUInt32 nUserVersion;
101 2531 : memcpy(&nUserVersion, poOpenInfo->pabyHeader + knUserVersionPos, 4);
102 2531 : nUserVersion = CPL_MSBWORD32(nUserVersion);
103 2531 : if (nApplicationId != GP10_APPLICATION_ID &&
104 2512 : nApplicationId != GP11_APPLICATION_ID &&
105 2507 : 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 2288 : else if (nApplicationId == GPKG_APPLICATION_ID &&
143 : // Accept any 102XX version
144 2264 : !((nUserVersion >= GPKG_1_2_VERSION &&
145 2259 : nUserVersion < GPKG_1_2_VERSION + 99) ||
146 : // Accept any 103XX version
147 2125 : (nUserVersion >= GPKG_1_3_VERSION &&
148 2120 : nUserVersion < GPKG_1_3_VERSION + 99) ||
149 : // Accept any 104XX version
150 2121 : (nUserVersion >= GPKG_1_4_VERSION &&
151 2116 : 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 4558 : else if (!bIsRecognizedExtension
221 : #ifdef DEBUG
222 34 : && !EQUAL(CPLGetFilename(poOpenInfo->pszFilename), ".cur_input")
223 : #endif
224 34 : && !(STARTS_WITH(poOpenInfo->pszFilename, "/vsizip/") &&
225 2313 : 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 3094 : if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
238 803 : 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 2258 : return TRUE;
245 : }
246 :
247 64081 : static int OGRGeoPackageDriverIdentify(GDALOpenInfo *poOpenInfo)
248 : {
249 128162 : std::string osIgnored;
250 128162 : 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 2731 : OGRGeoPackageDriverGetSubdatasetInfo(const char *pszFileName)
318 : {
319 2731 : 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 2723 : return nullptr;
330 : }
331 :
332 : /************************************************************************/
333 : /* Open() */
334 : /************************************************************************/
335 :
336 1350 : static GDALDataset *OGRGeoPackageDriverOpen(GDALOpenInfo *poOpenInfo)
337 : {
338 2700 : std::string osFilenameInGpkgZip;
339 1350 : if (OGRGeoPackageDriverIdentify(poOpenInfo, osFilenameInGpkgZip, true) ==
340 : GDAL_IDENTIFY_FALSE)
341 0 : return nullptr;
342 :
343 1350 : GDALGeoPackageDataset *poDS = new GDALGeoPackageDataset();
344 :
345 1350 : if (!poDS->Open(poOpenInfo, osFilenameInGpkgZip))
346 : {
347 18 : delete poDS;
348 18 : poDS = nullptr;
349 : }
350 :
351 1350 : return poDS;
352 : }
353 :
354 : /************************************************************************/
355 : /* Create() */
356 : /************************************************************************/
357 :
358 1045 : static GDALDataset *OGRGeoPackageDriverCreate(const char *pszFilename,
359 : int nXSize, int nYSize,
360 : int nBands, GDALDataType eDT,
361 : CSLConstList papszOptions)
362 : {
363 1045 : if (strcmp(pszFilename, ":memory:") != 0)
364 : {
365 1041 : const size_t nFilenameLen = strlen(pszFilename);
366 1041 : if (nFilenameLen > strlen(".gpkg.zip") &&
367 1040 : !STARTS_WITH(pszFilename, "/vsizip/") &&
368 1040 : EQUAL(pszFilename + nFilenameLen - strlen(".gpkg.zip"),
369 : ".gpkg.zip"))
370 : {
371 : // do nothing
372 : }
373 : else
374 : {
375 2078 : const std::string osExt = CPLGetExtensionSafe(pszFilename);
376 : const bool bIsRecognizedExtension =
377 1039 : EQUAL(osExt.c_str(), "GPKG") || EQUAL(osExt.c_str(), "GPKX");
378 1039 : 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 1045 : GDALGeoPackageDataset *poDS = new GDALGeoPackageDataset();
390 :
391 1045 : if (!poDS->Create(pszFilename, nXSize, nYSize, nBands, eDT, papszOptions))
392 : {
393 40 : delete poDS;
394 40 : poDS = nullptr;
395 : }
396 :
397 1045 : 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 1800 : GDALGPKGDriver() = default;
432 :
433 : const char *GetMetadataItem(const char *pszName,
434 : const char *pszDomain) override;
435 :
436 597 : CSLConstList GetMetadata(const char *pszDomain) override
437 : {
438 1194 : std::lock_guard oLock(m_oMutex);
439 597 : InitializeCreationOptionList();
440 1194 : return GDALDriver::GetMetadata(pszDomain);
441 : }
442 : };
443 :
444 56688 : const char *GDALGPKGDriver::GetMetadataItem(const char *pszName,
445 : const char *pszDomain)
446 : {
447 113376 : std::lock_guard oLock(m_oMutex);
448 56688 : if (EQUAL(pszName, GDAL_DMD_CREATIONOPTIONLIST))
449 : {
450 1564 : InitializeCreationOptionList();
451 : }
452 113376 : 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 2161 : void GDALGPKGDriver::InitializeCreationOptionList()
475 : {
476 2161 : if (m_bInitialized)
477 1912 : return;
478 249 : m_bInitialized = true;
479 :
480 249 : 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 249 : 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 498 : std::string osOptions(pszCOBegin);
575 498 : const auto tmsList = gdal::TileMatrixSet::listPredefinedTileMatrixSets();
576 2490 : for (const auto &tmsName : tmsList)
577 : {
578 4482 : const auto poTM = gdal::TileMatrixSet::parse(tmsName.c_str());
579 4482 : if (poTM && poTM->haveAllLevelsSameTopLeft() &&
580 2241 : poTM->haveAllLevelsSameTileSize() &&
581 6225 : poTM->hasOnlyPowerOfTwoVaryingScales() &&
582 1743 : !poTM->hasVariableMatrixWidth())
583 : {
584 1743 : osOptions += " <Value>";
585 1743 : osOptions += tmsName;
586 1743 : osOptions += "</Value>";
587 : }
588 : }
589 249 : osOptions += pszCOEnd;
590 :
591 249 : 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 140 : OGRGeoPackageRepackAlgorithm()
606 140 : : GDALAlgorithm("repack", "Repack/vacuum in-place a GeoPackage dataset",
607 140 : "/programs/gdal_driver_gpkg_repack.html")
608 : {
609 140 : constexpr int type = GDAL_OF_RASTER | GDAL_OF_VECTOR | GDAL_OF_UPDATE;
610 : auto &arg =
611 280 : AddArg("dataset", 0, _("GeoPackage dataset"), &m_dataset, type)
612 140 : .SetPositional()
613 140 : .SetRequired();
614 140 : SetAutoCompleteFunctionForFilename(arg, type);
615 140 : }
616 :
617 : protected:
618 : bool RunImpl(GDALProgressFunc, void *) override;
619 :
620 : private:
621 : GDALArgDatasetValue m_dataset{};
622 : };
623 :
624 2 : bool OGRGeoPackageRepackAlgorithm::RunImpl(GDALProgressFunc, void *)
625 : {
626 : auto poDS =
627 2 : dynamic_cast<GDALGeoPackageDataset *>(m_dataset.GetDatasetRef());
628 2 : if (!poDS)
629 : {
630 1 : ReportError(CE_Failure, CPLE_AppDefined, "%s is not a GeoPackage",
631 1 : m_dataset.GetName().c_str());
632 1 : return false;
633 : }
634 1 : CPLErrorReset();
635 1 : delete poDS->ExecuteSQL("VACUUM", nullptr, nullptr);
636 1 : return CPLGetLastErrorType() == CE_None;
637 : }
638 :
639 : /************************************************************************/
640 : /* OGRGeoPackageValidateAlgorithm */
641 : /************************************************************************/
642 :
643 : class OGRGeoPackageValidateAlgorithm final : public GDALAlgorithm
644 : {
645 : public:
646 142 : OGRGeoPackageValidateAlgorithm()
647 142 : : GDALAlgorithm("validate",
648 : "Validate conformance of a GeoPackage dataset against "
649 : "the GeoPackage specification",
650 142 : "/programs/gdal_driver_gpkg_validate.html")
651 : {
652 142 : constexpr int type = GDAL_OF_RASTER | GDAL_OF_VECTOR;
653 : auto &arg =
654 284 : AddArg("dataset", 0, _("GeoPackage dataset"), &m_dataset, type)
655 142 : .SetPositional()
656 142 : .SetRequired();
657 142 : SetAutoCompleteFunctionForFilename(arg, type);
658 :
659 : AddArg("full-check", 0, _("Whether to perform full check"),
660 284 : &m_fullCheck)
661 142 : .SetDefault(m_fullCheck);
662 :
663 142 : AddOutputStringArg(&m_output);
664 142 : AddArg("verbose", 'v', _("Turn on verbose mode"), &m_verbose);
665 142 : AddProgressArg();
666 142 : }
667 :
668 : protected:
669 : bool RunImpl(GDALProgressFunc, void *) override;
670 :
671 : private:
672 : GDALArgDatasetValue m_dataset{};
673 : bool m_fullCheck = false;
674 : bool m_verbose = false;
675 : std::string m_output{};
676 : };
677 :
678 : /************************************************************************/
679 : /* RunImpl() */
680 : /************************************************************************/
681 :
682 4 : bool OGRGeoPackageValidateAlgorithm::RunImpl(GDALProgressFunc, void *)
683 : {
684 : auto poDS =
685 4 : dynamic_cast<GDALGeoPackageDataset *>(m_dataset.GetDatasetRef());
686 4 : if (!poDS)
687 : {
688 0 : ReportError(CE_Failure, CPLE_AppDefined, "%s is not a GeoPackage",
689 0 : m_dataset.GetName().c_str());
690 0 : return false;
691 : }
692 :
693 4 : if (!GDALPythonInitialize())
694 0 : return false;
695 :
696 8 : GIL_Holder oHolder(false);
697 :
698 8 : const CPLString osModuleName(CPLSPrintf("gpkg_validate_module_%p", this));
699 : PyObject *poCompiledString =
700 4 : Py_CompileString("from osgeo_utils.samples.validate_gpkg import "
701 : "main, get_output_string\n",
702 : osModuleName, Py_file_input);
703 4 : if (poCompiledString == nullptr || PyErr_Occurred())
704 : {
705 0 : CPLError(CE_Failure, CPLE_AppDefined, "Couldn't compile code:\n%s",
706 0 : GetPyExceptionString().c_str());
707 0 : return false;
708 : }
709 : PyObject *poModule =
710 4 : PyImport_ExecCodeModule(osModuleName, poCompiledString);
711 4 : Py_DecRef(poCompiledString);
712 :
713 4 : if (poModule == nullptr || PyErr_Occurred())
714 : {
715 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s",
716 0 : GetPyExceptionString().c_str());
717 0 : return false;
718 : }
719 :
720 4 : PyObject *poMain = PyObject_GetAttrString(poModule, "main");
721 4 : CPLAssert(poMain);
722 : PyObject *poGetOutput =
723 4 : PyObject_GetAttrString(poModule, "get_output_string");
724 4 : CPLAssert(poGetOutput);
725 :
726 4 : Py_DecRef(poModule);
727 :
728 24 : std::vector<std::string> args{"dummy", "-k", m_dataset.GetName()};
729 4 : if (m_quiet)
730 0 : args.push_back("-q");
731 4 : else if (m_verbose)
732 1 : args.push_back("-v");
733 4 : if (m_fullCheck)
734 2 : args.push_back("--extra");
735 :
736 4 : PyObject *pyArgv = PyTuple_New(args.size());
737 19 : for (size_t i = 0; i < args.size(); ++i)
738 : {
739 15 : PyTuple_SetItem(pyArgv, i, PyUnicode_FromString(args[i].c_str()));
740 : }
741 4 : PyObject *pyKwargs = PyDict_New();
742 4 : PyDict_SetItemString(pyKwargs, "argv", pyArgv);
743 4 : PyDict_SetItemString(pyKwargs, "output_in_string", PyBool_FromLong(true));
744 4 : PyObject *pyArgs = PyTuple_New(0);
745 4 : PyObject *pRetValue = PyObject_Call(poMain, pyArgs, pyKwargs);
746 4 : const auto nRetValue = pRetValue ? PyLong_AsLong(pRetValue) : -1;
747 4 : Py_DecRef(pyArgs);
748 4 : Py_DecRef(pyKwargs);
749 4 : Py_DecRef(pRetValue);
750 :
751 4 : if (!m_quiet || nRetValue)
752 : {
753 4 : pyArgs = PyTuple_New(0);
754 4 : pyKwargs = PyDict_New();
755 4 : PyObject *poMsg = PyObject_Call(poGetOutput, pyArgs, pyKwargs);
756 4 : Py_DecRef(pyArgs);
757 4 : Py_DecRef(pyKwargs);
758 4 : if (poMsg)
759 4 : m_output = GetString(poMsg);
760 4 : Py_DecRef(poMsg);
761 : }
762 :
763 4 : Py_DecRef(poMain);
764 4 : Py_DecRef(poGetOutput);
765 :
766 4 : if (nRetValue)
767 1 : m_output += "\nValidation failed";
768 3 : else if (!m_quiet)
769 : {
770 3 : if (!m_output.empty())
771 1 : m_output += '\n';
772 3 : m_output += "Validation succeeded";
773 : }
774 :
775 4 : if (nRetValue && !IsCalledFromCommandLine())
776 : {
777 1 : ReportError(CE_Failure, CPLE_AppDefined, "%s", m_output.c_str());
778 : }
779 :
780 4 : return nRetValue == 0;
781 : }
782 :
783 : /************************************************************************/
784 : /* OGRGeoPackageDriverInstantiateAlgorithm() */
785 : /************************************************************************/
786 :
787 : static GDALAlgorithm *
788 282 : OGRGeoPackageDriverInstantiateAlgorithm(const std::vector<std::string> &aosPath)
789 : {
790 282 : if (aosPath.size() == 1 && aosPath[0] == "repack")
791 : {
792 140 : return std::make_unique<OGRGeoPackageRepackAlgorithm>().release();
793 : }
794 142 : else if (aosPath.size() == 1 && aosPath[0] == "validate")
795 : {
796 142 : return std::make_unique<OGRGeoPackageValidateAlgorithm>().release();
797 : }
798 : else
799 : {
800 0 : return nullptr;
801 : }
802 : }
803 :
804 : /************************************************************************/
805 : /* RegisterOGRGeoPackage() */
806 : /************************************************************************/
807 :
808 2063 : void RegisterOGRGeoPackage()
809 : {
810 2063 : if (GDALGetDriverByName("GPKG") != nullptr)
811 263 : return;
812 :
813 1800 : GDALDriver *poDriver = new GDALGPKGDriver();
814 :
815 1800 : poDriver->SetDescription("GPKG");
816 1800 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
817 1800 : poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
818 1800 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
819 1800 : poDriver->SetMetadataItem(GDAL_DCAP_DELETE_LAYER, "YES");
820 1800 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
821 1800 : poDriver->SetMetadataItem(GDAL_DCAP_DELETE_FIELD, "YES");
822 1800 : poDriver->SetMetadataItem(GDAL_DCAP_REORDER_FIELDS, "YES");
823 1800 : poDriver->SetMetadataItem(GDAL_DCAP_CURVE_GEOMETRIES, "YES");
824 1800 : poDriver->SetMetadataItem(GDAL_DCAP_MEASURED_GEOMETRIES, "YES");
825 1800 : poDriver->SetMetadataItem(GDAL_DCAP_Z_GEOMETRIES, "YES");
826 1800 : poDriver->SetMetadataItem(GDAL_DCAP_CAN_READ_AFTER_DELETE, "YES");
827 1800 : poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
828 1800 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE_SUBDATASETS, "YES");
829 1800 : poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS,
830 1800 : "NATIVE OGRSQL SQLITE");
831 :
832 1800 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "GeoPackage");
833 1800 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "gpkg gpkg.zip");
834 1800 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/vector/gpkg.html");
835 1800 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES,
836 1800 : "Byte Int16 UInt16 Float32");
837 :
838 1800 : poDriver->SetMetadataItem(
839 : GDAL_DMD_OPENOPTIONLIST,
840 : "<OpenOptionList>"
841 : " <Option name='LIST_ALL_TABLES' type='string-select' scope='vector' "
842 : "description='Whether all tables, including those non listed in "
843 : "gpkg_contents, should be listed' default='AUTO'>"
844 : " <Value>AUTO</Value>"
845 : " <Value>YES</Value>"
846 : " <Value>NO</Value>"
847 : " </Option>"
848 : " <Option name='TABLE' type='string' scope='raster' description='Name "
849 : "of tile user-table'/>"
850 : " <Option name='ZOOM_LEVEL' type='integer' scope='raster' "
851 : "description='Zoom level of full resolution. If not specified, maximum "
852 : "non-empty zoom level'/>"
853 : " <Option name='BAND_COUNT' type='string-select' scope='raster' "
854 : "description='Number of raster bands (only for Byte data type)' "
855 : "default='AUTO'>"
856 : " <Value>AUTO</Value>"
857 : " <Value>1</Value>"
858 : " <Value>2</Value>"
859 : " <Value>3</Value>"
860 : " <Value>4</Value>"
861 : " </Option>"
862 : " <Option name='MINX' type='float' scope='raster' "
863 : "description='Minimum X of area of interest'/>"
864 : " <Option name='MINY' type='float' scope='raster' "
865 : "description='Minimum Y of area of interest'/>"
866 : " <Option name='MAXX' type='float' scope='raster' "
867 : "description='Maximum X of area of interest'/>"
868 : " <Option name='MAXY' type='float' scope='raster' "
869 : "description='Maximum Y of area of interest'/>"
870 : " <Option name='USE_TILE_EXTENT' type='boolean' scope='raster' "
871 : "description='Use tile extent of content to determine area of "
872 : "interest' default='NO'/>"
873 : " <Option name='WHERE' type='string' scope='raster' description='SQL "
874 : "WHERE clause to be appended to tile requests'/>" COMPRESSION_OPTIONS
875 : " <Option name='PRELUDE_STATEMENTS' type='string' "
876 : "scope='raster,vector' description='SQL statement(s) to send on the "
877 : "SQLite connection before any other ones'/>"
878 : " <Option name='NOLOCK' type='boolean' description='Whether the "
879 : "database should be opened in nolock mode'/>"
880 : " <Option name='IMMUTABLE' type='boolean' description='Whether the "
881 : "database should be opened in immutable mode'/>"
882 1800 : "</OpenOptionList>");
883 1800 : poDriver->SetMetadataItem(GDAL_DMD_OVERVIEW_CREATIONOPTIONLIST,
884 : "<OverviewCreationOptionList>"
885 1800 : "</OverviewCreationOptionList>");
886 :
887 1800 : poDriver->SetMetadataItem(
888 : GDAL_DS_LAYER_CREATIONOPTIONLIST,
889 : "<LayerCreationOptionList>"
890 : " <Option name='LAUNDER' type='boolean' description='Whether layer "
891 : "and field names will be laundered.' default='NO'/>"
892 : " <Option name='GEOMETRY_NAME' type='string' description='Name of "
893 : "geometry column.' default='geom' deprecated_alias='GEOMETRY_COLUMN'/>"
894 : " <Option name='GEOMETRY_NULLABLE' type='boolean' "
895 : "description='Whether the values of the geometry column can be NULL' "
896 : "default='YES'/>"
897 : " <Option name='SRID' type='integer' description='Forced srs_id of "
898 : "the "
899 : "entry in the gpkg_spatial_ref_sys table to point to'/>"
900 : " <Option name='DISCARD_COORD_LSB' type='boolean' "
901 : "description='Whether the geometry coordinate precision should be used "
902 : "to set to zero non-significant least-significant bits of geometries. "
903 : "Helps when further compression is used' default='NO'/>"
904 : " <Option name='UNDO_DISCARD_COORD_LSB_ON_READING' type='boolean' "
905 : "description='Whether to ask GDAL to take into coordinate precision to "
906 : "undo the effects of DISCARD_COORD_LSB' default='NO'/>"
907 : " <Option name='FID' type='string' description='Name of the FID "
908 : "column to create' default='fid'/>"
909 : " <Option name='OVERWRITE' type='boolean' description='Whether to "
910 : "overwrite an existing table with the layer name to be created' "
911 : "default='NO'/>"
912 : " <Option name='PRECISION' type='boolean' description='Whether text "
913 : "fields created should keep the width' default='YES'/>"
914 : " <Option name='TRUNCATE_FIELDS' type='boolean' description='Whether "
915 : "to truncate text content that exceeds maximum width' default='NO'/>"
916 : " <Option name='SPATIAL_INDEX' type='boolean' description='Whether to "
917 : "create a spatial index' default='YES'/>"
918 : " <Option name='IDENTIFIER' type='string' description='Identifier of "
919 : "the layer, as put in the contents table'/>"
920 : " <Option name='DESCRIPTION' type='string' description='Description "
921 : "of the layer, as put in the contents table'/>"
922 : " <Option name='ASPATIAL_VARIANT' type='string-select' "
923 : "description='How to register non spatial tables' "
924 : "default='GPKG_ATTRIBUTES'>"
925 : " <Value>GPKG_ATTRIBUTES</Value>"
926 : " <Value>NOT_REGISTERED</Value>"
927 : " </Option>"
928 : " <Option name='DATETIME_PRECISION' type='string-select' "
929 : "description='Number of components of datetime fields' "
930 : "default='AUTO'>"
931 : " <Value>AUTO</Value>"
932 : " <Value>MILLISECOND</Value>"
933 : " <Value>SECOND</Value>"
934 : " <Value>MINUTE</Value>"
935 : " </Option>"
936 1800 : "</LayerCreationOptionList>");
937 :
938 1800 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES,
939 : "Integer Integer64 Real String Date DateTime "
940 1800 : "Binary");
941 1800 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES,
942 1800 : "Boolean Int16 Float32 JSON");
943 1800 : poDriver->SetMetadataItem(GDAL_DMD_CREATION_FIELD_DEFN_FLAGS,
944 : "WidthPrecision Nullable Default Unique "
945 1800 : "Comment AlternativeName Domain");
946 :
947 1800 : poDriver->SetMetadataItem(GDAL_DMD_ALTER_FIELD_DEFN_FLAGS,
948 : "Name Type WidthPrecision Nullable Default "
949 1800 : "Unique Domain AlternativeName Comment");
950 :
951 1800 : poDriver->SetMetadataItem(GDAL_DCAP_NOTNULL_FIELDS, "YES");
952 1800 : poDriver->SetMetadataItem(GDAL_DCAP_DEFAULT_FIELDS, "YES");
953 1800 : poDriver->SetMetadataItem(GDAL_DCAP_UNIQUE_FIELDS, "YES");
954 1800 : poDriver->SetMetadataItem(GDAL_DCAP_NOTNULL_GEOMFIELDS, "YES");
955 1800 : poDriver->SetMetadataItem(GDAL_DCAP_MULTIPLE_VECTOR_LAYERS, "YES");
956 1800 : poDriver->SetMetadataItem(GDAL_DCAP_FIELD_DOMAINS, "YES");
957 1800 : poDriver->SetMetadataItem(GDAL_DCAP_RELATIONSHIPS, "YES");
958 1800 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE_RELATIONSHIP, "YES");
959 1800 : poDriver->SetMetadataItem(GDAL_DCAP_DELETE_RELATIONSHIP, "YES");
960 1800 : poDriver->SetMetadataItem(GDAL_DCAP_UPDATE_RELATIONSHIP, "YES");
961 1800 : poDriver->SetMetadataItem(GDAL_DCAP_FLUSHCACHE_CONSISTENT_STATE, "YES");
962 1800 : poDriver->SetMetadataItem(GDAL_DCAP_UPSERT, "YES");
963 :
964 1800 : poDriver->SetMetadataItem(GDAL_DMD_RELATIONSHIP_FLAGS,
965 1800 : "ManyToMany Association");
966 :
967 1800 : poDriver->SetMetadataItem(GDAL_DCAP_RENAME_LAYERS, "YES");
968 1800 : poDriver->SetMetadataItem(GDAL_DMD_CREATION_FIELD_DOMAIN_TYPES,
969 1800 : "Coded Range Glob");
970 :
971 1800 : poDriver->SetMetadataItem(GDAL_DMD_ALTER_GEOM_FIELD_DEFN_FLAGS,
972 1800 : "Name SRS CoordinateEpoch");
973 :
974 1800 : poDriver->SetMetadataItem(
975 : GDAL_DMD_RELATIONSHIP_RELATED_TABLE_TYPES,
976 1800 : "features media simple_attributes attributes tiles");
977 :
978 1800 : poDriver->SetMetadataItem(GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION, "YES");
979 :
980 : #ifdef ENABLE_SQL_GPKG_FORMAT
981 1800 : poDriver->SetMetadataItem("ENABLE_SQL_GPKG_FORMAT", "YES");
982 : #endif
983 : #ifdef SQLITE_HAS_COLUMN_METADATA
984 1800 : poDriver->SetMetadataItem("SQLITE_HAS_COLUMN_METADATA", "YES");
985 : #endif
986 :
987 1800 : poDriver->SetMetadataItem(GDAL_DCAP_UPDATE, "YES");
988 1800 : poDriver->SetMetadataItem(GDAL_DMD_UPDATE_ITEMS,
989 : "DatasetMetadata BandMetadata RasterValues "
990 1800 : "LayerMetadata Features");
991 :
992 1800 : poDriver->pfnOpen = OGRGeoPackageDriverOpen;
993 1800 : poDriver->pfnIdentify = OGRGeoPackageDriverIdentify;
994 1800 : poDriver->pfnCreate = OGRGeoPackageDriverCreate;
995 1800 : poDriver->pfnCreateCopy = GDALGeoPackageDataset::CreateCopy;
996 1800 : poDriver->pfnDelete = OGRGeoPackageDriverDelete;
997 1800 : poDriver->pfnGetSubdatasetInfoFunc = OGRGeoPackageDriverGetSubdatasetInfo;
998 :
999 1800 : poDriver->pfnInstantiateAlgorithm = OGRGeoPackageDriverInstantiateAlgorithm;
1000 3600 : poDriver->DeclareAlgorithm({"repack"});
1001 3600 : poDriver->DeclareAlgorithm({"validate"});
1002 :
1003 1800 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1004 :
1005 1800 : GetGDALDriverManager()->RegisterDriver(poDriver);
1006 : }
|