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