Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GeoPackage Translator
4 : * Purpose: Implements GDALGeoPackageDataset class
5 : * Author: Paul Ramsey <pramsey@boundlessgeo.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2013, Paul Ramsey <pramsey@boundlessgeo.com>
9 : * Copyright (c) 2014, Even Rouault <even dot rouault at spatialys.com>
10 : *
11 : * SPDX-License-Identifier: MIT
12 : ****************************************************************************/
13 :
14 : #include "ogr_geopackage.h"
15 : #include "ogr_p.h"
16 : #include "ogr_swq.h"
17 : #include "gdalwarper.h"
18 : #include "gdal_utils.h"
19 : #include "ogrgeopackageutility.h"
20 : #include "ogrsqliteutility.h"
21 : #include "ogr_wkb.h"
22 : #include "vrt/vrtdataset.h"
23 :
24 : #include "tilematrixset.hpp"
25 :
26 : #include <cstdlib>
27 :
28 : #include <algorithm>
29 : #include <limits>
30 : #include <sstream>
31 :
32 : #define COMPILATION_ALLOWED
33 : #define DEFINE_OGRSQLiteSQLFunctionsSetCaseSensitiveLike
34 : #include "ogrsqlitesqlfunctionscommon.cpp"
35 :
36 : // Keep in sync prototype of those 2 functions between gdalopeninfo.cpp,
37 : // ogrsqlitedatasource.cpp and ogrgeopackagedatasource.cpp
38 : void GDALOpenInfoDeclareFileNotToOpen(const char *pszFilename,
39 : const GByte *pabyHeader,
40 : int nHeaderBytes);
41 : void GDALOpenInfoUnDeclareFileNotToOpen(const char *pszFilename);
42 :
43 : /************************************************************************/
44 : /* Tiling schemes */
45 : /************************************************************************/
46 :
47 : typedef struct
48 : {
49 : const char *pszName;
50 : int nEPSGCode;
51 : double dfMinX;
52 : double dfMaxY;
53 : int nTileXCountZoomLevel0;
54 : int nTileYCountZoomLevel0;
55 : int nTileWidth;
56 : int nTileHeight;
57 : double dfPixelXSizeZoomLevel0;
58 : double dfPixelYSizeZoomLevel0;
59 : } TilingSchemeDefinition;
60 :
61 : static const TilingSchemeDefinition asTilingSchemes[] = {
62 : /* See http://portal.opengeospatial.org/files/?artifact_id=35326 (WMTS 1.0),
63 : Annex E.3 */
64 : {"GoogleCRS84Quad", 4326, -180.0, 180.0, 1, 1, 256, 256, 360.0 / 256,
65 : 360.0 / 256},
66 :
67 : /* See global-mercator at
68 : http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
69 : {"PseudoTMS_GlobalMercator", 3857, -20037508.34, 20037508.34, 2, 2, 256,
70 : 256, 78271.516, 78271.516},
71 : };
72 :
73 : // Setting it above 30 would lead to integer overflow ((1 << 31) > INT_MAX)
74 : constexpr int MAX_ZOOM_LEVEL = 30;
75 :
76 : /************************************************************************/
77 : /* GetTilingScheme() */
78 : /************************************************************************/
79 :
80 : static std::unique_ptr<TilingSchemeDefinition>
81 531 : GetTilingScheme(const char *pszName)
82 : {
83 531 : if (EQUAL(pszName, "CUSTOM"))
84 403 : return nullptr;
85 :
86 256 : for (const auto &tilingScheme : asTilingSchemes)
87 : {
88 195 : if (EQUAL(pszName, tilingScheme.pszName))
89 : {
90 : return std::unique_ptr<TilingSchemeDefinition>(
91 67 : new TilingSchemeDefinition(tilingScheme));
92 : }
93 : }
94 :
95 61 : if (EQUAL(pszName, "PseudoTMS_GlobalGeodetic"))
96 6 : pszName = "InspireCRS84Quad";
97 :
98 122 : auto poTM = gdal::TileMatrixSet::parse(pszName);
99 61 : if (poTM == nullptr)
100 1 : return nullptr;
101 60 : if (!poTM->haveAllLevelsSameTopLeft())
102 : {
103 0 : CPLError(CE_Failure, CPLE_NotSupported,
104 : "Unsupported tiling scheme: not all zoom levels have same top "
105 : "left corner");
106 0 : return nullptr;
107 : }
108 60 : if (!poTM->haveAllLevelsSameTileSize())
109 : {
110 0 : CPLError(CE_Failure, CPLE_NotSupported,
111 : "Unsupported tiling scheme: not all zoom levels have same "
112 : "tile size");
113 0 : return nullptr;
114 : }
115 60 : if (!poTM->hasOnlyPowerOfTwoVaryingScales())
116 : {
117 1 : CPLError(CE_Failure, CPLE_NotSupported,
118 : "Unsupported tiling scheme: resolution of consecutive zoom "
119 : "levels is not always 2");
120 1 : return nullptr;
121 : }
122 59 : if (poTM->hasVariableMatrixWidth())
123 : {
124 0 : CPLError(CE_Failure, CPLE_NotSupported,
125 : "Unsupported tiling scheme: some levels have variable matrix "
126 : "width");
127 0 : return nullptr;
128 : }
129 118 : auto poTilingScheme = std::make_unique<TilingSchemeDefinition>();
130 59 : poTilingScheme->pszName = pszName;
131 :
132 118 : OGRSpatialReference oSRS;
133 59 : if (oSRS.SetFromUserInput(poTM->crs().c_str()) != OGRERR_NONE)
134 : {
135 0 : return nullptr;
136 : }
137 59 : if (poTM->crs() == "http://www.opengis.net/def/crs/OGC/1.3/CRS84")
138 : {
139 6 : poTilingScheme->nEPSGCode = 4326;
140 : }
141 : else
142 : {
143 53 : const char *pszAuthName = oSRS.GetAuthorityName(nullptr);
144 53 : const char *pszAuthCode = oSRS.GetAuthorityCode(nullptr);
145 53 : if (pszAuthName == nullptr || !EQUAL(pszAuthName, "EPSG") ||
146 : pszAuthCode == nullptr)
147 : {
148 0 : CPLError(CE_Failure, CPLE_NotSupported,
149 : "Unsupported tiling scheme: only EPSG CRS supported");
150 0 : return nullptr;
151 : }
152 53 : poTilingScheme->nEPSGCode = atoi(pszAuthCode);
153 : }
154 59 : const auto &zoomLevel0 = poTM->tileMatrixList()[0];
155 59 : poTilingScheme->dfMinX = zoomLevel0.mTopLeftX;
156 59 : poTilingScheme->dfMaxY = zoomLevel0.mTopLeftY;
157 59 : poTilingScheme->nTileXCountZoomLevel0 = zoomLevel0.mMatrixWidth;
158 59 : poTilingScheme->nTileYCountZoomLevel0 = zoomLevel0.mMatrixHeight;
159 59 : poTilingScheme->nTileWidth = zoomLevel0.mTileWidth;
160 59 : poTilingScheme->nTileHeight = zoomLevel0.mTileHeight;
161 59 : poTilingScheme->dfPixelXSizeZoomLevel0 = zoomLevel0.mResX;
162 59 : poTilingScheme->dfPixelYSizeZoomLevel0 = zoomLevel0.mResY;
163 :
164 118 : const bool bInvertAxis = oSRS.EPSGTreatsAsLatLong() != FALSE ||
165 59 : oSRS.EPSGTreatsAsNorthingEasting() != FALSE;
166 59 : if (bInvertAxis)
167 : {
168 6 : std::swap(poTilingScheme->dfMinX, poTilingScheme->dfMaxY);
169 6 : std::swap(poTilingScheme->dfPixelXSizeZoomLevel0,
170 6 : poTilingScheme->dfPixelYSizeZoomLevel0);
171 : }
172 59 : return poTilingScheme;
173 : }
174 :
175 : static const char *pszCREATE_GPKG_GEOMETRY_COLUMNS =
176 : "CREATE TABLE gpkg_geometry_columns ("
177 : "table_name TEXT NOT NULL,"
178 : "column_name TEXT NOT NULL,"
179 : "geometry_type_name TEXT NOT NULL,"
180 : "srs_id INTEGER NOT NULL,"
181 : "z TINYINT NOT NULL,"
182 : "m TINYINT NOT NULL,"
183 : "CONSTRAINT pk_geom_cols PRIMARY KEY (table_name, column_name),"
184 : "CONSTRAINT uk_gc_table_name UNIQUE (table_name),"
185 : "CONSTRAINT fk_gc_tn FOREIGN KEY (table_name) REFERENCES "
186 : "gpkg_contents(table_name),"
187 : "CONSTRAINT fk_gc_srs FOREIGN KEY (srs_id) REFERENCES gpkg_spatial_ref_sys "
188 : "(srs_id)"
189 : ")";
190 :
191 782 : OGRErr GDALGeoPackageDataset::SetApplicationAndUserVersionId()
192 : {
193 782 : CPLAssert(hDB != nullptr);
194 :
195 782 : const CPLString osPragma(CPLString().Printf("PRAGMA application_id = %u;"
196 : "PRAGMA user_version = %u",
197 : m_nApplicationId,
198 1564 : m_nUserVersion));
199 1564 : return SQLCommand(hDB, osPragma.c_str());
200 : }
201 :
202 2261 : bool GDALGeoPackageDataset::CloseDB()
203 : {
204 2261 : OGRSQLiteUnregisterSQLFunctions(m_pSQLFunctionData);
205 2261 : m_pSQLFunctionData = nullptr;
206 2261 : return OGRSQLiteBaseDataSource::CloseDB();
207 : }
208 :
209 11 : bool GDALGeoPackageDataset::ReOpenDB()
210 : {
211 11 : CPLAssert(hDB != nullptr);
212 11 : CPLAssert(m_pszFilename != nullptr);
213 :
214 11 : FinishSpatialite();
215 :
216 11 : CloseDB();
217 :
218 : /* And re-open the file */
219 11 : return OpenOrCreateDB(SQLITE_OPEN_READWRITE);
220 : }
221 :
222 742 : static OGRErr GDALGPKGImportFromEPSG(OGRSpatialReference *poSpatialRef,
223 : int nEPSGCode)
224 : {
225 742 : CPLPushErrorHandler(CPLQuietErrorHandler);
226 742 : const OGRErr eErr = poSpatialRef->importFromEPSG(nEPSGCode);
227 742 : CPLPopErrorHandler();
228 742 : CPLErrorReset();
229 742 : return eErr;
230 : }
231 :
232 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
233 1122 : GDALGeoPackageDataset::GetSpatialRef(int iSrsId, bool bFallbackToEPSG,
234 : bool bEmitErrorIfNotFound)
235 : {
236 1122 : const auto oIter = m_oMapSrsIdToSrs.find(iSrsId);
237 1122 : if (oIter != m_oMapSrsIdToSrs.end())
238 : {
239 77 : if (oIter->second == nullptr)
240 31 : return nullptr;
241 46 : oIter->second->Reference();
242 : return std::unique_ptr<OGRSpatialReference,
243 46 : OGRSpatialReferenceReleaser>(oIter->second);
244 : }
245 :
246 1045 : if (iSrsId == 0 || iSrsId == -1)
247 : {
248 120 : OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
249 120 : poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
250 :
251 : // See corresponding tests in GDALGeoPackageDataset::GetSrsId
252 120 : if (iSrsId == 0)
253 : {
254 29 : poSpatialRef->SetGeogCS("Undefined geographic SRS", "unknown",
255 : "unknown", SRS_WGS84_SEMIMAJOR,
256 : SRS_WGS84_INVFLATTENING);
257 : }
258 91 : else if (iSrsId == -1)
259 : {
260 91 : poSpatialRef->SetLocalCS("Undefined Cartesian SRS");
261 91 : poSpatialRef->SetLinearUnits(SRS_UL_METER, 1.0);
262 : }
263 :
264 120 : m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
265 120 : poSpatialRef->Reference();
266 : return std::unique_ptr<OGRSpatialReference,
267 120 : OGRSpatialReferenceReleaser>(poSpatialRef);
268 : }
269 :
270 1850 : CPLString oSQL;
271 925 : oSQL.Printf("SELECT srs_name, definition, organization, "
272 : "organization_coordsys_id%s%s "
273 : "FROM gpkg_spatial_ref_sys WHERE "
274 : "srs_id = %d LIMIT 2",
275 925 : m_bHasDefinition12_063 ? ", definition_12_063" : "",
276 925 : m_bHasEpochColumn ? ", epoch" : "", iSrsId);
277 :
278 1850 : auto oResult = SQLQuery(hDB, oSQL.c_str());
279 :
280 925 : if (!oResult || oResult->RowCount() != 1)
281 : {
282 12 : if (bFallbackToEPSG)
283 : {
284 7 : CPLDebug("GPKG",
285 : "unable to read srs_id '%d' from gpkg_spatial_ref_sys",
286 : iSrsId);
287 7 : OGRSpatialReference *poSRS = new OGRSpatialReference();
288 7 : if (poSRS->importFromEPSG(iSrsId) == OGRERR_NONE)
289 : {
290 5 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
291 : return std::unique_ptr<OGRSpatialReference,
292 5 : OGRSpatialReferenceReleaser>(poSRS);
293 : }
294 2 : poSRS->Release();
295 : }
296 5 : else if (bEmitErrorIfNotFound)
297 : {
298 2 : CPLError(CE_Warning, CPLE_AppDefined,
299 : "unable to read srs_id '%d' from gpkg_spatial_ref_sys",
300 : iSrsId);
301 2 : m_oMapSrsIdToSrs[iSrsId] = nullptr;
302 : }
303 7 : return nullptr;
304 : }
305 :
306 913 : const char *pszName = oResult->GetValue(0, 0);
307 913 : if (pszName && EQUAL(pszName, "Undefined SRS"))
308 : {
309 369 : m_oMapSrsIdToSrs[iSrsId] = nullptr;
310 369 : return nullptr;
311 : }
312 544 : const char *pszWkt = oResult->GetValue(1, 0);
313 544 : if (pszWkt == nullptr)
314 0 : return nullptr;
315 544 : const char *pszOrganization = oResult->GetValue(2, 0);
316 544 : const char *pszOrganizationCoordsysID = oResult->GetValue(3, 0);
317 : const char *pszWkt2 =
318 544 : m_bHasDefinition12_063 ? oResult->GetValue(4, 0) : nullptr;
319 544 : if (pszWkt2 && !EQUAL(pszWkt2, "undefined"))
320 73 : pszWkt = pszWkt2;
321 : const char *pszCoordinateEpoch =
322 544 : m_bHasEpochColumn ? oResult->GetValue(5, 0) : nullptr;
323 : const double dfCoordinateEpoch =
324 544 : pszCoordinateEpoch ? CPLAtof(pszCoordinateEpoch) : 0.0;
325 :
326 544 : OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
327 544 : poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
328 : // Try to import first from EPSG code, and then from WKT
329 544 : if (!(pszOrganization && pszOrganizationCoordsysID &&
330 544 : EQUAL(pszOrganization, "EPSG") &&
331 525 : (atoi(pszOrganizationCoordsysID) == iSrsId ||
332 4 : (dfCoordinateEpoch > 0 && strstr(pszWkt, "DYNAMIC[") == nullptr)) &&
333 525 : GDALGPKGImportFromEPSG(
334 1088 : poSpatialRef, atoi(pszOrganizationCoordsysID)) == OGRERR_NONE) &&
335 19 : poSpatialRef->importFromWkt(pszWkt) != OGRERR_NONE)
336 : {
337 0 : CPLError(CE_Warning, CPLE_AppDefined,
338 : "Unable to parse srs_id '%d' well-known text '%s'", iSrsId,
339 : pszWkt);
340 0 : delete poSpatialRef;
341 0 : m_oMapSrsIdToSrs[iSrsId] = nullptr;
342 0 : return nullptr;
343 : }
344 :
345 544 : poSpatialRef->StripTOWGS84IfKnownDatumAndAllowed();
346 544 : poSpatialRef->SetCoordinateEpoch(dfCoordinateEpoch);
347 544 : m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
348 544 : poSpatialRef->Reference();
349 : return std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>(
350 544 : poSpatialRef);
351 : }
352 :
353 244 : const char *GDALGeoPackageDataset::GetSrsName(const OGRSpatialReference &oSRS)
354 : {
355 244 : const char *pszName = oSRS.GetName();
356 244 : if (pszName)
357 244 : return pszName;
358 :
359 : // Something odd. Return empty.
360 0 : return "Unnamed SRS";
361 : }
362 :
363 : /* Add the definition_12_063 column to an existing gpkg_spatial_ref_sys table */
364 6 : bool GDALGeoPackageDataset::ConvertGpkgSpatialRefSysToExtensionWkt2(
365 : bool bForceEpoch)
366 : {
367 6 : const bool bAddEpoch = (m_nUserVersion >= GPKG_1_4_VERSION || bForceEpoch);
368 : auto oResultTable = SQLQuery(
369 : hDB, "SELECT srs_name, srs_id, organization, organization_coordsys_id, "
370 12 : "definition, description FROM gpkg_spatial_ref_sys LIMIT 100000");
371 6 : if (!oResultTable)
372 0 : return false;
373 :
374 : // Temporary remove foreign key checks
375 : const GPKGTemporaryForeignKeyCheckDisabler
376 6 : oGPKGTemporaryForeignKeyCheckDisabler(this);
377 :
378 6 : bool bRet = SoftStartTransaction() == OGRERR_NONE;
379 :
380 6 : if (bRet)
381 : {
382 : std::string osSQL("CREATE TABLE gpkg_spatial_ref_sys_temp ("
383 : "srs_name TEXT NOT NULL,"
384 : "srs_id INTEGER NOT NULL PRIMARY KEY,"
385 : "organization TEXT NOT NULL,"
386 : "organization_coordsys_id INTEGER NOT NULL,"
387 : "definition TEXT NOT NULL,"
388 : "description TEXT, "
389 6 : "definition_12_063 TEXT NOT NULL");
390 6 : if (bAddEpoch)
391 3 : osSQL += ", epoch DOUBLE";
392 6 : osSQL += ")";
393 6 : bRet = SQLCommand(hDB, osSQL.c_str()) == OGRERR_NONE;
394 : }
395 :
396 6 : if (bRet)
397 : {
398 28 : for (int i = 0; bRet && i < oResultTable->RowCount(); i++)
399 : {
400 22 : const char *pszSrsName = oResultTable->GetValue(0, i);
401 22 : const char *pszSrsId = oResultTable->GetValue(1, i);
402 22 : const char *pszOrganization = oResultTable->GetValue(2, i);
403 : const char *pszOrganizationCoordsysID =
404 22 : oResultTable->GetValue(3, i);
405 22 : const char *pszDefinition = oResultTable->GetValue(4, i);
406 : if (pszSrsName == nullptr || pszSrsId == nullptr ||
407 : pszOrganization == nullptr ||
408 : pszOrganizationCoordsysID == nullptr)
409 : {
410 : // should not happen as there are NOT NULL constraints
411 : // But a database could lack such NOT NULL constraints or have
412 : // large values that would cause a memory allocation failure.
413 : }
414 22 : const char *pszDescription = oResultTable->GetValue(5, i);
415 : char *pszSQL;
416 :
417 44 : OGRSpatialReference oSRS;
418 22 : if (pszOrganization && pszOrganizationCoordsysID &&
419 22 : EQUAL(pszOrganization, "EPSG"))
420 : {
421 8 : oSRS.importFromEPSG(atoi(pszOrganizationCoordsysID));
422 : }
423 30 : if (!oSRS.IsEmpty() && pszDefinition &&
424 8 : !EQUAL(pszDefinition, "undefined"))
425 : {
426 8 : oSRS.SetFromUserInput(pszDefinition);
427 : }
428 22 : char *pszWKT2 = nullptr;
429 22 : if (!oSRS.IsEmpty())
430 : {
431 8 : const char *const apszOptionsWkt2[] = {"FORMAT=WKT2_2015",
432 : nullptr};
433 8 : oSRS.exportToWkt(&pszWKT2, apszOptionsWkt2);
434 8 : if (pszWKT2 && pszWKT2[0] == '\0')
435 : {
436 0 : CPLFree(pszWKT2);
437 0 : pszWKT2 = nullptr;
438 : }
439 : }
440 22 : if (pszWKT2 == nullptr)
441 : {
442 14 : pszWKT2 = CPLStrdup("undefined");
443 : }
444 :
445 22 : if (pszDescription)
446 : {
447 19 : pszSQL = sqlite3_mprintf(
448 : "INSERT INTO gpkg_spatial_ref_sys_temp(srs_name, srs_id, "
449 : "organization, organization_coordsys_id, definition, "
450 : "description, definition_12_063) VALUES ('%q', '%q', '%q', "
451 : "'%q', '%q', '%q', '%q')",
452 : pszSrsName, pszSrsId, pszOrganization,
453 : pszOrganizationCoordsysID, pszDefinition, pszDescription,
454 : pszWKT2);
455 : }
456 : else
457 : {
458 3 : pszSQL = sqlite3_mprintf(
459 : "INSERT INTO gpkg_spatial_ref_sys_temp(srs_name, srs_id, "
460 : "organization, organization_coordsys_id, definition, "
461 : "description, definition_12_063) VALUES ('%q', '%q', '%q', "
462 : "'%q', '%q', NULL, '%q')",
463 : pszSrsName, pszSrsId, pszOrganization,
464 : pszOrganizationCoordsysID, pszDefinition, pszWKT2);
465 : }
466 :
467 22 : CPLFree(pszWKT2);
468 22 : bRet &= SQLCommand(hDB, pszSQL) == OGRERR_NONE;
469 22 : sqlite3_free(pszSQL);
470 : }
471 : }
472 :
473 6 : if (bRet)
474 : {
475 6 : bRet =
476 6 : SQLCommand(hDB, "DROP TABLE gpkg_spatial_ref_sys") == OGRERR_NONE;
477 : }
478 6 : if (bRet)
479 : {
480 6 : bRet = SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys_temp RENAME "
481 : "TO gpkg_spatial_ref_sys") == OGRERR_NONE;
482 : }
483 6 : if (bRet)
484 : {
485 12 : bRet = OGRERR_NONE == CreateExtensionsTableIfNecessary() &&
486 6 : OGRERR_NONE == SQLCommand(hDB,
487 : "INSERT INTO gpkg_extensions "
488 : "(table_name, column_name, "
489 : "extension_name, definition, scope) "
490 : "VALUES "
491 : "('gpkg_spatial_ref_sys', "
492 : "'definition_12_063', 'gpkg_crs_wkt', "
493 : "'http://www.geopackage.org/spec120/"
494 : "#extension_crs_wkt', 'read-write')");
495 : }
496 6 : if (bRet && bAddEpoch)
497 : {
498 3 : bRet =
499 : OGRERR_NONE ==
500 3 : SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
501 : "'gpkg_crs_wkt_1_1' "
502 6 : "WHERE extension_name = 'gpkg_crs_wkt'") &&
503 : OGRERR_NONE ==
504 3 : SQLCommand(
505 : hDB,
506 : "INSERT INTO gpkg_extensions "
507 : "(table_name, column_name, extension_name, definition, "
508 : "scope) "
509 : "VALUES "
510 : "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
511 : "'http://www.geopackage.org/spec/#extension_crs_wkt', "
512 : "'read-write')");
513 : }
514 6 : if (bRet)
515 : {
516 6 : SoftCommitTransaction();
517 6 : m_bHasDefinition12_063 = true;
518 6 : if (bAddEpoch)
519 3 : m_bHasEpochColumn = true;
520 : }
521 : else
522 : {
523 0 : SoftRollbackTransaction();
524 : }
525 :
526 6 : return bRet;
527 : }
528 :
529 774 : int GDALGeoPackageDataset::GetSrsId(const OGRSpatialReference *poSRSIn)
530 : {
531 774 : const char *pszName = poSRSIn ? poSRSIn->GetName() : nullptr;
532 1136 : if (!poSRSIn || poSRSIn->IsEmpty() ||
533 362 : (pszName && EQUAL(pszName, "Undefined SRS")))
534 : {
535 414 : OGRErr err = OGRERR_NONE;
536 414 : const int nSRSId = SQLGetInteger(
537 : hDB,
538 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE srs_name = "
539 : "'Undefined SRS' AND organization = 'GDAL'",
540 : &err);
541 414 : if (err == OGRERR_NONE)
542 54 : return nSRSId;
543 :
544 : // The below WKT definitions are somehow questionable (using a unknown
545 : // unit). For GDAL >= 3.9, they won't be used. They will only be used
546 : // for earlier versions.
547 : const char *pszSQL;
548 : #define UNDEFINED_CRS_SRS_ID 99999
549 : static_assert(UNDEFINED_CRS_SRS_ID == FIRST_CUSTOM_SRSID - 1);
550 : #define STRINGIFY(x) #x
551 : #define XSTRINGIFY(x) STRINGIFY(x)
552 360 : if (m_bHasDefinition12_063)
553 : {
554 : /* clang-format off */
555 1 : pszSQL =
556 : "INSERT INTO gpkg_spatial_ref_sys "
557 : "(srs_name,srs_id,organization,organization_coordsys_id,"
558 : "definition, definition_12_063, description) VALUES "
559 : "('Undefined SRS'," XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ",'GDAL',"
560 : XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ","
561 : "'LOCAL_CS[\"Undefined SRS\",LOCAL_DATUM[\"unknown\",32767],"
562 : "UNIT[\"unknown\",0],AXIS[\"Easting\",EAST],"
563 : "AXIS[\"Northing\",NORTH]]',"
564 : "'ENGCRS[\"Undefined SRS\",EDATUM[\"unknown\"],CS[Cartesian,2],"
565 : "AXIS[\"easting\",east,ORDER[1],LENGTHUNIT[\"unknown\",0]],"
566 : "AXIS[\"northing\",north,ORDER[2],LENGTHUNIT[\"unknown\",0]]]',"
567 : "'Custom undefined coordinate reference system')";
568 : /* clang-format on */
569 : }
570 : else
571 : {
572 : /* clang-format off */
573 359 : pszSQL =
574 : "INSERT INTO gpkg_spatial_ref_sys "
575 : "(srs_name,srs_id,organization,organization_coordsys_id,"
576 : "definition, description) VALUES "
577 : "('Undefined SRS'," XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ",'GDAL',"
578 : XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ","
579 : "'LOCAL_CS[\"Undefined SRS\",LOCAL_DATUM[\"unknown\",32767],"
580 : "UNIT[\"unknown\",0],AXIS[\"Easting\",EAST],"
581 : "AXIS[\"Northing\",NORTH]]',"
582 : "'Custom undefined coordinate reference system')";
583 : /* clang-format on */
584 : }
585 360 : if (SQLCommand(hDB, pszSQL) == OGRERR_NONE)
586 360 : return UNDEFINED_CRS_SRS_ID;
587 : #undef UNDEFINED_CRS_SRS_ID
588 : #undef XSTRINGIFY
589 : #undef STRINGIFY
590 0 : return -1;
591 : }
592 :
593 720 : std::unique_ptr<OGRSpatialReference> poSRS(poSRSIn->Clone());
594 :
595 360 : if (poSRS->IsGeographic() || poSRS->IsLocal())
596 : {
597 : // See corresponding tests in GDALGeoPackageDataset::GetSpatialRef
598 134 : if (pszName != nullptr && strlen(pszName) > 0)
599 : {
600 134 : if (EQUAL(pszName, "Undefined geographic SRS"))
601 2 : return 0;
602 :
603 132 : if (EQUAL(pszName, "Undefined Cartesian SRS"))
604 1 : return -1;
605 : }
606 : }
607 :
608 357 : const char *pszAuthorityName = poSRS->GetAuthorityName(nullptr);
609 :
610 357 : if (pszAuthorityName == nullptr || strlen(pszAuthorityName) == 0)
611 : {
612 : // Try to force identify an EPSG code.
613 24 : poSRS->AutoIdentifyEPSG();
614 :
615 24 : pszAuthorityName = poSRS->GetAuthorityName(nullptr);
616 24 : if (pszAuthorityName != nullptr && EQUAL(pszAuthorityName, "EPSG"))
617 : {
618 0 : const char *pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
619 0 : if (pszAuthorityCode != nullptr && strlen(pszAuthorityCode) > 0)
620 : {
621 : /* Import 'clean' SRS */
622 0 : poSRS->importFromEPSG(atoi(pszAuthorityCode));
623 :
624 0 : pszAuthorityName = poSRS->GetAuthorityName(nullptr);
625 : }
626 : }
627 :
628 24 : poSRS->SetCoordinateEpoch(poSRSIn->GetCoordinateEpoch());
629 : }
630 :
631 : // Check whether the EPSG authority code is already mapped to a
632 : // SRS ID.
633 357 : char *pszSQL = nullptr;
634 357 : int nSRSId = DEFAULT_SRID;
635 357 : int nAuthorityCode = 0;
636 357 : OGRErr err = OGRERR_NONE;
637 357 : bool bCanUseAuthorityCode = false;
638 357 : const char *const apszIsSameOptions[] = {
639 : "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES",
640 : "IGNORE_COORDINATE_EPOCH=YES", nullptr};
641 357 : if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0)
642 : {
643 333 : const char *pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
644 333 : if (pszAuthorityCode)
645 : {
646 333 : if (CPLGetValueType(pszAuthorityCode) == CPL_VALUE_INTEGER)
647 : {
648 333 : nAuthorityCode = atoi(pszAuthorityCode);
649 : }
650 : else
651 : {
652 0 : CPLDebug("GPKG",
653 : "SRS has %s:%s identification, but the code not "
654 : "being an integer value cannot be stored as such "
655 : "in the database.",
656 : pszAuthorityName, pszAuthorityCode);
657 0 : pszAuthorityName = nullptr;
658 : }
659 : }
660 : }
661 :
662 690 : if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
663 333 : poSRSIn->GetCoordinateEpoch() == 0)
664 : {
665 : pszSQL =
666 328 : sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
667 : "upper(organization) = upper('%q') AND "
668 : "organization_coordsys_id = %d",
669 : pszAuthorityName, nAuthorityCode);
670 :
671 328 : nSRSId = SQLGetInteger(hDB, pszSQL, &err);
672 328 : sqlite3_free(pszSQL);
673 :
674 : // Got a match? Return it!
675 328 : if (OGRERR_NONE == err)
676 : {
677 109 : auto poRefSRS = GetSpatialRef(nSRSId);
678 : bool bOK =
679 109 : (poRefSRS == nullptr ||
680 110 : poSRS->IsSame(poRefSRS.get(), apszIsSameOptions) ||
681 1 : !CPLTestBool(CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")));
682 109 : if (bOK)
683 : {
684 108 : return nSRSId;
685 : }
686 : else
687 : {
688 1 : CPLError(CE_Warning, CPLE_AppDefined,
689 : "Passed SRS uses %s:%d identification, but its "
690 : "definition is not compatible with the "
691 : "definition of that object already in the database. "
692 : "Registering it as a new entry into the database.",
693 : pszAuthorityName, nAuthorityCode);
694 1 : pszAuthorityName = nullptr;
695 1 : nAuthorityCode = 0;
696 : }
697 : }
698 : }
699 :
700 : // Translate SRS to WKT.
701 249 : CPLCharUniquePtr pszWKT1;
702 249 : CPLCharUniquePtr pszWKT2_2015;
703 249 : CPLCharUniquePtr pszWKT2_2019;
704 249 : const char *const apszOptionsWkt1[] = {"FORMAT=WKT1_GDAL", nullptr};
705 249 : const char *const apszOptionsWkt2_2015[] = {"FORMAT=WKT2_2015", nullptr};
706 249 : const char *const apszOptionsWkt2_2019[] = {"FORMAT=WKT2_2019", nullptr};
707 :
708 498 : std::string osEpochTest;
709 249 : if (poSRSIn->GetCoordinateEpoch() > 0 && m_bHasEpochColumn)
710 : {
711 : osEpochTest =
712 3 : CPLSPrintf(" AND epoch = %.17g", poSRSIn->GetCoordinateEpoch());
713 : }
714 :
715 249 : if (!(poSRS->IsGeographic() && poSRS->GetAxesCount() == 3))
716 : {
717 242 : char *pszTmp = nullptr;
718 242 : poSRS->exportToWkt(&pszTmp, apszOptionsWkt1);
719 242 : pszWKT1.reset(pszTmp);
720 242 : if (pszWKT1 && pszWKT1.get()[0] == '\0')
721 : {
722 0 : pszWKT1.reset();
723 : }
724 : }
725 : {
726 249 : char *pszTmp = nullptr;
727 249 : poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2015);
728 249 : pszWKT2_2015.reset(pszTmp);
729 249 : if (pszWKT2_2015 && pszWKT2_2015.get()[0] == '\0')
730 : {
731 0 : pszWKT2_2015.reset();
732 : }
733 : }
734 : {
735 249 : char *pszTmp = nullptr;
736 249 : poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2019);
737 249 : pszWKT2_2019.reset(pszTmp);
738 249 : if (pszWKT2_2019 && pszWKT2_2019.get()[0] == '\0')
739 : {
740 0 : pszWKT2_2019.reset();
741 : }
742 : }
743 :
744 249 : if (!pszWKT1 && !pszWKT2_2015 && !pszWKT2_2019)
745 : {
746 0 : return DEFAULT_SRID;
747 : }
748 :
749 249 : if (poSRSIn->GetCoordinateEpoch() == 0 || m_bHasEpochColumn)
750 : {
751 : // Search if there is already an existing entry with this WKT
752 246 : if (m_bHasDefinition12_063 && (pszWKT2_2015 || pszWKT2_2019))
753 : {
754 40 : if (pszWKT1)
755 : {
756 140 : pszSQL = sqlite3_mprintf(
757 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
758 : "(definition = '%q' OR definition_12_063 IN ('%q','%q'))%s",
759 : pszWKT1.get(),
760 70 : pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
761 70 : pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
762 : osEpochTest.c_str());
763 : }
764 : else
765 : {
766 20 : pszSQL = sqlite3_mprintf(
767 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
768 : "definition_12_063 IN ('%q', '%q')%s",
769 10 : pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
770 10 : pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
771 : osEpochTest.c_str());
772 : }
773 : }
774 206 : else if (pszWKT1)
775 : {
776 : pszSQL =
777 204 : sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
778 : "definition = '%q'%s",
779 : pszWKT1.get(), osEpochTest.c_str());
780 : }
781 : else
782 : {
783 2 : pszSQL = nullptr;
784 : }
785 246 : if (pszSQL)
786 : {
787 244 : nSRSId = SQLGetInteger(hDB, pszSQL, &err);
788 244 : sqlite3_free(pszSQL);
789 244 : if (OGRERR_NONE == err)
790 : {
791 5 : return nSRSId;
792 : }
793 : }
794 : }
795 :
796 466 : if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
797 222 : poSRSIn->GetCoordinateEpoch() == 0)
798 : {
799 218 : bool bTryToReuseSRSId = true;
800 218 : if (EQUAL(pszAuthorityName, "EPSG"))
801 : {
802 434 : OGRSpatialReference oSRS_EPSG;
803 217 : if (GDALGPKGImportFromEPSG(&oSRS_EPSG, nAuthorityCode) ==
804 : OGRERR_NONE)
805 : {
806 218 : if (!poSRS->IsSame(&oSRS_EPSG, apszIsSameOptions) &&
807 1 : CPLTestBool(
808 : CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")))
809 : {
810 1 : bTryToReuseSRSId = false;
811 1 : CPLError(
812 : CE_Warning, CPLE_AppDefined,
813 : "Passed SRS uses %s:%d identification, but its "
814 : "definition is not compatible with the "
815 : "official definition of the object. "
816 : "Registering it as a non-%s entry into the database.",
817 : pszAuthorityName, nAuthorityCode, pszAuthorityName);
818 1 : pszAuthorityName = nullptr;
819 1 : nAuthorityCode = 0;
820 : }
821 : }
822 : }
823 218 : if (bTryToReuseSRSId)
824 : {
825 : // No match, but maybe we can use the nAuthorityCode as the nSRSId?
826 217 : pszSQL = sqlite3_mprintf(
827 : "SELECT Count(*) FROM gpkg_spatial_ref_sys WHERE "
828 : "srs_id = %d",
829 : nAuthorityCode);
830 :
831 : // Yep, we can!
832 217 : if (SQLGetInteger(hDB, pszSQL, nullptr) == 0)
833 216 : bCanUseAuthorityCode = true;
834 217 : sqlite3_free(pszSQL);
835 : }
836 : }
837 :
838 244 : bool bConvertGpkgSpatialRefSysToExtensionWkt2 = false;
839 244 : bool bForceEpoch = false;
840 246 : if (!m_bHasDefinition12_063 && pszWKT1 == nullptr &&
841 2 : (pszWKT2_2015 != nullptr || pszWKT2_2019 != nullptr))
842 : {
843 2 : bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
844 : }
845 :
846 : // Add epoch column if needed
847 244 : if (poSRSIn->GetCoordinateEpoch() > 0 && !m_bHasEpochColumn)
848 : {
849 3 : if (m_bHasDefinition12_063)
850 : {
851 0 : if (SoftStartTransaction() != OGRERR_NONE)
852 0 : return DEFAULT_SRID;
853 0 : if (SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys "
854 0 : "ADD COLUMN epoch DOUBLE") != OGRERR_NONE ||
855 0 : SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
856 : "'gpkg_crs_wkt_1_1' "
857 : "WHERE extension_name = 'gpkg_crs_wkt'") !=
858 0 : OGRERR_NONE ||
859 0 : SQLCommand(
860 : hDB,
861 : "INSERT INTO gpkg_extensions "
862 : "(table_name, column_name, extension_name, definition, "
863 : "scope) "
864 : "VALUES "
865 : "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
866 : "'http://www.geopackage.org/spec/#extension_crs_wkt', "
867 : "'read-write')") != OGRERR_NONE)
868 : {
869 0 : SoftRollbackTransaction();
870 0 : return DEFAULT_SRID;
871 : }
872 :
873 0 : if (SoftCommitTransaction() != OGRERR_NONE)
874 0 : return DEFAULT_SRID;
875 :
876 0 : m_bHasEpochColumn = true;
877 : }
878 : else
879 : {
880 3 : bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
881 3 : bForceEpoch = true;
882 : }
883 : }
884 :
885 249 : if (bConvertGpkgSpatialRefSysToExtensionWkt2 &&
886 5 : !ConvertGpkgSpatialRefSysToExtensionWkt2(bForceEpoch))
887 : {
888 0 : return DEFAULT_SRID;
889 : }
890 :
891 : // Reuse the authority code number as SRS_ID if we can
892 244 : if (bCanUseAuthorityCode)
893 : {
894 216 : nSRSId = nAuthorityCode;
895 : }
896 : // Otherwise, generate a new SRS_ID number (max + 1)
897 : else
898 : {
899 : // Get the current maximum srid in the srs table.
900 28 : const int nMaxSRSId = SQLGetInteger(
901 : hDB, "SELECT MAX(srs_id) FROM gpkg_spatial_ref_sys", nullptr);
902 28 : nSRSId = std::max(FIRST_CUSTOM_SRSID, nMaxSRSId + 1);
903 : }
904 :
905 488 : std::string osEpochColumn;
906 244 : std::string osEpochVal;
907 244 : if (poSRSIn->GetCoordinateEpoch() > 0)
908 : {
909 5 : osEpochColumn = ", epoch";
910 5 : osEpochVal = CPLSPrintf(", %.17g", poSRSIn->GetCoordinateEpoch());
911 : }
912 :
913 : // Add new SRS row to gpkg_spatial_ref_sys.
914 244 : if (m_bHasDefinition12_063)
915 : {
916 : // Force WKT2_2019 when we have a dynamic CRS and coordinate epoch
917 42 : const char *pszWKT2 = poSRSIn->IsDynamic() &&
918 8 : poSRSIn->GetCoordinateEpoch() > 0 &&
919 1 : pszWKT2_2019
920 1 : ? pszWKT2_2019.get()
921 41 : : pszWKT2_2015 ? pszWKT2_2015.get()
922 89 : : pszWKT2_2019.get();
923 :
924 42 : if (pszAuthorityName != nullptr && nAuthorityCode > 0)
925 : {
926 93 : pszSQL = sqlite3_mprintf(
927 : "INSERT INTO gpkg_spatial_ref_sys "
928 : "(srs_name,srs_id,organization,organization_coordsys_id,"
929 : "definition, definition_12_063%s) VALUES "
930 : "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
931 31 : osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId,
932 : pszAuthorityName, nAuthorityCode,
933 59 : pszWKT1 ? pszWKT1.get() : "undefined",
934 : pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
935 : }
936 : else
937 : {
938 33 : pszSQL = sqlite3_mprintf(
939 : "INSERT INTO gpkg_spatial_ref_sys "
940 : "(srs_name,srs_id,organization,organization_coordsys_id,"
941 : "definition, definition_12_063%s) VALUES "
942 : "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
943 11 : osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId, "NONE",
944 20 : nSRSId, pszWKT1 ? pszWKT1.get() : "undefined",
945 : pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
946 : }
947 : }
948 : else
949 : {
950 202 : if (pszAuthorityName != nullptr && nAuthorityCode > 0)
951 : {
952 380 : pszSQL = sqlite3_mprintf(
953 : "INSERT INTO gpkg_spatial_ref_sys "
954 : "(srs_name,srs_id,organization,organization_coordsys_id,"
955 : "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
956 190 : GetSrsName(*poSRS), nSRSId, pszAuthorityName, nAuthorityCode,
957 380 : pszWKT1 ? pszWKT1.get() : "undefined");
958 : }
959 : else
960 : {
961 24 : pszSQL = sqlite3_mprintf(
962 : "INSERT INTO gpkg_spatial_ref_sys "
963 : "(srs_name,srs_id,organization,organization_coordsys_id,"
964 : "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
965 12 : GetSrsName(*poSRS), nSRSId, "NONE", nSRSId,
966 24 : pszWKT1 ? pszWKT1.get() : "undefined");
967 : }
968 : }
969 :
970 : // Add new row to gpkg_spatial_ref_sys.
971 244 : CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
972 :
973 : // Free everything that was allocated.
974 244 : sqlite3_free(pszSQL);
975 :
976 244 : return nSRSId;
977 : }
978 :
979 : /************************************************************************/
980 : /* GDALGeoPackageDataset() */
981 : /************************************************************************/
982 :
983 2250 : GDALGeoPackageDataset::GDALGeoPackageDataset()
984 : : m_nApplicationId(GPKG_APPLICATION_ID), m_nUserVersion(GPKG_1_2_VERSION),
985 : m_papoLayers(nullptr), m_nLayers(0),
986 : #ifdef ENABLE_GPKG_OGR_CONTENTS
987 : m_bHasGPKGOGRContents(false),
988 : #endif
989 : m_bHasGPKGGeometryColumns(false), m_bHasDefinition12_063(false),
990 : m_bIdentifierAsCO(false), m_bDescriptionAsCO(false),
991 : m_bHasReadMetadataFromStorage(false), m_bMetadataDirty(false),
992 : m_bRecordInsertedInGPKGContent(false), m_bGeoTransformValid(false),
993 : m_nSRID(-1), // Unknown Cartesian.
994 : m_dfTMSMinX(0.0), m_dfTMSMaxY(0.0), m_nOverviewCount(0),
995 : m_papoOverviewDS(nullptr), m_bZoomOther(false), m_bInFlushCache(false),
996 : m_osTilingScheme("CUSTOM"), m_bMapTableToExtensionsBuilt(false),
997 2250 : m_bMapTableToContentsBuilt(false)
998 : {
999 2250 : m_adfGeoTransform[0] = 0.0;
1000 2250 : m_adfGeoTransform[1] = 1.0;
1001 2250 : m_adfGeoTransform[2] = 0.0;
1002 2250 : m_adfGeoTransform[3] = 0.0;
1003 2250 : m_adfGeoTransform[4] = 0.0;
1004 2250 : m_adfGeoTransform[5] = 1.0;
1005 2250 : }
1006 :
1007 : /************************************************************************/
1008 : /* ~GDALGeoPackageDataset() */
1009 : /************************************************************************/
1010 :
1011 4500 : GDALGeoPackageDataset::~GDALGeoPackageDataset()
1012 : {
1013 2250 : GDALGeoPackageDataset::Close();
1014 4500 : }
1015 :
1016 : /************************************************************************/
1017 : /* Close() */
1018 : /************************************************************************/
1019 :
1020 3751 : CPLErr GDALGeoPackageDataset::Close()
1021 : {
1022 3751 : CPLErr eErr = CE_None;
1023 3751 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
1024 : {
1025 1307 : if (eAccess == GA_Update && m_poParentDS == nullptr &&
1026 3557 : !m_osRasterTable.empty() && !m_bGeoTransformValid)
1027 : {
1028 3 : CPLError(CE_Failure, CPLE_AppDefined,
1029 : "Raster table %s not correctly initialized due to missing "
1030 : "call to SetGeoTransform()",
1031 : m_osRasterTable.c_str());
1032 : }
1033 :
1034 2250 : if (GDALGeoPackageDataset::FlushCache(true) != CE_None)
1035 7 : eErr = CE_Failure;
1036 :
1037 : // Destroy bands now since we don't want
1038 : // GDALGPKGMBTilesLikeRasterBand::FlushCache() to run after dataset
1039 : // destruction
1040 4042 : for (int i = 0; i < nBands; i++)
1041 1792 : delete papoBands[i];
1042 2250 : nBands = 0;
1043 2250 : CPLFree(papoBands);
1044 2250 : papoBands = nullptr;
1045 :
1046 : // Destroy overviews before cleaning m_hTempDB as they could still
1047 : // need it
1048 2575 : for (int i = 0; i < m_nOverviewCount; i++)
1049 325 : delete m_papoOverviewDS[i];
1050 :
1051 2250 : if (m_poParentDS)
1052 : {
1053 325 : hDB = nullptr;
1054 : }
1055 :
1056 5892 : for (int i = 0; i < m_nLayers; i++)
1057 3642 : delete m_papoLayers[i];
1058 :
1059 2250 : CPLFree(m_papoLayers);
1060 2250 : CPLFree(m_papoOverviewDS);
1061 :
1062 : std::map<int, OGRSpatialReference *>::iterator oIter =
1063 2250 : m_oMapSrsIdToSrs.begin();
1064 3285 : for (; oIter != m_oMapSrsIdToSrs.end(); ++oIter)
1065 : {
1066 1035 : OGRSpatialReference *poSRS = oIter->second;
1067 1035 : if (poSRS)
1068 664 : poSRS->Release();
1069 : }
1070 :
1071 2250 : if (!CloseDB())
1072 0 : eErr = CE_Failure;
1073 :
1074 2250 : if (OGRSQLiteBaseDataSource::Close() != CE_None)
1075 0 : eErr = CE_Failure;
1076 : }
1077 3751 : return eErr;
1078 : }
1079 :
1080 : /************************************************************************/
1081 : /* ICanIWriteBlock() */
1082 : /************************************************************************/
1083 :
1084 5684 : bool GDALGeoPackageDataset::ICanIWriteBlock()
1085 : {
1086 5684 : if (!GetUpdate())
1087 : {
1088 0 : CPLError(
1089 : CE_Failure, CPLE_NotSupported,
1090 : "IWriteBlock() not supported on dataset opened in read-only mode");
1091 0 : return false;
1092 : }
1093 :
1094 5684 : if (m_pabyCachedTiles == nullptr)
1095 : {
1096 0 : return false;
1097 : }
1098 :
1099 5684 : if (!m_bGeoTransformValid || m_nSRID == UNKNOWN_SRID)
1100 : {
1101 0 : CPLError(CE_Failure, CPLE_NotSupported,
1102 : "IWriteBlock() not supported if georeferencing not set");
1103 0 : return false;
1104 : }
1105 5684 : return true;
1106 : }
1107 :
1108 : /************************************************************************/
1109 : /* IRasterIO() */
1110 : /************************************************************************/
1111 :
1112 121 : CPLErr GDALGeoPackageDataset::IRasterIO(
1113 : GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
1114 : void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
1115 : int nBandCount, BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
1116 : GSpacing nLineSpace, GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
1117 :
1118 : {
1119 121 : CPLErr eErr = OGRSQLiteBaseDataSource::IRasterIO(
1120 : eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
1121 : eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace, nBandSpace,
1122 : psExtraArg);
1123 :
1124 : // If writing all bands, in non-shifted mode, flush all entirely written
1125 : // tiles This can avoid "stressing" the block cache with too many dirty
1126 : // blocks. Note: this logic would be useless with a per-dataset block cache.
1127 121 : if (eErr == CE_None && eRWFlag == GF_Write && nXSize == nBufXSize &&
1128 112 : nYSize == nBufYSize && nBandCount == nBands &&
1129 109 : m_nShiftXPixelsMod == 0 && m_nShiftYPixelsMod == 0)
1130 : {
1131 : auto poBand =
1132 105 : cpl::down_cast<GDALGPKGMBTilesLikeRasterBand *>(GetRasterBand(1));
1133 : int nBlockXSize, nBlockYSize;
1134 105 : poBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
1135 105 : const int nBlockXStart = DIV_ROUND_UP(nXOff, nBlockXSize);
1136 105 : const int nBlockYStart = DIV_ROUND_UP(nYOff, nBlockYSize);
1137 105 : const int nBlockXEnd = (nXOff + nXSize) / nBlockXSize;
1138 105 : const int nBlockYEnd = (nYOff + nYSize) / nBlockYSize;
1139 259 : for (int nBlockY = nBlockXStart; nBlockY < nBlockYEnd; nBlockY++)
1140 : {
1141 4371 : for (int nBlockX = nBlockYStart; nBlockX < nBlockXEnd; nBlockX++)
1142 : {
1143 : GDALRasterBlock *poBlock =
1144 4217 : poBand->AccessibleTryGetLockedBlockRef(nBlockX, nBlockY);
1145 4217 : if (poBlock)
1146 : {
1147 : // GetDirty() should be true in most situation (otherwise
1148 : // it means the block cache is under extreme pressure!)
1149 4215 : if (poBlock->GetDirty())
1150 : {
1151 : // IWriteBlock() on one band will check the dirty state
1152 : // of the corresponding blocks in other bands, to decide
1153 : // if it can call WriteTile(), so we have only to do
1154 : // that on one of the bands
1155 4215 : if (poBlock->Write() != CE_None)
1156 250 : eErr = CE_Failure;
1157 : }
1158 4215 : poBlock->DropLock();
1159 : }
1160 : }
1161 : }
1162 : }
1163 :
1164 121 : return eErr;
1165 : }
1166 :
1167 : /************************************************************************/
1168 : /* GetOGRTableLimit() */
1169 : /************************************************************************/
1170 :
1171 3624 : static int GetOGRTableLimit()
1172 : {
1173 3624 : return atoi(CPLGetConfigOption("OGR_TABLE_LIMIT", "10000"));
1174 : }
1175 :
1176 : /************************************************************************/
1177 : /* GetNameTypeMapFromSQliteMaster() */
1178 : /************************************************************************/
1179 :
1180 : const std::map<CPLString, CPLString> &
1181 1123 : GDALGeoPackageDataset::GetNameTypeMapFromSQliteMaster()
1182 : {
1183 1123 : if (!m_oMapNameToType.empty())
1184 318 : return m_oMapNameToType;
1185 :
1186 : CPLString osSQL(
1187 : "SELECT name, type FROM sqlite_master WHERE "
1188 : "type IN ('view', 'table') OR "
1189 1610 : "(name LIKE 'trigger_%_feature_count_%' AND type = 'trigger')");
1190 805 : const int nTableLimit = GetOGRTableLimit();
1191 805 : if (nTableLimit > 0)
1192 : {
1193 805 : osSQL += " LIMIT ";
1194 805 : osSQL += CPLSPrintf("%d", 1 + 3 * nTableLimit);
1195 : }
1196 :
1197 805 : auto oResult = SQLQuery(hDB, osSQL);
1198 805 : if (oResult)
1199 : {
1200 13491 : for (int i = 0; i < oResult->RowCount(); i++)
1201 : {
1202 12686 : const char *pszName = oResult->GetValue(0, i);
1203 12686 : const char *pszType = oResult->GetValue(1, i);
1204 12686 : m_oMapNameToType[CPLString(pszName).toupper()] = pszType;
1205 : }
1206 : }
1207 :
1208 805 : return m_oMapNameToType;
1209 : }
1210 :
1211 : /************************************************************************/
1212 : /* RemoveTableFromSQLiteMasterCache() */
1213 : /************************************************************************/
1214 :
1215 53 : void GDALGeoPackageDataset::RemoveTableFromSQLiteMasterCache(
1216 : const char *pszTableName)
1217 : {
1218 53 : m_oMapNameToType.erase(CPLString(pszTableName).toupper());
1219 53 : }
1220 :
1221 : /************************************************************************/
1222 : /* GetUnknownExtensionsTableSpecific() */
1223 : /************************************************************************/
1224 :
1225 : const std::map<CPLString, std::vector<GPKGExtensionDesc>> &
1226 769 : GDALGeoPackageDataset::GetUnknownExtensionsTableSpecific()
1227 : {
1228 769 : if (m_bMapTableToExtensionsBuilt)
1229 79 : return m_oMapTableToExtensions;
1230 690 : m_bMapTableToExtensionsBuilt = true;
1231 :
1232 690 : if (!HasExtensionsTable())
1233 38 : return m_oMapTableToExtensions;
1234 :
1235 : CPLString osSQL(
1236 : "SELECT table_name, extension_name, definition, scope "
1237 : "FROM gpkg_extensions WHERE "
1238 : "table_name IS NOT NULL "
1239 : "AND extension_name IS NOT NULL "
1240 : "AND definition IS NOT NULL "
1241 : "AND scope IS NOT NULL "
1242 : "AND extension_name NOT IN ('gpkg_geom_CIRCULARSTRING', "
1243 : "'gpkg_geom_COMPOUNDCURVE', 'gpkg_geom_CURVEPOLYGON', "
1244 : "'gpkg_geom_MULTICURVE', "
1245 : "'gpkg_geom_MULTISURFACE', 'gpkg_geom_CURVE', 'gpkg_geom_SURFACE', "
1246 : "'gpkg_geom_POLYHEDRALSURFACE', 'gpkg_geom_TIN', 'gpkg_geom_TRIANGLE', "
1247 : "'gpkg_rtree_index', 'gpkg_geometry_type_trigger', "
1248 : "'gpkg_srs_id_trigger', "
1249 : "'gpkg_crs_wkt', 'gpkg_crs_wkt_1_1', 'gpkg_schema', "
1250 : "'gpkg_related_tables', 'related_tables'"
1251 : #ifdef HAVE_SPATIALITE
1252 : ", 'gdal_spatialite_computed_geom_column'"
1253 : #endif
1254 1304 : ")");
1255 652 : const int nTableLimit = GetOGRTableLimit();
1256 652 : if (nTableLimit > 0)
1257 : {
1258 652 : osSQL += " LIMIT ";
1259 652 : osSQL += CPLSPrintf("%d", 1 + 10 * nTableLimit);
1260 : }
1261 :
1262 652 : auto oResult = SQLQuery(hDB, osSQL);
1263 652 : if (oResult)
1264 : {
1265 1269 : for (int i = 0; i < oResult->RowCount(); i++)
1266 : {
1267 617 : const char *pszTableName = oResult->GetValue(0, i);
1268 617 : const char *pszExtensionName = oResult->GetValue(1, i);
1269 617 : const char *pszDefinition = oResult->GetValue(2, i);
1270 617 : const char *pszScope = oResult->GetValue(3, i);
1271 617 : if (pszTableName && pszExtensionName && pszDefinition && pszScope)
1272 : {
1273 617 : GPKGExtensionDesc oDesc;
1274 617 : oDesc.osExtensionName = pszExtensionName;
1275 617 : oDesc.osDefinition = pszDefinition;
1276 617 : oDesc.osScope = pszScope;
1277 1234 : m_oMapTableToExtensions[CPLString(pszTableName).toupper()]
1278 617 : .push_back(oDesc);
1279 : }
1280 : }
1281 : }
1282 :
1283 652 : return m_oMapTableToExtensions;
1284 : }
1285 :
1286 : /************************************************************************/
1287 : /* GetContents() */
1288 : /************************************************************************/
1289 :
1290 : const std::map<CPLString, GPKGContentsDesc> &
1291 752 : GDALGeoPackageDataset::GetContents()
1292 : {
1293 752 : if (m_bMapTableToContentsBuilt)
1294 64 : return m_oMapTableToContents;
1295 688 : m_bMapTableToContentsBuilt = true;
1296 :
1297 : CPLString osSQL("SELECT table_name, data_type, identifier, "
1298 : "description, min_x, min_y, max_x, max_y "
1299 1376 : "FROM gpkg_contents");
1300 688 : const int nTableLimit = GetOGRTableLimit();
1301 688 : if (nTableLimit > 0)
1302 : {
1303 688 : osSQL += " LIMIT ";
1304 688 : osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1305 : }
1306 :
1307 688 : auto oResult = SQLQuery(hDB, osSQL);
1308 688 : if (oResult)
1309 : {
1310 1483 : for (int i = 0; i < oResult->RowCount(); i++)
1311 : {
1312 795 : const char *pszTableName = oResult->GetValue(0, i);
1313 795 : if (pszTableName == nullptr)
1314 0 : continue;
1315 795 : const char *pszDataType = oResult->GetValue(1, i);
1316 795 : const char *pszIdentifier = oResult->GetValue(2, i);
1317 795 : const char *pszDescription = oResult->GetValue(3, i);
1318 795 : const char *pszMinX = oResult->GetValue(4, i);
1319 795 : const char *pszMinY = oResult->GetValue(5, i);
1320 795 : const char *pszMaxX = oResult->GetValue(6, i);
1321 795 : const char *pszMaxY = oResult->GetValue(7, i);
1322 795 : GPKGContentsDesc oDesc;
1323 795 : if (pszDataType)
1324 795 : oDesc.osDataType = pszDataType;
1325 795 : if (pszIdentifier)
1326 795 : oDesc.osIdentifier = pszIdentifier;
1327 795 : if (pszDescription)
1328 794 : oDesc.osDescription = pszDescription;
1329 795 : if (pszMinX)
1330 558 : oDesc.osMinX = pszMinX;
1331 795 : if (pszMinY)
1332 558 : oDesc.osMinY = pszMinY;
1333 795 : if (pszMaxX)
1334 558 : oDesc.osMaxX = pszMaxX;
1335 795 : if (pszMaxY)
1336 558 : oDesc.osMaxY = pszMaxY;
1337 1590 : m_oMapTableToContents[CPLString(pszTableName).toupper()] =
1338 1590 : std::move(oDesc);
1339 : }
1340 : }
1341 :
1342 688 : return m_oMapTableToContents;
1343 : }
1344 :
1345 : /************************************************************************/
1346 : /* Open() */
1347 : /************************************************************************/
1348 :
1349 1119 : int GDALGeoPackageDataset::Open(GDALOpenInfo *poOpenInfo,
1350 : const std::string &osFilenameInZip)
1351 : {
1352 1119 : m_osFilenameInZip = osFilenameInZip;
1353 1119 : CPLAssert(m_nLayers == 0);
1354 1119 : CPLAssert(hDB == nullptr);
1355 :
1356 1119 : SetDescription(poOpenInfo->pszFilename);
1357 2238 : CPLString osFilename(poOpenInfo->pszFilename);
1358 2238 : CPLString osSubdatasetTableName;
1359 : GByte abyHeaderLetMeHerePlease[100];
1360 1119 : const GByte *pabyHeader = poOpenInfo->pabyHeader;
1361 1119 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GPKG:"))
1362 : {
1363 240 : char **papszTokens = CSLTokenizeString2(poOpenInfo->pszFilename, ":",
1364 : CSLT_HONOURSTRINGS);
1365 240 : int nCount = CSLCount(papszTokens);
1366 240 : if (nCount < 2)
1367 : {
1368 0 : CSLDestroy(papszTokens);
1369 0 : return FALSE;
1370 : }
1371 :
1372 240 : if (nCount <= 3)
1373 : {
1374 238 : osFilename = papszTokens[1];
1375 : }
1376 : /* GPKG:C:\BLA.GPKG:foo */
1377 2 : else if (nCount == 4 && strlen(papszTokens[1]) == 1 &&
1378 2 : (papszTokens[2][0] == '/' || papszTokens[2][0] == '\\'))
1379 : {
1380 2 : osFilename = CPLString(papszTokens[1]) + ":" + papszTokens[2];
1381 : }
1382 : // GPKG:/vsicurl/http[s]://[user:passwd@]example.com[:8080]/foo.gpkg:bar
1383 0 : else if (/*nCount >= 4 && */
1384 0 : (EQUAL(papszTokens[1], "/vsicurl/http") ||
1385 0 : EQUAL(papszTokens[1], "/vsicurl/https")))
1386 : {
1387 0 : osFilename = CPLString(papszTokens[1]);
1388 0 : for (int i = 2; i < nCount - 1; i++)
1389 : {
1390 0 : osFilename += ':';
1391 0 : osFilename += papszTokens[i];
1392 : }
1393 : }
1394 240 : if (nCount >= 3)
1395 12 : osSubdatasetTableName = papszTokens[nCount - 1];
1396 :
1397 240 : CSLDestroy(papszTokens);
1398 240 : VSILFILE *fp = VSIFOpenL(osFilename, "rb");
1399 240 : if (fp != nullptr)
1400 : {
1401 240 : VSIFReadL(abyHeaderLetMeHerePlease, 1, 100, fp);
1402 240 : VSIFCloseL(fp);
1403 : }
1404 240 : pabyHeader = abyHeaderLetMeHerePlease;
1405 : }
1406 879 : else if (poOpenInfo->pabyHeader &&
1407 879 : STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
1408 : "SQLite format 3"))
1409 : {
1410 873 : m_bCallUndeclareFileNotToOpen = true;
1411 873 : GDALOpenInfoDeclareFileNotToOpen(osFilename, poOpenInfo->pabyHeader,
1412 : poOpenInfo->nHeaderBytes);
1413 : }
1414 :
1415 1119 : eAccess = poOpenInfo->eAccess;
1416 1119 : if (!m_osFilenameInZip.empty())
1417 : {
1418 1 : m_pszFilename = CPLStrdup(CPLSPrintf(
1419 : "/vsizip/{%s}/%s", osFilename.c_str(), m_osFilenameInZip.c_str()));
1420 : }
1421 : else
1422 : {
1423 1118 : m_pszFilename = CPLStrdup(osFilename);
1424 : }
1425 :
1426 1119 : if (poOpenInfo->papszOpenOptions)
1427 : {
1428 99 : CSLDestroy(papszOpenOptions);
1429 99 : papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
1430 : }
1431 :
1432 : #ifdef ENABLE_SQL_GPKG_FORMAT
1433 1119 : if (poOpenInfo->pabyHeader &&
1434 879 : STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
1435 5 : "-- SQL GPKG") &&
1436 5 : poOpenInfo->fpL != nullptr)
1437 : {
1438 5 : if (sqlite3_open_v2(":memory:", &hDB, SQLITE_OPEN_READWRITE, nullptr) !=
1439 : SQLITE_OK)
1440 : {
1441 0 : return FALSE;
1442 : }
1443 :
1444 5 : InstallSQLFunctions();
1445 :
1446 : // Ingest the lines of the dump
1447 5 : VSIFSeekL(poOpenInfo->fpL, 0, SEEK_SET);
1448 : const char *pszLine;
1449 76 : while ((pszLine = CPLReadLineL(poOpenInfo->fpL)) != nullptr)
1450 : {
1451 71 : if (STARTS_WITH(pszLine, "--"))
1452 5 : continue;
1453 :
1454 : // Reject a few words tat might have security implications
1455 : // Basically we just want to allow CREATE TABLE and INSERT INTO
1456 66 : if (CPLString(pszLine).ifind("ATTACH") != std::string::npos ||
1457 132 : CPLString(pszLine).ifind("DETACH") != std::string::npos ||
1458 132 : CPLString(pszLine).ifind("PRAGMA") != std::string::npos ||
1459 132 : CPLString(pszLine).ifind("SELECT") != std::string::npos ||
1460 128 : CPLString(pszLine).ifind("UPDATE") != std::string::npos ||
1461 128 : CPLString(pszLine).ifind("REPLACE") != std::string::npos ||
1462 128 : CPLString(pszLine).ifind("DELETE") != std::string::npos ||
1463 128 : CPLString(pszLine).ifind("DROP") != std::string::npos ||
1464 260 : CPLString(pszLine).ifind("ALTER") != std::string::npos ||
1465 128 : CPLString(pszLine).ifind("VIRTUAL") != std::string::npos)
1466 : {
1467 8 : bool bOK = false;
1468 : // Accept creation of spatial index
1469 8 : if (STARTS_WITH_CI(pszLine, "CREATE VIRTUAL TABLE "))
1470 : {
1471 4 : const char *pszStr =
1472 : pszLine + strlen("CREATE VIRTUAL TABLE ");
1473 4 : if (*pszStr == '"')
1474 0 : pszStr++;
1475 52 : while ((*pszStr >= 'a' && *pszStr <= 'z') ||
1476 64 : (*pszStr >= 'A' && *pszStr <= 'Z') || *pszStr == '_')
1477 : {
1478 60 : pszStr++;
1479 : }
1480 4 : if (*pszStr == '"')
1481 0 : pszStr++;
1482 4 : if (EQUAL(pszStr,
1483 : " USING rtree(id, minx, maxx, miny, maxy);"))
1484 : {
1485 4 : bOK = true;
1486 : }
1487 : }
1488 : // Accept INSERT INTO rtree_poly_geom SELECT fid, ST_MinX(geom),
1489 : // ST_MaxX(geom), ST_MinY(geom), ST_MaxY(geom) FROM poly;
1490 8 : else if (STARTS_WITH_CI(pszLine, "INSERT INTO rtree_") &&
1491 8 : CPLString(pszLine).ifind("SELECT") !=
1492 : std::string::npos)
1493 : {
1494 : char **papszTokens =
1495 4 : CSLTokenizeString2(pszLine, " (),,", 0);
1496 4 : if (CSLCount(papszTokens) == 15 &&
1497 4 : EQUAL(papszTokens[3], "SELECT") &&
1498 4 : EQUAL(papszTokens[5], "ST_MinX") &&
1499 4 : EQUAL(papszTokens[7], "ST_MaxX") &&
1500 4 : EQUAL(papszTokens[9], "ST_MinY") &&
1501 12 : EQUAL(papszTokens[11], "ST_MaxY") &&
1502 4 : EQUAL(papszTokens[13], "FROM"))
1503 : {
1504 4 : bOK = TRUE;
1505 : }
1506 4 : CSLDestroy(papszTokens);
1507 : }
1508 :
1509 8 : if (!bOK)
1510 : {
1511 0 : CPLError(CE_Failure, CPLE_NotSupported,
1512 : "Rejected statement: %s", pszLine);
1513 0 : return FALSE;
1514 : }
1515 : }
1516 66 : char *pszErrMsg = nullptr;
1517 66 : if (sqlite3_exec(hDB, pszLine, nullptr, nullptr, &pszErrMsg) !=
1518 : SQLITE_OK)
1519 : {
1520 0 : if (pszErrMsg)
1521 0 : CPLDebug("SQLITE", "Error %s", pszErrMsg);
1522 : }
1523 66 : sqlite3_free(pszErrMsg);
1524 5 : }
1525 : }
1526 :
1527 1114 : else if (pabyHeader != nullptr)
1528 : #endif
1529 : {
1530 1114 : if (poOpenInfo->fpL)
1531 : {
1532 : // See above comment about -wal locking for the importance of
1533 : // closing that file, prior to calling sqlite3_open()
1534 774 : VSIFCloseL(poOpenInfo->fpL);
1535 774 : poOpenInfo->fpL = nullptr;
1536 : }
1537 :
1538 : /* See if we can open the SQLite database */
1539 1114 : if (!OpenOrCreateDB(GetUpdate() ? SQLITE_OPEN_READWRITE
1540 : : SQLITE_OPEN_READONLY))
1541 2 : return FALSE;
1542 :
1543 1112 : memcpy(&m_nApplicationId, pabyHeader + knApplicationIdPos, 4);
1544 1112 : m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
1545 1112 : memcpy(&m_nUserVersion, pabyHeader + knUserVersionPos, 4);
1546 1112 : m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
1547 1112 : if (m_nApplicationId == GP10_APPLICATION_ID)
1548 : {
1549 7 : CPLDebug("GPKG", "GeoPackage v1.0");
1550 : }
1551 1105 : else if (m_nApplicationId == GP11_APPLICATION_ID)
1552 : {
1553 2 : CPLDebug("GPKG", "GeoPackage v1.1");
1554 : }
1555 1103 : else if (m_nApplicationId == GPKG_APPLICATION_ID &&
1556 1100 : m_nUserVersion >= GPKG_1_2_VERSION)
1557 : {
1558 1098 : CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
1559 1098 : (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
1560 : }
1561 : }
1562 :
1563 : /* Requirement 6: The SQLite PRAGMA integrity_check SQL command SHALL return
1564 : * “ok” */
1565 : /* http://opengis.github.io/geopackage/#_file_integrity */
1566 : /* Disable integrity check by default, since it is expensive on big files */
1567 1117 : if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_INTEGRITY_CHECK", "NO")) &&
1568 0 : OGRERR_NONE != PragmaCheck("integrity_check", "ok", 1))
1569 : {
1570 0 : CPLError(CE_Failure, CPLE_AppDefined,
1571 : "pragma integrity_check on '%s' failed", m_pszFilename);
1572 0 : return FALSE;
1573 : }
1574 :
1575 : /* Requirement 7: The SQLite PRAGMA foreign_key_check() SQL with no */
1576 : /* parameter value SHALL return an empty result set */
1577 : /* http://opengis.github.io/geopackage/#_file_integrity */
1578 : /* Disable the check by default, since it is to corrupt databases, and */
1579 : /* that causes issues to downstream software that can't open them. */
1580 1117 : if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_FOREIGN_KEY_CHECK", "NO")) &&
1581 0 : OGRERR_NONE != PragmaCheck("foreign_key_check", "", 0))
1582 : {
1583 0 : CPLError(CE_Failure, CPLE_AppDefined,
1584 : "pragma foreign_key_check on '%s' failed.", m_pszFilename);
1585 0 : return FALSE;
1586 : }
1587 :
1588 : /* Check for requirement metadata tables */
1589 : /* Requirement 10: gpkg_spatial_ref_sys must exist */
1590 : /* Requirement 13: gpkg_contents must exist */
1591 1117 : if (SQLGetInteger(hDB,
1592 : "SELECT COUNT(*) FROM sqlite_master WHERE "
1593 : "name IN ('gpkg_spatial_ref_sys', 'gpkg_contents') AND "
1594 : "type IN ('table', 'view')",
1595 1117 : nullptr) != 2)
1596 : {
1597 0 : CPLError(CE_Failure, CPLE_AppDefined,
1598 : "At least one of the required GeoPackage tables, "
1599 : "gpkg_spatial_ref_sys or gpkg_contents, is missing");
1600 0 : return FALSE;
1601 : }
1602 :
1603 1117 : DetectSpatialRefSysColumns();
1604 :
1605 : #ifdef ENABLE_GPKG_OGR_CONTENTS
1606 1117 : if (SQLGetInteger(hDB,
1607 : "SELECT 1 FROM sqlite_master WHERE "
1608 : "name = 'gpkg_ogr_contents' AND type = 'table'",
1609 1117 : nullptr) == 1)
1610 : {
1611 1109 : m_bHasGPKGOGRContents = true;
1612 : }
1613 : #endif
1614 :
1615 1117 : CheckUnknownExtensions();
1616 :
1617 1117 : int bRet = FALSE;
1618 1117 : bool bHasGPKGExtRelations = false;
1619 1117 : if (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR)
1620 : {
1621 932 : m_bHasGPKGGeometryColumns =
1622 932 : SQLGetInteger(hDB,
1623 : "SELECT 1 FROM sqlite_master WHERE "
1624 : "name = 'gpkg_geometry_columns' AND "
1625 : "type IN ('table', 'view')",
1626 932 : nullptr) == 1;
1627 932 : bHasGPKGExtRelations = HasGpkgextRelationsTable();
1628 : }
1629 1117 : if (m_bHasGPKGGeometryColumns)
1630 : {
1631 : /* Load layer definitions for all tables in gpkg_contents &
1632 : * gpkg_geometry_columns */
1633 : /* and non-spatial tables as well */
1634 : std::string osSQL =
1635 : "SELECT c.table_name, c.identifier, 1 as is_spatial, "
1636 : "g.column_name, g.geometry_type_name, g.z, g.m, c.min_x, c.min_y, "
1637 : "c.max_x, c.max_y, 1 AS is_in_gpkg_contents, "
1638 : "(SELECT type FROM sqlite_master WHERE lower(name) = "
1639 : "lower(c.table_name) AND type IN ('table', 'view')) AS object_type "
1640 : " FROM gpkg_geometry_columns g "
1641 : " JOIN gpkg_contents c ON (g.table_name = c.table_name)"
1642 : " WHERE "
1643 : " c.table_name <> 'ogr_empty_table' AND"
1644 : " c.data_type = 'features' "
1645 : // aspatial: Was the only method available in OGR 2.0 and 2.1
1646 : // attributes: GPKG 1.2 or later
1647 : "UNION ALL "
1648 : "SELECT table_name, identifier, 0 as is_spatial, NULL, NULL, 0, 0, "
1649 : "0 AS xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 1 AS "
1650 : "is_in_gpkg_contents, "
1651 : "(SELECT type FROM sqlite_master WHERE lower(name) = "
1652 : "lower(table_name) AND type IN ('table', 'view')) AS object_type "
1653 : " FROM gpkg_contents"
1654 931 : " WHERE data_type IN ('aspatial', 'attributes') ";
1655 :
1656 1862 : const char *pszListAllTables = CSLFetchNameValueDef(
1657 931 : poOpenInfo->papszOpenOptions, "LIST_ALL_TABLES", "AUTO");
1658 931 : bool bHasASpatialOrAttributes = HasGDALAspatialExtension();
1659 931 : if (!bHasASpatialOrAttributes)
1660 : {
1661 : auto oResultTable =
1662 : SQLQuery(hDB, "SELECT * FROM gpkg_contents WHERE "
1663 930 : "data_type = 'attributes' LIMIT 1");
1664 930 : bHasASpatialOrAttributes =
1665 930 : (oResultTable && oResultTable->RowCount() == 1);
1666 : }
1667 931 : if (bHasGPKGExtRelations)
1668 : {
1669 : osSQL += "UNION ALL "
1670 : "SELECT mapping_table_name, mapping_table_name, 0 as "
1671 : "is_spatial, NULL, NULL, 0, 0, 0 AS "
1672 : "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
1673 : "is_in_gpkg_contents, 'table' AS object_type "
1674 : "FROM gpkgext_relations WHERE "
1675 : "lower(mapping_table_name) NOT IN (SELECT "
1676 : "lower(table_name) FROM gpkg_contents) AND "
1677 : "EXISTS (SELECT 1 FROM sqlite_master WHERE "
1678 : "type IN ('table', 'view') AND "
1679 18 : "lower(name) = lower(mapping_table_name))";
1680 : }
1681 931 : if (EQUAL(pszListAllTables, "YES") ||
1682 930 : (!bHasASpatialOrAttributes && EQUAL(pszListAllTables, "AUTO")))
1683 : {
1684 : // vgpkg_ is Spatialite virtual table
1685 : osSQL +=
1686 : "UNION ALL "
1687 : "SELECT name, name, 0 as is_spatial, NULL, NULL, 0, 0, 0 AS "
1688 : "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
1689 : "is_in_gpkg_contents, type AS object_type "
1690 : "FROM sqlite_master WHERE type IN ('table', 'view') "
1691 : "AND name NOT LIKE 'gpkg_%' "
1692 : "AND name NOT LIKE 'vgpkg_%' "
1693 : "AND name NOT LIKE 'rtree_%' AND name NOT LIKE 'sqlite_%' "
1694 : // Avoid reading those views from simple_sewer_features.gpkg
1695 : "AND name NOT IN ('st_spatial_ref_sys', 'spatial_ref_sys', "
1696 : "'st_geometry_columns', 'geometry_columns') "
1697 : "AND lower(name) NOT IN (SELECT lower(table_name) FROM "
1698 872 : "gpkg_contents)";
1699 872 : if (bHasGPKGExtRelations)
1700 : {
1701 : osSQL += " AND lower(name) NOT IN (SELECT "
1702 : "lower(mapping_table_name) FROM "
1703 13 : "gpkgext_relations)";
1704 : }
1705 : }
1706 931 : const int nTableLimit = GetOGRTableLimit();
1707 931 : if (nTableLimit > 0)
1708 : {
1709 931 : osSQL += " LIMIT ";
1710 931 : osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1711 : }
1712 :
1713 931 : auto oResult = SQLQuery(hDB, osSQL.c_str());
1714 931 : if (!oResult)
1715 : {
1716 0 : return FALSE;
1717 : }
1718 :
1719 931 : if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
1720 : {
1721 1 : CPLError(CE_Warning, CPLE_AppDefined,
1722 : "File has more than %d vector tables. "
1723 : "Limiting to first %d (can be overridden with "
1724 : "OGR_TABLE_LIMIT config option)",
1725 : nTableLimit, nTableLimit);
1726 1 : oResult->LimitRowCount(nTableLimit);
1727 : }
1728 :
1729 931 : if (oResult->RowCount() > 0)
1730 : {
1731 818 : bRet = TRUE;
1732 :
1733 1636 : m_papoLayers = static_cast<OGRGeoPackageTableLayer **>(CPLMalloc(
1734 818 : sizeof(OGRGeoPackageTableLayer *) * oResult->RowCount()));
1735 :
1736 1636 : std::map<std::string, int> oMapTableRefCount;
1737 3815 : for (int i = 0; i < oResult->RowCount(); i++)
1738 : {
1739 2997 : const char *pszTableName = oResult->GetValue(0, i);
1740 2997 : if (pszTableName == nullptr)
1741 0 : continue;
1742 2997 : if (++oMapTableRefCount[pszTableName] == 2)
1743 : {
1744 : // This should normally not happen if all constraints are
1745 : // properly set
1746 2 : CPLError(CE_Warning, CPLE_AppDefined,
1747 : "Table %s appearing several times in "
1748 : "gpkg_contents and/or gpkg_geometry_columns",
1749 : pszTableName);
1750 : }
1751 : }
1752 :
1753 1636 : std::set<std::string> oExistingLayers;
1754 3815 : for (int i = 0; i < oResult->RowCount(); i++)
1755 : {
1756 2997 : const char *pszTableName = oResult->GetValue(0, i);
1757 2997 : if (pszTableName == nullptr)
1758 2 : continue;
1759 : const bool bTableHasSeveralGeomColumns =
1760 2997 : oMapTableRefCount[pszTableName] > 1;
1761 2997 : bool bIsSpatial = CPL_TO_BOOL(oResult->GetValueAsInteger(2, i));
1762 2997 : const char *pszGeomColName = oResult->GetValue(3, i);
1763 2997 : const char *pszGeomType = oResult->GetValue(4, i);
1764 2997 : const char *pszZ = oResult->GetValue(5, i);
1765 2997 : const char *pszM = oResult->GetValue(6, i);
1766 : bool bIsInGpkgContents =
1767 2997 : CPL_TO_BOOL(oResult->GetValueAsInteger(11, i));
1768 2997 : if (!bIsInGpkgContents)
1769 44 : m_bNonSpatialTablesNonRegisteredInGpkgContentsFound = true;
1770 2997 : const char *pszObjectType = oResult->GetValue(12, i);
1771 2997 : if (pszObjectType == nullptr ||
1772 2996 : !(EQUAL(pszObjectType, "table") ||
1773 21 : EQUAL(pszObjectType, "view")))
1774 : {
1775 1 : CPLError(CE_Warning, CPLE_AppDefined,
1776 : "Table/view %s is referenced in gpkg_contents, "
1777 : "but does not exist",
1778 : pszTableName);
1779 1 : continue;
1780 : }
1781 : // Non-standard and undocumented behavior:
1782 : // if the same table appears to have several geometry columns,
1783 : // handle it for now as multiple layers named
1784 : // "table_name (geom_col_name)"
1785 : // The way we handle that might change in the future (e.g
1786 : // could be a single layer with multiple geometry columns)
1787 : const std::string osLayerNameWithGeomColName =
1788 5666 : pszGeomColName ? std::string(pszTableName) + " (" +
1789 : pszGeomColName + ')'
1790 5992 : : std::string(pszTableName);
1791 2996 : if (cpl::contains(oExistingLayers, osLayerNameWithGeomColName))
1792 1 : continue;
1793 2995 : oExistingLayers.insert(osLayerNameWithGeomColName);
1794 : const std::string osLayerName = bTableHasSeveralGeomColumns
1795 : ? osLayerNameWithGeomColName
1796 2995 : : std::string(pszTableName);
1797 : OGRGeoPackageTableLayer *poLayer =
1798 2995 : new OGRGeoPackageTableLayer(this, osLayerName.c_str());
1799 2995 : bool bHasZ = pszZ && atoi(pszZ) > 0;
1800 2995 : bool bHasM = pszM && atoi(pszM) > 0;
1801 2995 : if (pszGeomType && EQUAL(pszGeomType, "GEOMETRY"))
1802 : {
1803 570 : if (pszZ && atoi(pszZ) == 2)
1804 7 : bHasZ = false;
1805 570 : if (pszM && atoi(pszM) == 2)
1806 6 : bHasM = false;
1807 : }
1808 2995 : poLayer->SetOpeningParameters(
1809 : pszTableName, pszObjectType, bIsInGpkgContents, bIsSpatial,
1810 : pszGeomColName, pszGeomType, bHasZ, bHasM);
1811 2995 : m_papoLayers[m_nLayers++] = poLayer;
1812 : }
1813 : }
1814 : }
1815 :
1816 1117 : bool bHasTileMatrixSet = false;
1817 1117 : if (poOpenInfo->nOpenFlags & GDAL_OF_RASTER)
1818 : {
1819 549 : bHasTileMatrixSet = SQLGetInteger(hDB,
1820 : "SELECT 1 FROM sqlite_master WHERE "
1821 : "name = 'gpkg_tile_matrix_set' AND "
1822 : "type IN ('table', 'view')",
1823 : nullptr) == 1;
1824 : }
1825 1117 : if (bHasTileMatrixSet)
1826 : {
1827 : std::string osSQL =
1828 : "SELECT c.table_name, c.identifier, c.description, c.srs_id, "
1829 : "c.min_x, c.min_y, c.max_x, c.max_y, "
1830 : "tms.min_x, tms.min_y, tms.max_x, tms.max_y, c.data_type "
1831 : "FROM gpkg_contents c JOIN gpkg_tile_matrix_set tms ON "
1832 : "c.table_name = tms.table_name WHERE "
1833 548 : "data_type IN ('tiles', '2d-gridded-coverage')";
1834 548 : if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE"))
1835 : osSubdatasetTableName =
1836 2 : CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE");
1837 548 : if (!osSubdatasetTableName.empty())
1838 : {
1839 14 : char *pszTmp = sqlite3_mprintf(" AND c.table_name='%q'",
1840 : osSubdatasetTableName.c_str());
1841 14 : osSQL += pszTmp;
1842 14 : sqlite3_free(pszTmp);
1843 14 : SetPhysicalFilename(osFilename.c_str());
1844 : }
1845 548 : const int nTableLimit = GetOGRTableLimit();
1846 548 : if (nTableLimit > 0)
1847 : {
1848 548 : osSQL += " LIMIT ";
1849 548 : osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1850 : }
1851 :
1852 548 : auto oResult = SQLQuery(hDB, osSQL.c_str());
1853 548 : if (!oResult)
1854 : {
1855 0 : return FALSE;
1856 : }
1857 :
1858 548 : if (oResult->RowCount() == 0 && !osSubdatasetTableName.empty())
1859 : {
1860 1 : CPLError(CE_Failure, CPLE_AppDefined,
1861 : "Cannot find table '%s' in GeoPackage dataset",
1862 : osSubdatasetTableName.c_str());
1863 : }
1864 547 : else if (oResult->RowCount() == 1)
1865 : {
1866 271 : const char *pszTableName = oResult->GetValue(0, 0);
1867 271 : const char *pszIdentifier = oResult->GetValue(1, 0);
1868 271 : const char *pszDescription = oResult->GetValue(2, 0);
1869 271 : const char *pszSRSId = oResult->GetValue(3, 0);
1870 271 : const char *pszMinX = oResult->GetValue(4, 0);
1871 271 : const char *pszMinY = oResult->GetValue(5, 0);
1872 271 : const char *pszMaxX = oResult->GetValue(6, 0);
1873 271 : const char *pszMaxY = oResult->GetValue(7, 0);
1874 271 : const char *pszTMSMinX = oResult->GetValue(8, 0);
1875 271 : const char *pszTMSMinY = oResult->GetValue(9, 0);
1876 271 : const char *pszTMSMaxX = oResult->GetValue(10, 0);
1877 271 : const char *pszTMSMaxY = oResult->GetValue(11, 0);
1878 271 : const char *pszDataType = oResult->GetValue(12, 0);
1879 271 : if (pszTableName && pszTMSMinX && pszTMSMinY && pszTMSMaxX &&
1880 : pszTMSMaxY)
1881 : {
1882 542 : bRet = OpenRaster(
1883 : pszTableName, pszIdentifier, pszDescription,
1884 271 : pszSRSId ? atoi(pszSRSId) : 0, CPLAtof(pszTMSMinX),
1885 : CPLAtof(pszTMSMinY), CPLAtof(pszTMSMaxX),
1886 : CPLAtof(pszTMSMaxY), pszMinX, pszMinY, pszMaxX, pszMaxY,
1887 271 : EQUAL(pszDataType, "tiles"), poOpenInfo->papszOpenOptions);
1888 : }
1889 : }
1890 276 : else if (oResult->RowCount() >= 1)
1891 : {
1892 5 : bRet = TRUE;
1893 :
1894 5 : if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
1895 : {
1896 1 : CPLError(CE_Warning, CPLE_AppDefined,
1897 : "File has more than %d raster tables. "
1898 : "Limiting to first %d (can be overridden with "
1899 : "OGR_TABLE_LIMIT config option)",
1900 : nTableLimit, nTableLimit);
1901 1 : oResult->LimitRowCount(nTableLimit);
1902 : }
1903 :
1904 5 : int nSDSCount = 0;
1905 2013 : for (int i = 0; i < oResult->RowCount(); i++)
1906 : {
1907 2008 : const char *pszTableName = oResult->GetValue(0, i);
1908 2008 : const char *pszIdentifier = oResult->GetValue(1, i);
1909 2008 : if (pszTableName == nullptr)
1910 0 : continue;
1911 : m_aosSubDatasets.AddNameValue(
1912 : CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
1913 2008 : CPLSPrintf("GPKG:%s:%s", m_pszFilename, pszTableName));
1914 : m_aosSubDatasets.AddNameValue(
1915 : CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
1916 : pszIdentifier
1917 2008 : ? CPLSPrintf("%s - %s", pszTableName, pszIdentifier)
1918 4016 : : pszTableName);
1919 2008 : nSDSCount++;
1920 : }
1921 : }
1922 : }
1923 :
1924 1117 : if (!bRet && (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR))
1925 : {
1926 30 : if ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE))
1927 : {
1928 20 : bRet = TRUE;
1929 : }
1930 : else
1931 : {
1932 10 : CPLDebug("GPKG",
1933 : "This GeoPackage has no vector content and is opened "
1934 : "in read-only mode. If you open it in update mode, "
1935 : "opening will be successful.");
1936 : }
1937 : }
1938 :
1939 1117 : if (eAccess == GA_Update)
1940 : {
1941 218 : FixupWrongRTreeTrigger();
1942 218 : FixupWrongMedataReferenceColumnNameUpdate();
1943 : }
1944 :
1945 1117 : SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
1946 :
1947 1117 : return bRet;
1948 : }
1949 :
1950 : /************************************************************************/
1951 : /* DetectSpatialRefSysColumns() */
1952 : /************************************************************************/
1953 :
1954 1125 : void GDALGeoPackageDataset::DetectSpatialRefSysColumns()
1955 : {
1956 : // Detect definition_12_063 column
1957 : {
1958 1125 : sqlite3_stmt *hSQLStmt = nullptr;
1959 1125 : int rc = sqlite3_prepare_v2(
1960 : hDB, "SELECT definition_12_063 FROM gpkg_spatial_ref_sys ", -1,
1961 : &hSQLStmt, nullptr);
1962 1125 : if (rc == SQLITE_OK)
1963 : {
1964 80 : m_bHasDefinition12_063 = true;
1965 80 : sqlite3_finalize(hSQLStmt);
1966 : }
1967 : }
1968 :
1969 : // Detect epoch column
1970 1125 : if (m_bHasDefinition12_063)
1971 : {
1972 80 : sqlite3_stmt *hSQLStmt = nullptr;
1973 : int rc =
1974 80 : sqlite3_prepare_v2(hDB, "SELECT epoch FROM gpkg_spatial_ref_sys ",
1975 : -1, &hSQLStmt, nullptr);
1976 80 : if (rc == SQLITE_OK)
1977 : {
1978 5 : m_bHasEpochColumn = true;
1979 5 : sqlite3_finalize(hSQLStmt);
1980 : }
1981 : }
1982 1125 : }
1983 :
1984 : /************************************************************************/
1985 : /* FixupWrongRTreeTrigger() */
1986 : /************************************************************************/
1987 :
1988 218 : void GDALGeoPackageDataset::FixupWrongRTreeTrigger()
1989 : {
1990 : auto oResult = SQLQuery(
1991 : hDB,
1992 : "SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND "
1993 218 : "NAME LIKE 'rtree_%_update3' AND sql LIKE '% AFTER UPDATE OF % ON %'");
1994 218 : if (oResult == nullptr)
1995 0 : return;
1996 218 : if (oResult->RowCount() > 0)
1997 : {
1998 1 : CPLDebug("GPKG", "Fixing incorrect trigger(s) related to RTree");
1999 : }
2000 220 : for (int i = 0; i < oResult->RowCount(); i++)
2001 : {
2002 2 : const char *pszName = oResult->GetValue(0, i);
2003 2 : const char *pszSQL = oResult->GetValue(1, i);
2004 2 : const char *pszPtr1 = strstr(pszSQL, " AFTER UPDATE OF ");
2005 2 : if (pszPtr1)
2006 : {
2007 2 : const char *pszPtr = pszPtr1 + strlen(" AFTER UPDATE OF ");
2008 : // Skipping over geometry column name
2009 4 : while (*pszPtr == ' ')
2010 2 : pszPtr++;
2011 2 : if (pszPtr[0] == '"' || pszPtr[0] == '\'')
2012 : {
2013 1 : char chStringDelim = pszPtr[0];
2014 1 : pszPtr++;
2015 9 : while (*pszPtr != '\0' && *pszPtr != chStringDelim)
2016 : {
2017 8 : if (*pszPtr == '\\' && pszPtr[1] == chStringDelim)
2018 0 : pszPtr += 2;
2019 : else
2020 8 : pszPtr += 1;
2021 : }
2022 1 : if (*pszPtr == chStringDelim)
2023 1 : pszPtr++;
2024 : }
2025 : else
2026 : {
2027 1 : pszPtr++;
2028 8 : while (*pszPtr != ' ')
2029 7 : pszPtr++;
2030 : }
2031 2 : if (*pszPtr == ' ')
2032 : {
2033 2 : SQLCommand(hDB,
2034 4 : ("DROP TRIGGER \"" + SQLEscapeName(pszName) + "\"")
2035 : .c_str());
2036 4 : CPLString newSQL;
2037 2 : newSQL.assign(pszSQL, pszPtr1 - pszSQL);
2038 2 : newSQL += " AFTER UPDATE";
2039 2 : newSQL += pszPtr;
2040 2 : SQLCommand(hDB, newSQL);
2041 : }
2042 : }
2043 : }
2044 : }
2045 :
2046 : /************************************************************************/
2047 : /* FixupWrongMedataReferenceColumnNameUpdate() */
2048 : /************************************************************************/
2049 :
2050 218 : void GDALGeoPackageDataset::FixupWrongMedataReferenceColumnNameUpdate()
2051 : {
2052 : // Fix wrong trigger that was generated by GDAL < 2.4.0
2053 : // See https://github.com/qgis/QGIS/issues/42768
2054 : auto oResult = SQLQuery(
2055 : hDB, "SELECT sql FROM sqlite_master WHERE type = 'trigger' AND "
2056 : "NAME ='gpkg_metadata_reference_column_name_update' AND "
2057 218 : "sql LIKE '%column_nameIS%'");
2058 218 : if (oResult == nullptr)
2059 0 : return;
2060 218 : if (oResult->RowCount() == 1)
2061 : {
2062 1 : CPLDebug("GPKG", "Fixing incorrect trigger "
2063 : "gpkg_metadata_reference_column_name_update");
2064 1 : const char *pszSQL = oResult->GetValue(0, 0);
2065 : std::string osNewSQL(
2066 3 : CPLString(pszSQL).replaceAll("column_nameIS", "column_name IS"));
2067 :
2068 1 : SQLCommand(hDB,
2069 : "DROP TRIGGER gpkg_metadata_reference_column_name_update");
2070 1 : SQLCommand(hDB, osNewSQL.c_str());
2071 : }
2072 : }
2073 :
2074 : /************************************************************************/
2075 : /* ClearCachedRelationships() */
2076 : /************************************************************************/
2077 :
2078 36 : void GDALGeoPackageDataset::ClearCachedRelationships()
2079 : {
2080 36 : m_bHasPopulatedRelationships = false;
2081 36 : m_osMapRelationships.clear();
2082 36 : }
2083 :
2084 : /************************************************************************/
2085 : /* LoadRelationships() */
2086 : /************************************************************************/
2087 :
2088 80 : void GDALGeoPackageDataset::LoadRelationships() const
2089 : {
2090 80 : m_osMapRelationships.clear();
2091 :
2092 80 : std::vector<std::string> oExcludedTables;
2093 80 : if (HasGpkgextRelationsTable())
2094 : {
2095 37 : LoadRelationshipsUsingRelatedTablesExtension();
2096 :
2097 89 : for (const auto &oRelationship : m_osMapRelationships)
2098 : {
2099 : oExcludedTables.emplace_back(
2100 52 : oRelationship.second->GetMappingTableName());
2101 : }
2102 : }
2103 :
2104 : // Also load relationships defined using foreign keys (i.e. one-to-many
2105 : // relationships). Here we must exclude any relationships defined from the
2106 : // related tables extension, we don't want them included twice.
2107 80 : LoadRelationshipsFromForeignKeys(oExcludedTables);
2108 80 : m_bHasPopulatedRelationships = true;
2109 80 : }
2110 :
2111 : /************************************************************************/
2112 : /* LoadRelationshipsUsingRelatedTablesExtension() */
2113 : /************************************************************************/
2114 :
2115 37 : void GDALGeoPackageDataset::LoadRelationshipsUsingRelatedTablesExtension() const
2116 : {
2117 37 : m_osMapRelationships.clear();
2118 :
2119 : auto oResultTable = SQLQuery(
2120 37 : hDB, "SELECT base_table_name, base_primary_column, "
2121 : "related_table_name, related_primary_column, relation_name, "
2122 74 : "mapping_table_name FROM gpkgext_relations");
2123 37 : if (oResultTable && oResultTable->RowCount() > 0)
2124 : {
2125 86 : for (int i = 0; i < oResultTable->RowCount(); i++)
2126 : {
2127 53 : const char *pszBaseTableName = oResultTable->GetValue(0, i);
2128 53 : if (!pszBaseTableName)
2129 : {
2130 0 : CPLError(CE_Warning, CPLE_AppDefined,
2131 : "Could not retrieve base_table_name from "
2132 : "gpkgext_relations");
2133 1 : continue;
2134 : }
2135 53 : const char *pszBasePrimaryColumn = oResultTable->GetValue(1, i);
2136 53 : if (!pszBasePrimaryColumn)
2137 : {
2138 0 : CPLError(CE_Warning, CPLE_AppDefined,
2139 : "Could not retrieve base_primary_column from "
2140 : "gpkgext_relations");
2141 0 : continue;
2142 : }
2143 53 : const char *pszRelatedTableName = oResultTable->GetValue(2, i);
2144 53 : if (!pszRelatedTableName)
2145 : {
2146 0 : CPLError(CE_Warning, CPLE_AppDefined,
2147 : "Could not retrieve related_table_name from "
2148 : "gpkgext_relations");
2149 0 : continue;
2150 : }
2151 53 : const char *pszRelatedPrimaryColumn = oResultTable->GetValue(3, i);
2152 53 : if (!pszRelatedPrimaryColumn)
2153 : {
2154 0 : CPLError(CE_Warning, CPLE_AppDefined,
2155 : "Could not retrieve related_primary_column from "
2156 : "gpkgext_relations");
2157 0 : continue;
2158 : }
2159 53 : const char *pszRelationName = oResultTable->GetValue(4, i);
2160 53 : if (!pszRelationName)
2161 : {
2162 0 : CPLError(
2163 : CE_Warning, CPLE_AppDefined,
2164 : "Could not retrieve relation_name from gpkgext_relations");
2165 0 : continue;
2166 : }
2167 53 : const char *pszMappingTableName = oResultTable->GetValue(5, i);
2168 53 : if (!pszMappingTableName)
2169 : {
2170 0 : CPLError(CE_Warning, CPLE_AppDefined,
2171 : "Could not retrieve mapping_table_name from "
2172 : "gpkgext_relations");
2173 0 : continue;
2174 : }
2175 :
2176 : // confirm that mapping table exists
2177 : char *pszSQL =
2178 53 : sqlite3_mprintf("SELECT 1 FROM sqlite_master WHERE "
2179 : "name='%q' AND type IN ('table', 'view')",
2180 : pszMappingTableName);
2181 53 : const int nMappingTableCount = SQLGetInteger(hDB, pszSQL, nullptr);
2182 53 : sqlite3_free(pszSQL);
2183 :
2184 55 : if (nMappingTableCount < 1 &&
2185 2 : !const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
2186 2 : pszMappingTableName))
2187 : {
2188 1 : CPLError(CE_Warning, CPLE_AppDefined,
2189 : "Relationship mapping table %s does not exist",
2190 : pszMappingTableName);
2191 1 : continue;
2192 : }
2193 :
2194 : const std::string osRelationName = GenerateNameForRelationship(
2195 104 : pszBaseTableName, pszRelatedTableName, pszRelationName);
2196 :
2197 104 : std::string osType{};
2198 : // defined requirement classes -- for these types the relation name
2199 : // will be specific string value from the related tables extension.
2200 : // In this case we need to construct a unique relationship name
2201 : // based on the related tables
2202 52 : if (EQUAL(pszRelationName, "media") ||
2203 40 : EQUAL(pszRelationName, "simple_attributes") ||
2204 40 : EQUAL(pszRelationName, "features") ||
2205 18 : EQUAL(pszRelationName, "attributes") ||
2206 2 : EQUAL(pszRelationName, "tiles"))
2207 : {
2208 50 : osType = pszRelationName;
2209 : }
2210 : else
2211 : {
2212 : // user defined types default to features
2213 2 : osType = "features";
2214 : }
2215 :
2216 : std::unique_ptr<GDALRelationship> poRelationship(
2217 : new GDALRelationship(osRelationName, pszBaseTableName,
2218 156 : pszRelatedTableName, GRC_MANY_TO_MANY));
2219 :
2220 104 : poRelationship->SetLeftTableFields({pszBasePrimaryColumn});
2221 104 : poRelationship->SetRightTableFields({pszRelatedPrimaryColumn});
2222 104 : poRelationship->SetLeftMappingTableFields({"base_id"});
2223 104 : poRelationship->SetRightMappingTableFields({"related_id"});
2224 52 : poRelationship->SetMappingTableName(pszMappingTableName);
2225 52 : poRelationship->SetRelatedTableType(osType);
2226 :
2227 52 : m_osMapRelationships[osRelationName] = std::move(poRelationship);
2228 : }
2229 : }
2230 37 : }
2231 :
2232 : /************************************************************************/
2233 : /* GenerateNameForRelationship() */
2234 : /************************************************************************/
2235 :
2236 76 : std::string GDALGeoPackageDataset::GenerateNameForRelationship(
2237 : const char *pszBaseTableName, const char *pszRelatedTableName,
2238 : const char *pszType)
2239 : {
2240 : // defined requirement classes -- for these types the relation name will be
2241 : // specific string value from the related tables extension. In this case we
2242 : // need to construct a unique relationship name based on the related tables
2243 76 : if (EQUAL(pszType, "media") || EQUAL(pszType, "simple_attributes") ||
2244 53 : EQUAL(pszType, "features") || EQUAL(pszType, "attributes") ||
2245 8 : EQUAL(pszType, "tiles"))
2246 : {
2247 136 : std::ostringstream stream;
2248 : stream << pszBaseTableName << '_' << pszRelatedTableName << '_'
2249 68 : << pszType;
2250 68 : return stream.str();
2251 : }
2252 : else
2253 : {
2254 : // user defined types default to features
2255 8 : return pszType;
2256 : }
2257 : }
2258 :
2259 : /************************************************************************/
2260 : /* ValidateRelationship() */
2261 : /************************************************************************/
2262 :
2263 28 : bool GDALGeoPackageDataset::ValidateRelationship(
2264 : const GDALRelationship *poRelationship, std::string &failureReason)
2265 : {
2266 :
2267 28 : if (poRelationship->GetCardinality() !=
2268 : GDALRelationshipCardinality::GRC_MANY_TO_MANY)
2269 : {
2270 3 : failureReason = "Only many to many relationships are supported";
2271 3 : return false;
2272 : }
2273 :
2274 50 : std::string osRelatedTableType = poRelationship->GetRelatedTableType();
2275 65 : if (!osRelatedTableType.empty() && osRelatedTableType != "features" &&
2276 30 : osRelatedTableType != "media" &&
2277 20 : osRelatedTableType != "simple_attributes" &&
2278 55 : osRelatedTableType != "attributes" && osRelatedTableType != "tiles")
2279 : {
2280 : failureReason =
2281 4 : ("Related table type " + osRelatedTableType +
2282 : " is not a valid value for the GeoPackage specification. "
2283 : "Valid values are: features, media, simple_attributes, "
2284 : "attributes, tiles.")
2285 2 : .c_str();
2286 2 : return false;
2287 : }
2288 :
2289 23 : const std::string &osLeftTableName = poRelationship->GetLeftTableName();
2290 23 : OGRGeoPackageLayer *poLeftTable = cpl::down_cast<OGRGeoPackageLayer *>(
2291 23 : GetLayerByName(osLeftTableName.c_str()));
2292 23 : if (!poLeftTable)
2293 : {
2294 4 : failureReason = ("Left table " + osLeftTableName +
2295 : " is not an existing layer in the dataset")
2296 2 : .c_str();
2297 2 : return false;
2298 : }
2299 21 : const std::string &osRightTableName = poRelationship->GetRightTableName();
2300 21 : OGRGeoPackageLayer *poRightTable = cpl::down_cast<OGRGeoPackageLayer *>(
2301 21 : GetLayerByName(osRightTableName.c_str()));
2302 21 : if (!poRightTable)
2303 : {
2304 4 : failureReason = ("Right table " + osRightTableName +
2305 : " is not an existing layer in the dataset")
2306 2 : .c_str();
2307 2 : return false;
2308 : }
2309 :
2310 19 : const auto &aosLeftTableFields = poRelationship->GetLeftTableFields();
2311 19 : if (aosLeftTableFields.empty())
2312 : {
2313 1 : failureReason = "No left table fields were specified";
2314 1 : return false;
2315 : }
2316 18 : else if (aosLeftTableFields.size() > 1)
2317 : {
2318 : failureReason = "Only a single left table field is permitted for the "
2319 1 : "GeoPackage specification";
2320 1 : return false;
2321 : }
2322 : else
2323 : {
2324 : // validate left field exists
2325 34 : if (poLeftTable->GetLayerDefn()->GetFieldIndex(
2326 37 : aosLeftTableFields[0].c_str()) < 0 &&
2327 3 : !EQUAL(poLeftTable->GetFIDColumn(), aosLeftTableFields[0].c_str()))
2328 : {
2329 2 : failureReason = ("Left table field " + aosLeftTableFields[0] +
2330 2 : " does not exist in " + osLeftTableName)
2331 1 : .c_str();
2332 1 : return false;
2333 : }
2334 : }
2335 :
2336 16 : const auto &aosRightTableFields = poRelationship->GetRightTableFields();
2337 16 : if (aosRightTableFields.empty())
2338 : {
2339 1 : failureReason = "No right table fields were specified";
2340 1 : return false;
2341 : }
2342 15 : else if (aosRightTableFields.size() > 1)
2343 : {
2344 : failureReason = "Only a single right table field is permitted for the "
2345 1 : "GeoPackage specification";
2346 1 : return false;
2347 : }
2348 : else
2349 : {
2350 : // validate right field exists
2351 28 : if (poRightTable->GetLayerDefn()->GetFieldIndex(
2352 32 : aosRightTableFields[0].c_str()) < 0 &&
2353 4 : !EQUAL(poRightTable->GetFIDColumn(),
2354 : aosRightTableFields[0].c_str()))
2355 : {
2356 4 : failureReason = ("Right table field " + aosRightTableFields[0] +
2357 4 : " does not exist in " + osRightTableName)
2358 2 : .c_str();
2359 2 : return false;
2360 : }
2361 : }
2362 :
2363 12 : return true;
2364 : }
2365 :
2366 : /************************************************************************/
2367 : /* InitRaster() */
2368 : /************************************************************************/
2369 :
2370 355 : bool GDALGeoPackageDataset::InitRaster(
2371 : GDALGeoPackageDataset *poParentDS, const char *pszTableName, double dfMinX,
2372 : double dfMinY, double dfMaxX, double dfMaxY, const char *pszContentsMinX,
2373 : const char *pszContentsMinY, const char *pszContentsMaxX,
2374 : const char *pszContentsMaxY, char **papszOpenOptionsIn,
2375 : const SQLResult &oResult, int nIdxInResult)
2376 : {
2377 355 : m_osRasterTable = pszTableName;
2378 355 : m_dfTMSMinX = dfMinX;
2379 355 : m_dfTMSMaxY = dfMaxY;
2380 :
2381 : // Despite prior checking, the type might be Binary and
2382 : // SQLResultGetValue() not working properly on it
2383 355 : int nZoomLevel = atoi(oResult.GetValue(0, nIdxInResult));
2384 355 : if (nZoomLevel < 0 || nZoomLevel > 65536)
2385 : {
2386 0 : return false;
2387 : }
2388 355 : double dfPixelXSize = CPLAtof(oResult.GetValue(1, nIdxInResult));
2389 355 : double dfPixelYSize = CPLAtof(oResult.GetValue(2, nIdxInResult));
2390 355 : if (dfPixelXSize <= 0 || dfPixelYSize <= 0)
2391 : {
2392 0 : return false;
2393 : }
2394 355 : int nTileWidth = atoi(oResult.GetValue(3, nIdxInResult));
2395 355 : int nTileHeight = atoi(oResult.GetValue(4, nIdxInResult));
2396 355 : if (nTileWidth <= 0 || nTileWidth > 65536 || nTileHeight <= 0 ||
2397 : nTileHeight > 65536)
2398 : {
2399 0 : return false;
2400 : }
2401 : int nTileMatrixWidth = static_cast<int>(
2402 710 : std::min(static_cast<GIntBig>(INT_MAX),
2403 355 : CPLAtoGIntBig(oResult.GetValue(5, nIdxInResult))));
2404 : int nTileMatrixHeight = static_cast<int>(
2405 710 : std::min(static_cast<GIntBig>(INT_MAX),
2406 355 : CPLAtoGIntBig(oResult.GetValue(6, nIdxInResult))));
2407 355 : if (nTileMatrixWidth <= 0 || nTileMatrixHeight <= 0)
2408 : {
2409 0 : return false;
2410 : }
2411 :
2412 : /* Use content bounds in priority over tile_matrix_set bounds */
2413 355 : double dfGDALMinX = dfMinX;
2414 355 : double dfGDALMinY = dfMinY;
2415 355 : double dfGDALMaxX = dfMaxX;
2416 355 : double dfGDALMaxY = dfMaxY;
2417 : pszContentsMinX =
2418 355 : CSLFetchNameValueDef(papszOpenOptionsIn, "MINX", pszContentsMinX);
2419 : pszContentsMinY =
2420 355 : CSLFetchNameValueDef(papszOpenOptionsIn, "MINY", pszContentsMinY);
2421 : pszContentsMaxX =
2422 355 : CSLFetchNameValueDef(papszOpenOptionsIn, "MAXX", pszContentsMaxX);
2423 : pszContentsMaxY =
2424 355 : CSLFetchNameValueDef(papszOpenOptionsIn, "MAXY", pszContentsMaxY);
2425 355 : if (pszContentsMinX != nullptr && pszContentsMinY != nullptr &&
2426 355 : pszContentsMaxX != nullptr && pszContentsMaxY != nullptr)
2427 : {
2428 709 : if (CPLAtof(pszContentsMinX) < CPLAtof(pszContentsMaxX) &&
2429 354 : CPLAtof(pszContentsMinY) < CPLAtof(pszContentsMaxY))
2430 : {
2431 354 : dfGDALMinX = CPLAtof(pszContentsMinX);
2432 354 : dfGDALMinY = CPLAtof(pszContentsMinY);
2433 354 : dfGDALMaxX = CPLAtof(pszContentsMaxX);
2434 354 : dfGDALMaxY = CPLAtof(pszContentsMaxY);
2435 : }
2436 : else
2437 : {
2438 1 : CPLError(CE_Warning, CPLE_AppDefined,
2439 : "Illegal min_x/min_y/max_x/max_y values for %s in open "
2440 : "options and/or gpkg_contents. Using bounds of "
2441 : "gpkg_tile_matrix_set instead",
2442 : pszTableName);
2443 : }
2444 : }
2445 355 : if (dfGDALMinX >= dfGDALMaxX || dfGDALMinY >= dfGDALMaxY)
2446 : {
2447 0 : CPLError(CE_Failure, CPLE_AppDefined,
2448 : "Illegal min_x/min_y/max_x/max_y values for %s", pszTableName);
2449 0 : return false;
2450 : }
2451 :
2452 355 : int nBandCount = 0;
2453 : const char *pszBAND_COUNT =
2454 355 : CSLFetchNameValue(papszOpenOptionsIn, "BAND_COUNT");
2455 355 : if (poParentDS)
2456 : {
2457 86 : nBandCount = poParentDS->GetRasterCount();
2458 : }
2459 269 : else if (m_eDT != GDT_Byte)
2460 : {
2461 65 : if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO") &&
2462 0 : !EQUAL(pszBAND_COUNT, "1"))
2463 : {
2464 0 : CPLError(CE_Warning, CPLE_AppDefined,
2465 : "BAND_COUNT ignored for non-Byte data");
2466 : }
2467 65 : nBandCount = 1;
2468 : }
2469 : else
2470 : {
2471 204 : if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO"))
2472 : {
2473 69 : nBandCount = atoi(pszBAND_COUNT);
2474 69 : if (nBandCount == 1)
2475 5 : GetMetadata("IMAGE_STRUCTURE");
2476 : }
2477 : else
2478 : {
2479 135 : GetMetadata("IMAGE_STRUCTURE");
2480 135 : nBandCount = m_nBandCountFromMetadata;
2481 135 : if (nBandCount == 1)
2482 38 : m_eTF = GPKG_TF_PNG;
2483 : }
2484 204 : if (nBandCount == 1 && !m_osTFFromMetadata.empty())
2485 : {
2486 2 : m_eTF = GDALGPKGMBTilesGetTileFormat(m_osTFFromMetadata.c_str());
2487 : }
2488 204 : if (nBandCount <= 0 || nBandCount > 4)
2489 83 : nBandCount = 4;
2490 : }
2491 :
2492 355 : return InitRaster(poParentDS, pszTableName, nZoomLevel, nBandCount, dfMinX,
2493 : dfMaxY, dfPixelXSize, dfPixelYSize, nTileWidth,
2494 : nTileHeight, nTileMatrixWidth, nTileMatrixHeight,
2495 355 : dfGDALMinX, dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
2496 : }
2497 :
2498 : /************************************************************************/
2499 : /* ComputeTileAndPixelShifts() */
2500 : /************************************************************************/
2501 :
2502 765 : bool GDALGeoPackageDataset::ComputeTileAndPixelShifts()
2503 : {
2504 : int nTileWidth, nTileHeight;
2505 765 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
2506 :
2507 : // Compute shift between GDAL origin and TileMatrixSet origin
2508 765 : const double dfShiftXPixels =
2509 765 : (m_adfGeoTransform[0] - m_dfTMSMinX) / m_adfGeoTransform[1];
2510 765 : if (dfShiftXPixels / nTileWidth <= INT_MIN ||
2511 763 : dfShiftXPixels / nTileWidth > INT_MAX)
2512 2 : return false;
2513 763 : const int64_t nShiftXPixels =
2514 763 : static_cast<int64_t>(floor(0.5 + dfShiftXPixels));
2515 763 : m_nShiftXTiles = static_cast<int>(nShiftXPixels / nTileWidth);
2516 763 : if (nShiftXPixels < 0 && (nShiftXPixels % nTileWidth) != 0)
2517 11 : m_nShiftXTiles--;
2518 763 : m_nShiftXPixelsMod =
2519 763 : (static_cast<int>(nShiftXPixels % nTileWidth) + nTileWidth) %
2520 : nTileWidth;
2521 :
2522 763 : const double dfShiftYPixels =
2523 763 : (m_adfGeoTransform[3] - m_dfTMSMaxY) / m_adfGeoTransform[5];
2524 763 : if (dfShiftYPixels / nTileHeight <= INT_MIN ||
2525 763 : dfShiftYPixels / nTileHeight > INT_MAX)
2526 1 : return false;
2527 762 : const int64_t nShiftYPixels =
2528 762 : static_cast<int64_t>(floor(0.5 + dfShiftYPixels));
2529 762 : m_nShiftYTiles = static_cast<int>(nShiftYPixels / nTileHeight);
2530 762 : if (nShiftYPixels < 0 && (nShiftYPixels % nTileHeight) != 0)
2531 11 : m_nShiftYTiles--;
2532 762 : m_nShiftYPixelsMod =
2533 762 : (static_cast<int>(nShiftYPixels % nTileHeight) + nTileHeight) %
2534 : nTileHeight;
2535 762 : return true;
2536 : }
2537 :
2538 : /************************************************************************/
2539 : /* AllocCachedTiles() */
2540 : /************************************************************************/
2541 :
2542 762 : bool GDALGeoPackageDataset::AllocCachedTiles()
2543 : {
2544 : int nTileWidth, nTileHeight;
2545 762 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
2546 :
2547 : // We currently need 4 caches because of
2548 : // GDALGPKGMBTilesLikePseudoDataset::ReadTile(int nRow, int nCol)
2549 762 : const int nCacheCount = 4;
2550 : /*
2551 : (m_nShiftXPixelsMod != 0 || m_nShiftYPixelsMod != 0) ? 4 :
2552 : (GetUpdate() && m_eDT == GDT_Byte) ? 2 : 1;
2553 : */
2554 762 : m_pabyCachedTiles = static_cast<GByte *>(VSI_MALLOC3_VERBOSE(
2555 : cpl::fits_on<int>(nCacheCount * (m_eDT == GDT_Byte ? 4 : 1) *
2556 : m_nDTSize),
2557 : nTileWidth, nTileHeight));
2558 762 : if (m_pabyCachedTiles == nullptr)
2559 : {
2560 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too big tiles: %d x %d",
2561 : nTileWidth, nTileHeight);
2562 0 : return false;
2563 : }
2564 :
2565 762 : return true;
2566 : }
2567 :
2568 : /************************************************************************/
2569 : /* InitRaster() */
2570 : /************************************************************************/
2571 :
2572 594 : bool GDALGeoPackageDataset::InitRaster(
2573 : GDALGeoPackageDataset *poParentDS, const char *pszTableName, int nZoomLevel,
2574 : int nBandCount, double dfTMSMinX, double dfTMSMaxY, double dfPixelXSize,
2575 : double dfPixelYSize, int nTileWidth, int nTileHeight, int nTileMatrixWidth,
2576 : int nTileMatrixHeight, double dfGDALMinX, double dfGDALMinY,
2577 : double dfGDALMaxX, double dfGDALMaxY)
2578 : {
2579 594 : m_osRasterTable = pszTableName;
2580 594 : m_dfTMSMinX = dfTMSMinX;
2581 594 : m_dfTMSMaxY = dfTMSMaxY;
2582 594 : m_nZoomLevel = nZoomLevel;
2583 594 : m_nTileMatrixWidth = nTileMatrixWidth;
2584 594 : m_nTileMatrixHeight = nTileMatrixHeight;
2585 :
2586 594 : m_bGeoTransformValid = true;
2587 594 : m_adfGeoTransform[0] = dfGDALMinX;
2588 594 : m_adfGeoTransform[1] = dfPixelXSize;
2589 594 : m_adfGeoTransform[3] = dfGDALMaxY;
2590 594 : m_adfGeoTransform[5] = -dfPixelYSize;
2591 594 : double dfRasterXSize = 0.5 + (dfGDALMaxX - dfGDALMinX) / dfPixelXSize;
2592 594 : double dfRasterYSize = 0.5 + (dfGDALMaxY - dfGDALMinY) / dfPixelYSize;
2593 594 : if (dfRasterXSize > INT_MAX || dfRasterYSize > INT_MAX)
2594 : {
2595 0 : CPLError(CE_Failure, CPLE_NotSupported, "Too big raster: %f x %f",
2596 : dfRasterXSize, dfRasterYSize);
2597 0 : return false;
2598 : }
2599 594 : nRasterXSize = std::max(1, static_cast<int>(dfRasterXSize));
2600 594 : nRasterYSize = std::max(1, static_cast<int>(dfRasterYSize));
2601 :
2602 594 : if (poParentDS)
2603 : {
2604 325 : m_poParentDS = poParentDS;
2605 325 : eAccess = poParentDS->eAccess;
2606 325 : hDB = poParentDS->hDB;
2607 325 : m_eTF = poParentDS->m_eTF;
2608 325 : m_eDT = poParentDS->m_eDT;
2609 325 : m_nDTSize = poParentDS->m_nDTSize;
2610 325 : m_dfScale = poParentDS->m_dfScale;
2611 325 : m_dfOffset = poParentDS->m_dfOffset;
2612 325 : m_dfPrecision = poParentDS->m_dfPrecision;
2613 325 : m_usGPKGNull = poParentDS->m_usGPKGNull;
2614 325 : m_nQuality = poParentDS->m_nQuality;
2615 325 : m_nZLevel = poParentDS->m_nZLevel;
2616 325 : m_bDither = poParentDS->m_bDither;
2617 : /*m_nSRID = poParentDS->m_nSRID;*/
2618 325 : m_osWHERE = poParentDS->m_osWHERE;
2619 325 : SetDescription(CPLSPrintf("%s - zoom_level=%d",
2620 325 : poParentDS->GetDescription(), m_nZoomLevel));
2621 : }
2622 :
2623 2079 : for (int i = 1; i <= nBandCount; i++)
2624 : {
2625 : GDALGeoPackageRasterBand *poNewBand =
2626 1485 : new GDALGeoPackageRasterBand(this, nTileWidth, nTileHeight);
2627 1485 : if (poParentDS)
2628 : {
2629 761 : int bHasNoData = FALSE;
2630 : double dfNoDataValue =
2631 761 : poParentDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
2632 761 : if (bHasNoData)
2633 24 : poNewBand->SetNoDataValueInternal(dfNoDataValue);
2634 : }
2635 1485 : SetBand(i, poNewBand);
2636 :
2637 1485 : if (nBandCount == 1 && m_poCTFromMetadata)
2638 : {
2639 3 : poNewBand->AssignColorTable(m_poCTFromMetadata.get());
2640 : }
2641 1485 : if (!m_osNodataValueFromMetadata.empty())
2642 : {
2643 8 : poNewBand->SetNoDataValueInternal(
2644 : CPLAtof(m_osNodataValueFromMetadata.c_str()));
2645 : }
2646 : }
2647 :
2648 594 : if (!ComputeTileAndPixelShifts())
2649 : {
2650 3 : CPLError(CE_Failure, CPLE_AppDefined,
2651 : "Overflow occurred in ComputeTileAndPixelShifts()");
2652 3 : return false;
2653 : }
2654 :
2655 591 : GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
2656 591 : GDALPamDataset::SetMetadataItem("ZOOM_LEVEL",
2657 : CPLSPrintf("%d", m_nZoomLevel));
2658 :
2659 591 : return AllocCachedTiles();
2660 : }
2661 :
2662 : /************************************************************************/
2663 : /* GDALGPKGMBTilesGetTileFormat() */
2664 : /************************************************************************/
2665 :
2666 80 : GPKGTileFormat GDALGPKGMBTilesGetTileFormat(const char *pszTF)
2667 : {
2668 80 : GPKGTileFormat eTF = GPKG_TF_PNG_JPEG;
2669 80 : if (pszTF)
2670 : {
2671 80 : if (EQUAL(pszTF, "PNG_JPEG") || EQUAL(pszTF, "AUTO"))
2672 1 : eTF = GPKG_TF_PNG_JPEG;
2673 79 : else if (EQUAL(pszTF, "PNG"))
2674 46 : eTF = GPKG_TF_PNG;
2675 33 : else if (EQUAL(pszTF, "PNG8"))
2676 6 : eTF = GPKG_TF_PNG8;
2677 27 : else if (EQUAL(pszTF, "JPEG"))
2678 14 : eTF = GPKG_TF_JPEG;
2679 13 : else if (EQUAL(pszTF, "WEBP"))
2680 13 : eTF = GPKG_TF_WEBP;
2681 : else
2682 : {
2683 0 : CPLError(CE_Failure, CPLE_NotSupported,
2684 : "Unsuppoted value for TILE_FORMAT: %s", pszTF);
2685 : }
2686 : }
2687 80 : return eTF;
2688 : }
2689 :
2690 28 : const char *GDALMBTilesGetTileFormatName(GPKGTileFormat eTF)
2691 : {
2692 28 : switch (eTF)
2693 : {
2694 26 : case GPKG_TF_PNG:
2695 : case GPKG_TF_PNG8:
2696 26 : return "png";
2697 1 : case GPKG_TF_JPEG:
2698 1 : return "jpg";
2699 1 : case GPKG_TF_WEBP:
2700 1 : return "webp";
2701 0 : default:
2702 0 : break;
2703 : }
2704 0 : CPLError(CE_Failure, CPLE_NotSupported,
2705 : "Unsuppoted value for TILE_FORMAT: %d", static_cast<int>(eTF));
2706 0 : return nullptr;
2707 : }
2708 :
2709 : /************************************************************************/
2710 : /* OpenRaster() */
2711 : /************************************************************************/
2712 :
2713 271 : bool GDALGeoPackageDataset::OpenRaster(
2714 : const char *pszTableName, const char *pszIdentifier,
2715 : const char *pszDescription, int nSRSId, double dfMinX, double dfMinY,
2716 : double dfMaxX, double dfMaxY, const char *pszContentsMinX,
2717 : const char *pszContentsMinY, const char *pszContentsMaxX,
2718 : const char *pszContentsMaxY, bool bIsTiles, char **papszOpenOptionsIn)
2719 : {
2720 271 : if (dfMinX >= dfMaxX || dfMinY >= dfMaxY)
2721 0 : return false;
2722 :
2723 : // Config option just for debug, and for example force set to NaN
2724 : // which is not supported
2725 542 : CPLString osDataNull = CPLGetConfigOption("GPKG_NODATA", "");
2726 542 : CPLString osUom;
2727 542 : CPLString osFieldName;
2728 542 : CPLString osGridCellEncoding;
2729 271 : if (!bIsTiles)
2730 : {
2731 65 : char *pszSQL = sqlite3_mprintf(
2732 : "SELECT datatype, scale, offset, data_null, precision FROM "
2733 : "gpkg_2d_gridded_coverage_ancillary "
2734 : "WHERE tile_matrix_set_name = '%q' "
2735 : "AND datatype IN ('integer', 'float')"
2736 : "AND (scale > 0 OR scale IS NULL)",
2737 : pszTableName);
2738 65 : auto oResult = SQLQuery(hDB, pszSQL);
2739 65 : sqlite3_free(pszSQL);
2740 65 : if (!oResult || oResult->RowCount() == 0)
2741 : {
2742 0 : return false;
2743 : }
2744 65 : const char *pszDataType = oResult->GetValue(0, 0);
2745 65 : const char *pszScale = oResult->GetValue(1, 0);
2746 65 : const char *pszOffset = oResult->GetValue(2, 0);
2747 65 : const char *pszDataNull = oResult->GetValue(3, 0);
2748 65 : const char *pszPrecision = oResult->GetValue(4, 0);
2749 65 : if (pszDataNull)
2750 23 : osDataNull = pszDataNull;
2751 65 : if (EQUAL(pszDataType, "float"))
2752 : {
2753 6 : SetDataType(GDT_Float32);
2754 6 : m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
2755 : }
2756 : else
2757 : {
2758 59 : SetDataType(GDT_Float32);
2759 59 : m_eTF = GPKG_TF_PNG_16BIT;
2760 59 : const double dfScale = pszScale ? CPLAtof(pszScale) : 1.0;
2761 59 : const double dfOffset = pszOffset ? CPLAtof(pszOffset) : 0.0;
2762 59 : if (dfScale == 1.0)
2763 : {
2764 59 : if (dfOffset == 0.0)
2765 : {
2766 24 : SetDataType(GDT_UInt16);
2767 : }
2768 35 : else if (dfOffset == -32768.0)
2769 : {
2770 35 : SetDataType(GDT_Int16);
2771 : }
2772 : // coverity[tainted_data]
2773 0 : else if (dfOffset == -32767.0 && !osDataNull.empty() &&
2774 0 : CPLAtof(osDataNull) == 65535.0)
2775 : // Given that we will map the nodata value to -32768
2776 : {
2777 0 : SetDataType(GDT_Int16);
2778 : }
2779 : }
2780 :
2781 : // Check that the tile offset and scales are compatible of a
2782 : // final integer result.
2783 59 : if (m_eDT != GDT_Float32)
2784 : {
2785 : // coverity[tainted_data]
2786 59 : if (dfScale == 1.0 && dfOffset == -32768.0 &&
2787 118 : !osDataNull.empty() && CPLAtof(osDataNull) == 65535.0)
2788 : {
2789 : // Given that we will map the nodata value to -32768
2790 9 : pszSQL = sqlite3_mprintf(
2791 : "SELECT 1 FROM "
2792 : "gpkg_2d_gridded_tile_ancillary WHERE "
2793 : "tpudt_name = '%q' "
2794 : "AND NOT ((offset = 0.0 or offset = 1.0) "
2795 : "AND scale = 1.0) "
2796 : "LIMIT 1",
2797 : pszTableName);
2798 : }
2799 : else
2800 : {
2801 50 : pszSQL = sqlite3_mprintf(
2802 : "SELECT 1 FROM "
2803 : "gpkg_2d_gridded_tile_ancillary WHERE "
2804 : "tpudt_name = '%q' "
2805 : "AND NOT (offset = 0.0 AND scale = 1.0) LIMIT 1",
2806 : pszTableName);
2807 : }
2808 59 : sqlite3_stmt *hSQLStmt = nullptr;
2809 : int rc =
2810 59 : sqlite3_prepare_v2(hDB, pszSQL, -1, &hSQLStmt, nullptr);
2811 :
2812 59 : if (rc == SQLITE_OK)
2813 : {
2814 59 : if (sqlite3_step(hSQLStmt) == SQLITE_ROW)
2815 : {
2816 8 : SetDataType(GDT_Float32);
2817 : }
2818 59 : sqlite3_finalize(hSQLStmt);
2819 : }
2820 : else
2821 : {
2822 0 : CPLError(CE_Failure, CPLE_AppDefined,
2823 : "Error when running %s", pszSQL);
2824 : }
2825 59 : sqlite3_free(pszSQL);
2826 : }
2827 :
2828 59 : SetGlobalOffsetScale(dfOffset, dfScale);
2829 : }
2830 65 : if (pszPrecision)
2831 65 : m_dfPrecision = CPLAtof(pszPrecision);
2832 :
2833 : // Request those columns in a separate query, so as to keep
2834 : // compatibility with pre OGC 17-066r1 databases
2835 : pszSQL =
2836 65 : sqlite3_mprintf("SELECT uom, field_name, grid_cell_encoding FROM "
2837 : "gpkg_2d_gridded_coverage_ancillary "
2838 : "WHERE tile_matrix_set_name = '%q'",
2839 : pszTableName);
2840 65 : CPLPushErrorHandler(CPLQuietErrorHandler);
2841 65 : oResult = SQLQuery(hDB, pszSQL);
2842 65 : CPLPopErrorHandler();
2843 65 : sqlite3_free(pszSQL);
2844 65 : if (oResult && oResult->RowCount() == 1)
2845 : {
2846 64 : const char *pszUom = oResult->GetValue(0, 0);
2847 64 : if (pszUom)
2848 2 : osUom = pszUom;
2849 64 : const char *pszFieldName = oResult->GetValue(1, 0);
2850 64 : if (pszFieldName)
2851 64 : osFieldName = pszFieldName;
2852 64 : const char *pszGridCellEncoding = oResult->GetValue(2, 0);
2853 64 : if (pszGridCellEncoding)
2854 64 : osGridCellEncoding = pszGridCellEncoding;
2855 : }
2856 : }
2857 :
2858 271 : m_bRecordInsertedInGPKGContent = true;
2859 271 : m_nSRID = nSRSId;
2860 :
2861 541 : if (auto poSRS = GetSpatialRef(nSRSId))
2862 : {
2863 270 : m_oSRS = *(poSRS.get());
2864 : }
2865 :
2866 : /* Various sanity checks added in the SELECT */
2867 271 : char *pszQuotedTableName = sqlite3_mprintf("'%q'", pszTableName);
2868 542 : CPLString osQuotedTableName(pszQuotedTableName);
2869 271 : sqlite3_free(pszQuotedTableName);
2870 271 : char *pszSQL = sqlite3_mprintf(
2871 : "SELECT zoom_level, pixel_x_size, pixel_y_size, tile_width, "
2872 : "tile_height, matrix_width, matrix_height "
2873 : "FROM gpkg_tile_matrix tm "
2874 : "WHERE table_name = %s "
2875 : // INT_MAX would be the theoretical maximum value to avoid
2876 : // overflows, but that's already a insane value.
2877 : "AND zoom_level >= 0 AND zoom_level <= 65536 "
2878 : "AND pixel_x_size > 0 AND pixel_y_size > 0 "
2879 : "AND tile_width >= 1 AND tile_width <= 65536 "
2880 : "AND tile_height >= 1 AND tile_height <= 65536 "
2881 : "AND matrix_width >= 1 AND matrix_height >= 1",
2882 : osQuotedTableName.c_str());
2883 542 : CPLString osSQL(pszSQL);
2884 : const char *pszZoomLevel =
2885 271 : CSLFetchNameValue(papszOpenOptionsIn, "ZOOM_LEVEL");
2886 271 : if (pszZoomLevel)
2887 : {
2888 5 : if (GetUpdate())
2889 1 : osSQL += CPLSPrintf(" AND zoom_level <= %d", atoi(pszZoomLevel));
2890 : else
2891 : {
2892 : osSQL += CPLSPrintf(
2893 : " AND (zoom_level = %d OR (zoom_level < %d AND EXISTS(SELECT 1 "
2894 : "FROM %s WHERE zoom_level = tm.zoom_level LIMIT 1)))",
2895 : atoi(pszZoomLevel), atoi(pszZoomLevel),
2896 4 : osQuotedTableName.c_str());
2897 : }
2898 : }
2899 : // In read-only mode, only lists non empty zoom levels
2900 266 : else if (!GetUpdate())
2901 : {
2902 : osSQL += CPLSPrintf(" AND EXISTS(SELECT 1 FROM %s WHERE zoom_level = "
2903 : "tm.zoom_level LIMIT 1)",
2904 213 : osQuotedTableName.c_str());
2905 : }
2906 : else // if( pszZoomLevel == nullptr )
2907 : {
2908 : osSQL +=
2909 : CPLSPrintf(" AND zoom_level <= (SELECT MAX(zoom_level) FROM %s)",
2910 53 : osQuotedTableName.c_str());
2911 : }
2912 271 : osSQL += " ORDER BY zoom_level DESC";
2913 : // To avoid denial of service.
2914 271 : osSQL += " LIMIT 100";
2915 :
2916 542 : auto oResult = SQLQuery(hDB, osSQL.c_str());
2917 271 : if (!oResult || oResult->RowCount() == 0)
2918 : {
2919 108 : if (oResult && oResult->RowCount() == 0 && pszContentsMinX != nullptr &&
2920 108 : pszContentsMinY != nullptr && pszContentsMaxX != nullptr &&
2921 : pszContentsMaxY != nullptr)
2922 : {
2923 53 : osSQL = pszSQL;
2924 53 : osSQL += " ORDER BY zoom_level DESC";
2925 53 : if (!GetUpdate())
2926 27 : osSQL += " LIMIT 1";
2927 53 : oResult = SQLQuery(hDB, osSQL.c_str());
2928 : }
2929 54 : if (!oResult || oResult->RowCount() == 0)
2930 : {
2931 1 : if (oResult && pszZoomLevel != nullptr)
2932 : {
2933 1 : CPLError(CE_Failure, CPLE_AppDefined,
2934 : "ZOOM_LEVEL is probably not valid w.r.t tile "
2935 : "table content");
2936 : }
2937 1 : sqlite3_free(pszSQL);
2938 1 : return false;
2939 : }
2940 : }
2941 270 : sqlite3_free(pszSQL);
2942 :
2943 : // If USE_TILE_EXTENT=YES, then query the tile table to find which tiles
2944 : // actually exist.
2945 :
2946 : // CAUTION: Do not move those variables inside inner scope !
2947 540 : CPLString osContentsMinX, osContentsMinY, osContentsMaxX, osContentsMaxY;
2948 :
2949 270 : if (CPLTestBool(
2950 : CSLFetchNameValueDef(papszOpenOptionsIn, "USE_TILE_EXTENT", "NO")))
2951 : {
2952 13 : pszSQL = sqlite3_mprintf(
2953 : "SELECT MIN(tile_column), MIN(tile_row), MAX(tile_column), "
2954 : "MAX(tile_row) FROM \"%w\" WHERE zoom_level = %d",
2955 : pszTableName, atoi(oResult->GetValue(0, 0)));
2956 13 : auto oResult2 = SQLQuery(hDB, pszSQL);
2957 13 : sqlite3_free(pszSQL);
2958 26 : if (!oResult2 || oResult2->RowCount() == 0 ||
2959 : // Can happen if table is empty
2960 38 : oResult2->GetValue(0, 0) == nullptr ||
2961 : // Can happen if table has no NOT NULL constraint on tile_row
2962 : // and that all tile_row are NULL
2963 12 : oResult2->GetValue(1, 0) == nullptr)
2964 : {
2965 1 : return false;
2966 : }
2967 12 : const double dfPixelXSize = CPLAtof(oResult->GetValue(1, 0));
2968 12 : const double dfPixelYSize = CPLAtof(oResult->GetValue(2, 0));
2969 12 : const int nTileWidth = atoi(oResult->GetValue(3, 0));
2970 12 : const int nTileHeight = atoi(oResult->GetValue(4, 0));
2971 : osContentsMinX =
2972 24 : CPLSPrintf("%.17g", dfMinX + dfPixelXSize * nTileWidth *
2973 12 : atoi(oResult2->GetValue(0, 0)));
2974 : osContentsMaxY =
2975 24 : CPLSPrintf("%.17g", dfMaxY - dfPixelYSize * nTileHeight *
2976 12 : atoi(oResult2->GetValue(1, 0)));
2977 : osContentsMaxX = CPLSPrintf(
2978 24 : "%.17g", dfMinX + dfPixelXSize * nTileWidth *
2979 12 : (1 + atoi(oResult2->GetValue(2, 0))));
2980 : osContentsMinY = CPLSPrintf(
2981 24 : "%.17g", dfMaxY - dfPixelYSize * nTileHeight *
2982 12 : (1 + atoi(oResult2->GetValue(3, 0))));
2983 12 : pszContentsMinX = osContentsMinX.c_str();
2984 12 : pszContentsMinY = osContentsMinY.c_str();
2985 12 : pszContentsMaxX = osContentsMaxX.c_str();
2986 12 : pszContentsMaxY = osContentsMaxY.c_str();
2987 : }
2988 :
2989 269 : if (!InitRaster(nullptr, pszTableName, dfMinX, dfMinY, dfMaxX, dfMaxY,
2990 : pszContentsMinX, pszContentsMinY, pszContentsMaxX,
2991 269 : pszContentsMaxY, papszOpenOptionsIn, *oResult, 0))
2992 : {
2993 3 : return false;
2994 : }
2995 :
2996 : auto poBand =
2997 266 : reinterpret_cast<GDALGeoPackageRasterBand *>(GetRasterBand(1));
2998 266 : if (!osDataNull.empty())
2999 : {
3000 23 : double dfGPKGNoDataValue = CPLAtof(osDataNull);
3001 23 : if (m_eTF == GPKG_TF_PNG_16BIT)
3002 : {
3003 21 : if (dfGPKGNoDataValue < 0 || dfGPKGNoDataValue > 65535 ||
3004 21 : static_cast<int>(dfGPKGNoDataValue) != dfGPKGNoDataValue)
3005 : {
3006 0 : CPLError(CE_Warning, CPLE_AppDefined,
3007 : "data_null = %.17g is invalid for integer data_type",
3008 : dfGPKGNoDataValue);
3009 : }
3010 : else
3011 : {
3012 21 : m_usGPKGNull = static_cast<GUInt16>(dfGPKGNoDataValue);
3013 21 : if (m_eDT == GDT_Int16 && m_usGPKGNull > 32767)
3014 9 : dfGPKGNoDataValue = -32768.0;
3015 12 : else if (m_eDT == GDT_Float32)
3016 : {
3017 : // Pick a value that is unlikely to be hit with offset &
3018 : // scale
3019 4 : dfGPKGNoDataValue = -std::numeric_limits<float>::max();
3020 : }
3021 21 : poBand->SetNoDataValueInternal(dfGPKGNoDataValue);
3022 : }
3023 : }
3024 : else
3025 : {
3026 2 : poBand->SetNoDataValueInternal(
3027 2 : static_cast<float>(dfGPKGNoDataValue));
3028 : }
3029 : }
3030 266 : if (!osUom.empty())
3031 : {
3032 2 : poBand->SetUnitTypeInternal(osUom);
3033 : }
3034 266 : if (!osFieldName.empty())
3035 : {
3036 64 : GetRasterBand(1)->GDALRasterBand::SetDescription(osFieldName);
3037 : }
3038 266 : if (!osGridCellEncoding.empty())
3039 : {
3040 64 : if (osGridCellEncoding == "grid-value-is-center")
3041 : {
3042 15 : GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
3043 : GDALMD_AOP_POINT);
3044 : }
3045 49 : else if (osGridCellEncoding == "grid-value-is-area")
3046 : {
3047 45 : GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
3048 : GDALMD_AOP_AREA);
3049 : }
3050 : else
3051 : {
3052 4 : GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
3053 : GDALMD_AOP_POINT);
3054 4 : GetRasterBand(1)->GDALRasterBand::SetMetadataItem(
3055 : "GRID_CELL_ENCODING", osGridCellEncoding);
3056 : }
3057 : }
3058 :
3059 266 : CheckUnknownExtensions(true);
3060 :
3061 : // Do this after CheckUnknownExtensions() so that m_eTF is set to
3062 : // GPKG_TF_WEBP if the table already registers the gpkg_webp extension
3063 266 : const char *pszTF = CSLFetchNameValue(papszOpenOptionsIn, "TILE_FORMAT");
3064 266 : if (pszTF)
3065 : {
3066 4 : if (!GetUpdate())
3067 : {
3068 0 : CPLError(CE_Warning, CPLE_AppDefined,
3069 : "TILE_FORMAT open option ignored in read-only mode");
3070 : }
3071 4 : else if (m_eTF == GPKG_TF_PNG_16BIT ||
3072 4 : m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
3073 : {
3074 0 : CPLError(CE_Warning, CPLE_AppDefined,
3075 : "TILE_FORMAT open option ignored on gridded coverages");
3076 : }
3077 : else
3078 : {
3079 4 : GPKGTileFormat eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
3080 4 : if (eTF == GPKG_TF_WEBP && m_eTF != eTF)
3081 : {
3082 1 : if (!RegisterWebPExtension())
3083 0 : return false;
3084 : }
3085 4 : m_eTF = eTF;
3086 : }
3087 : }
3088 :
3089 266 : ParseCompressionOptions(papszOpenOptionsIn);
3090 :
3091 266 : m_osWHERE = CSLFetchNameValueDef(papszOpenOptionsIn, "WHERE", "");
3092 :
3093 : // Set metadata
3094 266 : if (pszIdentifier && pszIdentifier[0])
3095 266 : GDALPamDataset::SetMetadataItem("IDENTIFIER", pszIdentifier);
3096 266 : if (pszDescription && pszDescription[0])
3097 21 : GDALPamDataset::SetMetadataItem("DESCRIPTION", pszDescription);
3098 :
3099 : // Add overviews
3100 351 : for (int i = 1; i < oResult->RowCount(); i++)
3101 : {
3102 86 : GDALGeoPackageDataset *poOvrDS = new GDALGeoPackageDataset();
3103 86 : poOvrDS->ShareLockWithParentDataset(this);
3104 86 : if (!poOvrDS->InitRaster(this, pszTableName, dfMinX, dfMinY, dfMaxX,
3105 : dfMaxY, pszContentsMinX, pszContentsMinY,
3106 : pszContentsMaxX, pszContentsMaxY,
3107 86 : papszOpenOptionsIn, *oResult, i))
3108 : {
3109 0 : delete poOvrDS;
3110 1 : break;
3111 : }
3112 :
3113 86 : m_papoOverviewDS = static_cast<GDALGeoPackageDataset **>(
3114 172 : CPLRealloc(m_papoOverviewDS, sizeof(GDALGeoPackageDataset *) *
3115 86 : (m_nOverviewCount + 1)));
3116 86 : m_papoOverviewDS[m_nOverviewCount++] = poOvrDS;
3117 :
3118 : int nTileWidth, nTileHeight;
3119 86 : poOvrDS->GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3120 87 : if (eAccess == GA_ReadOnly && poOvrDS->GetRasterXSize() < nTileWidth &&
3121 1 : poOvrDS->GetRasterYSize() < nTileHeight)
3122 : {
3123 1 : break;
3124 : }
3125 : }
3126 :
3127 266 : return true;
3128 : }
3129 :
3130 : /************************************************************************/
3131 : /* GetSpatialRef() */
3132 : /************************************************************************/
3133 :
3134 14 : const OGRSpatialReference *GDALGeoPackageDataset::GetSpatialRef() const
3135 : {
3136 14 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
3137 : }
3138 :
3139 : /************************************************************************/
3140 : /* SetSpatialRef() */
3141 : /************************************************************************/
3142 :
3143 145 : CPLErr GDALGeoPackageDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
3144 : {
3145 145 : if (nBands == 0)
3146 : {
3147 1 : CPLError(CE_Failure, CPLE_NotSupported,
3148 : "SetProjection() not supported on a dataset with 0 band");
3149 1 : return CE_Failure;
3150 : }
3151 144 : if (eAccess != GA_Update)
3152 : {
3153 1 : CPLError(CE_Failure, CPLE_NotSupported,
3154 : "SetProjection() not supported on read-only dataset");
3155 1 : return CE_Failure;
3156 : }
3157 :
3158 143 : const int nSRID = GetSrsId(poSRS);
3159 286 : const auto poTS = GetTilingScheme(m_osTilingScheme);
3160 143 : if (poTS && nSRID != poTS->nEPSGCode)
3161 : {
3162 2 : CPLError(CE_Failure, CPLE_NotSupported,
3163 : "Projection should be EPSG:%d for %s tiling scheme",
3164 1 : poTS->nEPSGCode, m_osTilingScheme.c_str());
3165 1 : return CE_Failure;
3166 : }
3167 :
3168 142 : m_nSRID = nSRID;
3169 142 : m_oSRS.Clear();
3170 142 : if (poSRS)
3171 141 : m_oSRS = *poSRS;
3172 :
3173 142 : if (m_bRecordInsertedInGPKGContent)
3174 : {
3175 117 : char *pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET srs_id = %d "
3176 : "WHERE lower(table_name) = lower('%q')",
3177 : m_nSRID, m_osRasterTable.c_str());
3178 117 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3179 117 : sqlite3_free(pszSQL);
3180 117 : if (eErr != OGRERR_NONE)
3181 0 : return CE_Failure;
3182 :
3183 117 : pszSQL = sqlite3_mprintf("UPDATE gpkg_tile_matrix_set SET srs_id = %d "
3184 : "WHERE lower(table_name) = lower('%q')",
3185 : m_nSRID, m_osRasterTable.c_str());
3186 117 : eErr = SQLCommand(hDB, pszSQL);
3187 117 : sqlite3_free(pszSQL);
3188 117 : if (eErr != OGRERR_NONE)
3189 0 : return CE_Failure;
3190 : }
3191 :
3192 142 : return CE_None;
3193 : }
3194 :
3195 : /************************************************************************/
3196 : /* GetGeoTransform() */
3197 : /************************************************************************/
3198 :
3199 33 : CPLErr GDALGeoPackageDataset::GetGeoTransform(double *padfGeoTransform)
3200 : {
3201 33 : memcpy(padfGeoTransform, m_adfGeoTransform, 6 * sizeof(double));
3202 33 : if (!m_bGeoTransformValid)
3203 2 : return CE_Failure;
3204 : else
3205 31 : return CE_None;
3206 : }
3207 :
3208 : /************************************************************************/
3209 : /* SetGeoTransform() */
3210 : /************************************************************************/
3211 :
3212 176 : CPLErr GDALGeoPackageDataset::SetGeoTransform(double *padfGeoTransform)
3213 : {
3214 176 : if (nBands == 0)
3215 : {
3216 2 : CPLError(CE_Failure, CPLE_NotSupported,
3217 : "SetGeoTransform() not supported on a dataset with 0 band");
3218 2 : return CE_Failure;
3219 : }
3220 174 : if (eAccess != GA_Update)
3221 : {
3222 1 : CPLError(CE_Failure, CPLE_NotSupported,
3223 : "SetGeoTransform() not supported on read-only dataset");
3224 1 : return CE_Failure;
3225 : }
3226 173 : if (m_bGeoTransformValid)
3227 : {
3228 1 : CPLError(CE_Failure, CPLE_NotSupported,
3229 : "Cannot modify geotransform once set");
3230 1 : return CE_Failure;
3231 : }
3232 172 : if (padfGeoTransform[2] != 0.0 || padfGeoTransform[4] != 0 ||
3233 172 : padfGeoTransform[5] > 0.0)
3234 : {
3235 0 : CPLError(CE_Failure, CPLE_NotSupported,
3236 : "Only north-up non rotated geotransform supported");
3237 0 : return CE_Failure;
3238 : }
3239 :
3240 172 : if (m_nZoomLevel < 0)
3241 : {
3242 171 : const auto poTS = GetTilingScheme(m_osTilingScheme);
3243 171 : if (poTS)
3244 : {
3245 20 : double dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
3246 20 : double dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
3247 199 : for (m_nZoomLevel = 0; m_nZoomLevel < MAX_ZOOM_LEVEL;
3248 179 : m_nZoomLevel++)
3249 : {
3250 198 : double dfExpectedPixelXSize =
3251 198 : dfPixelXSizeZoomLevel0 / (1 << m_nZoomLevel);
3252 198 : double dfExpectedPixelYSize =
3253 198 : dfPixelYSizeZoomLevel0 / (1 << m_nZoomLevel);
3254 198 : if (fabs(padfGeoTransform[1] - dfExpectedPixelXSize) <
3255 198 : 1e-8 * dfExpectedPixelXSize &&
3256 19 : fabs(fabs(padfGeoTransform[5]) - dfExpectedPixelYSize) <
3257 19 : 1e-8 * dfExpectedPixelYSize)
3258 : {
3259 19 : break;
3260 : }
3261 : }
3262 20 : if (m_nZoomLevel == MAX_ZOOM_LEVEL)
3263 : {
3264 1 : m_nZoomLevel = -1;
3265 1 : CPLError(
3266 : CE_Failure, CPLE_NotSupported,
3267 : "Could not find an appropriate zoom level of %s tiling "
3268 : "scheme that matches raster pixel size",
3269 : m_osTilingScheme.c_str());
3270 1 : return CE_Failure;
3271 : }
3272 : }
3273 : }
3274 :
3275 171 : memcpy(m_adfGeoTransform, padfGeoTransform, 6 * sizeof(double));
3276 171 : m_bGeoTransformValid = true;
3277 :
3278 171 : return FinalizeRasterRegistration();
3279 : }
3280 :
3281 : /************************************************************************/
3282 : /* FinalizeRasterRegistration() */
3283 : /************************************************************************/
3284 :
3285 171 : CPLErr GDALGeoPackageDataset::FinalizeRasterRegistration()
3286 : {
3287 : OGRErr eErr;
3288 :
3289 171 : m_dfTMSMinX = m_adfGeoTransform[0];
3290 171 : m_dfTMSMaxY = m_adfGeoTransform[3];
3291 :
3292 : int nTileWidth, nTileHeight;
3293 171 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3294 :
3295 171 : if (m_nZoomLevel < 0)
3296 : {
3297 151 : m_nZoomLevel = 0;
3298 225 : while ((nRasterXSize >> m_nZoomLevel) > nTileWidth ||
3299 151 : (nRasterYSize >> m_nZoomLevel) > nTileHeight)
3300 74 : m_nZoomLevel++;
3301 : }
3302 :
3303 171 : double dfPixelXSizeZoomLevel0 = m_adfGeoTransform[1] * (1 << m_nZoomLevel);
3304 171 : double dfPixelYSizeZoomLevel0 =
3305 171 : fabs(m_adfGeoTransform[5]) * (1 << m_nZoomLevel);
3306 : int nTileXCountZoomLevel0 =
3307 171 : std::max(1, DIV_ROUND_UP((nRasterXSize >> m_nZoomLevel), nTileWidth));
3308 : int nTileYCountZoomLevel0 =
3309 171 : std::max(1, DIV_ROUND_UP((nRasterYSize >> m_nZoomLevel), nTileHeight));
3310 :
3311 342 : const auto poTS = GetTilingScheme(m_osTilingScheme);
3312 171 : if (poTS)
3313 : {
3314 20 : CPLAssert(m_nZoomLevel >= 0);
3315 20 : m_dfTMSMinX = poTS->dfMinX;
3316 20 : m_dfTMSMaxY = poTS->dfMaxY;
3317 20 : dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
3318 20 : dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
3319 20 : nTileXCountZoomLevel0 = poTS->nTileXCountZoomLevel0;
3320 20 : nTileYCountZoomLevel0 = poTS->nTileYCountZoomLevel0;
3321 : }
3322 171 : m_nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << m_nZoomLevel);
3323 171 : m_nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << m_nZoomLevel);
3324 :
3325 171 : if (!ComputeTileAndPixelShifts())
3326 : {
3327 0 : CPLError(CE_Failure, CPLE_AppDefined,
3328 : "Overflow occurred in ComputeTileAndPixelShifts()");
3329 0 : return CE_Failure;
3330 : }
3331 :
3332 171 : if (!AllocCachedTiles())
3333 : {
3334 0 : return CE_Failure;
3335 : }
3336 :
3337 171 : double dfGDALMinX = m_adfGeoTransform[0];
3338 171 : double dfGDALMinY =
3339 171 : m_adfGeoTransform[3] + nRasterYSize * m_adfGeoTransform[5];
3340 171 : double dfGDALMaxX =
3341 171 : m_adfGeoTransform[0] + nRasterXSize * m_adfGeoTransform[1];
3342 171 : double dfGDALMaxY = m_adfGeoTransform[3];
3343 :
3344 171 : if (SoftStartTransaction() != OGRERR_NONE)
3345 0 : return CE_Failure;
3346 :
3347 : const char *pszCurrentDate =
3348 171 : CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
3349 : CPLString osInsertGpkgContentsFormatting(
3350 : "INSERT INTO gpkg_contents "
3351 : "(table_name,data_type,identifier,description,min_x,min_y,max_x,max_y,"
3352 : "last_change,srs_id) VALUES "
3353 342 : "('%q','%q','%q','%q',%.17g,%.17g,%.17g,%.17g,");
3354 171 : osInsertGpkgContentsFormatting += (pszCurrentDate) ? "'%q'" : "%s";
3355 171 : osInsertGpkgContentsFormatting += ",%d)";
3356 342 : char *pszSQL = sqlite3_mprintf(
3357 : osInsertGpkgContentsFormatting.c_str(), m_osRasterTable.c_str(),
3358 171 : (m_eDT == GDT_Byte) ? "tiles" : "2d-gridded-coverage",
3359 : m_osIdentifier.c_str(), m_osDescription.c_str(), dfGDALMinX, dfGDALMinY,
3360 : dfGDALMaxX, dfGDALMaxY,
3361 : pszCurrentDate ? pszCurrentDate
3362 : : "strftime('%Y-%m-%dT%H:%M:%fZ','now')",
3363 : m_nSRID);
3364 :
3365 171 : eErr = SQLCommand(hDB, pszSQL);
3366 171 : sqlite3_free(pszSQL);
3367 171 : if (eErr != OGRERR_NONE)
3368 : {
3369 0 : SoftRollbackTransaction();
3370 0 : return CE_Failure;
3371 : }
3372 :
3373 171 : double dfTMSMaxX = m_dfTMSMinX + nTileXCountZoomLevel0 * nTileWidth *
3374 : dfPixelXSizeZoomLevel0;
3375 171 : double dfTMSMinY = m_dfTMSMaxY - nTileYCountZoomLevel0 * nTileHeight *
3376 : dfPixelYSizeZoomLevel0;
3377 :
3378 : pszSQL =
3379 171 : sqlite3_mprintf("INSERT INTO gpkg_tile_matrix_set "
3380 : "(table_name,srs_id,min_x,min_y,max_x,max_y) VALUES "
3381 : "('%q',%d,%.17g,%.17g,%.17g,%.17g)",
3382 : m_osRasterTable.c_str(), m_nSRID, m_dfTMSMinX,
3383 : dfTMSMinY, dfTMSMaxX, m_dfTMSMaxY);
3384 171 : eErr = SQLCommand(hDB, pszSQL);
3385 171 : sqlite3_free(pszSQL);
3386 171 : if (eErr != OGRERR_NONE)
3387 : {
3388 0 : SoftRollbackTransaction();
3389 0 : return CE_Failure;
3390 : }
3391 :
3392 171 : m_papoOverviewDS = static_cast<GDALGeoPackageDataset **>(
3393 171 : CPLCalloc(sizeof(GDALGeoPackageDataset *), m_nZoomLevel));
3394 :
3395 577 : for (int i = 0; i <= m_nZoomLevel; i++)
3396 : {
3397 406 : double dfPixelXSizeZoomLevel = 0.0;
3398 406 : double dfPixelYSizeZoomLevel = 0.0;
3399 406 : int nTileMatrixWidth = 0;
3400 406 : int nTileMatrixHeight = 0;
3401 406 : if (EQUAL(m_osTilingScheme, "CUSTOM"))
3402 : {
3403 225 : dfPixelXSizeZoomLevel =
3404 225 : m_adfGeoTransform[1] * (1 << (m_nZoomLevel - i));
3405 225 : dfPixelYSizeZoomLevel =
3406 225 : fabs(m_adfGeoTransform[5]) * (1 << (m_nZoomLevel - i));
3407 : }
3408 : else
3409 : {
3410 181 : dfPixelXSizeZoomLevel = dfPixelXSizeZoomLevel0 / (1 << i);
3411 181 : dfPixelYSizeZoomLevel = dfPixelYSizeZoomLevel0 / (1 << i);
3412 : }
3413 406 : nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << i);
3414 406 : nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << i);
3415 :
3416 406 : pszSQL = sqlite3_mprintf(
3417 : "INSERT INTO gpkg_tile_matrix "
3418 : "(table_name,zoom_level,matrix_width,matrix_height,tile_width,tile_"
3419 : "height,pixel_x_size,pixel_y_size) VALUES "
3420 : "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
3421 : m_osRasterTable.c_str(), i, nTileMatrixWidth, nTileMatrixHeight,
3422 : nTileWidth, nTileHeight, dfPixelXSizeZoomLevel,
3423 : dfPixelYSizeZoomLevel);
3424 406 : eErr = SQLCommand(hDB, pszSQL);
3425 406 : sqlite3_free(pszSQL);
3426 406 : if (eErr != OGRERR_NONE)
3427 : {
3428 0 : SoftRollbackTransaction();
3429 0 : return CE_Failure;
3430 : }
3431 :
3432 406 : if (i < m_nZoomLevel)
3433 : {
3434 235 : GDALGeoPackageDataset *poOvrDS = new GDALGeoPackageDataset();
3435 235 : poOvrDS->ShareLockWithParentDataset(this);
3436 235 : poOvrDS->InitRaster(this, m_osRasterTable, i, nBands, m_dfTMSMinX,
3437 : m_dfTMSMaxY, dfPixelXSizeZoomLevel,
3438 : dfPixelYSizeZoomLevel, nTileWidth, nTileHeight,
3439 : nTileMatrixWidth, nTileMatrixHeight, dfGDALMinX,
3440 : dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
3441 :
3442 235 : m_papoOverviewDS[m_nZoomLevel - 1 - i] = poOvrDS;
3443 : }
3444 : }
3445 :
3446 171 : if (!m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.empty())
3447 : {
3448 40 : eErr = SQLCommand(
3449 : hDB, m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.c_str());
3450 40 : m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.clear();
3451 40 : if (eErr != OGRERR_NONE)
3452 : {
3453 0 : SoftRollbackTransaction();
3454 0 : return CE_Failure;
3455 : }
3456 : }
3457 :
3458 171 : SoftCommitTransaction();
3459 :
3460 171 : m_nOverviewCount = m_nZoomLevel;
3461 171 : m_bRecordInsertedInGPKGContent = true;
3462 :
3463 171 : return CE_None;
3464 : }
3465 :
3466 : /************************************************************************/
3467 : /* FlushCache() */
3468 : /************************************************************************/
3469 :
3470 2415 : CPLErr GDALGeoPackageDataset::FlushCache(bool bAtClosing)
3471 : {
3472 2415 : if (m_bInFlushCache)
3473 0 : return CE_None;
3474 :
3475 2415 : if (eAccess == GA_Update || !m_bMetadataDirty)
3476 : {
3477 2412 : SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
3478 : }
3479 :
3480 2415 : if (m_bRemoveOGREmptyTable)
3481 : {
3482 586 : m_bRemoveOGREmptyTable = false;
3483 586 : RemoveOGREmptyTable();
3484 : }
3485 :
3486 2415 : CPLErr eErr = IFlushCacheWithErrCode(bAtClosing);
3487 :
3488 2415 : FlushMetadata();
3489 :
3490 2415 : if (eAccess == GA_Update || !m_bMetadataDirty)
3491 : {
3492 : // Needed again as above IFlushCacheWithErrCode()
3493 : // may have call GDALGeoPackageRasterBand::InvalidateStatistics()
3494 : // which modifies metadata
3495 2415 : SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
3496 : }
3497 :
3498 2415 : return eErr;
3499 : }
3500 :
3501 4623 : CPLErr GDALGeoPackageDataset::IFlushCacheWithErrCode(bool bAtClosing)
3502 :
3503 : {
3504 4623 : if (m_bInFlushCache)
3505 2141 : return CE_None;
3506 2482 : m_bInFlushCache = true;
3507 2482 : if (hDB && eAccess == GA_ReadOnly && bAtClosing)
3508 : {
3509 : // Clean-up metadata that will go to PAM by removing items that
3510 : // are reconstructed.
3511 1824 : CPLStringList aosMD;
3512 1531 : for (CSLConstList papszIter = GetMetadata(); papszIter && *papszIter;
3513 : ++papszIter)
3514 : {
3515 619 : char *pszKey = nullptr;
3516 619 : CPLParseNameValue(*papszIter, &pszKey);
3517 1238 : if (pszKey &&
3518 619 : (EQUAL(pszKey, "AREA_OR_POINT") ||
3519 473 : EQUAL(pszKey, "IDENTIFIER") || EQUAL(pszKey, "DESCRIPTION") ||
3520 254 : EQUAL(pszKey, "ZOOM_LEVEL") ||
3521 649 : STARTS_WITH(pszKey, "GPKG_METADATA_ITEM_")))
3522 : {
3523 : // remove it
3524 : }
3525 : else
3526 : {
3527 30 : aosMD.AddString(*papszIter);
3528 : }
3529 619 : CPLFree(pszKey);
3530 : }
3531 912 : oMDMD.SetMetadata(aosMD.List());
3532 912 : oMDMD.SetMetadata(nullptr, "IMAGE_STRUCTURE");
3533 :
3534 1824 : GDALPamDataset::FlushCache(bAtClosing);
3535 : }
3536 : else
3537 : {
3538 : // Short circuit GDALPamDataset to avoid serialization to .aux.xml
3539 1570 : GDALDataset::FlushCache(bAtClosing);
3540 : }
3541 :
3542 6229 : for (int i = 0; i < m_nLayers; i++)
3543 : {
3544 3747 : m_papoLayers[i]->RunDeferredCreationIfNecessary();
3545 3747 : m_papoLayers[i]->CreateSpatialIndexIfNecessary();
3546 : }
3547 :
3548 : // Update raster table last_change column in gpkg_contents if needed
3549 2482 : if (m_bHasModifiedTiles)
3550 : {
3551 510 : for (int i = 1; i <= nBands; ++i)
3552 : {
3553 : auto poBand =
3554 341 : cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
3555 341 : if (!poBand->HaveStatsMetadataBeenSetInThisSession())
3556 : {
3557 334 : poBand->InvalidateStatistics();
3558 334 : if (psPam && psPam->pszPamFilename)
3559 334 : VSIUnlink(psPam->pszPamFilename);
3560 : }
3561 : }
3562 :
3563 169 : UpdateGpkgContentsLastChange(m_osRasterTable);
3564 :
3565 169 : m_bHasModifiedTiles = false;
3566 : }
3567 :
3568 2482 : CPLErr eErr = FlushTiles();
3569 :
3570 2482 : m_bInFlushCache = false;
3571 2482 : return eErr;
3572 : }
3573 :
3574 : /************************************************************************/
3575 : /* GetCurrentDateEscapedSQL() */
3576 : /************************************************************************/
3577 :
3578 1730 : std::string GDALGeoPackageDataset::GetCurrentDateEscapedSQL()
3579 : {
3580 : const char *pszCurrentDate =
3581 1730 : CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
3582 1730 : if (pszCurrentDate)
3583 6 : return '\'' + SQLEscapeLiteral(pszCurrentDate) + '\'';
3584 1727 : return "strftime('%Y-%m-%dT%H:%M:%fZ','now')";
3585 : }
3586 :
3587 : /************************************************************************/
3588 : /* UpdateGpkgContentsLastChange() */
3589 : /************************************************************************/
3590 :
3591 : OGRErr
3592 743 : GDALGeoPackageDataset::UpdateGpkgContentsLastChange(const char *pszTableName)
3593 : {
3594 : char *pszSQL =
3595 743 : sqlite3_mprintf("UPDATE gpkg_contents SET "
3596 : "last_change = %s "
3597 : "WHERE lower(table_name) = lower('%q')",
3598 1486 : GetCurrentDateEscapedSQL().c_str(), pszTableName);
3599 743 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3600 743 : sqlite3_free(pszSQL);
3601 743 : return eErr;
3602 : }
3603 :
3604 : /************************************************************************/
3605 : /* IBuildOverviews() */
3606 : /************************************************************************/
3607 :
3608 20 : CPLErr GDALGeoPackageDataset::IBuildOverviews(
3609 : const char *pszResampling, int nOverviews, const int *panOverviewList,
3610 : int nBandsIn, const int * /*panBandList*/, GDALProgressFunc pfnProgress,
3611 : void *pProgressData, CSLConstList papszOptions)
3612 : {
3613 20 : if (GetAccess() != GA_Update)
3614 : {
3615 1 : CPLError(CE_Failure, CPLE_NotSupported,
3616 : "Overview building not supported on a database opened in "
3617 : "read-only mode");
3618 1 : return CE_Failure;
3619 : }
3620 19 : if (m_poParentDS != nullptr)
3621 : {
3622 1 : CPLError(CE_Failure, CPLE_NotSupported,
3623 : "Overview building not supported on overview dataset");
3624 1 : return CE_Failure;
3625 : }
3626 :
3627 18 : if (nOverviews == 0)
3628 : {
3629 5 : for (int i = 0; i < m_nOverviewCount; i++)
3630 3 : m_papoOverviewDS[i]->FlushCache(false);
3631 :
3632 2 : SoftStartTransaction();
3633 :
3634 2 : if (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
3635 : {
3636 1 : char *pszSQL = sqlite3_mprintf(
3637 : "DELETE FROM gpkg_2d_gridded_tile_ancillary WHERE id IN "
3638 : "(SELECT y.id FROM \"%w\" x "
3639 : "JOIN gpkg_2d_gridded_tile_ancillary y "
3640 : "ON x.id = y.tpudt_id AND y.tpudt_name = '%q' AND "
3641 : "x.zoom_level < %d)",
3642 : m_osRasterTable.c_str(), m_osRasterTable.c_str(), m_nZoomLevel);
3643 1 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3644 1 : sqlite3_free(pszSQL);
3645 1 : if (eErr != OGRERR_NONE)
3646 : {
3647 0 : SoftRollbackTransaction();
3648 0 : return CE_Failure;
3649 : }
3650 : }
3651 :
3652 : char *pszSQL =
3653 2 : sqlite3_mprintf("DELETE FROM \"%w\" WHERE zoom_level < %d",
3654 : m_osRasterTable.c_str(), m_nZoomLevel);
3655 2 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3656 2 : sqlite3_free(pszSQL);
3657 2 : if (eErr != OGRERR_NONE)
3658 : {
3659 0 : SoftRollbackTransaction();
3660 0 : return CE_Failure;
3661 : }
3662 :
3663 2 : SoftCommitTransaction();
3664 :
3665 2 : return CE_None;
3666 : }
3667 :
3668 16 : if (nBandsIn != nBands)
3669 : {
3670 0 : CPLError(CE_Failure, CPLE_NotSupported,
3671 : "Generation of overviews in GPKG only"
3672 : "supported when operating on all bands.");
3673 0 : return CE_Failure;
3674 : }
3675 :
3676 16 : if (m_nOverviewCount == 0)
3677 : {
3678 0 : CPLError(CE_Failure, CPLE_AppDefined,
3679 : "Image too small to support overviews");
3680 0 : return CE_Failure;
3681 : }
3682 :
3683 16 : FlushCache(false);
3684 60 : for (int i = 0; i < nOverviews; i++)
3685 : {
3686 47 : if (panOverviewList[i] < 2)
3687 : {
3688 1 : CPLError(CE_Failure, CPLE_IllegalArg,
3689 : "Overview factor must be >= 2");
3690 1 : return CE_Failure;
3691 : }
3692 :
3693 46 : bool bFound = false;
3694 46 : int jCandidate = -1;
3695 46 : int nMaxOvFactor = 0;
3696 196 : for (int j = 0; j < m_nOverviewCount; j++)
3697 : {
3698 190 : auto poODS = m_papoOverviewDS[j];
3699 190 : const int nOvFactor = static_cast<int>(
3700 190 : 0.5 + poODS->m_adfGeoTransform[1] / m_adfGeoTransform[1]);
3701 :
3702 190 : nMaxOvFactor = nOvFactor;
3703 :
3704 190 : if (nOvFactor == panOverviewList[i])
3705 : {
3706 40 : bFound = true;
3707 40 : break;
3708 : }
3709 :
3710 150 : if (jCandidate < 0 && nOvFactor > panOverviewList[i])
3711 1 : jCandidate = j;
3712 : }
3713 :
3714 46 : if (!bFound)
3715 : {
3716 : /* Mostly for debug */
3717 6 : if (!CPLTestBool(CPLGetConfigOption(
3718 : "ALLOW_GPKG_ZOOM_OTHER_EXTENSION", "YES")))
3719 : {
3720 2 : CPLString osOvrList;
3721 4 : for (int j = 0; j < m_nOverviewCount; j++)
3722 : {
3723 2 : auto poODS = m_papoOverviewDS[j];
3724 2 : const int nOvFactor =
3725 2 : static_cast<int>(0.5 + poODS->m_adfGeoTransform[1] /
3726 2 : m_adfGeoTransform[1]);
3727 :
3728 2 : if (j != 0)
3729 0 : osOvrList += " ";
3730 2 : osOvrList += CPLSPrintf("%d", nOvFactor);
3731 : }
3732 2 : CPLError(CE_Failure, CPLE_NotSupported,
3733 : "Only overviews %s can be computed",
3734 : osOvrList.c_str());
3735 2 : return CE_Failure;
3736 : }
3737 : else
3738 : {
3739 4 : int nOvFactor = panOverviewList[i];
3740 4 : if (jCandidate < 0)
3741 3 : jCandidate = m_nOverviewCount;
3742 :
3743 4 : int nOvXSize = std::max(1, GetRasterXSize() / nOvFactor);
3744 4 : int nOvYSize = std::max(1, GetRasterYSize() / nOvFactor);
3745 4 : if (!(jCandidate == m_nOverviewCount &&
3746 3 : nOvFactor == 2 * nMaxOvFactor) &&
3747 1 : !m_bZoomOther)
3748 : {
3749 1 : CPLError(CE_Warning, CPLE_AppDefined,
3750 : "Use of overview factor %d causes gpkg_zoom_other "
3751 : "extension to be needed",
3752 : nOvFactor);
3753 1 : RegisterZoomOtherExtension();
3754 1 : m_bZoomOther = true;
3755 : }
3756 :
3757 4 : SoftStartTransaction();
3758 :
3759 4 : CPLAssert(jCandidate > 0);
3760 4 : int nNewZoomLevel =
3761 4 : m_papoOverviewDS[jCandidate - 1]->m_nZoomLevel;
3762 :
3763 : char *pszSQL;
3764 : OGRErr eErr;
3765 24 : for (int k = 0; k <= jCandidate; k++)
3766 : {
3767 60 : pszSQL = sqlite3_mprintf(
3768 : "UPDATE gpkg_tile_matrix SET zoom_level = %d "
3769 : "WHERE lower(table_name) = lower('%q') AND zoom_level "
3770 : "= %d",
3771 20 : m_nZoomLevel - k + 1, m_osRasterTable.c_str(),
3772 20 : m_nZoomLevel - k);
3773 20 : eErr = SQLCommand(hDB, pszSQL);
3774 20 : sqlite3_free(pszSQL);
3775 20 : if (eErr != OGRERR_NONE)
3776 : {
3777 0 : SoftRollbackTransaction();
3778 0 : return CE_Failure;
3779 : }
3780 :
3781 : pszSQL =
3782 20 : sqlite3_mprintf("UPDATE \"%w\" SET zoom_level = %d "
3783 : "WHERE zoom_level = %d",
3784 : m_osRasterTable.c_str(),
3785 20 : m_nZoomLevel - k + 1, m_nZoomLevel - k);
3786 20 : eErr = SQLCommand(hDB, pszSQL);
3787 20 : sqlite3_free(pszSQL);
3788 20 : if (eErr != OGRERR_NONE)
3789 : {
3790 0 : SoftRollbackTransaction();
3791 0 : return CE_Failure;
3792 : }
3793 : }
3794 :
3795 4 : double dfGDALMinX = m_adfGeoTransform[0];
3796 4 : double dfGDALMinY =
3797 4 : m_adfGeoTransform[3] + nRasterYSize * m_adfGeoTransform[5];
3798 4 : double dfGDALMaxX =
3799 4 : m_adfGeoTransform[0] + nRasterXSize * m_adfGeoTransform[1];
3800 4 : double dfGDALMaxY = m_adfGeoTransform[3];
3801 4 : double dfPixelXSizeZoomLevel = m_adfGeoTransform[1] * nOvFactor;
3802 4 : double dfPixelYSizeZoomLevel =
3803 4 : fabs(m_adfGeoTransform[5]) * nOvFactor;
3804 : int nTileWidth, nTileHeight;
3805 4 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3806 4 : int nTileMatrixWidth = (nOvXSize + nTileWidth - 1) / nTileWidth;
3807 4 : int nTileMatrixHeight =
3808 4 : (nOvYSize + nTileHeight - 1) / nTileHeight;
3809 4 : pszSQL = sqlite3_mprintf(
3810 : "INSERT INTO gpkg_tile_matrix "
3811 : "(table_name,zoom_level,matrix_width,matrix_height,tile_"
3812 : "width,tile_height,pixel_x_size,pixel_y_size) VALUES "
3813 : "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
3814 : m_osRasterTable.c_str(), nNewZoomLevel, nTileMatrixWidth,
3815 : nTileMatrixHeight, nTileWidth, nTileHeight,
3816 : dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel);
3817 4 : eErr = SQLCommand(hDB, pszSQL);
3818 4 : sqlite3_free(pszSQL);
3819 4 : if (eErr != OGRERR_NONE)
3820 : {
3821 0 : SoftRollbackTransaction();
3822 0 : return CE_Failure;
3823 : }
3824 :
3825 4 : SoftCommitTransaction();
3826 :
3827 4 : m_nZoomLevel++; /* this change our zoom level as well as
3828 : previous overviews */
3829 20 : for (int k = 0; k < jCandidate; k++)
3830 16 : m_papoOverviewDS[k]->m_nZoomLevel++;
3831 :
3832 4 : GDALGeoPackageDataset *poOvrDS = new GDALGeoPackageDataset();
3833 4 : poOvrDS->ShareLockWithParentDataset(this);
3834 4 : poOvrDS->InitRaster(
3835 : this, m_osRasterTable, nNewZoomLevel, nBands, m_dfTMSMinX,
3836 : m_dfTMSMaxY, dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel,
3837 : nTileWidth, nTileHeight, nTileMatrixWidth,
3838 : nTileMatrixHeight, dfGDALMinX, dfGDALMinY, dfGDALMaxX,
3839 : dfGDALMaxY);
3840 4 : m_papoOverviewDS =
3841 8 : static_cast<GDALGeoPackageDataset **>(CPLRealloc(
3842 4 : m_papoOverviewDS, sizeof(GDALGeoPackageDataset *) *
3843 4 : (m_nOverviewCount + 1)));
3844 :
3845 4 : if (jCandidate < m_nOverviewCount)
3846 : {
3847 1 : memmove(m_papoOverviewDS + jCandidate + 1,
3848 1 : m_papoOverviewDS + jCandidate,
3849 : sizeof(GDALGeoPackageDataset *) *
3850 1 : (m_nOverviewCount - jCandidate));
3851 : }
3852 4 : m_papoOverviewDS[jCandidate] = poOvrDS;
3853 4 : m_nOverviewCount++;
3854 : }
3855 : }
3856 : }
3857 :
3858 : GDALRasterBand ***papapoOverviewBands = static_cast<GDALRasterBand ***>(
3859 13 : CPLCalloc(sizeof(GDALRasterBand **), nBands));
3860 13 : CPLErr eErr = CE_None;
3861 49 : for (int iBand = 0; eErr == CE_None && iBand < nBands; iBand++)
3862 : {
3863 72 : papapoOverviewBands[iBand] = static_cast<GDALRasterBand **>(
3864 36 : CPLCalloc(sizeof(GDALRasterBand *), nOverviews));
3865 36 : int iCurOverview = 0;
3866 185 : for (int i = 0; i < nOverviews; i++)
3867 : {
3868 149 : int j = 0; // Used after for.
3869 724 : for (; j < m_nOverviewCount; j++)
3870 : {
3871 724 : auto poODS = m_papoOverviewDS[j];
3872 724 : const int nOvFactor = static_cast<int>(
3873 724 : 0.5 + poODS->m_adfGeoTransform[1] / m_adfGeoTransform[1]);
3874 :
3875 724 : if (nOvFactor == panOverviewList[i])
3876 : {
3877 298 : papapoOverviewBands[iBand][iCurOverview] =
3878 149 : poODS->GetRasterBand(iBand + 1);
3879 149 : iCurOverview++;
3880 149 : break;
3881 : }
3882 : }
3883 149 : if (j == m_nOverviewCount)
3884 : {
3885 0 : CPLError(CE_Failure, CPLE_AppDefined,
3886 : "Could not find dataset corresponding to ov factor %d",
3887 0 : panOverviewList[i]);
3888 0 : eErr = CE_Failure;
3889 : }
3890 : }
3891 36 : if (eErr == CE_None)
3892 : {
3893 36 : CPLAssert(iCurOverview == nOverviews);
3894 : }
3895 : }
3896 :
3897 13 : if (eErr == CE_None)
3898 13 : eErr = GDALRegenerateOverviewsMultiBand(
3899 13 : nBands, papoBands, nOverviews, papapoOverviewBands, pszResampling,
3900 : pfnProgress, pProgressData, papszOptions);
3901 :
3902 49 : for (int iBand = 0; iBand < nBands; iBand++)
3903 : {
3904 36 : CPLFree(papapoOverviewBands[iBand]);
3905 : }
3906 13 : CPLFree(papapoOverviewBands);
3907 :
3908 13 : return eErr;
3909 : }
3910 :
3911 : /************************************************************************/
3912 : /* GetFileList() */
3913 : /************************************************************************/
3914 :
3915 37 : char **GDALGeoPackageDataset::GetFileList()
3916 : {
3917 37 : TryLoadXML();
3918 37 : return GDALPamDataset::GetFileList();
3919 : }
3920 :
3921 : /************************************************************************/
3922 : /* GetMetadataDomainList() */
3923 : /************************************************************************/
3924 :
3925 41 : char **GDALGeoPackageDataset::GetMetadataDomainList()
3926 : {
3927 41 : GetMetadata();
3928 41 : if (!m_osRasterTable.empty())
3929 5 : GetMetadata("GEOPACKAGE");
3930 41 : return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
3931 41 : TRUE, "SUBDATASETS", nullptr);
3932 : }
3933 :
3934 : /************************************************************************/
3935 : /* CheckMetadataDomain() */
3936 : /************************************************************************/
3937 :
3938 4819 : const char *GDALGeoPackageDataset::CheckMetadataDomain(const char *pszDomain)
3939 : {
3940 4991 : if (pszDomain != nullptr && EQUAL(pszDomain, "GEOPACKAGE") &&
3941 172 : m_osRasterTable.empty())
3942 : {
3943 4 : CPLError(
3944 : CE_Warning, CPLE_IllegalArg,
3945 : "Using GEOPACKAGE for a non-raster geopackage is not supported. "
3946 : "Using default domain instead");
3947 4 : return nullptr;
3948 : }
3949 4815 : return pszDomain;
3950 : }
3951 :
3952 : /************************************************************************/
3953 : /* HasMetadataTables() */
3954 : /************************************************************************/
3955 :
3956 4770 : bool GDALGeoPackageDataset::HasMetadataTables() const
3957 : {
3958 4770 : if (m_nHasMetadataTables < 0)
3959 : {
3960 : const int nCount =
3961 1822 : SQLGetInteger(hDB,
3962 : "SELECT COUNT(*) FROM sqlite_master WHERE name IN "
3963 : "('gpkg_metadata', 'gpkg_metadata_reference') "
3964 : "AND type IN ('table', 'view')",
3965 : nullptr);
3966 1822 : m_nHasMetadataTables = nCount == 2;
3967 : }
3968 4770 : return CPL_TO_BOOL(m_nHasMetadataTables);
3969 : }
3970 :
3971 : /************************************************************************/
3972 : /* HasDataColumnsTable() */
3973 : /************************************************************************/
3974 :
3975 946 : bool GDALGeoPackageDataset::HasDataColumnsTable() const
3976 : {
3977 1892 : const int nCount = SQLGetInteger(
3978 946 : hDB,
3979 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_data_columns'"
3980 : "AND type IN ('table', 'view')",
3981 : nullptr);
3982 946 : return nCount == 1;
3983 : }
3984 :
3985 : /************************************************************************/
3986 : /* HasDataColumnConstraintsTable() */
3987 : /************************************************************************/
3988 :
3989 119 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTable() const
3990 : {
3991 119 : const int nCount = SQLGetInteger(hDB,
3992 : "SELECT 1 FROM sqlite_master WHERE name = "
3993 : "'gpkg_data_column_constraints'"
3994 : "AND type IN ('table', 'view')",
3995 : nullptr);
3996 119 : return nCount == 1;
3997 : }
3998 :
3999 : /************************************************************************/
4000 : /* HasDataColumnConstraintsTableGPKG_1_0() */
4001 : /************************************************************************/
4002 :
4003 73 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTableGPKG_1_0() const
4004 : {
4005 73 : if (m_nApplicationId != GP10_APPLICATION_ID)
4006 71 : return false;
4007 : // In GPKG 1.0, the columns were named minIsInclusive, maxIsInclusive
4008 : // They were changed in 1.1 to min_is_inclusive, max_is_inclusive
4009 2 : bool bRet = false;
4010 2 : sqlite3_stmt *hSQLStmt = nullptr;
4011 2 : int rc = sqlite3_prepare_v2(hDB,
4012 : "SELECT minIsInclusive, maxIsInclusive FROM "
4013 : "gpkg_data_column_constraints",
4014 : -1, &hSQLStmt, nullptr);
4015 2 : if (rc == SQLITE_OK)
4016 : {
4017 2 : bRet = true;
4018 2 : sqlite3_finalize(hSQLStmt);
4019 : }
4020 2 : return bRet;
4021 : }
4022 :
4023 : /************************************************************************/
4024 : /* CreateColumnsTableAndColumnConstraintsTablesIfNecessary() */
4025 : /************************************************************************/
4026 :
4027 49 : bool GDALGeoPackageDataset::
4028 : CreateColumnsTableAndColumnConstraintsTablesIfNecessary()
4029 : {
4030 49 : if (!HasDataColumnsTable())
4031 : {
4032 : // Geopackage < 1.3 had
4033 : // CONSTRAINT fk_gdc_tn FOREIGN KEY (table_name) REFERENCES
4034 : // gpkg_contents(table_name) instead of the unique constraint.
4035 10 : if (OGRERR_NONE !=
4036 10 : SQLCommand(
4037 : GetDB(),
4038 : "CREATE TABLE gpkg_data_columns ("
4039 : "table_name TEXT NOT NULL,"
4040 : "column_name TEXT NOT NULL,"
4041 : "name TEXT,"
4042 : "title TEXT,"
4043 : "description TEXT,"
4044 : "mime_type TEXT,"
4045 : "constraint_name TEXT,"
4046 : "CONSTRAINT pk_gdc PRIMARY KEY (table_name, column_name),"
4047 : "CONSTRAINT gdc_tn UNIQUE (table_name, name));"))
4048 : {
4049 0 : return false;
4050 : }
4051 : }
4052 49 : if (!HasDataColumnConstraintsTable())
4053 : {
4054 22 : const char *min_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
4055 11 : ? "min_is_inclusive"
4056 : : "minIsInclusive";
4057 22 : const char *max_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
4058 11 : ? "max_is_inclusive"
4059 : : "maxIsInclusive";
4060 :
4061 : const std::string osSQL(
4062 : CPLSPrintf("CREATE TABLE gpkg_data_column_constraints ("
4063 : "constraint_name TEXT NOT NULL,"
4064 : "constraint_type TEXT NOT NULL,"
4065 : "value TEXT,"
4066 : "min NUMERIC,"
4067 : "%s BOOLEAN,"
4068 : "max NUMERIC,"
4069 : "%s BOOLEAN,"
4070 : "description TEXT,"
4071 : "CONSTRAINT gdcc_ntv UNIQUE (constraint_name, "
4072 : "constraint_type, value));",
4073 11 : min_is_inclusive, max_is_inclusive));
4074 11 : if (OGRERR_NONE != SQLCommand(GetDB(), osSQL.c_str()))
4075 : {
4076 0 : return false;
4077 : }
4078 : }
4079 49 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
4080 : {
4081 0 : return false;
4082 : }
4083 49 : if (SQLGetInteger(GetDB(),
4084 : "SELECT 1 FROM gpkg_extensions WHERE "
4085 : "table_name = 'gpkg_data_columns'",
4086 49 : nullptr) != 1)
4087 : {
4088 11 : if (OGRERR_NONE !=
4089 11 : SQLCommand(
4090 : GetDB(),
4091 : "INSERT INTO gpkg_extensions "
4092 : "(table_name,column_name,extension_name,definition,scope) "
4093 : "VALUES ('gpkg_data_columns', NULL, 'gpkg_schema', "
4094 : "'http://www.geopackage.org/spec121/#extension_schema', "
4095 : "'read-write')"))
4096 : {
4097 0 : return false;
4098 : }
4099 : }
4100 49 : if (SQLGetInteger(GetDB(),
4101 : "SELECT 1 FROM gpkg_extensions WHERE "
4102 : "table_name = 'gpkg_data_column_constraints'",
4103 49 : nullptr) != 1)
4104 : {
4105 11 : if (OGRERR_NONE !=
4106 11 : SQLCommand(
4107 : GetDB(),
4108 : "INSERT INTO gpkg_extensions "
4109 : "(table_name,column_name,extension_name,definition,scope) "
4110 : "VALUES ('gpkg_data_column_constraints', NULL, 'gpkg_schema', "
4111 : "'http://www.geopackage.org/spec121/#extension_schema', "
4112 : "'read-write')"))
4113 : {
4114 0 : return false;
4115 : }
4116 : }
4117 :
4118 49 : return true;
4119 : }
4120 :
4121 : /************************************************************************/
4122 : /* HasGpkgextRelationsTable() */
4123 : /************************************************************************/
4124 :
4125 1080 : bool GDALGeoPackageDataset::HasGpkgextRelationsTable() const
4126 : {
4127 2160 : const int nCount = SQLGetInteger(
4128 1080 : hDB,
4129 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkgext_relations'"
4130 : "AND type IN ('table', 'view')",
4131 : nullptr);
4132 1080 : return nCount == 1;
4133 : }
4134 :
4135 : /************************************************************************/
4136 : /* CreateRelationsTableIfNecessary() */
4137 : /************************************************************************/
4138 :
4139 9 : bool GDALGeoPackageDataset::CreateRelationsTableIfNecessary()
4140 : {
4141 9 : if (HasGpkgextRelationsTable())
4142 : {
4143 5 : return true;
4144 : }
4145 :
4146 4 : if (OGRERR_NONE !=
4147 4 : SQLCommand(GetDB(), "CREATE TABLE gpkgext_relations ("
4148 : "id INTEGER PRIMARY KEY AUTOINCREMENT,"
4149 : "base_table_name TEXT NOT NULL,"
4150 : "base_primary_column TEXT NOT NULL DEFAULT 'id',"
4151 : "related_table_name TEXT NOT NULL,"
4152 : "related_primary_column TEXT NOT NULL DEFAULT 'id',"
4153 : "relation_name TEXT NOT NULL,"
4154 : "mapping_table_name TEXT NOT NULL UNIQUE);"))
4155 : {
4156 0 : return false;
4157 : }
4158 :
4159 4 : return true;
4160 : }
4161 :
4162 : /************************************************************************/
4163 : /* HasQGISLayerStyles() */
4164 : /************************************************************************/
4165 :
4166 11 : bool GDALGeoPackageDataset::HasQGISLayerStyles() const
4167 : {
4168 : // QGIS layer_styles extension:
4169 : // https://github.com/pka/qgpkg/blob/master/qgis_geopackage_extension.md
4170 11 : bool bRet = false;
4171 : const int nCount =
4172 11 : SQLGetInteger(hDB,
4173 : "SELECT 1 FROM sqlite_master WHERE name = 'layer_styles'"
4174 : "AND type = 'table'",
4175 : nullptr);
4176 11 : if (nCount == 1)
4177 : {
4178 1 : sqlite3_stmt *hSQLStmt = nullptr;
4179 2 : int rc = sqlite3_prepare_v2(
4180 1 : hDB, "SELECT f_table_name, f_geometry_column FROM layer_styles", -1,
4181 : &hSQLStmt, nullptr);
4182 1 : if (rc == SQLITE_OK)
4183 : {
4184 1 : bRet = true;
4185 1 : sqlite3_finalize(hSQLStmt);
4186 : }
4187 : }
4188 11 : return bRet;
4189 : }
4190 :
4191 : /************************************************************************/
4192 : /* GetMetadata() */
4193 : /************************************************************************/
4194 :
4195 3243 : char **GDALGeoPackageDataset::GetMetadata(const char *pszDomain)
4196 :
4197 : {
4198 3243 : pszDomain = CheckMetadataDomain(pszDomain);
4199 3243 : if (pszDomain != nullptr && EQUAL(pszDomain, "SUBDATASETS"))
4200 60 : return m_aosSubDatasets.List();
4201 :
4202 3183 : if (m_bHasReadMetadataFromStorage)
4203 1411 : return GDALPamDataset::GetMetadata(pszDomain);
4204 :
4205 1772 : m_bHasReadMetadataFromStorage = true;
4206 :
4207 1772 : TryLoadXML();
4208 :
4209 1772 : if (!HasMetadataTables())
4210 1290 : return GDALPamDataset::GetMetadata(pszDomain);
4211 :
4212 482 : char *pszSQL = nullptr;
4213 482 : if (!m_osRasterTable.empty())
4214 : {
4215 169 : pszSQL = sqlite3_mprintf(
4216 : "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
4217 : "mdr.reference_scope FROM gpkg_metadata md "
4218 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4219 : "WHERE "
4220 : "(mdr.reference_scope = 'geopackage' OR "
4221 : "(mdr.reference_scope = 'table' AND lower(mdr.table_name) = "
4222 : "lower('%q'))) ORDER BY md.id "
4223 : "LIMIT 1000", // to avoid denial of service
4224 : m_osRasterTable.c_str());
4225 : }
4226 : else
4227 : {
4228 313 : pszSQL = sqlite3_mprintf(
4229 : "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
4230 : "mdr.reference_scope FROM gpkg_metadata md "
4231 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4232 : "WHERE "
4233 : "mdr.reference_scope = 'geopackage' ORDER BY md.id "
4234 : "LIMIT 1000" // to avoid denial of service
4235 : );
4236 : }
4237 :
4238 964 : auto oResult = SQLQuery(hDB, pszSQL);
4239 482 : sqlite3_free(pszSQL);
4240 482 : if (!oResult)
4241 : {
4242 0 : return GDALPamDataset::GetMetadata(pszDomain);
4243 : }
4244 :
4245 482 : char **papszMetadata = CSLDuplicate(GDALPamDataset::GetMetadata());
4246 :
4247 : /* GDAL metadata */
4248 671 : for (int i = 0; i < oResult->RowCount(); i++)
4249 : {
4250 189 : const char *pszMetadata = oResult->GetValue(0, i);
4251 189 : const char *pszMDStandardURI = oResult->GetValue(1, i);
4252 189 : const char *pszMimeType = oResult->GetValue(2, i);
4253 189 : const char *pszReferenceScope = oResult->GetValue(3, i);
4254 189 : if (pszMetadata && pszMDStandardURI && pszMimeType &&
4255 189 : pszReferenceScope && EQUAL(pszMDStandardURI, "http://gdal.org") &&
4256 173 : EQUAL(pszMimeType, "text/xml"))
4257 : {
4258 173 : CPLXMLNode *psXMLNode = CPLParseXMLString(pszMetadata);
4259 173 : if (psXMLNode)
4260 : {
4261 346 : GDALMultiDomainMetadata oLocalMDMD;
4262 173 : oLocalMDMD.XMLInit(psXMLNode, FALSE);
4263 331 : if (!m_osRasterTable.empty() &&
4264 158 : EQUAL(pszReferenceScope, "geopackage"))
4265 : {
4266 6 : oMDMD.SetMetadata(oLocalMDMD.GetMetadata(), "GEOPACKAGE");
4267 : }
4268 : else
4269 : {
4270 : papszMetadata =
4271 167 : CSLMerge(papszMetadata, oLocalMDMD.GetMetadata());
4272 167 : CSLConstList papszDomainList = oLocalMDMD.GetDomainList();
4273 167 : CSLConstList papszIter = papszDomainList;
4274 445 : while (papszIter && *papszIter)
4275 : {
4276 278 : if (EQUAL(*papszIter, "IMAGE_STRUCTURE"))
4277 : {
4278 : CSLConstList papszMD =
4279 125 : oLocalMDMD.GetMetadata(*papszIter);
4280 : const char *pszBAND_COUNT =
4281 125 : CSLFetchNameValue(papszMD, "BAND_COUNT");
4282 125 : if (pszBAND_COUNT)
4283 123 : m_nBandCountFromMetadata = atoi(pszBAND_COUNT);
4284 :
4285 : const char *pszCOLOR_TABLE =
4286 125 : CSLFetchNameValue(papszMD, "COLOR_TABLE");
4287 125 : if (pszCOLOR_TABLE)
4288 : {
4289 : const CPLStringList aosTokens(
4290 : CSLTokenizeString2(pszCOLOR_TABLE, "{,",
4291 26 : 0));
4292 13 : if ((aosTokens.size() % 4) == 0)
4293 : {
4294 13 : const int nColors = aosTokens.size() / 4;
4295 : m_poCTFromMetadata =
4296 13 : std::make_unique<GDALColorTable>();
4297 3341 : for (int iColor = 0; iColor < nColors;
4298 : ++iColor)
4299 : {
4300 : GDALColorEntry sEntry;
4301 3328 : sEntry.c1 = static_cast<short>(
4302 3328 : atoi(aosTokens[4 * iColor + 0]));
4303 3328 : sEntry.c2 = static_cast<short>(
4304 3328 : atoi(aosTokens[4 * iColor + 1]));
4305 3328 : sEntry.c3 = static_cast<short>(
4306 3328 : atoi(aosTokens[4 * iColor + 2]));
4307 3328 : sEntry.c4 = static_cast<short>(
4308 3328 : atoi(aosTokens[4 * iColor + 3]));
4309 3328 : m_poCTFromMetadata->SetColorEntry(
4310 : iColor, &sEntry);
4311 : }
4312 : }
4313 : }
4314 :
4315 : const char *pszTILE_FORMAT =
4316 125 : CSLFetchNameValue(papszMD, "TILE_FORMAT");
4317 125 : if (pszTILE_FORMAT)
4318 : {
4319 8 : m_osTFFromMetadata = pszTILE_FORMAT;
4320 8 : oMDMD.SetMetadataItem("TILE_FORMAT",
4321 : pszTILE_FORMAT,
4322 : "IMAGE_STRUCTURE");
4323 : }
4324 :
4325 : const char *pszNodataValue =
4326 125 : CSLFetchNameValue(papszMD, "NODATA_VALUE");
4327 125 : if (pszNodataValue)
4328 : {
4329 2 : m_osNodataValueFromMetadata = pszNodataValue;
4330 : }
4331 : }
4332 :
4333 153 : else if (!EQUAL(*papszIter, "") &&
4334 16 : !STARTS_WITH(*papszIter, "BAND_"))
4335 : {
4336 12 : oMDMD.SetMetadata(
4337 6 : oLocalMDMD.GetMetadata(*papszIter), *papszIter);
4338 : }
4339 278 : papszIter++;
4340 : }
4341 : }
4342 173 : CPLDestroyXMLNode(psXMLNode);
4343 : }
4344 : }
4345 : }
4346 :
4347 482 : GDALPamDataset::SetMetadata(papszMetadata);
4348 482 : CSLDestroy(papszMetadata);
4349 482 : papszMetadata = nullptr;
4350 :
4351 : /* Add non-GDAL metadata now */
4352 482 : int nNonGDALMDILocal = 1;
4353 482 : int nNonGDALMDIGeopackage = 1;
4354 671 : for (int i = 0; i < oResult->RowCount(); i++)
4355 : {
4356 189 : const char *pszMetadata = oResult->GetValue(0, i);
4357 189 : const char *pszMDStandardURI = oResult->GetValue(1, i);
4358 189 : const char *pszMimeType = oResult->GetValue(2, i);
4359 189 : const char *pszReferenceScope = oResult->GetValue(3, i);
4360 189 : if (pszMetadata == nullptr || pszMDStandardURI == nullptr ||
4361 189 : pszMimeType == nullptr || pszReferenceScope == nullptr)
4362 : {
4363 : // should not happen as there are NOT NULL constraints
4364 : // But a database could lack such NOT NULL constraints or have
4365 : // large values that would cause a memory allocation failure.
4366 0 : continue;
4367 : }
4368 189 : int bIsGPKGScope = EQUAL(pszReferenceScope, "geopackage");
4369 189 : if (EQUAL(pszMDStandardURI, "http://gdal.org") &&
4370 173 : EQUAL(pszMimeType, "text/xml"))
4371 173 : continue;
4372 :
4373 16 : if (!m_osRasterTable.empty() && bIsGPKGScope)
4374 : {
4375 8 : oMDMD.SetMetadataItem(
4376 : CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDIGeopackage),
4377 : pszMetadata, "GEOPACKAGE");
4378 8 : nNonGDALMDIGeopackage++;
4379 : }
4380 : /*else if( strcmp( pszMDStandardURI, "http://www.isotc211.org/2005/gmd"
4381 : ) == 0 && strcmp( pszMimeType, "text/xml" ) == 0 )
4382 : {
4383 : char* apszMD[2];
4384 : apszMD[0] = (char*)pszMetadata;
4385 : apszMD[1] = NULL;
4386 : oMDMD.SetMetadata(apszMD, "xml:MD_Metadata");
4387 : }*/
4388 : else
4389 : {
4390 8 : oMDMD.SetMetadataItem(
4391 : CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDILocal),
4392 : pszMetadata);
4393 8 : nNonGDALMDILocal++;
4394 : }
4395 : }
4396 :
4397 482 : return GDALPamDataset::GetMetadata(pszDomain);
4398 : }
4399 :
4400 : /************************************************************************/
4401 : /* WriteMetadata() */
4402 : /************************************************************************/
4403 :
4404 666 : void GDALGeoPackageDataset::WriteMetadata(
4405 : CPLXMLNode *psXMLNode, /* will be destroyed by the method */
4406 : const char *pszTableName)
4407 : {
4408 666 : const bool bIsEmpty = (psXMLNode == nullptr);
4409 666 : if (!HasMetadataTables())
4410 : {
4411 483 : if (bIsEmpty || !CreateMetadataTables())
4412 : {
4413 220 : CPLDestroyXMLNode(psXMLNode);
4414 220 : return;
4415 : }
4416 : }
4417 :
4418 446 : char *pszXML = nullptr;
4419 446 : if (!bIsEmpty)
4420 : {
4421 : CPLXMLNode *psMasterXMLNode =
4422 305 : CPLCreateXMLNode(nullptr, CXT_Element, "GDALMultiDomainMetadata");
4423 305 : psMasterXMLNode->psChild = psXMLNode;
4424 305 : pszXML = CPLSerializeXMLTree(psMasterXMLNode);
4425 305 : CPLDestroyXMLNode(psMasterXMLNode);
4426 : }
4427 : // cppcheck-suppress uselessAssignmentPtrArg
4428 446 : psXMLNode = nullptr;
4429 :
4430 446 : char *pszSQL = nullptr;
4431 446 : if (pszTableName && pszTableName[0] != '\0')
4432 : {
4433 313 : pszSQL = sqlite3_mprintf(
4434 : "SELECT md.id FROM gpkg_metadata md "
4435 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4436 : "WHERE md.md_scope = 'dataset' AND "
4437 : "md.md_standard_uri='http://gdal.org' "
4438 : "AND md.mime_type='text/xml' AND mdr.reference_scope = 'table' AND "
4439 : "lower(mdr.table_name) = lower('%q')",
4440 : pszTableName);
4441 : }
4442 : else
4443 : {
4444 133 : pszSQL = sqlite3_mprintf(
4445 : "SELECT md.id FROM gpkg_metadata md "
4446 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4447 : "WHERE md.md_scope = 'dataset' AND "
4448 : "md.md_standard_uri='http://gdal.org' "
4449 : "AND md.mime_type='text/xml' AND mdr.reference_scope = "
4450 : "'geopackage'");
4451 : }
4452 : OGRErr err;
4453 446 : int mdId = SQLGetInteger(hDB, pszSQL, &err);
4454 446 : if (err != OGRERR_NONE)
4455 417 : mdId = -1;
4456 446 : sqlite3_free(pszSQL);
4457 :
4458 446 : if (bIsEmpty)
4459 : {
4460 141 : if (mdId >= 0)
4461 : {
4462 6 : SQLCommand(
4463 : hDB,
4464 : CPLSPrintf(
4465 : "DELETE FROM gpkg_metadata_reference WHERE md_file_id = %d",
4466 : mdId));
4467 6 : SQLCommand(
4468 : hDB,
4469 : CPLSPrintf("DELETE FROM gpkg_metadata WHERE id = %d", mdId));
4470 : }
4471 : }
4472 : else
4473 : {
4474 305 : if (mdId >= 0)
4475 : {
4476 23 : pszSQL = sqlite3_mprintf(
4477 : "UPDATE gpkg_metadata SET metadata = '%q' WHERE id = %d",
4478 : pszXML, mdId);
4479 : }
4480 : else
4481 : {
4482 : pszSQL =
4483 282 : sqlite3_mprintf("INSERT INTO gpkg_metadata (md_scope, "
4484 : "md_standard_uri, mime_type, metadata) VALUES "
4485 : "('dataset','http://gdal.org','text/xml','%q')",
4486 : pszXML);
4487 : }
4488 305 : SQLCommand(hDB, pszSQL);
4489 305 : sqlite3_free(pszSQL);
4490 :
4491 305 : CPLFree(pszXML);
4492 :
4493 305 : if (mdId < 0)
4494 : {
4495 282 : const sqlite_int64 nFID = sqlite3_last_insert_rowid(hDB);
4496 282 : if (pszTableName != nullptr && pszTableName[0] != '\0')
4497 : {
4498 270 : pszSQL = sqlite3_mprintf(
4499 : "INSERT INTO gpkg_metadata_reference (reference_scope, "
4500 : "table_name, timestamp, md_file_id) VALUES "
4501 : "('table', '%q', %s, %d)",
4502 540 : pszTableName, GetCurrentDateEscapedSQL().c_str(),
4503 : static_cast<int>(nFID));
4504 : }
4505 : else
4506 : {
4507 12 : pszSQL = sqlite3_mprintf(
4508 : "INSERT INTO gpkg_metadata_reference (reference_scope, "
4509 : "timestamp, md_file_id) VALUES "
4510 : "('geopackage', %s, %d)",
4511 24 : GetCurrentDateEscapedSQL().c_str(), static_cast<int>(nFID));
4512 : }
4513 : }
4514 : else
4515 : {
4516 23 : pszSQL = sqlite3_mprintf("UPDATE gpkg_metadata_reference SET "
4517 : "timestamp = %s WHERE md_file_id = %d",
4518 46 : GetCurrentDateEscapedSQL().c_str(), mdId);
4519 : }
4520 305 : SQLCommand(hDB, pszSQL);
4521 305 : sqlite3_free(pszSQL);
4522 : }
4523 : }
4524 :
4525 : /************************************************************************/
4526 : /* CreateMetadataTables() */
4527 : /************************************************************************/
4528 :
4529 275 : bool GDALGeoPackageDataset::CreateMetadataTables()
4530 : {
4531 : const bool bCreateTriggers =
4532 275 : CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "NO"));
4533 :
4534 : /* From C.10. gpkg_metadata Table 35. gpkg_metadata Table Definition SQL */
4535 : CPLString osSQL = "CREATE TABLE gpkg_metadata ("
4536 : "id INTEGER CONSTRAINT m_pk PRIMARY KEY ASC NOT NULL,"
4537 : "md_scope TEXT NOT NULL DEFAULT 'dataset',"
4538 : "md_standard_uri TEXT NOT NULL,"
4539 : "mime_type TEXT NOT NULL DEFAULT 'text/xml',"
4540 : "metadata TEXT NOT NULL DEFAULT ''"
4541 550 : ")";
4542 :
4543 : /* From D.2. metadata Table 40. metadata Trigger Definition SQL */
4544 275 : const char *pszMetadataTriggers =
4545 : "CREATE TRIGGER 'gpkg_metadata_md_scope_insert' "
4546 : "BEFORE INSERT ON 'gpkg_metadata' "
4547 : "FOR EACH ROW BEGIN "
4548 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata violates "
4549 : "constraint: md_scope must be one of undefined | fieldSession | "
4550 : "collectionSession | series | dataset | featureType | feature | "
4551 : "attributeType | attribute | tile | model | catalogue | schema | "
4552 : "taxonomy software | service | collectionHardware | "
4553 : "nonGeographicDataset | dimensionGroup') "
4554 : "WHERE NOT(NEW.md_scope IN "
4555 : "('undefined','fieldSession','collectionSession','series','dataset', "
4556 : "'featureType','feature','attributeType','attribute','tile','model', "
4557 : "'catalogue','schema','taxonomy','software','service', "
4558 : "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
4559 : "END; "
4560 : "CREATE TRIGGER 'gpkg_metadata_md_scope_update' "
4561 : "BEFORE UPDATE OF 'md_scope' ON 'gpkg_metadata' "
4562 : "FOR EACH ROW BEGIN "
4563 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata violates "
4564 : "constraint: md_scope must be one of undefined | fieldSession | "
4565 : "collectionSession | series | dataset | featureType | feature | "
4566 : "attributeType | attribute | tile | model | catalogue | schema | "
4567 : "taxonomy software | service | collectionHardware | "
4568 : "nonGeographicDataset | dimensionGroup') "
4569 : "WHERE NOT(NEW.md_scope IN "
4570 : "('undefined','fieldSession','collectionSession','series','dataset', "
4571 : "'featureType','feature','attributeType','attribute','tile','model', "
4572 : "'catalogue','schema','taxonomy','software','service', "
4573 : "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
4574 : "END";
4575 275 : if (bCreateTriggers)
4576 : {
4577 0 : osSQL += ";";
4578 0 : osSQL += pszMetadataTriggers;
4579 : }
4580 :
4581 : /* From C.11. gpkg_metadata_reference Table 36. gpkg_metadata_reference
4582 : * Table Definition SQL */
4583 : osSQL += ";"
4584 : "CREATE TABLE gpkg_metadata_reference ("
4585 : "reference_scope TEXT NOT NULL,"
4586 : "table_name TEXT,"
4587 : "column_name TEXT,"
4588 : "row_id_value INTEGER,"
4589 : "timestamp DATETIME NOT NULL DEFAULT "
4590 : "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
4591 : "md_file_id INTEGER NOT NULL,"
4592 : "md_parent_id INTEGER,"
4593 : "CONSTRAINT crmr_mfi_fk FOREIGN KEY (md_file_id) REFERENCES "
4594 : "gpkg_metadata(id),"
4595 : "CONSTRAINT crmr_mpi_fk FOREIGN KEY (md_parent_id) REFERENCES "
4596 : "gpkg_metadata(id)"
4597 275 : ")";
4598 :
4599 : /* From D.3. metadata_reference Table 41. gpkg_metadata_reference Trigger
4600 : * Definition SQL */
4601 275 : const char *pszMetadataReferenceTriggers =
4602 : "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_insert' "
4603 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4604 : "FOR EACH ROW BEGIN "
4605 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4606 : "violates constraint: reference_scope must be one of \"geopackage\", "
4607 : "table\", \"column\", \"row\", \"row/col\"') "
4608 : "WHERE NOT NEW.reference_scope IN "
4609 : "('geopackage','table','column','row','row/col'); "
4610 : "END; "
4611 : "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_update' "
4612 : "BEFORE UPDATE OF 'reference_scope' ON 'gpkg_metadata_reference' "
4613 : "FOR EACH ROW BEGIN "
4614 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4615 : "violates constraint: reference_scope must be one of \"geopackage\", "
4616 : "\"table\", \"column\", \"row\", \"row/col\"') "
4617 : "WHERE NOT NEW.reference_scope IN "
4618 : "('geopackage','table','column','row','row/col'); "
4619 : "END; "
4620 : "CREATE TRIGGER 'gpkg_metadata_reference_column_name_insert' "
4621 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4622 : "FOR EACH ROW BEGIN "
4623 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4624 : "violates constraint: column name must be NULL when reference_scope "
4625 : "is \"geopackage\", \"table\" or \"row\"') "
4626 : "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
4627 : "AND NEW.column_name IS NOT NULL); "
4628 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4629 : "violates constraint: column name must be defined for the specified "
4630 : "table when reference_scope is \"column\" or \"row/col\"') "
4631 : "WHERE (NEW.reference_scope IN ('column','row/col') "
4632 : "AND NOT NEW.table_name IN ( "
4633 : "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
4634 : "AND name = NEW.table_name "
4635 : "AND sql LIKE ('%' || NEW.column_name || '%'))); "
4636 : "END; "
4637 : "CREATE TRIGGER 'gpkg_metadata_reference_column_name_update' "
4638 : "BEFORE UPDATE OF column_name ON 'gpkg_metadata_reference' "
4639 : "FOR EACH ROW BEGIN "
4640 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4641 : "violates constraint: column name must be NULL when reference_scope "
4642 : "is \"geopackage\", \"table\" or \"row\"') "
4643 : "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
4644 : "AND NEW.column_name IS NOT NULL); "
4645 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4646 : "violates constraint: column name must be defined for the specified "
4647 : "table when reference_scope is \"column\" or \"row/col\"') "
4648 : "WHERE (NEW.reference_scope IN ('column','row/col') "
4649 : "AND NOT NEW.table_name IN ( "
4650 : "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
4651 : "AND name = NEW.table_name "
4652 : "AND sql LIKE ('%' || NEW.column_name || '%'))); "
4653 : "END; "
4654 : "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_insert' "
4655 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4656 : "FOR EACH ROW BEGIN "
4657 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4658 : "violates constraint: row_id_value must be NULL when reference_scope "
4659 : "is \"geopackage\", \"table\" or \"column\"') "
4660 : "WHERE NEW.reference_scope IN ('geopackage','table','column') "
4661 : "AND NEW.row_id_value IS NOT NULL; "
4662 : "END; "
4663 : "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_update' "
4664 : "BEFORE UPDATE OF 'row_id_value' ON 'gpkg_metadata_reference' "
4665 : "FOR EACH ROW BEGIN "
4666 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4667 : "violates constraint: row_id_value must be NULL when reference_scope "
4668 : "is \"geopackage\", \"table\" or \"column\"') "
4669 : "WHERE NEW.reference_scope IN ('geopackage','table','column') "
4670 : "AND NEW.row_id_value IS NOT NULL; "
4671 : "END; "
4672 : "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_insert' "
4673 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4674 : "FOR EACH ROW BEGIN "
4675 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4676 : "violates constraint: timestamp must be a valid time in ISO 8601 "
4677 : "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
4678 : "WHERE NOT (NEW.timestamp GLOB "
4679 : "'[1-2][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9]T[0-2][0-9]:[0-5][0-9]:[0-"
4680 : "5][0-9].[0-9][0-9][0-9]Z' "
4681 : "AND strftime('%s',NEW.timestamp) NOT NULL); "
4682 : "END; "
4683 : "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_update' "
4684 : "BEFORE UPDATE OF 'timestamp' ON 'gpkg_metadata_reference' "
4685 : "FOR EACH ROW BEGIN "
4686 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4687 : "violates constraint: timestamp must be a valid time in ISO 8601 "
4688 : "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
4689 : "WHERE NOT (NEW.timestamp GLOB "
4690 : "'[1-2][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9]T[0-2][0-9]:[0-5][0-9]:[0-"
4691 : "5][0-9].[0-9][0-9][0-9]Z' "
4692 : "AND strftime('%s',NEW.timestamp) NOT NULL); "
4693 : "END";
4694 275 : if (bCreateTriggers)
4695 : {
4696 0 : osSQL += ";";
4697 0 : osSQL += pszMetadataReferenceTriggers;
4698 : }
4699 :
4700 275 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
4701 0 : return false;
4702 :
4703 275 : osSQL += ";";
4704 : osSQL += "INSERT INTO gpkg_extensions "
4705 : "(table_name, column_name, extension_name, definition, scope) "
4706 : "VALUES "
4707 : "('gpkg_metadata', NULL, 'gpkg_metadata', "
4708 : "'http://www.geopackage.org/spec120/#extension_metadata', "
4709 275 : "'read-write')";
4710 :
4711 275 : osSQL += ";";
4712 : osSQL += "INSERT INTO gpkg_extensions "
4713 : "(table_name, column_name, extension_name, definition, scope) "
4714 : "VALUES "
4715 : "('gpkg_metadata_reference', NULL, 'gpkg_metadata', "
4716 : "'http://www.geopackage.org/spec120/#extension_metadata', "
4717 275 : "'read-write')";
4718 :
4719 275 : const bool bOK = SQLCommand(hDB, osSQL) == OGRERR_NONE;
4720 275 : m_nHasMetadataTables = bOK;
4721 275 : return bOK;
4722 : }
4723 :
4724 : /************************************************************************/
4725 : /* FlushMetadata() */
4726 : /************************************************************************/
4727 :
4728 7983 : void GDALGeoPackageDataset::FlushMetadata()
4729 : {
4730 7983 : if (!m_bMetadataDirty || m_poParentDS != nullptr ||
4731 335 : m_nCreateMetadataTables == FALSE)
4732 7654 : return;
4733 329 : m_bMetadataDirty = false;
4734 :
4735 329 : if (eAccess == GA_ReadOnly)
4736 : {
4737 3 : return;
4738 : }
4739 :
4740 326 : bool bCanWriteAreaOrPoint =
4741 650 : !m_bGridCellEncodingAsCO &&
4742 324 : (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT);
4743 326 : if (!m_osRasterTable.empty())
4744 : {
4745 : const char *pszIdentifier =
4746 131 : GDALGeoPackageDataset::GetMetadataItem("IDENTIFIER");
4747 : const char *pszDescription =
4748 131 : GDALGeoPackageDataset::GetMetadataItem("DESCRIPTION");
4749 159 : if (!m_bIdentifierAsCO && pszIdentifier != nullptr &&
4750 28 : pszIdentifier != m_osIdentifier)
4751 : {
4752 14 : m_osIdentifier = pszIdentifier;
4753 : char *pszSQL =
4754 14 : sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
4755 : "WHERE lower(table_name) = lower('%q')",
4756 : pszIdentifier, m_osRasterTable.c_str());
4757 14 : SQLCommand(hDB, pszSQL);
4758 14 : sqlite3_free(pszSQL);
4759 : }
4760 138 : if (!m_bDescriptionAsCO && pszDescription != nullptr &&
4761 7 : pszDescription != m_osDescription)
4762 : {
4763 7 : m_osDescription = pszDescription;
4764 : char *pszSQL =
4765 7 : sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
4766 : "WHERE lower(table_name) = lower('%q')",
4767 : pszDescription, m_osRasterTable.c_str());
4768 7 : SQLCommand(hDB, pszSQL);
4769 7 : sqlite3_free(pszSQL);
4770 : }
4771 131 : if (bCanWriteAreaOrPoint)
4772 : {
4773 : const char *pszAreaOrPoint =
4774 28 : GDALGeoPackageDataset::GetMetadataItem(GDALMD_AREA_OR_POINT);
4775 28 : if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_AREA))
4776 : {
4777 23 : bCanWriteAreaOrPoint = false;
4778 23 : char *pszSQL = sqlite3_mprintf(
4779 : "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
4780 : "grid_cell_encoding = 'grid-value-is-area' WHERE "
4781 : "lower(tile_matrix_set_name) = lower('%q')",
4782 : m_osRasterTable.c_str());
4783 23 : SQLCommand(hDB, pszSQL);
4784 23 : sqlite3_free(pszSQL);
4785 : }
4786 5 : else if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_POINT))
4787 : {
4788 1 : bCanWriteAreaOrPoint = false;
4789 1 : char *pszSQL = sqlite3_mprintf(
4790 : "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
4791 : "grid_cell_encoding = 'grid-value-is-center' WHERE "
4792 : "lower(tile_matrix_set_name) = lower('%q')",
4793 : m_osRasterTable.c_str());
4794 1 : SQLCommand(hDB, pszSQL);
4795 1 : sqlite3_free(pszSQL);
4796 : }
4797 : }
4798 : }
4799 :
4800 326 : char **papszMDDup = nullptr;
4801 518 : for (char **papszIter = GDALGeoPackageDataset::GetMetadata();
4802 518 : papszIter && *papszIter; ++papszIter)
4803 : {
4804 192 : if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
4805 28 : continue;
4806 164 : if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
4807 8 : continue;
4808 156 : if (STARTS_WITH_CI(*papszIter, "ZOOM_LEVEL="))
4809 14 : continue;
4810 142 : if (STARTS_WITH_CI(*papszIter, "GPKG_METADATA_ITEM_"))
4811 4 : continue;
4812 138 : if ((m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT) &&
4813 29 : !bCanWriteAreaOrPoint &&
4814 26 : STARTS_WITH_CI(*papszIter, GDALMD_AREA_OR_POINT))
4815 : {
4816 26 : continue;
4817 : }
4818 112 : papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
4819 : }
4820 :
4821 326 : CPLXMLNode *psXMLNode = nullptr;
4822 : {
4823 326 : GDALMultiDomainMetadata oLocalMDMD;
4824 326 : CSLConstList papszDomainList = oMDMD.GetDomainList();
4825 326 : CSLConstList papszIter = papszDomainList;
4826 326 : oLocalMDMD.SetMetadata(papszMDDup);
4827 627 : while (papszIter && *papszIter)
4828 : {
4829 301 : if (!EQUAL(*papszIter, "") &&
4830 146 : !EQUAL(*papszIter, "IMAGE_STRUCTURE") &&
4831 15 : !EQUAL(*papszIter, "GEOPACKAGE"))
4832 : {
4833 8 : oLocalMDMD.SetMetadata(oMDMD.GetMetadata(*papszIter),
4834 : *papszIter);
4835 : }
4836 301 : papszIter++;
4837 : }
4838 326 : if (m_nBandCountFromMetadata > 0)
4839 : {
4840 66 : oLocalMDMD.SetMetadataItem(
4841 : "BAND_COUNT", CPLSPrintf("%d", m_nBandCountFromMetadata),
4842 : "IMAGE_STRUCTURE");
4843 66 : if (nBands == 1)
4844 : {
4845 44 : const auto poCT = GetRasterBand(1)->GetColorTable();
4846 44 : if (poCT)
4847 : {
4848 16 : std::string osVal("{");
4849 8 : const int nColorCount = poCT->GetColorEntryCount();
4850 2056 : for (int i = 0; i < nColorCount; ++i)
4851 : {
4852 2048 : if (i > 0)
4853 2040 : osVal += ',';
4854 2048 : const GDALColorEntry *psEntry = poCT->GetColorEntry(i);
4855 : osVal +=
4856 2048 : CPLSPrintf("{%d,%d,%d,%d}", psEntry->c1,
4857 2048 : psEntry->c2, psEntry->c3, psEntry->c4);
4858 : }
4859 8 : osVal += '}';
4860 8 : oLocalMDMD.SetMetadataItem("COLOR_TABLE", osVal.c_str(),
4861 : "IMAGE_STRUCTURE");
4862 : }
4863 : }
4864 66 : if (nBands == 1)
4865 : {
4866 44 : const char *pszTILE_FORMAT = nullptr;
4867 44 : switch (m_eTF)
4868 : {
4869 0 : case GPKG_TF_PNG_JPEG:
4870 0 : pszTILE_FORMAT = "JPEG_PNG";
4871 0 : break;
4872 38 : case GPKG_TF_PNG:
4873 38 : break;
4874 0 : case GPKG_TF_PNG8:
4875 0 : pszTILE_FORMAT = "PNG8";
4876 0 : break;
4877 3 : case GPKG_TF_JPEG:
4878 3 : pszTILE_FORMAT = "JPEG";
4879 3 : break;
4880 3 : case GPKG_TF_WEBP:
4881 3 : pszTILE_FORMAT = "WEBP";
4882 3 : break;
4883 0 : case GPKG_TF_PNG_16BIT:
4884 0 : break;
4885 0 : case GPKG_TF_TIFF_32BIT_FLOAT:
4886 0 : break;
4887 : }
4888 44 : if (pszTILE_FORMAT)
4889 6 : oLocalMDMD.SetMetadataItem("TILE_FORMAT", pszTILE_FORMAT,
4890 : "IMAGE_STRUCTURE");
4891 : }
4892 : }
4893 457 : if (GetRasterCount() > 0 &&
4894 131 : GetRasterBand(1)->GetRasterDataType() == GDT_Byte)
4895 : {
4896 101 : int bHasNoData = FALSE;
4897 : const double dfNoDataValue =
4898 101 : GetRasterBand(1)->GetNoDataValue(&bHasNoData);
4899 101 : if (bHasNoData)
4900 : {
4901 3 : oLocalMDMD.SetMetadataItem("NODATA_VALUE",
4902 : CPLSPrintf("%.17g", dfNoDataValue),
4903 : "IMAGE_STRUCTURE");
4904 : }
4905 : }
4906 562 : for (int i = 1; i <= GetRasterCount(); ++i)
4907 : {
4908 : auto poBand =
4909 236 : cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
4910 236 : poBand->AddImplicitStatistics(false);
4911 236 : char **papszMD = GetRasterBand(i)->GetMetadata();
4912 236 : poBand->AddImplicitStatistics(true);
4913 236 : if (papszMD)
4914 : {
4915 12 : oLocalMDMD.SetMetadata(papszMD, CPLSPrintf("BAND_%d", i));
4916 : }
4917 : }
4918 326 : psXMLNode = oLocalMDMD.Serialize();
4919 : }
4920 :
4921 326 : CSLDestroy(papszMDDup);
4922 326 : papszMDDup = nullptr;
4923 :
4924 326 : WriteMetadata(psXMLNode, m_osRasterTable.c_str());
4925 :
4926 326 : if (!m_osRasterTable.empty())
4927 : {
4928 : char **papszGeopackageMD =
4929 131 : GDALGeoPackageDataset::GetMetadata("GEOPACKAGE");
4930 :
4931 131 : papszMDDup = nullptr;
4932 140 : for (char **papszIter = papszGeopackageMD; papszIter && *papszIter;
4933 : ++papszIter)
4934 : {
4935 9 : papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
4936 : }
4937 :
4938 262 : GDALMultiDomainMetadata oLocalMDMD;
4939 131 : oLocalMDMD.SetMetadata(papszMDDup);
4940 131 : CSLDestroy(papszMDDup);
4941 131 : papszMDDup = nullptr;
4942 131 : psXMLNode = oLocalMDMD.Serialize();
4943 :
4944 131 : WriteMetadata(psXMLNode, nullptr);
4945 : }
4946 :
4947 535 : for (int i = 0; i < m_nLayers; i++)
4948 : {
4949 : const char *pszIdentifier =
4950 209 : m_papoLayers[i]->GetMetadataItem("IDENTIFIER");
4951 : const char *pszDescription =
4952 209 : m_papoLayers[i]->GetMetadataItem("DESCRIPTION");
4953 209 : if (pszIdentifier != nullptr)
4954 : {
4955 : char *pszSQL =
4956 3 : sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
4957 : "WHERE lower(table_name) = lower('%q')",
4958 3 : pszIdentifier, m_papoLayers[i]->GetName());
4959 3 : SQLCommand(hDB, pszSQL);
4960 3 : sqlite3_free(pszSQL);
4961 : }
4962 209 : if (pszDescription != nullptr)
4963 : {
4964 : char *pszSQL =
4965 3 : sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
4966 : "WHERE lower(table_name) = lower('%q')",
4967 3 : pszDescription, m_papoLayers[i]->GetName());
4968 3 : SQLCommand(hDB, pszSQL);
4969 3 : sqlite3_free(pszSQL);
4970 : }
4971 :
4972 209 : papszMDDup = nullptr;
4973 582 : for (char **papszIter = m_papoLayers[i]->GetMetadata();
4974 582 : papszIter && *papszIter; ++papszIter)
4975 : {
4976 373 : if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
4977 3 : continue;
4978 370 : if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
4979 3 : continue;
4980 367 : if (STARTS_WITH_CI(*papszIter, "OLMD_FID64="))
4981 0 : continue;
4982 367 : papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
4983 : }
4984 :
4985 : {
4986 209 : GDALMultiDomainMetadata oLocalMDMD;
4987 209 : char **papszDomainList = m_papoLayers[i]->GetMetadataDomainList();
4988 209 : char **papszIter = papszDomainList;
4989 209 : oLocalMDMD.SetMetadata(papszMDDup);
4990 456 : while (papszIter && *papszIter)
4991 : {
4992 247 : if (!EQUAL(*papszIter, ""))
4993 102 : oLocalMDMD.SetMetadata(
4994 51 : m_papoLayers[i]->GetMetadata(*papszIter), *papszIter);
4995 247 : papszIter++;
4996 : }
4997 209 : CSLDestroy(papszDomainList);
4998 209 : psXMLNode = oLocalMDMD.Serialize();
4999 : }
5000 :
5001 209 : CSLDestroy(papszMDDup);
5002 209 : papszMDDup = nullptr;
5003 :
5004 209 : WriteMetadata(psXMLNode, m_papoLayers[i]->GetName());
5005 : }
5006 : }
5007 :
5008 : /************************************************************************/
5009 : /* GetMetadataItem() */
5010 : /************************************************************************/
5011 :
5012 1434 : const char *GDALGeoPackageDataset::GetMetadataItem(const char *pszName,
5013 : const char *pszDomain)
5014 : {
5015 1434 : pszDomain = CheckMetadataDomain(pszDomain);
5016 1434 : return CSLFetchNameValue(GetMetadata(pszDomain), pszName);
5017 : }
5018 :
5019 : /************************************************************************/
5020 : /* SetMetadata() */
5021 : /************************************************************************/
5022 :
5023 121 : CPLErr GDALGeoPackageDataset::SetMetadata(char **papszMetadata,
5024 : const char *pszDomain)
5025 : {
5026 121 : pszDomain = CheckMetadataDomain(pszDomain);
5027 121 : m_bMetadataDirty = true;
5028 121 : GetMetadata(); /* force loading from storage if needed */
5029 121 : return GDALPamDataset::SetMetadata(papszMetadata, pszDomain);
5030 : }
5031 :
5032 : /************************************************************************/
5033 : /* SetMetadataItem() */
5034 : /************************************************************************/
5035 :
5036 21 : CPLErr GDALGeoPackageDataset::SetMetadataItem(const char *pszName,
5037 : const char *pszValue,
5038 : const char *pszDomain)
5039 : {
5040 21 : pszDomain = CheckMetadataDomain(pszDomain);
5041 21 : m_bMetadataDirty = true;
5042 21 : GetMetadata(); /* force loading from storage if needed */
5043 21 : return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
5044 : }
5045 :
5046 : /************************************************************************/
5047 : /* Create() */
5048 : /************************************************************************/
5049 :
5050 806 : int GDALGeoPackageDataset::Create(const char *pszFilename, int nXSize,
5051 : int nYSize, int nBandsIn, GDALDataType eDT,
5052 : char **papszOptions)
5053 : {
5054 1612 : CPLString osCommand;
5055 :
5056 : /* First, ensure there isn't any such file yet. */
5057 : VSIStatBufL sStatBuf;
5058 :
5059 806 : if (nBandsIn != 0)
5060 : {
5061 208 : if (eDT == GDT_Byte)
5062 : {
5063 138 : if (nBandsIn != 1 && nBandsIn != 2 && nBandsIn != 3 &&
5064 : nBandsIn != 4)
5065 : {
5066 1 : CPLError(CE_Failure, CPLE_NotSupported,
5067 : "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), "
5068 : "3 (RGB) or 4 (RGBA) band dataset supported for "
5069 : "Byte datatype");
5070 1 : return FALSE;
5071 : }
5072 : }
5073 70 : else if (eDT == GDT_Int16 || eDT == GDT_UInt16 || eDT == GDT_Float32)
5074 : {
5075 43 : if (nBandsIn != 1)
5076 : {
5077 3 : CPLError(CE_Failure, CPLE_NotSupported,
5078 : "Only single band dataset supported for non Byte "
5079 : "datatype");
5080 3 : return FALSE;
5081 : }
5082 : }
5083 : else
5084 : {
5085 27 : CPLError(CE_Failure, CPLE_NotSupported,
5086 : "Only Byte, Int16, UInt16 or Float32 supported");
5087 27 : return FALSE;
5088 : }
5089 : }
5090 :
5091 775 : const size_t nFilenameLen = strlen(pszFilename);
5092 775 : const bool bGpkgZip =
5093 770 : (nFilenameLen > strlen(".gpkg.zip") &&
5094 1545 : !STARTS_WITH(pszFilename, "/vsizip/") &&
5095 770 : EQUAL(pszFilename + nFilenameLen - strlen(".gpkg.zip"), ".gpkg.zip"));
5096 :
5097 : const bool bUseTempFile =
5098 776 : bGpkgZip || (CPLTestBool(CPLGetConfigOption(
5099 1 : "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", "NO")) &&
5100 1 : (VSIHasOptimizedReadMultiRange(pszFilename) != FALSE ||
5101 1 : EQUAL(CPLGetConfigOption(
5102 : "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", ""),
5103 775 : "FORCED")));
5104 :
5105 775 : bool bFileExists = false;
5106 775 : if (VSIStatL(pszFilename, &sStatBuf) == 0)
5107 : {
5108 8 : bFileExists = true;
5109 16 : if (nBandsIn == 0 || bUseTempFile ||
5110 8 : !CPLTestBool(
5111 : CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")))
5112 : {
5113 0 : CPLError(CE_Failure, CPLE_AppDefined,
5114 : "A file system object called '%s' already exists.",
5115 : pszFilename);
5116 :
5117 0 : return FALSE;
5118 : }
5119 : }
5120 :
5121 775 : if (bUseTempFile)
5122 : {
5123 3 : if (bGpkgZip)
5124 : {
5125 2 : std::string osFilenameInZip(CPLGetFilename(pszFilename));
5126 2 : osFilenameInZip.resize(osFilenameInZip.size() - strlen(".zip"));
5127 : m_osFinalFilename =
5128 2 : std::string("/vsizip/{") + pszFilename + "}/" + osFilenameInZip;
5129 : }
5130 : else
5131 : {
5132 1 : m_osFinalFilename = pszFilename;
5133 : }
5134 3 : m_pszFilename = CPLStrdup(
5135 6 : CPLGenerateTempFilenameSafe(CPLGetFilename(pszFilename)).c_str());
5136 3 : CPLDebug("GPKG", "Creating temporary file %s", m_pszFilename);
5137 : }
5138 : else
5139 : {
5140 772 : m_pszFilename = CPLStrdup(pszFilename);
5141 : }
5142 775 : m_bNew = true;
5143 775 : eAccess = GA_Update;
5144 775 : m_bDateTimeWithTZ =
5145 775 : EQUAL(CSLFetchNameValueDef(papszOptions, "DATETIME_FORMAT", "WITH_TZ"),
5146 : "WITH_TZ");
5147 :
5148 : // for test/debug purposes only. true is the nominal value
5149 775 : m_bPNGSupports2Bands =
5150 775 : CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_2BANDS", "TRUE"));
5151 775 : m_bPNGSupportsCT =
5152 775 : CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_CT", "TRUE"));
5153 :
5154 775 : if (!OpenOrCreateDB(bFileExists
5155 : ? SQLITE_OPEN_READWRITE
5156 : : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE))
5157 4 : return FALSE;
5158 :
5159 : /* Default to synchronous=off for performance for new file */
5160 1534 : if (!bFileExists &&
5161 763 : CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
5162 : {
5163 317 : SQLCommand(hDB, "PRAGMA synchronous = OFF");
5164 : }
5165 :
5166 : /* OGR UTF-8 support. If we set the UTF-8 Pragma early on, it */
5167 : /* will be written into the main file and supported henceforth */
5168 771 : SQLCommand(hDB, "PRAGMA encoding = \"UTF-8\"");
5169 :
5170 771 : if (bFileExists)
5171 : {
5172 8 : VSILFILE *fp = VSIFOpenL(pszFilename, "rb");
5173 8 : if (fp)
5174 : {
5175 : GByte abyHeader[100];
5176 8 : VSIFReadL(abyHeader, 1, sizeof(abyHeader), fp);
5177 8 : VSIFCloseL(fp);
5178 :
5179 8 : memcpy(&m_nApplicationId, abyHeader + knApplicationIdPos, 4);
5180 8 : m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
5181 8 : memcpy(&m_nUserVersion, abyHeader + knUserVersionPos, 4);
5182 8 : m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
5183 :
5184 8 : if (m_nApplicationId == GP10_APPLICATION_ID)
5185 : {
5186 0 : CPLDebug("GPKG", "GeoPackage v1.0");
5187 : }
5188 8 : else if (m_nApplicationId == GP11_APPLICATION_ID)
5189 : {
5190 0 : CPLDebug("GPKG", "GeoPackage v1.1");
5191 : }
5192 8 : else if (m_nApplicationId == GPKG_APPLICATION_ID &&
5193 8 : m_nUserVersion >= GPKG_1_2_VERSION)
5194 : {
5195 8 : CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
5196 8 : (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
5197 : }
5198 : }
5199 :
5200 8 : DetectSpatialRefSysColumns();
5201 : }
5202 :
5203 771 : const char *pszVersion = CSLFetchNameValue(papszOptions, "VERSION");
5204 771 : if (pszVersion && !EQUAL(pszVersion, "AUTO"))
5205 : {
5206 33 : if (EQUAL(pszVersion, "1.0"))
5207 : {
5208 2 : m_nApplicationId = GP10_APPLICATION_ID;
5209 2 : m_nUserVersion = 0;
5210 : }
5211 31 : else if (EQUAL(pszVersion, "1.1"))
5212 : {
5213 1 : m_nApplicationId = GP11_APPLICATION_ID;
5214 1 : m_nUserVersion = 0;
5215 : }
5216 30 : else if (EQUAL(pszVersion, "1.2"))
5217 : {
5218 12 : m_nApplicationId = GPKG_APPLICATION_ID;
5219 12 : m_nUserVersion = GPKG_1_2_VERSION;
5220 : }
5221 18 : else if (EQUAL(pszVersion, "1.3"))
5222 : {
5223 3 : m_nApplicationId = GPKG_APPLICATION_ID;
5224 3 : m_nUserVersion = GPKG_1_3_VERSION;
5225 : }
5226 15 : else if (EQUAL(pszVersion, "1.4"))
5227 : {
5228 15 : m_nApplicationId = GPKG_APPLICATION_ID;
5229 15 : m_nUserVersion = GPKG_1_4_VERSION;
5230 : }
5231 : }
5232 :
5233 771 : SoftStartTransaction();
5234 :
5235 1542 : CPLString osSQL;
5236 771 : if (!bFileExists)
5237 : {
5238 : /* Requirement 10: A GeoPackage SHALL include a gpkg_spatial_ref_sys
5239 : * table */
5240 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5241 : osSQL = "CREATE TABLE gpkg_spatial_ref_sys ("
5242 : "srs_name TEXT NOT NULL,"
5243 : "srs_id INTEGER NOT NULL PRIMARY KEY,"
5244 : "organization TEXT NOT NULL,"
5245 : "organization_coordsys_id INTEGER NOT NULL,"
5246 : "definition TEXT NOT NULL,"
5247 763 : "description TEXT";
5248 763 : if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "CRS_WKT_EXTENSION",
5249 929 : "NO")) ||
5250 166 : (nBandsIn != 0 && eDT != GDT_Byte))
5251 : {
5252 42 : m_bHasDefinition12_063 = true;
5253 42 : osSQL += ", definition_12_063 TEXT NOT NULL";
5254 42 : if (m_nUserVersion >= GPKG_1_4_VERSION)
5255 : {
5256 1 : osSQL += ", epoch DOUBLE";
5257 1 : m_bHasEpochColumn = true;
5258 : }
5259 : }
5260 : osSQL += ")"
5261 : ";"
5262 : /* Requirement 11: The gpkg_spatial_ref_sys table in a
5263 : GeoPackage SHALL */
5264 : /* contain a record for EPSG:4326, the geodetic WGS84 SRS */
5265 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5266 :
5267 : "INSERT INTO gpkg_spatial_ref_sys ("
5268 : "srs_name, srs_id, organization, organization_coordsys_id, "
5269 763 : "definition, description";
5270 763 : if (m_bHasDefinition12_063)
5271 42 : osSQL += ", definition_12_063";
5272 : osSQL +=
5273 : ") VALUES ("
5274 : "'WGS 84 geodetic', 4326, 'EPSG', 4326, '"
5275 : "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS "
5276 : "84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],"
5277 : "AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY["
5278 : "\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY["
5279 : "\"EPSG\",\"9122\"]],AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\","
5280 : "EAST],AUTHORITY[\"EPSG\",\"4326\"]]"
5281 : "', 'longitude/latitude coordinates in decimal degrees on the WGS "
5282 763 : "84 spheroid'";
5283 763 : if (m_bHasDefinition12_063)
5284 : osSQL +=
5285 : ", 'GEODCRS[\"WGS 84\", DATUM[\"World Geodetic System 1984\", "
5286 : "ELLIPSOID[\"WGS 84\",6378137, 298.257223563, "
5287 : "LENGTHUNIT[\"metre\", 1.0]]], PRIMEM[\"Greenwich\", 0.0, "
5288 : "ANGLEUNIT[\"degree\",0.0174532925199433]], CS[ellipsoidal, "
5289 : "2], AXIS[\"latitude\", north, ORDER[1]], AXIS[\"longitude\", "
5290 : "east, ORDER[2]], ANGLEUNIT[\"degree\", 0.0174532925199433], "
5291 42 : "ID[\"EPSG\", 4326]]'";
5292 : osSQL +=
5293 : ")"
5294 : ";"
5295 : /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
5296 : SHALL */
5297 : /* contain a record with an srs_id of -1, an organization of “NONE”,
5298 : */
5299 : /* an organization_coordsys_id of -1, and definition “undefined” */
5300 : /* for undefined Cartesian coordinate reference systems */
5301 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5302 : "INSERT INTO gpkg_spatial_ref_sys ("
5303 : "srs_name, srs_id, organization, organization_coordsys_id, "
5304 763 : "definition, description";
5305 763 : if (m_bHasDefinition12_063)
5306 42 : osSQL += ", definition_12_063";
5307 : osSQL += ") VALUES ("
5308 : "'Undefined Cartesian SRS', -1, 'NONE', -1, 'undefined', "
5309 763 : "'undefined Cartesian coordinate reference system'";
5310 763 : if (m_bHasDefinition12_063)
5311 42 : osSQL += ", 'undefined'";
5312 : osSQL +=
5313 : ")"
5314 : ";"
5315 : /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
5316 : SHALL */
5317 : /* contain a record with an srs_id of 0, an organization of “NONE”,
5318 : */
5319 : /* an organization_coordsys_id of 0, and definition “undefined” */
5320 : /* for undefined geographic coordinate reference systems */
5321 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5322 : "INSERT INTO gpkg_spatial_ref_sys ("
5323 : "srs_name, srs_id, organization, organization_coordsys_id, "
5324 763 : "definition, description";
5325 763 : if (m_bHasDefinition12_063)
5326 42 : osSQL += ", definition_12_063";
5327 : osSQL += ") VALUES ("
5328 : "'Undefined geographic SRS', 0, 'NONE', 0, 'undefined', "
5329 763 : "'undefined geographic coordinate reference system'";
5330 763 : if (m_bHasDefinition12_063)
5331 42 : osSQL += ", 'undefined'";
5332 : osSQL += ")"
5333 : ";"
5334 : /* Requirement 13: A GeoPackage file SHALL include a
5335 : gpkg_contents table */
5336 : /* http://opengis.github.io/geopackage/#_contents */
5337 : "CREATE TABLE gpkg_contents ("
5338 : "table_name TEXT NOT NULL PRIMARY KEY,"
5339 : "data_type TEXT NOT NULL,"
5340 : "identifier TEXT UNIQUE,"
5341 : "description TEXT DEFAULT '',"
5342 : "last_change DATETIME NOT NULL DEFAULT "
5343 : "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
5344 : "min_x DOUBLE, min_y DOUBLE,"
5345 : "max_x DOUBLE, max_y DOUBLE,"
5346 : "srs_id INTEGER,"
5347 : "CONSTRAINT fk_gc_r_srs_id FOREIGN KEY (srs_id) REFERENCES "
5348 : "gpkg_spatial_ref_sys(srs_id)"
5349 763 : ")";
5350 :
5351 : #ifdef ENABLE_GPKG_OGR_CONTENTS
5352 763 : if (CPLFetchBool(papszOptions, "ADD_GPKG_OGR_CONTENTS", true))
5353 : {
5354 758 : m_bHasGPKGOGRContents = true;
5355 : osSQL += ";"
5356 : "CREATE TABLE gpkg_ogr_contents("
5357 : "table_name TEXT NOT NULL PRIMARY KEY,"
5358 : "feature_count INTEGER DEFAULT NULL"
5359 758 : ")";
5360 : }
5361 : #endif
5362 :
5363 : /* Requirement 21: A GeoPackage with a gpkg_contents table row with a
5364 : * “features” */
5365 : /* data_type SHALL contain a gpkg_geometry_columns table or updateable
5366 : * view */
5367 : /* http://opengis.github.io/geopackage/#_geometry_columns */
5368 : const bool bCreateGeometryColumns =
5369 763 : CPLTestBool(CPLGetConfigOption("CREATE_GEOMETRY_COLUMNS", "YES"));
5370 763 : if (bCreateGeometryColumns)
5371 : {
5372 762 : m_bHasGPKGGeometryColumns = true;
5373 762 : osSQL += ";";
5374 762 : osSQL += pszCREATE_GPKG_GEOMETRY_COLUMNS;
5375 : }
5376 : }
5377 :
5378 : const bool bCreateTriggers =
5379 771 : CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "YES"));
5380 8 : if ((bFileExists && nBandsIn != 0 &&
5381 8 : SQLGetInteger(
5382 : hDB,
5383 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_tile_matrix_set' "
5384 : "AND type in ('table', 'view')",
5385 1542 : nullptr) == 0) ||
5386 770 : (!bFileExists &&
5387 763 : CPLTestBool(CPLGetConfigOption("CREATE_RASTER_TABLES", "YES"))))
5388 : {
5389 763 : if (!osSQL.empty())
5390 762 : osSQL += ";";
5391 :
5392 : /* From C.5. gpkg_tile_matrix_set Table 28. gpkg_tile_matrix_set Table
5393 : * Creation SQL */
5394 : osSQL += "CREATE TABLE gpkg_tile_matrix_set ("
5395 : "table_name TEXT NOT NULL PRIMARY KEY,"
5396 : "srs_id INTEGER NOT NULL,"
5397 : "min_x DOUBLE NOT NULL,"
5398 : "min_y DOUBLE NOT NULL,"
5399 : "max_x DOUBLE NOT NULL,"
5400 : "max_y DOUBLE NOT NULL,"
5401 : "CONSTRAINT fk_gtms_table_name FOREIGN KEY (table_name) "
5402 : "REFERENCES gpkg_contents(table_name),"
5403 : "CONSTRAINT fk_gtms_srs FOREIGN KEY (srs_id) REFERENCES "
5404 : "gpkg_spatial_ref_sys (srs_id)"
5405 : ")"
5406 : ";"
5407 :
5408 : /* From C.6. gpkg_tile_matrix Table 29. gpkg_tile_matrix Table
5409 : Creation SQL */
5410 : "CREATE TABLE gpkg_tile_matrix ("
5411 : "table_name TEXT NOT NULL,"
5412 : "zoom_level INTEGER NOT NULL,"
5413 : "matrix_width INTEGER NOT NULL,"
5414 : "matrix_height INTEGER NOT NULL,"
5415 : "tile_width INTEGER NOT NULL,"
5416 : "tile_height INTEGER NOT NULL,"
5417 : "pixel_x_size DOUBLE NOT NULL,"
5418 : "pixel_y_size DOUBLE NOT NULL,"
5419 : "CONSTRAINT pk_ttm PRIMARY KEY (table_name, zoom_level),"
5420 : "CONSTRAINT fk_tmm_table_name FOREIGN KEY (table_name) "
5421 : "REFERENCES gpkg_contents(table_name)"
5422 763 : ")";
5423 :
5424 763 : if (bCreateTriggers)
5425 : {
5426 : /* From D.1. gpkg_tile_matrix Table 39. gpkg_tile_matrix Trigger
5427 : * Definition SQL */
5428 763 : const char *pszTileMatrixTrigger =
5429 : "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_insert' "
5430 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5431 : "FOR EACH ROW BEGIN "
5432 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5433 : "violates constraint: zoom_level cannot be less than 0') "
5434 : "WHERE (NEW.zoom_level < 0); "
5435 : "END; "
5436 : "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_update' "
5437 : "BEFORE UPDATE of zoom_level ON 'gpkg_tile_matrix' "
5438 : "FOR EACH ROW BEGIN "
5439 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5440 : "violates constraint: zoom_level cannot be less than 0') "
5441 : "WHERE (NEW.zoom_level < 0); "
5442 : "END; "
5443 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_insert' "
5444 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5445 : "FOR EACH ROW BEGIN "
5446 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5447 : "violates constraint: matrix_width cannot be less than 1') "
5448 : "WHERE (NEW.matrix_width < 1); "
5449 : "END; "
5450 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_update' "
5451 : "BEFORE UPDATE OF matrix_width ON 'gpkg_tile_matrix' "
5452 : "FOR EACH ROW BEGIN "
5453 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5454 : "violates constraint: matrix_width cannot be less than 1') "
5455 : "WHERE (NEW.matrix_width < 1); "
5456 : "END; "
5457 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_insert' "
5458 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5459 : "FOR EACH ROW BEGIN "
5460 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5461 : "violates constraint: matrix_height cannot be less than 1') "
5462 : "WHERE (NEW.matrix_height < 1); "
5463 : "END; "
5464 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_update' "
5465 : "BEFORE UPDATE OF matrix_height ON 'gpkg_tile_matrix' "
5466 : "FOR EACH ROW BEGIN "
5467 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5468 : "violates constraint: matrix_height cannot be less than 1') "
5469 : "WHERE (NEW.matrix_height < 1); "
5470 : "END; "
5471 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_insert' "
5472 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5473 : "FOR EACH ROW BEGIN "
5474 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5475 : "violates constraint: pixel_x_size must be greater than 0') "
5476 : "WHERE NOT (NEW.pixel_x_size > 0); "
5477 : "END; "
5478 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_update' "
5479 : "BEFORE UPDATE OF pixel_x_size ON 'gpkg_tile_matrix' "
5480 : "FOR EACH ROW BEGIN "
5481 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5482 : "violates constraint: pixel_x_size must be greater than 0') "
5483 : "WHERE NOT (NEW.pixel_x_size > 0); "
5484 : "END; "
5485 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_insert' "
5486 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5487 : "FOR EACH ROW BEGIN "
5488 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5489 : "violates constraint: pixel_y_size must be greater than 0') "
5490 : "WHERE NOT (NEW.pixel_y_size > 0); "
5491 : "END; "
5492 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_update' "
5493 : "BEFORE UPDATE OF pixel_y_size ON 'gpkg_tile_matrix' "
5494 : "FOR EACH ROW BEGIN "
5495 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5496 : "violates constraint: pixel_y_size must be greater than 0') "
5497 : "WHERE NOT (NEW.pixel_y_size > 0); "
5498 : "END;";
5499 763 : osSQL += ";";
5500 763 : osSQL += pszTileMatrixTrigger;
5501 : }
5502 : }
5503 :
5504 771 : if (!osSQL.empty() && OGRERR_NONE != SQLCommand(hDB, osSQL))
5505 0 : return FALSE;
5506 :
5507 771 : if (!bFileExists)
5508 : {
5509 : const char *pszMetadataTables =
5510 763 : CSLFetchNameValue(papszOptions, "METADATA_TABLES");
5511 763 : if (pszMetadataTables)
5512 6 : m_nCreateMetadataTables = int(CPLTestBool(pszMetadataTables));
5513 :
5514 763 : if (m_nCreateMetadataTables == TRUE && !CreateMetadataTables())
5515 0 : return FALSE;
5516 :
5517 763 : if (m_bHasDefinition12_063)
5518 : {
5519 84 : if (OGRERR_NONE != CreateExtensionsTableIfNecessary() ||
5520 : OGRERR_NONE !=
5521 42 : SQLCommand(hDB, "INSERT INTO gpkg_extensions "
5522 : "(table_name, column_name, extension_name, "
5523 : "definition, scope) "
5524 : "VALUES "
5525 : "('gpkg_spatial_ref_sys', "
5526 : "'definition_12_063', 'gpkg_crs_wkt', "
5527 : "'http://www.geopackage.org/spec120/"
5528 : "#extension_crs_wkt', 'read-write')"))
5529 : {
5530 0 : return FALSE;
5531 : }
5532 42 : if (m_bHasEpochColumn)
5533 : {
5534 1 : if (OGRERR_NONE !=
5535 1 : SQLCommand(
5536 : hDB, "UPDATE gpkg_extensions SET extension_name = "
5537 : "'gpkg_crs_wkt_1_1' "
5538 2 : "WHERE extension_name = 'gpkg_crs_wkt'") ||
5539 : OGRERR_NONE !=
5540 1 : SQLCommand(hDB, "INSERT INTO gpkg_extensions "
5541 : "(table_name, column_name, "
5542 : "extension_name, definition, scope) "
5543 : "VALUES "
5544 : "('gpkg_spatial_ref_sys', 'epoch', "
5545 : "'gpkg_crs_wkt_1_1', "
5546 : "'http://www.geopackage.org/spec/"
5547 : "#extension_crs_wkt', "
5548 : "'read-write')"))
5549 : {
5550 0 : return FALSE;
5551 : }
5552 : }
5553 : }
5554 : }
5555 :
5556 771 : if (nBandsIn != 0)
5557 : {
5558 174 : const std::string osTableName = CPLGetBasenameSafe(m_pszFilename);
5559 : m_osRasterTable = CSLFetchNameValueDef(papszOptions, "RASTER_TABLE",
5560 174 : osTableName.c_str());
5561 174 : if (m_osRasterTable.empty())
5562 : {
5563 0 : CPLError(CE_Failure, CPLE_AppDefined,
5564 : "RASTER_TABLE must be set to a non empty value");
5565 0 : return FALSE;
5566 : }
5567 174 : m_bIdentifierAsCO =
5568 174 : CSLFetchNameValue(papszOptions, "RASTER_IDENTIFIER") != nullptr;
5569 : m_osIdentifier = CSLFetchNameValueDef(papszOptions, "RASTER_IDENTIFIER",
5570 174 : m_osRasterTable);
5571 174 : m_bDescriptionAsCO =
5572 174 : CSLFetchNameValue(papszOptions, "RASTER_DESCRIPTION") != nullptr;
5573 : m_osDescription =
5574 174 : CSLFetchNameValueDef(papszOptions, "RASTER_DESCRIPTION", "");
5575 174 : SetDataType(eDT);
5576 174 : if (eDT == GDT_Int16)
5577 16 : SetGlobalOffsetScale(-32768.0, 1.0);
5578 :
5579 : /* From C.7. sample_tile_pyramid (Informative) Table 31. EXAMPLE: tiles
5580 : * table Create Table SQL (Informative) */
5581 : char *pszSQL =
5582 174 : sqlite3_mprintf("CREATE TABLE \"%w\" ("
5583 : "id INTEGER PRIMARY KEY AUTOINCREMENT,"
5584 : "zoom_level INTEGER NOT NULL,"
5585 : "tile_column INTEGER NOT NULL,"
5586 : "tile_row INTEGER NOT NULL,"
5587 : "tile_data BLOB NOT NULL,"
5588 : "UNIQUE (zoom_level, tile_column, tile_row)"
5589 : ")",
5590 : m_osRasterTable.c_str());
5591 174 : osSQL = pszSQL;
5592 174 : sqlite3_free(pszSQL);
5593 :
5594 174 : if (bCreateTriggers)
5595 : {
5596 174 : osSQL += ";";
5597 174 : osSQL += CreateRasterTriggersSQL(m_osRasterTable);
5598 : }
5599 :
5600 174 : OGRErr eErr = SQLCommand(hDB, osSQL);
5601 174 : if (OGRERR_NONE != eErr)
5602 0 : return FALSE;
5603 :
5604 174 : const char *pszTF = CSLFetchNameValue(papszOptions, "TILE_FORMAT");
5605 174 : if (eDT == GDT_Int16 || eDT == GDT_UInt16)
5606 : {
5607 27 : m_eTF = GPKG_TF_PNG_16BIT;
5608 27 : if (pszTF)
5609 : {
5610 1 : if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "PNG"))
5611 : {
5612 0 : CPLError(CE_Warning, CPLE_NotSupported,
5613 : "Only AUTO or PNG supported "
5614 : "as tile format for Int16 / UInt16");
5615 : }
5616 : }
5617 : }
5618 147 : else if (eDT == GDT_Float32)
5619 : {
5620 13 : m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
5621 13 : if (pszTF)
5622 : {
5623 5 : if (EQUAL(pszTF, "PNG"))
5624 5 : m_eTF = GPKG_TF_PNG_16BIT;
5625 0 : else if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "TIFF"))
5626 : {
5627 0 : CPLError(CE_Warning, CPLE_NotSupported,
5628 : "Only AUTO, PNG or TIFF supported "
5629 : "as tile format for Float32");
5630 : }
5631 : }
5632 : }
5633 : else
5634 : {
5635 134 : if (pszTF)
5636 : {
5637 71 : m_eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
5638 71 : if (nBandsIn == 1 && m_eTF != GPKG_TF_PNG)
5639 7 : m_bMetadataDirty = true;
5640 : }
5641 63 : else if (nBandsIn == 1)
5642 52 : m_eTF = GPKG_TF_PNG;
5643 : }
5644 :
5645 174 : if (eDT != GDT_Byte)
5646 : {
5647 40 : if (!CreateTileGriddedTable(papszOptions))
5648 0 : return FALSE;
5649 : }
5650 :
5651 174 : nRasterXSize = nXSize;
5652 174 : nRasterYSize = nYSize;
5653 :
5654 : const char *pszTileSize =
5655 174 : CSLFetchNameValueDef(papszOptions, "BLOCKSIZE", "256");
5656 : const char *pszTileWidth =
5657 174 : CSLFetchNameValueDef(papszOptions, "BLOCKXSIZE", pszTileSize);
5658 : const char *pszTileHeight =
5659 174 : CSLFetchNameValueDef(papszOptions, "BLOCKYSIZE", pszTileSize);
5660 174 : int nTileWidth = atoi(pszTileWidth);
5661 174 : int nTileHeight = atoi(pszTileHeight);
5662 174 : if ((nTileWidth < 8 || nTileWidth > 4096 || nTileHeight < 8 ||
5663 348 : nTileHeight > 4096) &&
5664 1 : !CPLTestBool(CPLGetConfigOption("GPKG_ALLOW_CRAZY_SETTINGS", "NO")))
5665 : {
5666 0 : CPLError(CE_Failure, CPLE_AppDefined,
5667 : "Invalid block dimensions: %dx%d", nTileWidth,
5668 : nTileHeight);
5669 0 : return FALSE;
5670 : }
5671 :
5672 481 : for (int i = 1; i <= nBandsIn; i++)
5673 307 : SetBand(
5674 307 : i, new GDALGeoPackageRasterBand(this, nTileWidth, nTileHeight));
5675 :
5676 174 : GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL",
5677 : "IMAGE_STRUCTURE");
5678 174 : GDALPamDataset::SetMetadataItem("IDENTIFIER", m_osIdentifier);
5679 174 : if (!m_osDescription.empty())
5680 1 : GDALPamDataset::SetMetadataItem("DESCRIPTION", m_osDescription);
5681 :
5682 174 : ParseCompressionOptions(papszOptions);
5683 :
5684 174 : if (m_eTF == GPKG_TF_WEBP)
5685 : {
5686 10 : if (!RegisterWebPExtension())
5687 0 : return FALSE;
5688 : }
5689 :
5690 : m_osTilingScheme =
5691 174 : CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
5692 174 : if (!EQUAL(m_osTilingScheme, "CUSTOM"))
5693 : {
5694 22 : const auto poTS = GetTilingScheme(m_osTilingScheme);
5695 22 : if (!poTS)
5696 0 : return FALSE;
5697 :
5698 43 : if (nTileWidth != poTS->nTileWidth ||
5699 21 : nTileHeight != poTS->nTileHeight)
5700 : {
5701 2 : CPLError(CE_Failure, CPLE_NotSupported,
5702 : "Tile dimension should be %dx%d for %s tiling scheme",
5703 1 : poTS->nTileWidth, poTS->nTileHeight,
5704 : m_osTilingScheme.c_str());
5705 1 : return FALSE;
5706 : }
5707 :
5708 : const char *pszZoomLevel =
5709 21 : CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
5710 21 : if (pszZoomLevel)
5711 : {
5712 1 : m_nZoomLevel = atoi(pszZoomLevel);
5713 1 : int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
5714 1 : while ((1 << nMaxZoomLevelForThisTM) >
5715 2 : INT_MAX / poTS->nTileXCountZoomLevel0 ||
5716 1 : (1 << nMaxZoomLevelForThisTM) >
5717 1 : INT_MAX / poTS->nTileYCountZoomLevel0)
5718 : {
5719 0 : --nMaxZoomLevelForThisTM;
5720 : }
5721 :
5722 1 : if (m_nZoomLevel < 0 || m_nZoomLevel > nMaxZoomLevelForThisTM)
5723 : {
5724 0 : CPLError(CE_Failure, CPLE_AppDefined,
5725 : "ZOOM_LEVEL = %s is invalid. It should be in "
5726 : "[0,%d] range",
5727 : pszZoomLevel, nMaxZoomLevelForThisTM);
5728 0 : return FALSE;
5729 : }
5730 : }
5731 :
5732 : // Implicitly sets SRS.
5733 21 : OGRSpatialReference oSRS;
5734 21 : if (oSRS.importFromEPSG(poTS->nEPSGCode) != OGRERR_NONE)
5735 0 : return FALSE;
5736 21 : char *pszWKT = nullptr;
5737 21 : oSRS.exportToWkt(&pszWKT);
5738 21 : SetProjection(pszWKT);
5739 21 : CPLFree(pszWKT);
5740 : }
5741 : else
5742 : {
5743 152 : if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
5744 : {
5745 0 : CPLError(
5746 : CE_Failure, CPLE_NotSupported,
5747 : "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
5748 0 : return false;
5749 : }
5750 : }
5751 : }
5752 :
5753 770 : if (bFileExists && nBandsIn > 0 && eDT == GDT_Byte)
5754 : {
5755 : // If there was an ogr_empty_table table, we can remove it
5756 7 : RemoveOGREmptyTable();
5757 : }
5758 :
5759 770 : SoftCommitTransaction();
5760 :
5761 : /* Requirement 2 */
5762 : /* We have to do this after there's some content so the database file */
5763 : /* is not zero length */
5764 770 : SetApplicationAndUserVersionId();
5765 :
5766 : /* Default to synchronous=off for performance for new file */
5767 1532 : if (!bFileExists &&
5768 762 : CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
5769 : {
5770 317 : SQLCommand(hDB, "PRAGMA synchronous = OFF");
5771 : }
5772 :
5773 770 : return TRUE;
5774 : }
5775 :
5776 : /************************************************************************/
5777 : /* RemoveOGREmptyTable() */
5778 : /************************************************************************/
5779 :
5780 593 : void GDALGeoPackageDataset::RemoveOGREmptyTable()
5781 : {
5782 : // Run with sqlite3_exec since we don't want errors to be emitted
5783 593 : sqlite3_exec(hDB, "DROP TABLE IF EXISTS ogr_empty_table", nullptr, nullptr,
5784 : nullptr);
5785 593 : sqlite3_exec(
5786 : hDB, "DELETE FROM gpkg_contents WHERE table_name = 'ogr_empty_table'",
5787 : nullptr, nullptr, nullptr);
5788 : #ifdef ENABLE_GPKG_OGR_CONTENTS
5789 593 : if (m_bHasGPKGOGRContents)
5790 : {
5791 581 : sqlite3_exec(hDB,
5792 : "DELETE FROM gpkg_ogr_contents WHERE "
5793 : "table_name = 'ogr_empty_table'",
5794 : nullptr, nullptr, nullptr);
5795 : }
5796 : #endif
5797 593 : sqlite3_exec(hDB,
5798 : "DELETE FROM gpkg_geometry_columns WHERE "
5799 : "table_name = 'ogr_empty_table'",
5800 : nullptr, nullptr, nullptr);
5801 593 : }
5802 :
5803 : /************************************************************************/
5804 : /* CreateTileGriddedTable() */
5805 : /************************************************************************/
5806 :
5807 40 : bool GDALGeoPackageDataset::CreateTileGriddedTable(char **papszOptions)
5808 : {
5809 80 : CPLString osSQL;
5810 40 : if (!HasGriddedCoverageAncillaryTable())
5811 : {
5812 : // It doesn't exist. So create gpkg_extensions table if necessary, and
5813 : // gpkg_2d_gridded_coverage_ancillary & gpkg_2d_gridded_tile_ancillary,
5814 : // and register them as extensions.
5815 40 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
5816 0 : return false;
5817 :
5818 : // Req 1 /table-defs/coverage-ancillary
5819 : osSQL = "CREATE TABLE gpkg_2d_gridded_coverage_ancillary ("
5820 : "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
5821 : "tile_matrix_set_name TEXT NOT NULL UNIQUE,"
5822 : "datatype TEXT NOT NULL DEFAULT 'integer',"
5823 : "scale REAL NOT NULL DEFAULT 1.0,"
5824 : "offset REAL NOT NULL DEFAULT 0.0,"
5825 : "precision REAL DEFAULT 1.0,"
5826 : "data_null REAL,"
5827 : "grid_cell_encoding TEXT DEFAULT 'grid-value-is-center',"
5828 : "uom TEXT,"
5829 : "field_name TEXT DEFAULT 'Height',"
5830 : "quantity_definition TEXT DEFAULT 'Height',"
5831 : "CONSTRAINT fk_g2dgtct_name FOREIGN KEY(tile_matrix_set_name) "
5832 : "REFERENCES gpkg_tile_matrix_set ( table_name ) "
5833 : "CHECK (datatype in ('integer','float')))"
5834 : ";"
5835 : // Requirement 2 /table-defs/tile-ancillary
5836 : "CREATE TABLE gpkg_2d_gridded_tile_ancillary ("
5837 : "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
5838 : "tpudt_name TEXT NOT NULL,"
5839 : "tpudt_id INTEGER NOT NULL,"
5840 : "scale REAL NOT NULL DEFAULT 1.0,"
5841 : "offset REAL NOT NULL DEFAULT 0.0,"
5842 : "min REAL DEFAULT NULL,"
5843 : "max REAL DEFAULT NULL,"
5844 : "mean REAL DEFAULT NULL,"
5845 : "std_dev REAL DEFAULT NULL,"
5846 : "CONSTRAINT fk_g2dgtat_name FOREIGN KEY (tpudt_name) "
5847 : "REFERENCES gpkg_contents(table_name),"
5848 : "UNIQUE (tpudt_name, tpudt_id))"
5849 : ";"
5850 : // Requirement 6 /gpkg-extensions
5851 : "INSERT INTO gpkg_extensions "
5852 : "(table_name, column_name, extension_name, definition, scope) "
5853 : "VALUES ('gpkg_2d_gridded_coverage_ancillary', NULL, "
5854 : "'gpkg_2d_gridded_coverage', "
5855 : "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5856 : "'read-write')"
5857 : ";"
5858 : // Requirement 6 /gpkg-extensions
5859 : "INSERT INTO gpkg_extensions "
5860 : "(table_name, column_name, extension_name, definition, scope) "
5861 : "VALUES ('gpkg_2d_gridded_tile_ancillary', NULL, "
5862 : "'gpkg_2d_gridded_coverage', "
5863 : "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5864 : "'read-write')"
5865 40 : ";";
5866 : }
5867 :
5868 : // Requirement 6 /gpkg-extensions
5869 40 : char *pszSQL = sqlite3_mprintf(
5870 : "INSERT INTO gpkg_extensions "
5871 : "(table_name, column_name, extension_name, definition, scope) "
5872 : "VALUES ('%q', 'tile_data', "
5873 : "'gpkg_2d_gridded_coverage', "
5874 : "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5875 : "'read-write')",
5876 : m_osRasterTable.c_str());
5877 40 : osSQL += pszSQL;
5878 40 : osSQL += ";";
5879 40 : sqlite3_free(pszSQL);
5880 :
5881 : // Requirement 7 /gpkg-2d-gridded-coverage-ancillary
5882 : // Requirement 8 /gpkg-2d-gridded-coverage-ancillary-set-name
5883 : // Requirement 9 /gpkg-2d-gridded-coverage-ancillary-datatype
5884 40 : m_dfPrecision =
5885 40 : CPLAtof(CSLFetchNameValueDef(papszOptions, "PRECISION", "1"));
5886 : CPLString osGridCellEncoding(CSLFetchNameValueDef(
5887 80 : papszOptions, "GRID_CELL_ENCODING", "grid-value-is-center"));
5888 40 : m_bGridCellEncodingAsCO =
5889 40 : CSLFetchNameValue(papszOptions, "GRID_CELL_ENCODING") != nullptr;
5890 80 : CPLString osUom(CSLFetchNameValueDef(papszOptions, "UOM", ""));
5891 : CPLString osFieldName(
5892 80 : CSLFetchNameValueDef(papszOptions, "FIELD_NAME", "Height"));
5893 : CPLString osQuantityDefinition(
5894 80 : CSLFetchNameValueDef(papszOptions, "QUANTITY_DEFINITION", "Height"));
5895 :
5896 121 : pszSQL = sqlite3_mprintf(
5897 : "INSERT INTO gpkg_2d_gridded_coverage_ancillary "
5898 : "(tile_matrix_set_name, datatype, scale, offset, precision, "
5899 : "grid_cell_encoding, uom, field_name, quantity_definition) "
5900 : "VALUES (%Q, '%s', %.17g, %.17g, %.17g, %Q, %Q, %Q, %Q)",
5901 : m_osRasterTable.c_str(),
5902 40 : (m_eTF == GPKG_TF_PNG_16BIT) ? "integer" : "float", m_dfScale,
5903 : m_dfOffset, m_dfPrecision, osGridCellEncoding.c_str(),
5904 41 : osUom.empty() ? nullptr : osUom.c_str(), osFieldName.c_str(),
5905 : osQuantityDefinition.c_str());
5906 40 : m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary = pszSQL;
5907 40 : sqlite3_free(pszSQL);
5908 :
5909 : // Requirement 3 /gpkg-spatial-ref-sys-row
5910 : auto oResultTable = SQLQuery(
5911 80 : hDB, "SELECT * FROM gpkg_spatial_ref_sys WHERE srs_id = 4979 LIMIT 2");
5912 40 : bool bHasEPSG4979 = (oResultTable && oResultTable->RowCount() == 1);
5913 40 : if (!bHasEPSG4979)
5914 : {
5915 41 : if (!m_bHasDefinition12_063 &&
5916 1 : !ConvertGpkgSpatialRefSysToExtensionWkt2(/*bForceEpoch=*/false))
5917 : {
5918 0 : return false;
5919 : }
5920 :
5921 : // This is WKT 2...
5922 40 : const char *pszWKT =
5923 : "GEODCRS[\"WGS 84\","
5924 : "DATUM[\"World Geodetic System 1984\","
5925 : " ELLIPSOID[\"WGS 84\",6378137,298.257223563,"
5926 : "LENGTHUNIT[\"metre\",1.0]]],"
5927 : "CS[ellipsoidal,3],"
5928 : " AXIS[\"latitude\",north,ORDER[1],ANGLEUNIT[\"degree\","
5929 : "0.0174532925199433]],"
5930 : " AXIS[\"longitude\",east,ORDER[2],ANGLEUNIT[\"degree\","
5931 : "0.0174532925199433]],"
5932 : " AXIS[\"ellipsoidal height\",up,ORDER[3],"
5933 : "LENGTHUNIT[\"metre\",1.0]],"
5934 : "ID[\"EPSG\",4979]]";
5935 :
5936 40 : pszSQL = sqlite3_mprintf(
5937 : "INSERT INTO gpkg_spatial_ref_sys "
5938 : "(srs_name,srs_id,organization,organization_coordsys_id,"
5939 : "definition,definition_12_063) VALUES "
5940 : "('WGS 84 3D', 4979, 'EPSG', 4979, 'undefined', '%q')",
5941 : pszWKT);
5942 40 : osSQL += ";";
5943 40 : osSQL += pszSQL;
5944 40 : sqlite3_free(pszSQL);
5945 : }
5946 :
5947 40 : return SQLCommand(hDB, osSQL) == OGRERR_NONE;
5948 : }
5949 :
5950 : /************************************************************************/
5951 : /* HasGriddedCoverageAncillaryTable() */
5952 : /************************************************************************/
5953 :
5954 44 : bool GDALGeoPackageDataset::HasGriddedCoverageAncillaryTable()
5955 : {
5956 : auto oResultTable = SQLQuery(
5957 : hDB, "SELECT * FROM sqlite_master WHERE type IN ('table', 'view') AND "
5958 44 : "name = 'gpkg_2d_gridded_coverage_ancillary'");
5959 44 : bool bHasTable = (oResultTable && oResultTable->RowCount() == 1);
5960 88 : return bHasTable;
5961 : }
5962 :
5963 : /************************************************************************/
5964 : /* GetUnderlyingDataset() */
5965 : /************************************************************************/
5966 :
5967 3 : static GDALDataset *GetUnderlyingDataset(GDALDataset *poSrcDS)
5968 : {
5969 3 : if (auto poVRTDS = dynamic_cast<VRTDataset *>(poSrcDS))
5970 : {
5971 0 : auto poTmpDS = poVRTDS->GetSingleSimpleSource();
5972 0 : if (poTmpDS)
5973 0 : return poTmpDS;
5974 : }
5975 :
5976 3 : return poSrcDS;
5977 : }
5978 :
5979 : /************************************************************************/
5980 : /* CreateCopy() */
5981 : /************************************************************************/
5982 :
5983 : typedef struct
5984 : {
5985 : const char *pszName;
5986 : GDALResampleAlg eResampleAlg;
5987 : } WarpResamplingAlg;
5988 :
5989 : static const WarpResamplingAlg asResamplingAlg[] = {
5990 : {"NEAREST", GRA_NearestNeighbour},
5991 : {"BILINEAR", GRA_Bilinear},
5992 : {"CUBIC", GRA_Cubic},
5993 : {"CUBICSPLINE", GRA_CubicSpline},
5994 : {"LANCZOS", GRA_Lanczos},
5995 : {"MODE", GRA_Mode},
5996 : {"AVERAGE", GRA_Average},
5997 : {"RMS", GRA_RMS},
5998 : };
5999 :
6000 148 : GDALDataset *GDALGeoPackageDataset::CreateCopy(const char *pszFilename,
6001 : GDALDataset *poSrcDS,
6002 : int bStrict, char **papszOptions,
6003 : GDALProgressFunc pfnProgress,
6004 : void *pProgressData)
6005 : {
6006 148 : const int nBands = poSrcDS->GetRasterCount();
6007 148 : if (nBands == 0)
6008 : {
6009 2 : GDALDataset *poDS = nullptr;
6010 : GDALDriver *poThisDriver =
6011 2 : GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
6012 2 : if (poThisDriver != nullptr)
6013 : {
6014 2 : poDS = poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS,
6015 : bStrict, papszOptions,
6016 : pfnProgress, pProgressData);
6017 : }
6018 2 : return poDS;
6019 : }
6020 :
6021 : const char *pszTilingScheme =
6022 146 : CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
6023 :
6024 292 : CPLStringList apszUpdatedOptions(CSLDuplicate(papszOptions));
6025 146 : if (CPLTestBool(
6026 152 : CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")) &&
6027 6 : CSLFetchNameValue(papszOptions, "RASTER_TABLE") == nullptr)
6028 : {
6029 : const std::string osBasename(CPLGetBasenameSafe(
6030 6 : GetUnderlyingDataset(poSrcDS)->GetDescription()));
6031 3 : apszUpdatedOptions.SetNameValue("RASTER_TABLE", osBasename.c_str());
6032 : }
6033 :
6034 146 : if (nBands != 1 && nBands != 2 && nBands != 3 && nBands != 4)
6035 : {
6036 1 : CPLError(CE_Failure, CPLE_NotSupported,
6037 : "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), 3 (RGB) or "
6038 : "4 (RGBA) band dataset supported");
6039 1 : return nullptr;
6040 : }
6041 :
6042 145 : const char *pszUnitType = poSrcDS->GetRasterBand(1)->GetUnitType();
6043 290 : if (CSLFetchNameValue(papszOptions, "UOM") == nullptr && pszUnitType &&
6044 145 : !EQUAL(pszUnitType, ""))
6045 : {
6046 1 : apszUpdatedOptions.SetNameValue("UOM", pszUnitType);
6047 : }
6048 :
6049 145 : if (EQUAL(pszTilingScheme, "CUSTOM"))
6050 : {
6051 121 : if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
6052 : {
6053 0 : CPLError(CE_Failure, CPLE_NotSupported,
6054 : "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
6055 0 : return nullptr;
6056 : }
6057 :
6058 121 : GDALGeoPackageDataset *poDS = nullptr;
6059 : GDALDriver *poThisDriver =
6060 121 : GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
6061 121 : if (poThisDriver != nullptr)
6062 : {
6063 121 : apszUpdatedOptions.SetNameValue("SKIP_HOLES", "YES");
6064 121 : poDS = cpl::down_cast<GDALGeoPackageDataset *>(
6065 : poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
6066 : apszUpdatedOptions, pfnProgress,
6067 121 : pProgressData));
6068 :
6069 225 : if (poDS != nullptr &&
6070 121 : poSrcDS->GetRasterBand(1)->GetRasterDataType() == GDT_Byte &&
6071 : nBands <= 3)
6072 : {
6073 68 : poDS->m_nBandCountFromMetadata = nBands;
6074 68 : poDS->m_bMetadataDirty = true;
6075 : }
6076 : }
6077 121 : if (poDS)
6078 104 : poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
6079 121 : return poDS;
6080 : }
6081 :
6082 48 : const auto poTS = GetTilingScheme(pszTilingScheme);
6083 24 : if (!poTS)
6084 : {
6085 2 : return nullptr;
6086 : }
6087 22 : const int nEPSGCode = poTS->nEPSGCode;
6088 :
6089 44 : OGRSpatialReference oSRS;
6090 22 : if (oSRS.importFromEPSG(nEPSGCode) != OGRERR_NONE)
6091 : {
6092 0 : return nullptr;
6093 : }
6094 22 : char *pszWKT = nullptr;
6095 22 : oSRS.exportToWkt(&pszWKT);
6096 22 : char **papszTO = CSLSetNameValue(nullptr, "DST_SRS", pszWKT);
6097 :
6098 22 : void *hTransformArg = nullptr;
6099 :
6100 : // Hack to compensate for GDALSuggestedWarpOutput2() failure (or not
6101 : // ideal suggestion with PROJ 8) when reprojecting latitude = +/- 90 to
6102 : // EPSG:3857.
6103 : double adfSrcGeoTransform[6];
6104 22 : std::unique_ptr<GDALDataset> poTmpDS;
6105 22 : bool bEPSG3857Adjust = false;
6106 30 : if (nEPSGCode == 3857 &&
6107 8 : poSrcDS->GetGeoTransform(adfSrcGeoTransform) == CE_None &&
6108 38 : adfSrcGeoTransform[2] == 0 && adfSrcGeoTransform[4] == 0 &&
6109 8 : adfSrcGeoTransform[5] < 0)
6110 : {
6111 8 : const auto poSrcSRS = poSrcDS->GetSpatialRef();
6112 8 : if (poSrcSRS && poSrcSRS->IsGeographic())
6113 : {
6114 2 : double maxLat = adfSrcGeoTransform[3];
6115 2 : double minLat = adfSrcGeoTransform[3] +
6116 2 : poSrcDS->GetRasterYSize() * adfSrcGeoTransform[5];
6117 : // Corresponds to the latitude of below MAX_GM
6118 2 : constexpr double MAX_LAT = 85.0511287798066;
6119 2 : bool bModified = false;
6120 2 : if (maxLat > MAX_LAT)
6121 : {
6122 2 : maxLat = MAX_LAT;
6123 2 : bModified = true;
6124 : }
6125 2 : if (minLat < -MAX_LAT)
6126 : {
6127 2 : minLat = -MAX_LAT;
6128 2 : bModified = true;
6129 : }
6130 2 : if (bModified)
6131 : {
6132 4 : CPLStringList aosOptions;
6133 2 : aosOptions.AddString("-of");
6134 2 : aosOptions.AddString("VRT");
6135 2 : aosOptions.AddString("-projwin");
6136 : aosOptions.AddString(
6137 2 : CPLSPrintf("%.17g", adfSrcGeoTransform[0]));
6138 2 : aosOptions.AddString(CPLSPrintf("%.17g", maxLat));
6139 : aosOptions.AddString(
6140 2 : CPLSPrintf("%.17g", adfSrcGeoTransform[0] +
6141 2 : poSrcDS->GetRasterXSize() *
6142 2 : adfSrcGeoTransform[1]));
6143 2 : aosOptions.AddString(CPLSPrintf("%.17g", minLat));
6144 : auto psOptions =
6145 2 : GDALTranslateOptionsNew(aosOptions.List(), nullptr);
6146 2 : poTmpDS.reset(GDALDataset::FromHandle(GDALTranslate(
6147 : "", GDALDataset::ToHandle(poSrcDS), psOptions, nullptr)));
6148 2 : GDALTranslateOptionsFree(psOptions);
6149 2 : if (poTmpDS)
6150 : {
6151 2 : bEPSG3857Adjust = true;
6152 2 : hTransformArg = GDALCreateGenImgProjTransformer2(
6153 2 : GDALDataset::FromHandle(poTmpDS.get()), nullptr,
6154 : papszTO);
6155 : }
6156 : }
6157 : }
6158 : }
6159 22 : if (hTransformArg == nullptr)
6160 : {
6161 : hTransformArg =
6162 20 : GDALCreateGenImgProjTransformer2(poSrcDS, nullptr, papszTO);
6163 : }
6164 :
6165 22 : if (hTransformArg == nullptr)
6166 : {
6167 1 : CPLFree(pszWKT);
6168 1 : CSLDestroy(papszTO);
6169 1 : return nullptr;
6170 : }
6171 :
6172 21 : GDALTransformerInfo *psInfo =
6173 : static_cast<GDALTransformerInfo *>(hTransformArg);
6174 : double adfGeoTransform[6];
6175 : double adfExtent[4];
6176 : int nXSize, nYSize;
6177 :
6178 21 : if (GDALSuggestedWarpOutput2(poSrcDS, psInfo->pfnTransform, hTransformArg,
6179 : adfGeoTransform, &nXSize, &nYSize, adfExtent,
6180 21 : 0) != CE_None)
6181 : {
6182 0 : CPLFree(pszWKT);
6183 0 : CSLDestroy(papszTO);
6184 0 : GDALDestroyGenImgProjTransformer(hTransformArg);
6185 0 : return nullptr;
6186 : }
6187 :
6188 21 : GDALDestroyGenImgProjTransformer(hTransformArg);
6189 21 : hTransformArg = nullptr;
6190 21 : poTmpDS.reset();
6191 :
6192 21 : if (bEPSG3857Adjust)
6193 : {
6194 2 : constexpr double SPHERICAL_RADIUS = 6378137.0;
6195 2 : constexpr double MAX_GM =
6196 : SPHERICAL_RADIUS * M_PI; // 20037508.342789244
6197 2 : double maxNorthing = adfGeoTransform[3];
6198 2 : double minNorthing = adfGeoTransform[3] + adfGeoTransform[5] * nYSize;
6199 2 : bool bChanged = false;
6200 2 : if (maxNorthing > MAX_GM)
6201 : {
6202 2 : bChanged = true;
6203 2 : maxNorthing = MAX_GM;
6204 : }
6205 2 : if (minNorthing < -MAX_GM)
6206 : {
6207 2 : bChanged = true;
6208 2 : minNorthing = -MAX_GM;
6209 : }
6210 2 : if (bChanged)
6211 : {
6212 2 : adfGeoTransform[3] = maxNorthing;
6213 2 : nYSize =
6214 2 : int((maxNorthing - minNorthing) / (-adfGeoTransform[5]) + 0.5);
6215 2 : adfExtent[1] = maxNorthing + nYSize * adfGeoTransform[5];
6216 2 : adfExtent[3] = maxNorthing;
6217 : }
6218 : }
6219 :
6220 21 : double dfComputedRes = adfGeoTransform[1];
6221 21 : double dfPrevRes = 0.0;
6222 21 : double dfRes = 0.0;
6223 21 : int nZoomLevel = 0; // Used after for.
6224 21 : const char *pszZoomLevel = CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
6225 21 : if (pszZoomLevel)
6226 : {
6227 2 : nZoomLevel = atoi(pszZoomLevel);
6228 :
6229 2 : int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
6230 2 : while ((1 << nMaxZoomLevelForThisTM) >
6231 4 : INT_MAX / poTS->nTileXCountZoomLevel0 ||
6232 2 : (1 << nMaxZoomLevelForThisTM) >
6233 2 : INT_MAX / poTS->nTileYCountZoomLevel0)
6234 : {
6235 0 : --nMaxZoomLevelForThisTM;
6236 : }
6237 :
6238 2 : if (nZoomLevel < 0 || nZoomLevel > nMaxZoomLevelForThisTM)
6239 : {
6240 1 : CPLError(CE_Failure, CPLE_AppDefined,
6241 : "ZOOM_LEVEL = %s is invalid. It should be in [0,%d] range",
6242 : pszZoomLevel, nMaxZoomLevelForThisTM);
6243 1 : CPLFree(pszWKT);
6244 1 : CSLDestroy(papszTO);
6245 1 : return nullptr;
6246 : }
6247 : }
6248 : else
6249 : {
6250 171 : for (; nZoomLevel < MAX_ZOOM_LEVEL; nZoomLevel++)
6251 : {
6252 171 : dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
6253 171 : if (dfComputedRes > dfRes ||
6254 152 : fabs(dfComputedRes - dfRes) / dfRes <= 1e-8)
6255 : break;
6256 152 : dfPrevRes = dfRes;
6257 : }
6258 38 : if (nZoomLevel == MAX_ZOOM_LEVEL ||
6259 38 : (1 << nZoomLevel) > INT_MAX / poTS->nTileXCountZoomLevel0 ||
6260 19 : (1 << nZoomLevel) > INT_MAX / poTS->nTileYCountZoomLevel0)
6261 : {
6262 0 : CPLError(CE_Failure, CPLE_AppDefined,
6263 : "Could not find an appropriate zoom level");
6264 0 : CPLFree(pszWKT);
6265 0 : CSLDestroy(papszTO);
6266 0 : return nullptr;
6267 : }
6268 :
6269 19 : if (nZoomLevel > 0 && fabs(dfComputedRes - dfRes) / dfRes > 1e-8)
6270 : {
6271 17 : const char *pszZoomLevelStrategy = CSLFetchNameValueDef(
6272 : papszOptions, "ZOOM_LEVEL_STRATEGY", "AUTO");
6273 17 : if (EQUAL(pszZoomLevelStrategy, "LOWER"))
6274 : {
6275 1 : nZoomLevel--;
6276 : }
6277 16 : else if (EQUAL(pszZoomLevelStrategy, "UPPER"))
6278 : {
6279 : /* do nothing */
6280 : }
6281 : else
6282 : {
6283 15 : if (dfPrevRes / dfComputedRes < dfComputedRes / dfRes)
6284 13 : nZoomLevel--;
6285 : }
6286 : }
6287 : }
6288 :
6289 20 : dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
6290 :
6291 20 : double dfMinX = adfExtent[0];
6292 20 : double dfMinY = adfExtent[1];
6293 20 : double dfMaxX = adfExtent[2];
6294 20 : double dfMaxY = adfExtent[3];
6295 :
6296 20 : nXSize = static_cast<int>(0.5 + (dfMaxX - dfMinX) / dfRes);
6297 20 : nYSize = static_cast<int>(0.5 + (dfMaxY - dfMinY) / dfRes);
6298 20 : adfGeoTransform[1] = dfRes;
6299 20 : adfGeoTransform[5] = -dfRes;
6300 :
6301 20 : const GDALDataType eDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
6302 20 : int nTargetBands = nBands;
6303 : /* For grey level or RGB, if there's reprojection involved, add an alpha */
6304 : /* channel */
6305 37 : if (eDT == GDT_Byte &&
6306 13 : ((nBands == 1 &&
6307 17 : poSrcDS->GetRasterBand(1)->GetColorTable() == nullptr) ||
6308 : nBands == 3))
6309 : {
6310 30 : OGRSpatialReference oSrcSRS;
6311 15 : oSrcSRS.SetFromUserInput(poSrcDS->GetProjectionRef());
6312 15 : oSrcSRS.AutoIdentifyEPSG();
6313 30 : if (oSrcSRS.GetAuthorityCode(nullptr) == nullptr ||
6314 15 : atoi(oSrcSRS.GetAuthorityCode(nullptr)) != nEPSGCode)
6315 : {
6316 13 : nTargetBands++;
6317 : }
6318 : }
6319 :
6320 20 : GDALResampleAlg eResampleAlg = GRA_Bilinear;
6321 20 : const char *pszResampling = CSLFetchNameValue(papszOptions, "RESAMPLING");
6322 20 : if (pszResampling)
6323 : {
6324 6 : for (size_t iAlg = 0;
6325 6 : iAlg < sizeof(asResamplingAlg) / sizeof(asResamplingAlg[0]);
6326 : iAlg++)
6327 : {
6328 6 : if (EQUAL(pszResampling, asResamplingAlg[iAlg].pszName))
6329 : {
6330 3 : eResampleAlg = asResamplingAlg[iAlg].eResampleAlg;
6331 3 : break;
6332 : }
6333 : }
6334 : }
6335 :
6336 16 : if (nBands == 1 && poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr &&
6337 36 : eResampleAlg != GRA_NearestNeighbour && eResampleAlg != GRA_Mode)
6338 : {
6339 0 : CPLError(
6340 : CE_Warning, CPLE_AppDefined,
6341 : "Input dataset has a color table, which will likely lead to "
6342 : "bad results when using a resampling method other than "
6343 : "nearest neighbour or mode. Converting the dataset to 24/32 bit "
6344 : "(e.g. with gdal_translate -expand rgb/rgba) is advised.");
6345 : }
6346 :
6347 20 : GDALGeoPackageDataset *poDS = new GDALGeoPackageDataset();
6348 20 : if (!(poDS->Create(pszFilename, nXSize, nYSize, nTargetBands, eDT,
6349 : apszUpdatedOptions)))
6350 : {
6351 1 : delete poDS;
6352 1 : CPLFree(pszWKT);
6353 1 : CSLDestroy(papszTO);
6354 1 : return nullptr;
6355 : }
6356 :
6357 : // Assign nodata values before the SetGeoTransform call.
6358 : // SetGeoTransform will trigger creation of the overview datasets for each
6359 : // zoom level and at that point the nodata value needs to be known.
6360 19 : int bHasNoData = FALSE;
6361 : double dfNoDataValue =
6362 19 : poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
6363 19 : if (eDT != GDT_Byte && bHasNoData)
6364 : {
6365 3 : poDS->GetRasterBand(1)->SetNoDataValue(dfNoDataValue);
6366 : }
6367 :
6368 19 : poDS->SetGeoTransform(adfGeoTransform);
6369 19 : poDS->SetProjection(pszWKT);
6370 19 : CPLFree(pszWKT);
6371 19 : pszWKT = nullptr;
6372 24 : if (nTargetBands == 1 && nBands == 1 &&
6373 5 : poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr)
6374 : {
6375 2 : poDS->GetRasterBand(1)->SetColorTable(
6376 1 : poSrcDS->GetRasterBand(1)->GetColorTable());
6377 : }
6378 :
6379 19 : hTransformArg = GDALCreateGenImgProjTransformer2(poSrcDS, poDS, papszTO);
6380 19 : CSLDestroy(papszTO);
6381 19 : if (hTransformArg == nullptr)
6382 : {
6383 0 : delete poDS;
6384 0 : return nullptr;
6385 : }
6386 :
6387 19 : poDS->SetMetadata(poSrcDS->GetMetadata());
6388 :
6389 : /* -------------------------------------------------------------------- */
6390 : /* Warp the transformer with a linear approximator */
6391 : /* -------------------------------------------------------------------- */
6392 19 : hTransformArg = GDALCreateApproxTransformer(GDALGenImgProjTransform,
6393 : hTransformArg, 0.125);
6394 19 : GDALApproxTransformerOwnsSubtransformer(hTransformArg, TRUE);
6395 :
6396 : /* -------------------------------------------------------------------- */
6397 : /* Setup warp options. */
6398 : /* -------------------------------------------------------------------- */
6399 19 : GDALWarpOptions *psWO = GDALCreateWarpOptions();
6400 :
6401 19 : psWO->papszWarpOptions = CSLSetNameValue(nullptr, "OPTIMIZE_SIZE", "YES");
6402 19 : psWO->papszWarpOptions =
6403 19 : CSLSetNameValue(psWO->papszWarpOptions, "SAMPLE_GRID", "YES");
6404 19 : if (bHasNoData)
6405 : {
6406 3 : if (dfNoDataValue == 0.0)
6407 : {
6408 : // Do not initialize in the case where nodata != 0, since we
6409 : // want the GeoPackage driver to return empty tiles at the nodata
6410 : // value instead of 0 as GDAL core would
6411 0 : psWO->papszWarpOptions =
6412 0 : CSLSetNameValue(psWO->papszWarpOptions, "INIT_DEST", "0");
6413 : }
6414 :
6415 3 : psWO->padfSrcNoDataReal =
6416 3 : static_cast<double *>(CPLMalloc(sizeof(double)));
6417 3 : psWO->padfSrcNoDataReal[0] = dfNoDataValue;
6418 :
6419 3 : psWO->padfDstNoDataReal =
6420 3 : static_cast<double *>(CPLMalloc(sizeof(double)));
6421 3 : psWO->padfDstNoDataReal[0] = dfNoDataValue;
6422 : }
6423 19 : psWO->eWorkingDataType = eDT;
6424 19 : psWO->eResampleAlg = eResampleAlg;
6425 :
6426 19 : psWO->hSrcDS = poSrcDS;
6427 19 : psWO->hDstDS = poDS;
6428 :
6429 19 : psWO->pfnTransformer = GDALApproxTransform;
6430 19 : psWO->pTransformerArg = hTransformArg;
6431 :
6432 19 : psWO->pfnProgress = pfnProgress;
6433 19 : psWO->pProgressArg = pProgressData;
6434 :
6435 : /* -------------------------------------------------------------------- */
6436 : /* Setup band mapping. */
6437 : /* -------------------------------------------------------------------- */
6438 :
6439 19 : if (nBands == 2 || nBands == 4)
6440 1 : psWO->nBandCount = nBands - 1;
6441 : else
6442 18 : psWO->nBandCount = nBands;
6443 :
6444 19 : psWO->panSrcBands =
6445 19 : static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
6446 19 : psWO->panDstBands =
6447 19 : static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
6448 :
6449 46 : for (int i = 0; i < psWO->nBandCount; i++)
6450 : {
6451 27 : psWO->panSrcBands[i] = i + 1;
6452 27 : psWO->panDstBands[i] = i + 1;
6453 : }
6454 :
6455 19 : if (nBands == 2 || nBands == 4)
6456 : {
6457 1 : psWO->nSrcAlphaBand = nBands;
6458 : }
6459 19 : if (nTargetBands == 2 || nTargetBands == 4)
6460 : {
6461 13 : psWO->nDstAlphaBand = nTargetBands;
6462 : }
6463 :
6464 : /* -------------------------------------------------------------------- */
6465 : /* Initialize and execute the warp. */
6466 : /* -------------------------------------------------------------------- */
6467 19 : GDALWarpOperation oWO;
6468 :
6469 19 : CPLErr eErr = oWO.Initialize(psWO);
6470 19 : if (eErr == CE_None)
6471 : {
6472 : /*if( bMulti )
6473 : eErr = oWO.ChunkAndWarpMulti( 0, 0, nXSize, nYSize );
6474 : else*/
6475 19 : eErr = oWO.ChunkAndWarpImage(0, 0, nXSize, nYSize);
6476 : }
6477 19 : if (eErr != CE_None)
6478 : {
6479 0 : delete poDS;
6480 0 : poDS = nullptr;
6481 : }
6482 :
6483 19 : GDALDestroyTransformer(hTransformArg);
6484 19 : GDALDestroyWarpOptions(psWO);
6485 :
6486 19 : if (poDS)
6487 19 : poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
6488 :
6489 19 : return poDS;
6490 : }
6491 :
6492 : /************************************************************************/
6493 : /* ParseCompressionOptions() */
6494 : /************************************************************************/
6495 :
6496 440 : void GDALGeoPackageDataset::ParseCompressionOptions(char **papszOptions)
6497 : {
6498 440 : const char *pszZLevel = CSLFetchNameValue(papszOptions, "ZLEVEL");
6499 440 : if (pszZLevel)
6500 0 : m_nZLevel = atoi(pszZLevel);
6501 :
6502 440 : const char *pszQuality = CSLFetchNameValue(papszOptions, "QUALITY");
6503 440 : if (pszQuality)
6504 0 : m_nQuality = atoi(pszQuality);
6505 :
6506 440 : const char *pszDither = CSLFetchNameValue(papszOptions, "DITHER");
6507 440 : if (pszDither)
6508 0 : m_bDither = CPLTestBool(pszDither);
6509 440 : }
6510 :
6511 : /************************************************************************/
6512 : /* RegisterWebPExtension() */
6513 : /************************************************************************/
6514 :
6515 11 : bool GDALGeoPackageDataset::RegisterWebPExtension()
6516 : {
6517 11 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
6518 0 : return false;
6519 :
6520 11 : char *pszSQL = sqlite3_mprintf(
6521 : "INSERT INTO gpkg_extensions "
6522 : "(table_name, column_name, extension_name, definition, scope) "
6523 : "VALUES "
6524 : "('%q', 'tile_data', 'gpkg_webp', "
6525 : "'http://www.geopackage.org/spec120/#extension_tiles_webp', "
6526 : "'read-write')",
6527 : m_osRasterTable.c_str());
6528 11 : const OGRErr eErr = SQLCommand(hDB, pszSQL);
6529 11 : sqlite3_free(pszSQL);
6530 :
6531 11 : return OGRERR_NONE == eErr;
6532 : }
6533 :
6534 : /************************************************************************/
6535 : /* RegisterZoomOtherExtension() */
6536 : /************************************************************************/
6537 :
6538 1 : bool GDALGeoPackageDataset::RegisterZoomOtherExtension()
6539 : {
6540 1 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
6541 0 : return false;
6542 :
6543 1 : char *pszSQL = sqlite3_mprintf(
6544 : "INSERT INTO gpkg_extensions "
6545 : "(table_name, column_name, extension_name, definition, scope) "
6546 : "VALUES "
6547 : "('%q', 'tile_data', 'gpkg_zoom_other', "
6548 : "'http://www.geopackage.org/spec120/#extension_zoom_other_intervals', "
6549 : "'read-write')",
6550 : m_osRasterTable.c_str());
6551 1 : const OGRErr eErr = SQLCommand(hDB, pszSQL);
6552 1 : sqlite3_free(pszSQL);
6553 1 : return OGRERR_NONE == eErr;
6554 : }
6555 :
6556 : /************************************************************************/
6557 : /* GetLayer() */
6558 : /************************************************************************/
6559 :
6560 13923 : OGRLayer *GDALGeoPackageDataset::GetLayer(int iLayer)
6561 :
6562 : {
6563 13923 : if (iLayer < 0 || iLayer >= m_nLayers)
6564 6 : return nullptr;
6565 : else
6566 13917 : return m_papoLayers[iLayer];
6567 : }
6568 :
6569 : /************************************************************************/
6570 : /* LaunderName() */
6571 : /************************************************************************/
6572 :
6573 : /** Launder identifiers (table, column names) according to guidance at
6574 : * https://www.geopackage.org/guidance/getting-started.html:
6575 : * "For maximum interoperability, start your database identifiers (table names,
6576 : * column names, etc.) with a lowercase character and only use lowercase
6577 : * characters, numbers 0-9, and underscores (_)."
6578 : */
6579 :
6580 : /* static */
6581 5 : std::string GDALGeoPackageDataset::LaunderName(const std::string &osStr)
6582 : {
6583 5 : char *pszASCII = CPLUTF8ForceToASCII(osStr.c_str(), '_');
6584 10 : const std::string osStrASCII(pszASCII);
6585 5 : CPLFree(pszASCII);
6586 :
6587 10 : std::string osRet;
6588 5 : osRet.reserve(osStrASCII.size());
6589 :
6590 29 : for (size_t i = 0; i < osStrASCII.size(); ++i)
6591 : {
6592 24 : if (osRet.empty())
6593 : {
6594 5 : if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
6595 : {
6596 2 : osRet += (osStrASCII[i] - 'A' + 'a');
6597 : }
6598 3 : else if (osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z')
6599 : {
6600 2 : osRet += osStrASCII[i];
6601 : }
6602 : else
6603 : {
6604 1 : continue;
6605 : }
6606 : }
6607 19 : else if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
6608 : {
6609 11 : osRet += (osStrASCII[i] - 'A' + 'a');
6610 : }
6611 9 : else if ((osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z') ||
6612 14 : (osStrASCII[i] >= '0' && osStrASCII[i] <= '9') ||
6613 5 : osStrASCII[i] == '_')
6614 : {
6615 7 : osRet += osStrASCII[i];
6616 : }
6617 : else
6618 : {
6619 1 : osRet += '_';
6620 : }
6621 : }
6622 :
6623 5 : if (osRet.empty() && !osStrASCII.empty())
6624 2 : return LaunderName(std::string("x").append(osStrASCII));
6625 :
6626 4 : if (osRet != osStr)
6627 : {
6628 3 : CPLDebug("PG", "LaunderName('%s') -> '%s'", osStr.c_str(),
6629 : osRet.c_str());
6630 : }
6631 :
6632 4 : return osRet;
6633 : }
6634 :
6635 : /************************************************************************/
6636 : /* ICreateLayer() */
6637 : /************************************************************************/
6638 :
6639 : OGRLayer *
6640 685 : GDALGeoPackageDataset::ICreateLayer(const char *pszLayerName,
6641 : const OGRGeomFieldDefn *poSrcGeomFieldDefn,
6642 : CSLConstList papszOptions)
6643 : {
6644 : /* -------------------------------------------------------------------- */
6645 : /* Verify we are in update mode. */
6646 : /* -------------------------------------------------------------------- */
6647 685 : if (!GetUpdate())
6648 : {
6649 0 : CPLError(CE_Failure, CPLE_NoWriteAccess,
6650 : "Data source %s opened read-only.\n"
6651 : "New layer %s cannot be created.\n",
6652 : m_pszFilename, pszLayerName);
6653 :
6654 0 : return nullptr;
6655 : }
6656 :
6657 : const bool bLaunder =
6658 685 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "LAUNDER", "NO"));
6659 : const std::string osTableName(bLaunder ? LaunderName(pszLayerName)
6660 2055 : : std::string(pszLayerName));
6661 :
6662 : const auto eGType =
6663 685 : poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone;
6664 : const auto poSpatialRef =
6665 685 : poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr;
6666 :
6667 685 : if (!m_bHasGPKGGeometryColumns)
6668 : {
6669 1 : if (SQLCommand(hDB, pszCREATE_GPKG_GEOMETRY_COLUMNS) != OGRERR_NONE)
6670 : {
6671 0 : return nullptr;
6672 : }
6673 1 : m_bHasGPKGGeometryColumns = true;
6674 : }
6675 :
6676 : // Check identifier unicity
6677 685 : const char *pszIdentifier = CSLFetchNameValue(papszOptions, "IDENTIFIER");
6678 685 : if (pszIdentifier != nullptr && pszIdentifier[0] == '\0')
6679 0 : pszIdentifier = nullptr;
6680 685 : if (pszIdentifier != nullptr)
6681 : {
6682 13 : for (int i = 0; i < m_nLayers; ++i)
6683 : {
6684 : const char *pszOtherIdentifier =
6685 9 : m_papoLayers[i]->GetMetadataItem("IDENTIFIER");
6686 9 : if (pszOtherIdentifier == nullptr)
6687 6 : pszOtherIdentifier = m_papoLayers[i]->GetName();
6688 18 : if (pszOtherIdentifier != nullptr &&
6689 12 : EQUAL(pszOtherIdentifier, pszIdentifier) &&
6690 3 : !EQUAL(m_papoLayers[i]->GetName(), osTableName.c_str()))
6691 : {
6692 2 : CPLError(CE_Failure, CPLE_AppDefined,
6693 : "Identifier %s is already used by table %s",
6694 2 : pszIdentifier, m_papoLayers[i]->GetName());
6695 3 : return nullptr;
6696 : }
6697 : }
6698 :
6699 : // In case there would be table in gpkg_contents not listed as a
6700 : // vector layer
6701 4 : char *pszSQL = sqlite3_mprintf(
6702 : "SELECT table_name FROM gpkg_contents WHERE identifier = '%q' "
6703 : "LIMIT 2",
6704 : pszIdentifier);
6705 4 : auto oResult = SQLQuery(hDB, pszSQL);
6706 4 : sqlite3_free(pszSQL);
6707 8 : if (oResult && oResult->RowCount() > 0 &&
6708 9 : oResult->GetValue(0, 0) != nullptr &&
6709 1 : !EQUAL(oResult->GetValue(0, 0), osTableName.c_str()))
6710 : {
6711 1 : CPLError(CE_Failure, CPLE_AppDefined,
6712 : "Identifier %s is already used by table %s", pszIdentifier,
6713 : oResult->GetValue(0, 0));
6714 1 : return nullptr;
6715 : }
6716 : }
6717 :
6718 : /* Read GEOMETRY_NAME option */
6719 : const char *pszGeomColumnName =
6720 682 : CSLFetchNameValue(papszOptions, "GEOMETRY_NAME");
6721 682 : if (pszGeomColumnName == nullptr) /* deprecated name */
6722 637 : pszGeomColumnName = CSLFetchNameValue(papszOptions, "GEOMETRY_COLUMN");
6723 682 : if (pszGeomColumnName == nullptr && poSrcGeomFieldDefn)
6724 : {
6725 585 : pszGeomColumnName = poSrcGeomFieldDefn->GetNameRef();
6726 585 : if (pszGeomColumnName && pszGeomColumnName[0] == 0)
6727 581 : pszGeomColumnName = nullptr;
6728 : }
6729 682 : if (pszGeomColumnName == nullptr)
6730 633 : pszGeomColumnName = "geom";
6731 : const bool bGeomNullable =
6732 682 : CPLFetchBool(papszOptions, "GEOMETRY_NULLABLE", true);
6733 :
6734 : /* Read FID option */
6735 682 : const char *pszFIDColumnName = CSLFetchNameValue(papszOptions, "FID");
6736 682 : if (pszFIDColumnName == nullptr)
6737 648 : pszFIDColumnName = "fid";
6738 :
6739 682 : if (CPLTestBool(CPLGetConfigOption("GPKG_NAME_CHECK", "YES")))
6740 : {
6741 682 : if (strspn(pszFIDColumnName, "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") > 0)
6742 : {
6743 0 : CPLError(CE_Failure, CPLE_AppDefined,
6744 : "The primary key (%s) name may not contain special "
6745 : "characters or spaces",
6746 : pszFIDColumnName);
6747 0 : return nullptr;
6748 : }
6749 :
6750 : /* Avoiding gpkg prefixes is not an official requirement, but seems wise
6751 : */
6752 682 : if (STARTS_WITH(osTableName.c_str(), "gpkg"))
6753 : {
6754 0 : CPLError(CE_Failure, CPLE_AppDefined,
6755 : "The layer name may not begin with 'gpkg' as it is a "
6756 : "reserved geopackage prefix");
6757 0 : return nullptr;
6758 : }
6759 :
6760 : /* Preemptively try and avoid sqlite3 syntax errors due to */
6761 : /* illegal characters. */
6762 682 : if (strspn(osTableName.c_str(), "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") >
6763 : 0)
6764 : {
6765 0 : CPLError(
6766 : CE_Failure, CPLE_AppDefined,
6767 : "The layer name may not contain special characters or spaces");
6768 0 : return nullptr;
6769 : }
6770 : }
6771 :
6772 : /* Check for any existing layers that already use this name */
6773 881 : for (int iLayer = 0; iLayer < m_nLayers; iLayer++)
6774 : {
6775 200 : if (EQUAL(osTableName.c_str(), m_papoLayers[iLayer]->GetName()))
6776 : {
6777 : const char *pszOverwrite =
6778 2 : CSLFetchNameValue(papszOptions, "OVERWRITE");
6779 2 : if (pszOverwrite != nullptr && CPLTestBool(pszOverwrite))
6780 : {
6781 1 : DeleteLayer(iLayer);
6782 : }
6783 : else
6784 : {
6785 1 : CPLError(CE_Failure, CPLE_AppDefined,
6786 : "Layer %s already exists, CreateLayer failed.\n"
6787 : "Use the layer creation option OVERWRITE=YES to "
6788 : "replace it.",
6789 : osTableName.c_str());
6790 1 : return nullptr;
6791 : }
6792 : }
6793 : }
6794 :
6795 681 : if (m_nLayers == 1)
6796 : {
6797 : // Async RTree building doesn't play well with multiple layer:
6798 : // SQLite3 locks being hold for a long time, random failed commits,
6799 : // etc.
6800 73 : m_papoLayers[0]->FinishOrDisableThreadedRTree();
6801 : }
6802 :
6803 : /* Create a blank layer. */
6804 : auto poLayer = std::unique_ptr<OGRGeoPackageTableLayer>(
6805 1362 : new OGRGeoPackageTableLayer(this, osTableName.c_str()));
6806 :
6807 681 : OGRSpatialReference *poSRS = nullptr;
6808 681 : if (poSpatialRef)
6809 : {
6810 214 : poSRS = poSpatialRef->Clone();
6811 214 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
6812 : }
6813 1363 : poLayer->SetCreationParameters(
6814 : eGType,
6815 682 : bLaunder ? LaunderName(pszGeomColumnName).c_str() : pszGeomColumnName,
6816 : bGeomNullable, poSRS, CSLFetchNameValue(papszOptions, "SRID"),
6817 1362 : poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetCoordinatePrecision()
6818 : : OGRGeomCoordinatePrecision(),
6819 681 : CPLTestBool(
6820 : CSLFetchNameValueDef(papszOptions, "DISCARD_COORD_LSB", "NO")),
6821 681 : CPLTestBool(CSLFetchNameValueDef(
6822 : papszOptions, "UNDO_DISCARD_COORD_LSB_ON_READING", "NO")),
6823 682 : bLaunder ? LaunderName(pszFIDColumnName).c_str() : pszFIDColumnName,
6824 : pszIdentifier, CSLFetchNameValue(papszOptions, "DESCRIPTION"));
6825 681 : if (poSRS)
6826 : {
6827 214 : poSRS->Release();
6828 : }
6829 :
6830 681 : poLayer->SetLaunder(bLaunder);
6831 :
6832 : /* Should we create a spatial index ? */
6833 681 : const char *pszSI = CSLFetchNameValue(papszOptions, "SPATIAL_INDEX");
6834 681 : int bCreateSpatialIndex = (pszSI == nullptr || CPLTestBool(pszSI));
6835 681 : if (eGType != wkbNone && bCreateSpatialIndex)
6836 : {
6837 607 : poLayer->SetDeferredSpatialIndexCreation(true);
6838 : }
6839 :
6840 681 : poLayer->SetPrecisionFlag(CPLFetchBool(papszOptions, "PRECISION", true));
6841 681 : poLayer->SetTruncateFieldsFlag(
6842 681 : CPLFetchBool(papszOptions, "TRUNCATE_FIELDS", false));
6843 681 : if (eGType == wkbNone)
6844 : {
6845 52 : const char *pszASpatialVariant = CSLFetchNameValueDef(
6846 : papszOptions, "ASPATIAL_VARIANT",
6847 52 : m_bNonSpatialTablesNonRegisteredInGpkgContentsFound
6848 : ? "NOT_REGISTERED"
6849 : : "GPKG_ATTRIBUTES");
6850 52 : GPKGASpatialVariant eASpatialVariant = GPKG_ATTRIBUTES;
6851 52 : if (EQUAL(pszASpatialVariant, "GPKG_ATTRIBUTES"))
6852 40 : eASpatialVariant = GPKG_ATTRIBUTES;
6853 12 : else if (EQUAL(pszASpatialVariant, "OGR_ASPATIAL"))
6854 : {
6855 0 : CPLError(CE_Failure, CPLE_NotSupported,
6856 : "ASPATIAL_VARIANT=OGR_ASPATIAL is no longer supported");
6857 0 : return nullptr;
6858 : }
6859 12 : else if (EQUAL(pszASpatialVariant, "NOT_REGISTERED"))
6860 12 : eASpatialVariant = NOT_REGISTERED;
6861 : else
6862 : {
6863 0 : CPLError(CE_Failure, CPLE_NotSupported,
6864 : "Unsupported value for ASPATIAL_VARIANT: %s",
6865 : pszASpatialVariant);
6866 0 : return nullptr;
6867 : }
6868 52 : poLayer->SetASpatialVariant(eASpatialVariant);
6869 : }
6870 :
6871 : const char *pszDateTimePrecision =
6872 681 : CSLFetchNameValueDef(papszOptions, "DATETIME_PRECISION", "AUTO");
6873 681 : if (EQUAL(pszDateTimePrecision, "MILLISECOND"))
6874 : {
6875 2 : poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
6876 : }
6877 679 : else if (EQUAL(pszDateTimePrecision, "SECOND"))
6878 : {
6879 1 : if (m_nUserVersion < GPKG_1_4_VERSION)
6880 0 : CPLError(
6881 : CE_Warning, CPLE_AppDefined,
6882 : "DATETIME_PRECISION=SECOND is only valid since GeoPackage 1.4");
6883 1 : poLayer->SetDateTimePrecision(OGRISO8601Precision::SECOND);
6884 : }
6885 678 : else if (EQUAL(pszDateTimePrecision, "MINUTE"))
6886 : {
6887 1 : if (m_nUserVersion < GPKG_1_4_VERSION)
6888 0 : CPLError(
6889 : CE_Warning, CPLE_AppDefined,
6890 : "DATETIME_PRECISION=MINUTE is only valid since GeoPackage 1.4");
6891 1 : poLayer->SetDateTimePrecision(OGRISO8601Precision::MINUTE);
6892 : }
6893 677 : else if (EQUAL(pszDateTimePrecision, "AUTO"))
6894 : {
6895 676 : if (m_nUserVersion < GPKG_1_4_VERSION)
6896 665 : poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
6897 : }
6898 : else
6899 : {
6900 1 : CPLError(CE_Failure, CPLE_NotSupported,
6901 : "Unsupported value for DATETIME_PRECISION: %s",
6902 : pszDateTimePrecision);
6903 1 : return nullptr;
6904 : }
6905 :
6906 : // If there was an ogr_empty_table table, we can remove it
6907 : // But do it at dataset closing, otherwise locking performance issues
6908 : // can arise (probably when transactions are used).
6909 680 : m_bRemoveOGREmptyTable = true;
6910 :
6911 1360 : m_papoLayers = static_cast<OGRGeoPackageTableLayer **>(CPLRealloc(
6912 680 : m_papoLayers, sizeof(OGRGeoPackageTableLayer *) * (m_nLayers + 1)));
6913 680 : auto poRet = poLayer.release();
6914 680 : m_papoLayers[m_nLayers] = poRet;
6915 680 : m_nLayers++;
6916 680 : return poRet;
6917 : }
6918 :
6919 : /************************************************************************/
6920 : /* FindLayerIndex() */
6921 : /************************************************************************/
6922 :
6923 27 : int GDALGeoPackageDataset::FindLayerIndex(const char *pszLayerName)
6924 :
6925 : {
6926 42 : for (int iLayer = 0; iLayer < m_nLayers; iLayer++)
6927 : {
6928 28 : if (EQUAL(pszLayerName, m_papoLayers[iLayer]->GetName()))
6929 13 : return iLayer;
6930 : }
6931 14 : return -1;
6932 : }
6933 :
6934 : /************************************************************************/
6935 : /* DeleteLayerCommon() */
6936 : /************************************************************************/
6937 :
6938 38 : OGRErr GDALGeoPackageDataset::DeleteLayerCommon(const char *pszLayerName)
6939 : {
6940 : // Temporary remove foreign key checks
6941 : const GPKGTemporaryForeignKeyCheckDisabler
6942 38 : oGPKGTemporaryForeignKeyCheckDisabler(this);
6943 :
6944 38 : char *pszSQL = sqlite3_mprintf(
6945 : "DELETE FROM gpkg_contents WHERE lower(table_name) = lower('%q')",
6946 : pszLayerName);
6947 38 : OGRErr eErr = SQLCommand(hDB, pszSQL);
6948 38 : sqlite3_free(pszSQL);
6949 :
6950 38 : if (eErr == OGRERR_NONE && HasExtensionsTable())
6951 : {
6952 36 : pszSQL = sqlite3_mprintf(
6953 : "DELETE FROM gpkg_extensions WHERE lower(table_name) = lower('%q')",
6954 : pszLayerName);
6955 36 : eErr = SQLCommand(hDB, pszSQL);
6956 36 : sqlite3_free(pszSQL);
6957 : }
6958 :
6959 38 : if (eErr == OGRERR_NONE && HasMetadataTables())
6960 : {
6961 : // Delete from gpkg_metadata metadata records that are only referenced
6962 : // by the table we are about to drop
6963 10 : pszSQL = sqlite3_mprintf(
6964 : "DELETE FROM gpkg_metadata WHERE id IN ("
6965 : "SELECT DISTINCT md_file_id FROM "
6966 : "gpkg_metadata_reference WHERE "
6967 : "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
6968 : "AND id NOT IN ("
6969 : "SELECT DISTINCT md_file_id FROM gpkg_metadata_reference WHERE "
6970 : "md_file_id IN (SELECT DISTINCT md_file_id FROM "
6971 : "gpkg_metadata_reference WHERE "
6972 : "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
6973 : "AND lower(table_name) <> lower('%q'))",
6974 : pszLayerName, pszLayerName, pszLayerName);
6975 10 : eErr = SQLCommand(hDB, pszSQL);
6976 10 : sqlite3_free(pszSQL);
6977 :
6978 10 : if (eErr == OGRERR_NONE)
6979 : {
6980 : pszSQL =
6981 10 : sqlite3_mprintf("DELETE FROM gpkg_metadata_reference WHERE "
6982 : "lower(table_name) = lower('%q')",
6983 : pszLayerName);
6984 10 : eErr = SQLCommand(hDB, pszSQL);
6985 10 : sqlite3_free(pszSQL);
6986 : }
6987 : }
6988 :
6989 38 : if (eErr == OGRERR_NONE && HasGpkgextRelationsTable())
6990 : {
6991 : // Remove reference to potential corresponding mapping table in
6992 : // gpkg_extensions
6993 4 : pszSQL = sqlite3_mprintf(
6994 : "DELETE FROM gpkg_extensions WHERE "
6995 : "extension_name IN ('related_tables', "
6996 : "'gpkg_related_tables') AND lower(table_name) = "
6997 : "(SELECT lower(mapping_table_name) FROM gpkgext_relations WHERE "
6998 : "lower(base_table_name) = lower('%q') OR "
6999 : "lower(related_table_name) = lower('%q') OR "
7000 : "lower(mapping_table_name) = lower('%q'))",
7001 : pszLayerName, pszLayerName, pszLayerName);
7002 4 : eErr = SQLCommand(hDB, pszSQL);
7003 4 : sqlite3_free(pszSQL);
7004 :
7005 4 : if (eErr == OGRERR_NONE)
7006 : {
7007 : // Remove reference to potential corresponding mapping table in
7008 : // gpkgext_relations
7009 : pszSQL =
7010 4 : sqlite3_mprintf("DELETE FROM gpkgext_relations WHERE "
7011 : "lower(base_table_name) = lower('%q') OR "
7012 : "lower(related_table_name) = lower('%q') OR "
7013 : "lower(mapping_table_name) = lower('%q')",
7014 : pszLayerName, pszLayerName, pszLayerName);
7015 4 : eErr = SQLCommand(hDB, pszSQL);
7016 4 : sqlite3_free(pszSQL);
7017 : }
7018 :
7019 4 : if (eErr == OGRERR_NONE && HasExtensionsTable())
7020 : {
7021 : // If there is no longer any mapping table, then completely
7022 : // remove any reference to the extension in gpkg_extensions
7023 : // as mandated per the related table specification.
7024 : OGRErr err;
7025 4 : if (SQLGetInteger(hDB,
7026 : "SELECT COUNT(*) FROM gpkg_extensions WHERE "
7027 : "extension_name IN ('related_tables', "
7028 : "'gpkg_related_tables') AND "
7029 : "lower(table_name) != 'gpkgext_relations'",
7030 4 : &err) == 0)
7031 : {
7032 2 : eErr = SQLCommand(hDB, "DELETE FROM gpkg_extensions WHERE "
7033 : "extension_name IN ('related_tables', "
7034 : "'gpkg_related_tables')");
7035 : }
7036 :
7037 4 : ClearCachedRelationships();
7038 : }
7039 : }
7040 :
7041 38 : if (eErr == OGRERR_NONE)
7042 : {
7043 38 : pszSQL = sqlite3_mprintf("DROP TABLE \"%w\"", pszLayerName);
7044 38 : eErr = SQLCommand(hDB, pszSQL);
7045 38 : sqlite3_free(pszSQL);
7046 : }
7047 :
7048 : // Check foreign key integrity
7049 38 : if (eErr == OGRERR_NONE)
7050 : {
7051 38 : eErr = PragmaCheck("foreign_key_check", "", 0);
7052 : }
7053 :
7054 76 : return eErr;
7055 : }
7056 :
7057 : /************************************************************************/
7058 : /* DeleteLayer() */
7059 : /************************************************************************/
7060 :
7061 35 : OGRErr GDALGeoPackageDataset::DeleteLayer(int iLayer)
7062 : {
7063 35 : if (!GetUpdate() || iLayer < 0 || iLayer >= m_nLayers)
7064 2 : return OGRERR_FAILURE;
7065 :
7066 33 : m_papoLayers[iLayer]->ResetReading();
7067 33 : m_papoLayers[iLayer]->SyncToDisk();
7068 :
7069 66 : CPLString osLayerName = m_papoLayers[iLayer]->GetName();
7070 :
7071 33 : CPLDebug("GPKG", "DeleteLayer(%s)", osLayerName.c_str());
7072 :
7073 : // Temporary remove foreign key checks
7074 : const GPKGTemporaryForeignKeyCheckDisabler
7075 33 : oGPKGTemporaryForeignKeyCheckDisabler(this);
7076 :
7077 33 : OGRErr eErr = SoftStartTransaction();
7078 :
7079 33 : if (eErr == OGRERR_NONE)
7080 : {
7081 33 : if (m_papoLayers[iLayer]->HasSpatialIndex())
7082 30 : m_papoLayers[iLayer]->DropSpatialIndex();
7083 :
7084 : char *pszSQL =
7085 33 : sqlite3_mprintf("DELETE FROM gpkg_geometry_columns WHERE "
7086 : "lower(table_name) = lower('%q')",
7087 : osLayerName.c_str());
7088 33 : eErr = SQLCommand(hDB, pszSQL);
7089 33 : sqlite3_free(pszSQL);
7090 : }
7091 :
7092 33 : if (eErr == OGRERR_NONE && HasDataColumnsTable())
7093 : {
7094 1 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_data_columns WHERE "
7095 : "lower(table_name) = lower('%q')",
7096 : osLayerName.c_str());
7097 1 : eErr = SQLCommand(hDB, pszSQL);
7098 1 : sqlite3_free(pszSQL);
7099 : }
7100 :
7101 : #ifdef ENABLE_GPKG_OGR_CONTENTS
7102 33 : if (eErr == OGRERR_NONE && m_bHasGPKGOGRContents)
7103 : {
7104 33 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_ogr_contents WHERE "
7105 : "lower(table_name) = lower('%q')",
7106 : osLayerName.c_str());
7107 33 : eErr = SQLCommand(hDB, pszSQL);
7108 33 : sqlite3_free(pszSQL);
7109 : }
7110 : #endif
7111 :
7112 33 : if (eErr == OGRERR_NONE)
7113 : {
7114 33 : eErr = DeleteLayerCommon(osLayerName.c_str());
7115 : }
7116 :
7117 33 : if (eErr == OGRERR_NONE)
7118 : {
7119 33 : eErr = SoftCommitTransaction();
7120 33 : if (eErr == OGRERR_NONE)
7121 : {
7122 : /* Delete the layer object and remove the gap in the layers list */
7123 33 : delete m_papoLayers[iLayer];
7124 33 : memmove(m_papoLayers + iLayer, m_papoLayers + iLayer + 1,
7125 33 : sizeof(void *) * (m_nLayers - iLayer - 1));
7126 33 : m_nLayers--;
7127 : }
7128 : }
7129 : else
7130 : {
7131 0 : SoftRollbackTransaction();
7132 : }
7133 :
7134 33 : return eErr;
7135 : }
7136 :
7137 : /************************************************************************/
7138 : /* DeleteRasterLayer() */
7139 : /************************************************************************/
7140 :
7141 2 : OGRErr GDALGeoPackageDataset::DeleteRasterLayer(const char *pszLayerName)
7142 : {
7143 : // Temporary remove foreign key checks
7144 : const GPKGTemporaryForeignKeyCheckDisabler
7145 2 : oGPKGTemporaryForeignKeyCheckDisabler(this);
7146 :
7147 2 : OGRErr eErr = SoftStartTransaction();
7148 :
7149 2 : if (eErr == OGRERR_NONE)
7150 : {
7151 2 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix WHERE "
7152 : "lower(table_name) = lower('%q')",
7153 : pszLayerName);
7154 2 : eErr = SQLCommand(hDB, pszSQL);
7155 2 : sqlite3_free(pszSQL);
7156 : }
7157 :
7158 2 : if (eErr == OGRERR_NONE)
7159 : {
7160 2 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix_set WHERE "
7161 : "lower(table_name) = lower('%q')",
7162 : pszLayerName);
7163 2 : eErr = SQLCommand(hDB, pszSQL);
7164 2 : sqlite3_free(pszSQL);
7165 : }
7166 :
7167 2 : if (eErr == OGRERR_NONE && HasGriddedCoverageAncillaryTable())
7168 : {
7169 : char *pszSQL =
7170 1 : sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_coverage_ancillary "
7171 : "WHERE lower(tile_matrix_set_name) = lower('%q')",
7172 : pszLayerName);
7173 1 : eErr = SQLCommand(hDB, pszSQL);
7174 1 : sqlite3_free(pszSQL);
7175 :
7176 1 : if (eErr == OGRERR_NONE)
7177 : {
7178 : pszSQL =
7179 1 : sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_tile_ancillary "
7180 : "WHERE lower(tpudt_name) = lower('%q')",
7181 : pszLayerName);
7182 1 : eErr = SQLCommand(hDB, pszSQL);
7183 1 : sqlite3_free(pszSQL);
7184 : }
7185 : }
7186 :
7187 2 : if (eErr == OGRERR_NONE)
7188 : {
7189 2 : eErr = DeleteLayerCommon(pszLayerName);
7190 : }
7191 :
7192 2 : if (eErr == OGRERR_NONE)
7193 : {
7194 2 : eErr = SoftCommitTransaction();
7195 : }
7196 : else
7197 : {
7198 0 : SoftRollbackTransaction();
7199 : }
7200 :
7201 4 : return eErr;
7202 : }
7203 :
7204 : /************************************************************************/
7205 : /* DeleteVectorOrRasterLayer() */
7206 : /************************************************************************/
7207 :
7208 13 : bool GDALGeoPackageDataset::DeleteVectorOrRasterLayer(const char *pszLayerName)
7209 : {
7210 :
7211 13 : int idx = FindLayerIndex(pszLayerName);
7212 13 : if (idx >= 0)
7213 : {
7214 5 : DeleteLayer(idx);
7215 5 : return true;
7216 : }
7217 :
7218 : char *pszSQL =
7219 8 : sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
7220 : "lower(table_name) = lower('%q') "
7221 : "AND data_type IN ('tiles', '2d-gridded-coverage')",
7222 : pszLayerName);
7223 8 : bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
7224 8 : sqlite3_free(pszSQL);
7225 8 : if (bIsRasterTable)
7226 : {
7227 2 : DeleteRasterLayer(pszLayerName);
7228 2 : return true;
7229 : }
7230 6 : return false;
7231 : }
7232 :
7233 7 : bool GDALGeoPackageDataset::RenameVectorOrRasterLayer(
7234 : const char *pszLayerName, const char *pszNewLayerName)
7235 : {
7236 7 : int idx = FindLayerIndex(pszLayerName);
7237 7 : if (idx >= 0)
7238 : {
7239 4 : m_papoLayers[idx]->Rename(pszNewLayerName);
7240 4 : return true;
7241 : }
7242 :
7243 : char *pszSQL =
7244 3 : sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
7245 : "lower(table_name) = lower('%q') "
7246 : "AND data_type IN ('tiles', '2d-gridded-coverage')",
7247 : pszLayerName);
7248 3 : const bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
7249 3 : sqlite3_free(pszSQL);
7250 :
7251 3 : if (bIsRasterTable)
7252 : {
7253 2 : return RenameRasterLayer(pszLayerName, pszNewLayerName);
7254 : }
7255 :
7256 1 : return false;
7257 : }
7258 :
7259 2 : bool GDALGeoPackageDataset::RenameRasterLayer(const char *pszLayerName,
7260 : const char *pszNewLayerName)
7261 : {
7262 4 : std::string osSQL;
7263 :
7264 2 : char *pszSQL = sqlite3_mprintf(
7265 : "SELECT 1 FROM sqlite_master WHERE lower(name) = lower('%q') "
7266 : "AND type IN ('table', 'view')",
7267 : pszNewLayerName);
7268 2 : const bool bAlreadyExists = SQLGetInteger(GetDB(), pszSQL, nullptr) == 1;
7269 2 : sqlite3_free(pszSQL);
7270 2 : if (bAlreadyExists)
7271 : {
7272 0 : CPLError(CE_Failure, CPLE_AppDefined, "Table %s already exists",
7273 : pszNewLayerName);
7274 0 : return false;
7275 : }
7276 :
7277 : // Temporary remove foreign key checks
7278 : const GPKGTemporaryForeignKeyCheckDisabler
7279 4 : oGPKGTemporaryForeignKeyCheckDisabler(this);
7280 :
7281 2 : if (SoftStartTransaction() != OGRERR_NONE)
7282 : {
7283 0 : return false;
7284 : }
7285 :
7286 2 : pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET table_name = '%q' WHERE "
7287 : "lower(table_name) = lower('%q');",
7288 : pszNewLayerName, pszLayerName);
7289 2 : osSQL = pszSQL;
7290 2 : sqlite3_free(pszSQL);
7291 :
7292 2 : pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' WHERE "
7293 : "lower(identifier) = lower('%q');",
7294 : pszNewLayerName, pszLayerName);
7295 2 : osSQL += pszSQL;
7296 2 : sqlite3_free(pszSQL);
7297 :
7298 : pszSQL =
7299 2 : sqlite3_mprintf("UPDATE gpkg_tile_matrix SET table_name = '%q' WHERE "
7300 : "lower(table_name) = lower('%q');",
7301 : pszNewLayerName, pszLayerName);
7302 2 : osSQL += pszSQL;
7303 2 : sqlite3_free(pszSQL);
7304 :
7305 2 : pszSQL = sqlite3_mprintf(
7306 : "UPDATE gpkg_tile_matrix_set SET table_name = '%q' WHERE "
7307 : "lower(table_name) = lower('%q');",
7308 : pszNewLayerName, pszLayerName);
7309 2 : osSQL += pszSQL;
7310 2 : sqlite3_free(pszSQL);
7311 :
7312 2 : if (HasGriddedCoverageAncillaryTable())
7313 : {
7314 1 : pszSQL = sqlite3_mprintf("UPDATE gpkg_2d_gridded_coverage_ancillary "
7315 : "SET tile_matrix_set_name = '%q' WHERE "
7316 : "lower(tile_matrix_set_name) = lower('%q');",
7317 : pszNewLayerName, pszLayerName);
7318 1 : osSQL += pszSQL;
7319 1 : sqlite3_free(pszSQL);
7320 :
7321 1 : pszSQL = sqlite3_mprintf(
7322 : "UPDATE gpkg_2d_gridded_tile_ancillary SET tpudt_name = '%q' WHERE "
7323 : "lower(tpudt_name) = lower('%q');",
7324 : pszNewLayerName, pszLayerName);
7325 1 : osSQL += pszSQL;
7326 1 : sqlite3_free(pszSQL);
7327 : }
7328 :
7329 2 : if (HasExtensionsTable())
7330 : {
7331 2 : pszSQL = sqlite3_mprintf(
7332 : "UPDATE gpkg_extensions SET table_name = '%q' WHERE "
7333 : "lower(table_name) = lower('%q');",
7334 : pszNewLayerName, pszLayerName);
7335 2 : osSQL += pszSQL;
7336 2 : sqlite3_free(pszSQL);
7337 : }
7338 :
7339 2 : if (HasMetadataTables())
7340 : {
7341 1 : pszSQL = sqlite3_mprintf(
7342 : "UPDATE gpkg_metadata_reference SET table_name = '%q' WHERE "
7343 : "lower(table_name) = lower('%q');",
7344 : pszNewLayerName, pszLayerName);
7345 1 : osSQL += pszSQL;
7346 1 : sqlite3_free(pszSQL);
7347 : }
7348 :
7349 2 : if (HasDataColumnsTable())
7350 : {
7351 0 : pszSQL = sqlite3_mprintf(
7352 : "UPDATE gpkg_data_columns SET table_name = '%q' WHERE "
7353 : "lower(table_name) = lower('%q');",
7354 : pszNewLayerName, pszLayerName);
7355 0 : osSQL += pszSQL;
7356 0 : sqlite3_free(pszSQL);
7357 : }
7358 :
7359 2 : if (HasQGISLayerStyles())
7360 : {
7361 : // Update QGIS styles
7362 : pszSQL =
7363 0 : sqlite3_mprintf("UPDATE layer_styles SET f_table_name = '%q' WHERE "
7364 : "lower(f_table_name) = lower('%q');",
7365 : pszNewLayerName, pszLayerName);
7366 0 : osSQL += pszSQL;
7367 0 : sqlite3_free(pszSQL);
7368 : }
7369 :
7370 : #ifdef ENABLE_GPKG_OGR_CONTENTS
7371 2 : if (m_bHasGPKGOGRContents)
7372 : {
7373 2 : pszSQL = sqlite3_mprintf(
7374 : "UPDATE gpkg_ogr_contents SET table_name = '%q' WHERE "
7375 : "lower(table_name) = lower('%q');",
7376 : pszNewLayerName, pszLayerName);
7377 2 : osSQL += pszSQL;
7378 2 : sqlite3_free(pszSQL);
7379 : }
7380 : #endif
7381 :
7382 2 : if (HasGpkgextRelationsTable())
7383 : {
7384 0 : pszSQL = sqlite3_mprintf(
7385 : "UPDATE gpkgext_relations SET base_table_name = '%q' WHERE "
7386 : "lower(base_table_name) = lower('%q');",
7387 : pszNewLayerName, pszLayerName);
7388 0 : osSQL += pszSQL;
7389 0 : sqlite3_free(pszSQL);
7390 :
7391 0 : pszSQL = sqlite3_mprintf(
7392 : "UPDATE gpkgext_relations SET related_table_name = '%q' WHERE "
7393 : "lower(related_table_name) = lower('%q');",
7394 : pszNewLayerName, pszLayerName);
7395 0 : osSQL += pszSQL;
7396 0 : sqlite3_free(pszSQL);
7397 :
7398 0 : pszSQL = sqlite3_mprintf(
7399 : "UPDATE gpkgext_relations SET mapping_table_name = '%q' WHERE "
7400 : "lower(mapping_table_name) = lower('%q');",
7401 : pszNewLayerName, pszLayerName);
7402 0 : osSQL += pszSQL;
7403 0 : sqlite3_free(pszSQL);
7404 : }
7405 :
7406 : // Drop all triggers for the layer
7407 2 : pszSQL = sqlite3_mprintf("SELECT name FROM sqlite_master WHERE type = "
7408 : "'trigger' AND tbl_name = '%q'",
7409 : pszLayerName);
7410 2 : auto oTriggerResult = SQLQuery(GetDB(), pszSQL);
7411 2 : sqlite3_free(pszSQL);
7412 2 : if (oTriggerResult)
7413 : {
7414 14 : for (int i = 0; i < oTriggerResult->RowCount(); i++)
7415 : {
7416 12 : const char *pszTriggerName = oTriggerResult->GetValue(0, i);
7417 12 : pszSQL = sqlite3_mprintf("DROP TRIGGER IF EXISTS \"%w\";",
7418 : pszTriggerName);
7419 12 : osSQL += pszSQL;
7420 12 : sqlite3_free(pszSQL);
7421 : }
7422 : }
7423 :
7424 2 : pszSQL = sqlite3_mprintf("ALTER TABLE \"%w\" RENAME TO \"%w\";",
7425 : pszLayerName, pszNewLayerName);
7426 2 : osSQL += pszSQL;
7427 2 : sqlite3_free(pszSQL);
7428 :
7429 : // Recreate all zoom/tile triggers
7430 2 : if (oTriggerResult)
7431 : {
7432 2 : osSQL += CreateRasterTriggersSQL(pszNewLayerName);
7433 : }
7434 :
7435 2 : OGRErr eErr = SQLCommand(GetDB(), osSQL.c_str());
7436 :
7437 : // Check foreign key integrity
7438 2 : if (eErr == OGRERR_NONE)
7439 : {
7440 2 : eErr = PragmaCheck("foreign_key_check", "", 0);
7441 : }
7442 :
7443 2 : if (eErr == OGRERR_NONE)
7444 : {
7445 2 : eErr = SoftCommitTransaction();
7446 : }
7447 : else
7448 : {
7449 0 : SoftRollbackTransaction();
7450 : }
7451 :
7452 2 : return eErr == OGRERR_NONE;
7453 : }
7454 :
7455 : /************************************************************************/
7456 : /* TestCapability() */
7457 : /************************************************************************/
7458 :
7459 398 : int GDALGeoPackageDataset::TestCapability(const char *pszCap)
7460 : {
7461 398 : if (EQUAL(pszCap, ODsCCreateLayer) || EQUAL(pszCap, ODsCDeleteLayer) ||
7462 247 : EQUAL(pszCap, "RenameLayer"))
7463 : {
7464 151 : return GetUpdate();
7465 : }
7466 247 : else if (EQUAL(pszCap, ODsCCurveGeometries))
7467 12 : return TRUE;
7468 235 : else if (EQUAL(pszCap, ODsCMeasuredGeometries))
7469 8 : return TRUE;
7470 227 : else if (EQUAL(pszCap, ODsCZGeometries))
7471 8 : return TRUE;
7472 219 : else if (EQUAL(pszCap, ODsCRandomLayerWrite) ||
7473 219 : EQUAL(pszCap, GDsCAddRelationship) ||
7474 219 : EQUAL(pszCap, GDsCDeleteRelationship) ||
7475 219 : EQUAL(pszCap, GDsCUpdateRelationship) ||
7476 219 : EQUAL(pszCap, ODsCAddFieldDomain))
7477 1 : return GetUpdate();
7478 :
7479 218 : return OGRSQLiteBaseDataSource::TestCapability(pszCap);
7480 : }
7481 :
7482 : /************************************************************************/
7483 : /* ResetReadingAllLayers() */
7484 : /************************************************************************/
7485 :
7486 52 : void GDALGeoPackageDataset::ResetReadingAllLayers()
7487 : {
7488 109 : for (int i = 0; i < m_nLayers; i++)
7489 : {
7490 57 : m_papoLayers[i]->ResetReading();
7491 : }
7492 52 : }
7493 :
7494 : /************************************************************************/
7495 : /* ExecuteSQL() */
7496 : /************************************************************************/
7497 :
7498 : static const char *const apszFuncsWithSideEffects[] = {
7499 : "CreateSpatialIndex",
7500 : "DisableSpatialIndex",
7501 : "HasSpatialIndex",
7502 : "RegisterGeometryExtension",
7503 : };
7504 :
7505 5360 : OGRLayer *GDALGeoPackageDataset::ExecuteSQL(const char *pszSQLCommand,
7506 : OGRGeometry *poSpatialFilter,
7507 : const char *pszDialect)
7508 :
7509 : {
7510 5360 : m_bHasReadMetadataFromStorage = false;
7511 :
7512 5360 : FlushMetadata();
7513 :
7514 5378 : while (*pszSQLCommand != '\0' &&
7515 5378 : isspace(static_cast<unsigned char>(*pszSQLCommand)))
7516 18 : pszSQLCommand++;
7517 :
7518 10720 : CPLString osSQLCommand(pszSQLCommand);
7519 5360 : if (!osSQLCommand.empty() && osSQLCommand.back() == ';')
7520 48 : osSQLCommand.pop_back();
7521 :
7522 5360 : if (pszDialect == nullptr || !EQUAL(pszDialect, "DEBUG"))
7523 : {
7524 : // Some SQL commands will influence the feature count behind our
7525 : // back, so disable it in that case.
7526 : #ifdef ENABLE_GPKG_OGR_CONTENTS
7527 : const bool bInsertOrDelete =
7528 5291 : osSQLCommand.ifind("insert into ") != std::string::npos ||
7529 2179 : osSQLCommand.ifind("insert or replace into ") !=
7530 7470 : std::string::npos ||
7531 2133 : osSQLCommand.ifind("delete from ") != std::string::npos;
7532 : const bool bRollback =
7533 5291 : osSQLCommand.ifind("rollback ") != std::string::npos;
7534 : #endif
7535 :
7536 6832 : for (int i = 0; i < m_nLayers; i++)
7537 : {
7538 1541 : if (m_papoLayers[i]->SyncToDisk() != OGRERR_NONE)
7539 0 : return nullptr;
7540 : #ifdef ENABLE_GPKG_OGR_CONTENTS
7541 1742 : if (bRollback || (bInsertOrDelete &&
7542 201 : osSQLCommand.ifind(m_papoLayers[i]->GetName()) !=
7543 : std::string::npos))
7544 : {
7545 155 : m_papoLayers[i]->DisableFeatureCount();
7546 : }
7547 : #endif
7548 : }
7549 : }
7550 :
7551 5360 : if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 0") ||
7552 5359 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=0") ||
7553 5359 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =0") ||
7554 5359 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 0"))
7555 : {
7556 1 : OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, false);
7557 : }
7558 5359 : else if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 1") ||
7559 5358 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=1") ||
7560 5358 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =1") ||
7561 5358 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 1"))
7562 : {
7563 1 : OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, true);
7564 : }
7565 :
7566 : /* -------------------------------------------------------------------- */
7567 : /* DEBUG "SELECT nolock" command. */
7568 : /* -------------------------------------------------------------------- */
7569 5429 : if (pszDialect != nullptr && EQUAL(pszDialect, "DEBUG") &&
7570 69 : EQUAL(osSQLCommand, "SELECT nolock"))
7571 : {
7572 3 : return new OGRSQLiteSingleFeatureLayer(osSQLCommand, m_bNoLock ? 1 : 0);
7573 : }
7574 :
7575 : /* -------------------------------------------------------------------- */
7576 : /* Special case DELLAYER: command. */
7577 : /* -------------------------------------------------------------------- */
7578 5357 : if (STARTS_WITH_CI(osSQLCommand, "DELLAYER:"))
7579 : {
7580 4 : const char *pszLayerName = osSQLCommand.c_str() + strlen("DELLAYER:");
7581 :
7582 4 : while (*pszLayerName == ' ')
7583 0 : pszLayerName++;
7584 :
7585 4 : if (!DeleteVectorOrRasterLayer(pszLayerName))
7586 : {
7587 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
7588 : pszLayerName);
7589 : }
7590 4 : return nullptr;
7591 : }
7592 :
7593 : /* -------------------------------------------------------------------- */
7594 : /* Special case RECOMPUTE EXTENT ON command. */
7595 : /* -------------------------------------------------------------------- */
7596 5353 : if (STARTS_WITH_CI(osSQLCommand, "RECOMPUTE EXTENT ON "))
7597 : {
7598 : const char *pszLayerName =
7599 4 : osSQLCommand.c_str() + strlen("RECOMPUTE EXTENT ON ");
7600 :
7601 4 : while (*pszLayerName == ' ')
7602 0 : pszLayerName++;
7603 :
7604 4 : int idx = FindLayerIndex(pszLayerName);
7605 4 : if (idx >= 0)
7606 : {
7607 4 : m_papoLayers[idx]->RecomputeExtent();
7608 : }
7609 : else
7610 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
7611 : pszLayerName);
7612 4 : return nullptr;
7613 : }
7614 :
7615 : /* -------------------------------------------------------------------- */
7616 : /* Intercept DROP TABLE */
7617 : /* -------------------------------------------------------------------- */
7618 5349 : if (STARTS_WITH_CI(osSQLCommand, "DROP TABLE "))
7619 : {
7620 9 : const char *pszLayerName = osSQLCommand.c_str() + strlen("DROP TABLE ");
7621 :
7622 9 : while (*pszLayerName == ' ')
7623 0 : pszLayerName++;
7624 :
7625 9 : if (DeleteVectorOrRasterLayer(SQLUnescape(pszLayerName)))
7626 4 : return nullptr;
7627 : }
7628 :
7629 : /* -------------------------------------------------------------------- */
7630 : /* Intercept ALTER TABLE src_table RENAME TO dst_table */
7631 : /* and ALTER TABLE table RENAME COLUMN src_name TO dst_name */
7632 : /* and ALTER TABLE table DROP COLUMN col_name */
7633 : /* */
7634 : /* We do this because SQLite mechanisms can't deal with updating */
7635 : /* literal values in gpkg_ tables that refer to table and column */
7636 : /* names. */
7637 : /* -------------------------------------------------------------------- */
7638 5345 : if (STARTS_WITH_CI(osSQLCommand, "ALTER TABLE "))
7639 : {
7640 9 : char **papszTokens = SQLTokenize(osSQLCommand);
7641 : /* ALTER TABLE src_table RENAME TO dst_table */
7642 16 : if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "RENAME") &&
7643 7 : EQUAL(papszTokens[4], "TO"))
7644 : {
7645 7 : const char *pszSrcTableName = papszTokens[2];
7646 7 : const char *pszDstTableName = papszTokens[5];
7647 7 : if (RenameVectorOrRasterLayer(SQLUnescape(pszSrcTableName),
7648 14 : SQLUnescape(pszDstTableName)))
7649 : {
7650 6 : CSLDestroy(papszTokens);
7651 6 : return nullptr;
7652 : }
7653 : }
7654 : /* ALTER TABLE table RENAME COLUMN src_name TO dst_name */
7655 2 : else if (CSLCount(papszTokens) == 8 &&
7656 1 : EQUAL(papszTokens[3], "RENAME") &&
7657 3 : EQUAL(papszTokens[4], "COLUMN") && EQUAL(papszTokens[6], "TO"))
7658 : {
7659 1 : const char *pszTableName = papszTokens[2];
7660 1 : const char *pszSrcColumn = papszTokens[5];
7661 1 : const char *pszDstColumn = papszTokens[7];
7662 : OGRGeoPackageTableLayer *poLayer =
7663 0 : dynamic_cast<OGRGeoPackageTableLayer *>(
7664 1 : GetLayerByName(SQLUnescape(pszTableName)));
7665 1 : if (poLayer)
7666 : {
7667 2 : int nSrcFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
7668 2 : SQLUnescape(pszSrcColumn));
7669 1 : if (nSrcFieldIdx >= 0)
7670 : {
7671 : // OFTString or any type will do as we just alter the name
7672 : // so it will be ignored.
7673 1 : OGRFieldDefn oFieldDefn(SQLUnescape(pszDstColumn),
7674 1 : OFTString);
7675 1 : poLayer->AlterFieldDefn(nSrcFieldIdx, &oFieldDefn,
7676 : ALTER_NAME_FLAG);
7677 1 : CSLDestroy(papszTokens);
7678 1 : return nullptr;
7679 : }
7680 : }
7681 : }
7682 : /* ALTER TABLE table DROP COLUMN col_name */
7683 2 : else if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "DROP") &&
7684 1 : EQUAL(papszTokens[4], "COLUMN"))
7685 : {
7686 1 : const char *pszTableName = papszTokens[2];
7687 1 : const char *pszColumnName = papszTokens[5];
7688 : OGRGeoPackageTableLayer *poLayer =
7689 0 : dynamic_cast<OGRGeoPackageTableLayer *>(
7690 1 : GetLayerByName(SQLUnescape(pszTableName)));
7691 1 : if (poLayer)
7692 : {
7693 2 : int nFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
7694 2 : SQLUnescape(pszColumnName));
7695 1 : if (nFieldIdx >= 0)
7696 : {
7697 1 : poLayer->DeleteField(nFieldIdx);
7698 1 : CSLDestroy(papszTokens);
7699 1 : return nullptr;
7700 : }
7701 : }
7702 : }
7703 1 : CSLDestroy(papszTokens);
7704 : }
7705 :
7706 5337 : if (EQUAL(osSQLCommand, "VACUUM"))
7707 : {
7708 12 : ResetReadingAllLayers();
7709 : }
7710 :
7711 5337 : if (EQUAL(osSQLCommand, "BEGIN"))
7712 : {
7713 0 : SoftStartTransaction();
7714 0 : return nullptr;
7715 : }
7716 5337 : else if (EQUAL(osSQLCommand, "COMMIT"))
7717 : {
7718 0 : SoftCommitTransaction();
7719 0 : return nullptr;
7720 : }
7721 5337 : else if (EQUAL(osSQLCommand, "ROLLBACK"))
7722 : {
7723 0 : SoftRollbackTransaction();
7724 0 : return nullptr;
7725 : }
7726 :
7727 5337 : else if (STARTS_WITH_CI(osSQLCommand, "DELETE FROM "))
7728 : {
7729 : // Optimize truncation of a table, especially if it has a spatial
7730 : // index.
7731 20 : const CPLStringList aosTokens(SQLTokenize(osSQLCommand));
7732 20 : if (aosTokens.size() == 3)
7733 : {
7734 14 : const char *pszTableName = aosTokens[2];
7735 : OGRGeoPackageTableLayer *poLayer =
7736 8 : dynamic_cast<OGRGeoPackageTableLayer *>(
7737 22 : GetLayerByName(SQLUnescape(pszTableName)));
7738 14 : if (poLayer)
7739 : {
7740 6 : poLayer->Truncate();
7741 6 : return nullptr;
7742 : }
7743 : }
7744 : }
7745 :
7746 5317 : else if (pszDialect != nullptr && EQUAL(pszDialect, "INDIRECT_SQLITE"))
7747 1 : return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter, "SQLITE");
7748 5316 : else if (pszDialect != nullptr && !EQUAL(pszDialect, "") &&
7749 66 : !EQUAL(pszDialect, "NATIVE") && !EQUAL(pszDialect, "SQLITE") &&
7750 66 : !EQUAL(pszDialect, "DEBUG"))
7751 0 : return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter,
7752 0 : pszDialect);
7753 :
7754 : /* -------------------------------------------------------------------- */
7755 : /* Prepare statement. */
7756 : /* -------------------------------------------------------------------- */
7757 5330 : sqlite3_stmt *hSQLStmt = nullptr;
7758 :
7759 : /* This will speed-up layer creation */
7760 : /* ORDER BY are costly to evaluate and are not necessary to establish */
7761 : /* the layer definition. */
7762 5330 : bool bUseStatementForGetNextFeature = true;
7763 5330 : bool bEmptyLayer = false;
7764 10660 : CPLString osSQLCommandTruncated(osSQLCommand);
7765 :
7766 17574 : if (osSQLCommand.ifind("SELECT ") == 0 &&
7767 6122 : CPLString(osSQLCommand.substr(1)).ifind("SELECT ") ==
7768 758 : std::string::npos &&
7769 758 : osSQLCommand.ifind(" UNION ") == std::string::npos &&
7770 6880 : osSQLCommand.ifind(" INTERSECT ") == std::string::npos &&
7771 758 : osSQLCommand.ifind(" EXCEPT ") == std::string::npos)
7772 : {
7773 758 : size_t nOrderByPos = osSQLCommand.ifind(" ORDER BY ");
7774 758 : if (nOrderByPos != std::string::npos)
7775 : {
7776 8 : osSQLCommandTruncated.resize(nOrderByPos);
7777 8 : bUseStatementForGetNextFeature = false;
7778 : }
7779 : }
7780 :
7781 5330 : int rc = prepareSql(hDB, osSQLCommandTruncated.c_str(),
7782 5330 : static_cast<int>(osSQLCommandTruncated.size()),
7783 : &hSQLStmt, nullptr);
7784 :
7785 5330 : if (rc != SQLITE_OK)
7786 : {
7787 9 : CPLError(CE_Failure, CPLE_AppDefined,
7788 : "In ExecuteSQL(): sqlite3_prepare_v2(%s):\n %s",
7789 : osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
7790 :
7791 9 : if (hSQLStmt != nullptr)
7792 : {
7793 0 : sqlite3_finalize(hSQLStmt);
7794 : }
7795 :
7796 9 : return nullptr;
7797 : }
7798 :
7799 : /* -------------------------------------------------------------------- */
7800 : /* Do we get a resultset? */
7801 : /* -------------------------------------------------------------------- */
7802 5321 : rc = sqlite3_step(hSQLStmt);
7803 :
7804 6884 : for (int i = 0; i < m_nLayers; i++)
7805 : {
7806 1563 : m_papoLayers[i]->RunDeferredDropRTreeTableIfNecessary();
7807 : }
7808 :
7809 5321 : if (rc != SQLITE_ROW)
7810 : {
7811 4609 : if (rc != SQLITE_DONE)
7812 : {
7813 7 : CPLError(CE_Failure, CPLE_AppDefined,
7814 : "In ExecuteSQL(): sqlite3_step(%s):\n %s",
7815 : osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
7816 :
7817 7 : sqlite3_finalize(hSQLStmt);
7818 7 : return nullptr;
7819 : }
7820 :
7821 4602 : if (EQUAL(osSQLCommand, "VACUUM"))
7822 : {
7823 12 : sqlite3_finalize(hSQLStmt);
7824 : /* VACUUM rewrites the DB, so we need to reset the application id */
7825 12 : SetApplicationAndUserVersionId();
7826 12 : return nullptr;
7827 : }
7828 :
7829 4590 : if (!STARTS_WITH_CI(osSQLCommand, "SELECT "))
7830 : {
7831 4466 : sqlite3_finalize(hSQLStmt);
7832 4466 : return nullptr;
7833 : }
7834 :
7835 124 : bUseStatementForGetNextFeature = false;
7836 124 : bEmptyLayer = true;
7837 : }
7838 :
7839 : /* -------------------------------------------------------------------- */
7840 : /* Special case for some functions which must be run */
7841 : /* only once */
7842 : /* -------------------------------------------------------------------- */
7843 836 : if (STARTS_WITH_CI(osSQLCommand, "SELECT "))
7844 : {
7845 3804 : for (unsigned int i = 0; i < sizeof(apszFuncsWithSideEffects) /
7846 : sizeof(apszFuncsWithSideEffects[0]);
7847 : i++)
7848 : {
7849 3069 : if (EQUALN(apszFuncsWithSideEffects[i], osSQLCommand.c_str() + 7,
7850 : strlen(apszFuncsWithSideEffects[i])))
7851 : {
7852 112 : if (sqlite3_column_count(hSQLStmt) == 1 &&
7853 56 : sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
7854 : {
7855 56 : int ret = sqlite3_column_int(hSQLStmt, 0);
7856 :
7857 56 : sqlite3_finalize(hSQLStmt);
7858 :
7859 : return new OGRSQLiteSingleFeatureLayer(
7860 56 : apszFuncsWithSideEffects[i], ret);
7861 : }
7862 : }
7863 : }
7864 : }
7865 45 : else if (STARTS_WITH_CI(osSQLCommand, "PRAGMA "))
7866 : {
7867 63 : if (sqlite3_column_count(hSQLStmt) == 1 &&
7868 18 : sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
7869 : {
7870 15 : int ret = sqlite3_column_int(hSQLStmt, 0);
7871 :
7872 15 : sqlite3_finalize(hSQLStmt);
7873 :
7874 15 : return new OGRSQLiteSingleFeatureLayer(osSQLCommand.c_str() + 7,
7875 15 : ret);
7876 : }
7877 33 : else if (sqlite3_column_count(hSQLStmt) == 1 &&
7878 3 : sqlite3_column_type(hSQLStmt, 0) == SQLITE_TEXT)
7879 : {
7880 : const char *pszRet = reinterpret_cast<const char *>(
7881 3 : sqlite3_column_text(hSQLStmt, 0));
7882 :
7883 : OGRLayer *poRet = new OGRSQLiteSingleFeatureLayer(
7884 3 : osSQLCommand.c_str() + 7, pszRet);
7885 :
7886 3 : sqlite3_finalize(hSQLStmt);
7887 :
7888 3 : return poRet;
7889 : }
7890 : }
7891 :
7892 : /* -------------------------------------------------------------------- */
7893 : /* Create layer. */
7894 : /* -------------------------------------------------------------------- */
7895 :
7896 : OGRLayer *poLayer = new OGRGeoPackageSelectLayer(
7897 : this, osSQLCommand, hSQLStmt, bUseStatementForGetNextFeature,
7898 762 : bEmptyLayer);
7899 :
7900 765 : if (poSpatialFilter != nullptr &&
7901 3 : poLayer->GetLayerDefn()->GetGeomFieldCount() > 0)
7902 3 : poLayer->SetSpatialFilter(0, poSpatialFilter);
7903 :
7904 762 : return poLayer;
7905 : }
7906 :
7907 : /************************************************************************/
7908 : /* ReleaseResultSet() */
7909 : /************************************************************************/
7910 :
7911 792 : void GDALGeoPackageDataset::ReleaseResultSet(OGRLayer *poLayer)
7912 :
7913 : {
7914 792 : delete poLayer;
7915 792 : }
7916 :
7917 : /************************************************************************/
7918 : /* HasExtensionsTable() */
7919 : /************************************************************************/
7920 :
7921 5870 : bool GDALGeoPackageDataset::HasExtensionsTable()
7922 : {
7923 5870 : return SQLGetInteger(
7924 : hDB,
7925 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_extensions' "
7926 : "AND type IN ('table', 'view')",
7927 5870 : nullptr) == 1;
7928 : }
7929 :
7930 : /************************************************************************/
7931 : /* CheckUnknownExtensions() */
7932 : /************************************************************************/
7933 :
7934 1383 : void GDALGeoPackageDataset::CheckUnknownExtensions(bool bCheckRasterTable)
7935 : {
7936 1383 : if (!HasExtensionsTable())
7937 193 : return;
7938 :
7939 1190 : char *pszSQL = nullptr;
7940 1190 : if (!bCheckRasterTable)
7941 984 : pszSQL = sqlite3_mprintf(
7942 : "SELECT extension_name, definition, scope FROM gpkg_extensions "
7943 : "WHERE (table_name IS NULL "
7944 : "AND extension_name IS NOT NULL "
7945 : "AND definition IS NOT NULL "
7946 : "AND scope IS NOT NULL "
7947 : "AND extension_name NOT IN ("
7948 : "'gdal_aspatial', "
7949 : "'gpkg_elevation_tiles', " // Old name before GPKG 1.2 approval
7950 : "'2d_gridded_coverage', " // Old name after GPKG 1.2 and before OGC
7951 : // 17-066r1 finalization
7952 : "'gpkg_2d_gridded_coverage', " // Name in OGC 17-066r1 final
7953 : "'gpkg_metadata', "
7954 : "'gpkg_schema', "
7955 : "'gpkg_crs_wkt', "
7956 : "'gpkg_crs_wkt_1_1', "
7957 : "'related_tables', 'gpkg_related_tables')) "
7958 : #ifdef WORKAROUND_SQLITE3_BUGS
7959 : "OR 0 "
7960 : #endif
7961 : "LIMIT 1000");
7962 : else
7963 206 : pszSQL = sqlite3_mprintf(
7964 : "SELECT extension_name, definition, scope FROM gpkg_extensions "
7965 : "WHERE (lower(table_name) = lower('%q') "
7966 : "AND extension_name IS NOT NULL "
7967 : "AND definition IS NOT NULL "
7968 : "AND scope IS NOT NULL "
7969 : "AND extension_name NOT IN ("
7970 : "'gpkg_elevation_tiles', " // Old name before GPKG 1.2 approval
7971 : "'2d_gridded_coverage', " // Old name after GPKG 1.2 and before OGC
7972 : // 17-066r1 finalization
7973 : "'gpkg_2d_gridded_coverage', " // Name in OGC 17-066r1 final
7974 : "'gpkg_metadata', "
7975 : "'gpkg_schema', "
7976 : "'gpkg_crs_wkt', "
7977 : "'gpkg_crs_wkt_1_1', "
7978 : "'related_tables', 'gpkg_related_tables')) "
7979 : #ifdef WORKAROUND_SQLITE3_BUGS
7980 : "OR 0 "
7981 : #endif
7982 : "LIMIT 1000",
7983 : m_osRasterTable.c_str());
7984 :
7985 2380 : auto oResultTable = SQLQuery(GetDB(), pszSQL);
7986 1190 : sqlite3_free(pszSQL);
7987 1190 : if (oResultTable && oResultTable->RowCount() > 0)
7988 : {
7989 44 : for (int i = 0; i < oResultTable->RowCount(); i++)
7990 : {
7991 22 : const char *pszExtName = oResultTable->GetValue(0, i);
7992 22 : const char *pszDefinition = oResultTable->GetValue(1, i);
7993 22 : const char *pszScope = oResultTable->GetValue(2, i);
7994 22 : if (pszExtName == nullptr || pszDefinition == nullptr ||
7995 : pszScope == nullptr)
7996 : {
7997 0 : continue;
7998 : }
7999 :
8000 22 : if (EQUAL(pszExtName, "gpkg_webp"))
8001 : {
8002 16 : if (GDALGetDriverByName("WEBP") == nullptr)
8003 : {
8004 1 : CPLError(
8005 : CE_Warning, CPLE_AppDefined,
8006 : "Table %s contains WEBP tiles, but GDAL configured "
8007 : "without WEBP support. Data will be missing",
8008 : m_osRasterTable.c_str());
8009 : }
8010 16 : m_eTF = GPKG_TF_WEBP;
8011 16 : continue;
8012 : }
8013 6 : if (EQUAL(pszExtName, "gpkg_zoom_other"))
8014 : {
8015 2 : m_bZoomOther = true;
8016 2 : continue;
8017 : }
8018 :
8019 4 : if (GetUpdate() && EQUAL(pszScope, "write-only"))
8020 : {
8021 1 : CPLError(
8022 : CE_Warning, CPLE_AppDefined,
8023 : "Database relies on the '%s' (%s) extension that should "
8024 : "be implemented for safe write-support, but is not "
8025 : "currently. "
8026 : "Update of that database are strongly discouraged to avoid "
8027 : "corruption.",
8028 : pszExtName, pszDefinition);
8029 : }
8030 3 : else if (GetUpdate() && EQUAL(pszScope, "read-write"))
8031 : {
8032 1 : CPLError(
8033 : CE_Warning, CPLE_AppDefined,
8034 : "Database relies on the '%s' (%s) extension that should "
8035 : "be implemented in order to read/write it safely, but is "
8036 : "not currently. "
8037 : "Some data may be missing while reading that database, and "
8038 : "updates are strongly discouraged.",
8039 : pszExtName, pszDefinition);
8040 : }
8041 2 : else if (EQUAL(pszScope, "read-write") &&
8042 : // None of the NGA extensions at
8043 : // http://ngageoint.github.io/GeoPackage/docs/extensions/
8044 : // affect read-only scenarios
8045 1 : !STARTS_WITH(pszExtName, "nga_"))
8046 : {
8047 1 : CPLError(
8048 : CE_Warning, CPLE_AppDefined,
8049 : "Database relies on the '%s' (%s) extension that should "
8050 : "be implemented in order to read it safely, but is not "
8051 : "currently. "
8052 : "Some data may be missing while reading that database.",
8053 : pszExtName, pszDefinition);
8054 : }
8055 : }
8056 : }
8057 : }
8058 :
8059 : /************************************************************************/
8060 : /* HasGDALAspatialExtension() */
8061 : /************************************************************************/
8062 :
8063 931 : bool GDALGeoPackageDataset::HasGDALAspatialExtension()
8064 : {
8065 931 : if (!HasExtensionsTable())
8066 86 : return false;
8067 :
8068 : auto oResultTable = SQLQuery(hDB, "SELECT * FROM gpkg_extensions "
8069 : "WHERE (extension_name = 'gdal_aspatial' "
8070 : "AND table_name IS NULL "
8071 : "AND column_name IS NULL)"
8072 : #ifdef WORKAROUND_SQLITE3_BUGS
8073 : " OR 0"
8074 : #endif
8075 845 : );
8076 845 : bool bHasExtension = (oResultTable && oResultTable->RowCount() == 1);
8077 845 : return bHasExtension;
8078 : }
8079 :
8080 : std::string
8081 176 : GDALGeoPackageDataset::CreateRasterTriggersSQL(const std::string &osTableName)
8082 : {
8083 : char *pszSQL;
8084 176 : std::string osSQL;
8085 : /* From D.5. sample_tile_pyramid Table 43. tiles table Trigger
8086 : * Definition SQL */
8087 176 : pszSQL = sqlite3_mprintf(
8088 : "CREATE TRIGGER \"%w_zoom_insert\" "
8089 : "BEFORE INSERT ON \"%w\" "
8090 : "FOR EACH ROW BEGIN "
8091 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
8092 : "constraint: zoom_level not specified for table in "
8093 : "gpkg_tile_matrix') "
8094 : "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
8095 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
8096 : "END; "
8097 : "CREATE TRIGGER \"%w_zoom_update\" "
8098 : "BEFORE UPDATE OF zoom_level ON \"%w\" "
8099 : "FOR EACH ROW BEGIN "
8100 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8101 : "constraint: zoom_level not specified for table in "
8102 : "gpkg_tile_matrix') "
8103 : "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
8104 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
8105 : "END; "
8106 : "CREATE TRIGGER \"%w_tile_column_insert\" "
8107 : "BEFORE INSERT ON \"%w\" "
8108 : "FOR EACH ROW BEGIN "
8109 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
8110 : "constraint: tile_column cannot be < 0') "
8111 : "WHERE (NEW.tile_column < 0) ; "
8112 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
8113 : "constraint: tile_column must by < matrix_width specified for "
8114 : "table and zoom level in gpkg_tile_matrix') "
8115 : "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
8116 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8117 : "zoom_level = NEW.zoom_level)); "
8118 : "END; "
8119 : "CREATE TRIGGER \"%w_tile_column_update\" "
8120 : "BEFORE UPDATE OF tile_column ON \"%w\" "
8121 : "FOR EACH ROW BEGIN "
8122 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8123 : "constraint: tile_column cannot be < 0') "
8124 : "WHERE (NEW.tile_column < 0) ; "
8125 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8126 : "constraint: tile_column must by < matrix_width specified for "
8127 : "table and zoom level in gpkg_tile_matrix') "
8128 : "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
8129 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8130 : "zoom_level = NEW.zoom_level)); "
8131 : "END; "
8132 : "CREATE TRIGGER \"%w_tile_row_insert\" "
8133 : "BEFORE INSERT ON \"%w\" "
8134 : "FOR EACH ROW BEGIN "
8135 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
8136 : "constraint: tile_row cannot be < 0') "
8137 : "WHERE (NEW.tile_row < 0) ; "
8138 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
8139 : "constraint: tile_row must by < matrix_height specified for "
8140 : "table and zoom level in gpkg_tile_matrix') "
8141 : "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
8142 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8143 : "zoom_level = NEW.zoom_level)); "
8144 : "END; "
8145 : "CREATE TRIGGER \"%w_tile_row_update\" "
8146 : "BEFORE UPDATE OF tile_row ON \"%w\" "
8147 : "FOR EACH ROW BEGIN "
8148 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8149 : "constraint: tile_row cannot be < 0') "
8150 : "WHERE (NEW.tile_row < 0) ; "
8151 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8152 : "constraint: tile_row must by < matrix_height specified for "
8153 : "table and zoom level in gpkg_tile_matrix') "
8154 : "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
8155 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8156 : "zoom_level = NEW.zoom_level)); "
8157 : "END; ",
8158 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8159 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8160 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8161 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8162 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8163 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8164 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8165 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8166 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8167 : osTableName.c_str());
8168 176 : osSQL = pszSQL;
8169 176 : sqlite3_free(pszSQL);
8170 176 : return osSQL;
8171 : }
8172 :
8173 : /************************************************************************/
8174 : /* CreateExtensionsTableIfNecessary() */
8175 : /************************************************************************/
8176 :
8177 1063 : OGRErr GDALGeoPackageDataset::CreateExtensionsTableIfNecessary()
8178 : {
8179 : /* Check if the table gpkg_extensions exists */
8180 1063 : if (HasExtensionsTable())
8181 391 : return OGRERR_NONE;
8182 :
8183 : /* Requirement 79 : Every extension of a GeoPackage SHALL be registered */
8184 : /* in a corresponding row in the gpkg_extensions table. The absence of a */
8185 : /* gpkg_extensions table or the absence of rows in gpkg_extensions table */
8186 : /* SHALL both indicate the absence of extensions to a GeoPackage. */
8187 672 : const char *pszCreateGpkgExtensions =
8188 : "CREATE TABLE gpkg_extensions ("
8189 : "table_name TEXT,"
8190 : "column_name TEXT,"
8191 : "extension_name TEXT NOT NULL,"
8192 : "definition TEXT NOT NULL,"
8193 : "scope TEXT NOT NULL,"
8194 : "CONSTRAINT ge_tce UNIQUE (table_name, column_name, extension_name)"
8195 : ")";
8196 :
8197 672 : return SQLCommand(hDB, pszCreateGpkgExtensions);
8198 : }
8199 :
8200 : /************************************************************************/
8201 : /* OGR_GPKG_Intersects_Spatial_Filter() */
8202 : /************************************************************************/
8203 :
8204 23135 : void OGR_GPKG_Intersects_Spatial_Filter(sqlite3_context *pContext, int argc,
8205 : sqlite3_value **argv)
8206 : {
8207 23135 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8208 : {
8209 0 : sqlite3_result_int(pContext, 0);
8210 23125 : return;
8211 : }
8212 :
8213 : auto poLayer =
8214 23135 : static_cast<OGRGeoPackageTableLayer *>(sqlite3_user_data(pContext));
8215 :
8216 23135 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8217 : const GByte *pabyBLOB =
8218 23135 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8219 :
8220 : GPkgHeader sHeader;
8221 46270 : if (poLayer->m_bFilterIsEnvelope &&
8222 23135 : OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false, 0))
8223 : {
8224 23135 : if (sHeader.bExtentHasXY)
8225 : {
8226 95 : OGREnvelope sEnvelope;
8227 95 : sEnvelope.MinX = sHeader.MinX;
8228 95 : sEnvelope.MinY = sHeader.MinY;
8229 95 : sEnvelope.MaxX = sHeader.MaxX;
8230 95 : sEnvelope.MaxY = sHeader.MaxY;
8231 95 : if (poLayer->m_sFilterEnvelope.Contains(sEnvelope))
8232 : {
8233 31 : sqlite3_result_int(pContext, 1);
8234 31 : return;
8235 : }
8236 : }
8237 :
8238 : // Check if at least one point falls into the layer filter envelope
8239 : // nHeaderLen is > 0 for GeoPackage geometries
8240 46208 : if (sHeader.nHeaderLen > 0 &&
8241 23104 : OGRWKBIntersectsPessimistic(pabyBLOB + sHeader.nHeaderLen,
8242 23104 : nBLOBLen - sHeader.nHeaderLen,
8243 23104 : poLayer->m_sFilterEnvelope))
8244 : {
8245 23094 : sqlite3_result_int(pContext, 1);
8246 23094 : return;
8247 : }
8248 : }
8249 :
8250 : auto poGeom = std::unique_ptr<OGRGeometry>(
8251 10 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8252 10 : if (poGeom == nullptr)
8253 : {
8254 : // Try also spatialite geometry blobs
8255 0 : OGRGeometry *poGeomSpatialite = nullptr;
8256 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8257 0 : &poGeomSpatialite) != OGRERR_NONE)
8258 : {
8259 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8260 0 : sqlite3_result_int(pContext, 0);
8261 0 : return;
8262 : }
8263 0 : poGeom.reset(poGeomSpatialite);
8264 : }
8265 :
8266 10 : sqlite3_result_int(pContext, poLayer->FilterGeometry(poGeom.get()));
8267 : }
8268 :
8269 : /************************************************************************/
8270 : /* OGRGeoPackageSTMinX() */
8271 : /************************************************************************/
8272 :
8273 242587 : static void OGRGeoPackageSTMinX(sqlite3_context *pContext, int argc,
8274 : sqlite3_value **argv)
8275 : {
8276 : GPkgHeader sHeader;
8277 242587 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8278 : {
8279 3 : sqlite3_result_null(pContext);
8280 3 : return;
8281 : }
8282 242584 : sqlite3_result_double(pContext, sHeader.MinX);
8283 : }
8284 :
8285 : /************************************************************************/
8286 : /* OGRGeoPackageSTMinY() */
8287 : /************************************************************************/
8288 :
8289 242585 : static void OGRGeoPackageSTMinY(sqlite3_context *pContext, int argc,
8290 : sqlite3_value **argv)
8291 : {
8292 : GPkgHeader sHeader;
8293 242585 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8294 : {
8295 1 : sqlite3_result_null(pContext);
8296 1 : return;
8297 : }
8298 242584 : sqlite3_result_double(pContext, sHeader.MinY);
8299 : }
8300 :
8301 : /************************************************************************/
8302 : /* OGRGeoPackageSTMaxX() */
8303 : /************************************************************************/
8304 :
8305 242585 : static void OGRGeoPackageSTMaxX(sqlite3_context *pContext, int argc,
8306 : sqlite3_value **argv)
8307 : {
8308 : GPkgHeader sHeader;
8309 242585 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8310 : {
8311 1 : sqlite3_result_null(pContext);
8312 1 : return;
8313 : }
8314 242584 : sqlite3_result_double(pContext, sHeader.MaxX);
8315 : }
8316 :
8317 : /************************************************************************/
8318 : /* OGRGeoPackageSTMaxY() */
8319 : /************************************************************************/
8320 :
8321 242585 : static void OGRGeoPackageSTMaxY(sqlite3_context *pContext, int argc,
8322 : sqlite3_value **argv)
8323 : {
8324 : GPkgHeader sHeader;
8325 242585 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8326 : {
8327 1 : sqlite3_result_null(pContext);
8328 1 : return;
8329 : }
8330 242584 : sqlite3_result_double(pContext, sHeader.MaxY);
8331 : }
8332 :
8333 : /************************************************************************/
8334 : /* OGRGeoPackageSTIsEmpty() */
8335 : /************************************************************************/
8336 :
8337 243919 : static void OGRGeoPackageSTIsEmpty(sqlite3_context *pContext, int argc,
8338 : sqlite3_value **argv)
8339 : {
8340 : GPkgHeader sHeader;
8341 243919 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8342 : {
8343 2 : sqlite3_result_null(pContext);
8344 2 : return;
8345 : }
8346 243917 : sqlite3_result_int(pContext, sHeader.bEmpty);
8347 : }
8348 :
8349 : /************************************************************************/
8350 : /* OGRGeoPackageSTGeometryType() */
8351 : /************************************************************************/
8352 :
8353 7 : static void OGRGeoPackageSTGeometryType(sqlite3_context *pContext, int /*argc*/,
8354 : sqlite3_value **argv)
8355 : {
8356 : GPkgHeader sHeader;
8357 :
8358 7 : int nBLOBLen = sqlite3_value_bytes(argv[0]);
8359 : const GByte *pabyBLOB =
8360 7 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8361 : OGRwkbGeometryType eGeometryType;
8362 :
8363 13 : if (nBLOBLen < 8 ||
8364 6 : GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
8365 : {
8366 2 : if (OGRSQLiteGetSpatialiteGeometryHeader(
8367 : pabyBLOB, nBLOBLen, nullptr, &eGeometryType, nullptr, nullptr,
8368 2 : nullptr, nullptr, nullptr) == OGRERR_NONE)
8369 : {
8370 1 : sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
8371 : SQLITE_TRANSIENT);
8372 4 : return;
8373 : }
8374 : else
8375 : {
8376 1 : sqlite3_result_null(pContext);
8377 1 : return;
8378 : }
8379 : }
8380 :
8381 5 : if (static_cast<size_t>(nBLOBLen) < sHeader.nHeaderLen + 5)
8382 : {
8383 2 : sqlite3_result_null(pContext);
8384 2 : return;
8385 : }
8386 :
8387 3 : OGRErr err = OGRReadWKBGeometryType(pabyBLOB + sHeader.nHeaderLen,
8388 : wkbVariantIso, &eGeometryType);
8389 3 : if (err != OGRERR_NONE)
8390 1 : sqlite3_result_null(pContext);
8391 : else
8392 2 : sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
8393 : SQLITE_TRANSIENT);
8394 : }
8395 :
8396 : /************************************************************************/
8397 : /* OGRGeoPackageSTEnvelopesIntersects() */
8398 : /************************************************************************/
8399 :
8400 118 : static void OGRGeoPackageSTEnvelopesIntersects(sqlite3_context *pContext,
8401 : int argc, sqlite3_value **argv)
8402 : {
8403 : GPkgHeader sHeader;
8404 118 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8405 : {
8406 2 : sqlite3_result_int(pContext, FALSE);
8407 107 : return;
8408 : }
8409 116 : const double dfMinX = sqlite3_value_double(argv[1]);
8410 116 : if (sHeader.MaxX < dfMinX)
8411 : {
8412 93 : sqlite3_result_int(pContext, FALSE);
8413 93 : return;
8414 : }
8415 23 : const double dfMinY = sqlite3_value_double(argv[2]);
8416 23 : if (sHeader.MaxY < dfMinY)
8417 : {
8418 11 : sqlite3_result_int(pContext, FALSE);
8419 11 : return;
8420 : }
8421 12 : const double dfMaxX = sqlite3_value_double(argv[3]);
8422 12 : if (sHeader.MinX > dfMaxX)
8423 : {
8424 1 : sqlite3_result_int(pContext, FALSE);
8425 1 : return;
8426 : }
8427 11 : const double dfMaxY = sqlite3_value_double(argv[4]);
8428 11 : sqlite3_result_int(pContext, sHeader.MinY <= dfMaxY);
8429 : }
8430 :
8431 : /************************************************************************/
8432 : /* OGRGeoPackageSTEnvelopesIntersectsTwoParams() */
8433 : /************************************************************************/
8434 :
8435 : static void
8436 3 : OGRGeoPackageSTEnvelopesIntersectsTwoParams(sqlite3_context *pContext, int argc,
8437 : sqlite3_value **argv)
8438 : {
8439 : GPkgHeader sHeader;
8440 3 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false, 0))
8441 : {
8442 0 : sqlite3_result_int(pContext, FALSE);
8443 2 : return;
8444 : }
8445 : GPkgHeader sHeader2;
8446 3 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader2, true, false,
8447 : 1))
8448 : {
8449 0 : sqlite3_result_int(pContext, FALSE);
8450 0 : return;
8451 : }
8452 3 : if (sHeader.MaxX < sHeader2.MinX)
8453 : {
8454 1 : sqlite3_result_int(pContext, FALSE);
8455 1 : return;
8456 : }
8457 2 : if (sHeader.MaxY < sHeader2.MinY)
8458 : {
8459 0 : sqlite3_result_int(pContext, FALSE);
8460 0 : return;
8461 : }
8462 2 : if (sHeader.MinX > sHeader2.MaxX)
8463 : {
8464 1 : sqlite3_result_int(pContext, FALSE);
8465 1 : return;
8466 : }
8467 1 : sqlite3_result_int(pContext, sHeader.MinY <= sHeader2.MaxY);
8468 : }
8469 :
8470 : /************************************************************************/
8471 : /* OGRGeoPackageGPKGIsAssignable() */
8472 : /************************************************************************/
8473 :
8474 8 : static void OGRGeoPackageGPKGIsAssignable(sqlite3_context *pContext,
8475 : int /*argc*/, sqlite3_value **argv)
8476 : {
8477 15 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8478 7 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
8479 : {
8480 2 : sqlite3_result_int(pContext, 0);
8481 2 : return;
8482 : }
8483 :
8484 : const char *pszExpected =
8485 6 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8486 : const char *pszActual =
8487 6 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8488 6 : int bIsAssignable = OGR_GT_IsSubClassOf(OGRFromOGCGeomType(pszActual),
8489 : OGRFromOGCGeomType(pszExpected));
8490 6 : sqlite3_result_int(pContext, bIsAssignable);
8491 : }
8492 :
8493 : /************************************************************************/
8494 : /* OGRGeoPackageSTSRID() */
8495 : /************************************************************************/
8496 :
8497 12 : static void OGRGeoPackageSTSRID(sqlite3_context *pContext, int argc,
8498 : sqlite3_value **argv)
8499 : {
8500 : GPkgHeader sHeader;
8501 12 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8502 : {
8503 2 : sqlite3_result_null(pContext);
8504 2 : return;
8505 : }
8506 10 : sqlite3_result_int(pContext, sHeader.iSrsId);
8507 : }
8508 :
8509 : /************************************************************************/
8510 : /* OGRGeoPackageSetSRID() */
8511 : /************************************************************************/
8512 :
8513 28 : static void OGRGeoPackageSetSRID(sqlite3_context *pContext, int /* argc */,
8514 : sqlite3_value **argv)
8515 : {
8516 28 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8517 : {
8518 1 : sqlite3_result_null(pContext);
8519 1 : return;
8520 : }
8521 27 : const int nDestSRID = sqlite3_value_int(argv[1]);
8522 : GPkgHeader sHeader;
8523 27 : int nBLOBLen = sqlite3_value_bytes(argv[0]);
8524 : const GByte *pabyBLOB =
8525 27 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8526 :
8527 54 : if (nBLOBLen < 8 ||
8528 27 : GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
8529 : {
8530 : // Try also spatialite geometry blobs
8531 0 : OGRGeometry *poGeom = nullptr;
8532 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeom) !=
8533 : OGRERR_NONE)
8534 : {
8535 0 : sqlite3_result_null(pContext);
8536 0 : return;
8537 : }
8538 0 : size_t nBLOBDestLen = 0;
8539 : GByte *pabyDestBLOB =
8540 0 : GPkgGeometryFromOGR(poGeom, nDestSRID, nullptr, &nBLOBDestLen);
8541 0 : if (!pabyDestBLOB)
8542 : {
8543 0 : sqlite3_result_null(pContext);
8544 0 : return;
8545 : }
8546 0 : sqlite3_result_blob(pContext, pabyDestBLOB,
8547 : static_cast<int>(nBLOBDestLen), VSIFree);
8548 0 : return;
8549 : }
8550 :
8551 27 : GByte *pabyDestBLOB = static_cast<GByte *>(CPLMalloc(nBLOBLen));
8552 27 : memcpy(pabyDestBLOB, pabyBLOB, nBLOBLen);
8553 27 : int32_t nSRIDToSerialize = nDestSRID;
8554 27 : if (OGR_SWAP(sHeader.eByteOrder))
8555 0 : nSRIDToSerialize = CPL_SWAP32(nSRIDToSerialize);
8556 27 : memcpy(pabyDestBLOB + 4, &nSRIDToSerialize, 4);
8557 27 : sqlite3_result_blob(pContext, pabyDestBLOB, nBLOBLen, VSIFree);
8558 : }
8559 :
8560 : /************************************************************************/
8561 : /* OGRGeoPackageSTMakeValid() */
8562 : /************************************************************************/
8563 :
8564 3 : static void OGRGeoPackageSTMakeValid(sqlite3_context *pContext, int argc,
8565 : sqlite3_value **argv)
8566 : {
8567 3 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8568 : {
8569 2 : sqlite3_result_null(pContext);
8570 2 : return;
8571 : }
8572 1 : int nBLOBLen = sqlite3_value_bytes(argv[0]);
8573 : const GByte *pabyBLOB =
8574 1 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8575 :
8576 : GPkgHeader sHeader;
8577 1 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8578 : {
8579 0 : sqlite3_result_null(pContext);
8580 0 : return;
8581 : }
8582 :
8583 : auto poGeom = std::unique_ptr<OGRGeometry>(
8584 1 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8585 1 : if (poGeom == nullptr)
8586 : {
8587 : // Try also spatialite geometry blobs
8588 0 : OGRGeometry *poGeomPtr = nullptr;
8589 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
8590 : OGRERR_NONE)
8591 : {
8592 0 : sqlite3_result_null(pContext);
8593 0 : return;
8594 : }
8595 0 : poGeom.reset(poGeomPtr);
8596 : }
8597 1 : auto poValid = std::unique_ptr<OGRGeometry>(poGeom->MakeValid());
8598 1 : if (poValid == nullptr)
8599 : {
8600 0 : sqlite3_result_null(pContext);
8601 0 : return;
8602 : }
8603 :
8604 1 : size_t nBLOBDestLen = 0;
8605 1 : GByte *pabyDestBLOB = GPkgGeometryFromOGR(poValid.get(), sHeader.iSrsId,
8606 : nullptr, &nBLOBDestLen);
8607 1 : if (!pabyDestBLOB)
8608 : {
8609 0 : sqlite3_result_null(pContext);
8610 0 : return;
8611 : }
8612 1 : sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
8613 : VSIFree);
8614 : }
8615 :
8616 : /************************************************************************/
8617 : /* OGRGeoPackageSTArea() */
8618 : /************************************************************************/
8619 :
8620 19 : static void OGRGeoPackageSTArea(sqlite3_context *pContext, int /*argc*/,
8621 : sqlite3_value **argv)
8622 : {
8623 19 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8624 : {
8625 1 : sqlite3_result_null(pContext);
8626 15 : return;
8627 : }
8628 18 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8629 : const GByte *pabyBLOB =
8630 18 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8631 :
8632 : GPkgHeader sHeader;
8633 0 : std::unique_ptr<OGRGeometry> poGeom;
8634 18 : if (GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) == OGRERR_NONE)
8635 : {
8636 16 : if (sHeader.bEmpty)
8637 : {
8638 3 : sqlite3_result_double(pContext, 0);
8639 13 : return;
8640 : }
8641 13 : const GByte *pabyWkb = pabyBLOB + sHeader.nHeaderLen;
8642 13 : size_t nWKBSize = nBLOBLen - sHeader.nHeaderLen;
8643 : bool bNeedSwap;
8644 : uint32_t nType;
8645 13 : if (OGRWKBGetGeomType(pabyWkb, nWKBSize, bNeedSwap, nType))
8646 : {
8647 13 : if (nType == wkbPolygon || nType == wkbPolygon25D ||
8648 11 : nType == wkbPolygon + 1000 || // wkbPolygonZ
8649 10 : nType == wkbPolygonM || nType == wkbPolygonZM)
8650 : {
8651 : double dfArea;
8652 5 : if (OGRWKBPolygonGetArea(pabyWkb, nWKBSize, dfArea))
8653 : {
8654 5 : sqlite3_result_double(pContext, dfArea);
8655 5 : return;
8656 0 : }
8657 : }
8658 8 : else if (nType == wkbMultiPolygon || nType == wkbMultiPolygon25D ||
8659 6 : nType == wkbMultiPolygon + 1000 || // wkbMultiPolygonZ
8660 5 : nType == wkbMultiPolygonM || nType == wkbMultiPolygonZM)
8661 : {
8662 : double dfArea;
8663 5 : if (OGRWKBMultiPolygonGetArea(pabyWkb, nWKBSize, dfArea))
8664 : {
8665 5 : sqlite3_result_double(pContext, dfArea);
8666 5 : return;
8667 : }
8668 : }
8669 : }
8670 :
8671 : // For curve geometries, fallback to OGRGeometry methods
8672 3 : poGeom.reset(GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8673 : }
8674 : else
8675 : {
8676 : // Try also spatialite geometry blobs
8677 2 : OGRGeometry *poGeomPtr = nullptr;
8678 2 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
8679 : OGRERR_NONE)
8680 : {
8681 1 : sqlite3_result_null(pContext);
8682 1 : return;
8683 : }
8684 1 : poGeom.reset(poGeomPtr);
8685 : }
8686 4 : auto poSurface = dynamic_cast<OGRSurface *>(poGeom.get());
8687 4 : if (poSurface == nullptr)
8688 : {
8689 2 : auto poMultiSurface = dynamic_cast<OGRMultiSurface *>(poGeom.get());
8690 2 : if (poMultiSurface == nullptr)
8691 : {
8692 1 : sqlite3_result_double(pContext, 0);
8693 : }
8694 : else
8695 : {
8696 1 : sqlite3_result_double(pContext, poMultiSurface->get_Area());
8697 : }
8698 : }
8699 : else
8700 : {
8701 2 : sqlite3_result_double(pContext, poSurface->get_Area());
8702 : }
8703 : }
8704 :
8705 : /************************************************************************/
8706 : /* OGRGeoPackageGeodesicArea() */
8707 : /************************************************************************/
8708 :
8709 5 : static void OGRGeoPackageGeodesicArea(sqlite3_context *pContext, int argc,
8710 : sqlite3_value **argv)
8711 : {
8712 5 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8713 : {
8714 1 : sqlite3_result_null(pContext);
8715 3 : return;
8716 : }
8717 4 : if (sqlite3_value_int(argv[1]) != 1)
8718 : {
8719 2 : CPLError(CE_Warning, CPLE_NotSupported,
8720 : "ST_Area(geom, use_ellipsoid) is only supported for "
8721 : "use_ellipsoid = 1");
8722 : }
8723 :
8724 4 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8725 : const GByte *pabyBLOB =
8726 4 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8727 : GPkgHeader sHeader;
8728 4 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8729 : {
8730 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8731 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8732 1 : return;
8733 : }
8734 :
8735 : GDALGeoPackageDataset *poDS =
8736 3 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8737 :
8738 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSrcSRS(
8739 3 : poDS->GetSpatialRef(sHeader.iSrsId, true));
8740 3 : if (poSrcSRS == nullptr)
8741 : {
8742 1 : CPLError(CE_Failure, CPLE_AppDefined,
8743 : "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8744 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8745 1 : return;
8746 : }
8747 :
8748 : auto poGeom = std::unique_ptr<OGRGeometry>(
8749 2 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8750 2 : if (poGeom == nullptr)
8751 : {
8752 : // Try also spatialite geometry blobs
8753 0 : OGRGeometry *poGeomSpatialite = nullptr;
8754 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8755 0 : &poGeomSpatialite) != OGRERR_NONE)
8756 : {
8757 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8758 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8759 0 : return;
8760 : }
8761 0 : poGeom.reset(poGeomSpatialite);
8762 : }
8763 :
8764 2 : poGeom->assignSpatialReference(poSrcSRS.get());
8765 2 : sqlite3_result_double(
8766 : pContext, OGR_G_GeodesicArea(OGRGeometry::ToHandle(poGeom.get())));
8767 : }
8768 :
8769 : /************************************************************************/
8770 : /* OGRGeoPackageLengthOrGeodesicLength() */
8771 : /************************************************************************/
8772 :
8773 8 : static void OGRGeoPackageLengthOrGeodesicLength(sqlite3_context *pContext,
8774 : int argc, sqlite3_value **argv)
8775 : {
8776 8 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8777 : {
8778 2 : sqlite3_result_null(pContext);
8779 5 : return;
8780 : }
8781 6 : if (argc == 2 && sqlite3_value_int(argv[1]) != 1)
8782 : {
8783 2 : CPLError(CE_Warning, CPLE_NotSupported,
8784 : "ST_Length(geom, use_ellipsoid) is only supported for "
8785 : "use_ellipsoid = 1");
8786 : }
8787 :
8788 6 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8789 : const GByte *pabyBLOB =
8790 6 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8791 : GPkgHeader sHeader;
8792 6 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8793 : {
8794 2 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8795 2 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8796 2 : return;
8797 : }
8798 :
8799 : GDALGeoPackageDataset *poDS =
8800 4 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8801 :
8802 0 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSrcSRS;
8803 4 : if (argc == 2)
8804 : {
8805 3 : poSrcSRS = poDS->GetSpatialRef(sHeader.iSrsId, true);
8806 3 : if (!poSrcSRS)
8807 : {
8808 1 : CPLError(CE_Failure, CPLE_AppDefined,
8809 : "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8810 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8811 1 : return;
8812 : }
8813 : }
8814 :
8815 : auto poGeom = std::unique_ptr<OGRGeometry>(
8816 3 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8817 3 : if (poGeom == nullptr)
8818 : {
8819 : // Try also spatialite geometry blobs
8820 0 : OGRGeometry *poGeomSpatialite = nullptr;
8821 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8822 0 : &poGeomSpatialite) != OGRERR_NONE)
8823 : {
8824 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8825 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8826 0 : return;
8827 : }
8828 0 : poGeom.reset(poGeomSpatialite);
8829 : }
8830 :
8831 3 : if (argc == 2)
8832 2 : poGeom->assignSpatialReference(poSrcSRS.get());
8833 :
8834 6 : sqlite3_result_double(
8835 : pContext,
8836 1 : argc == 1 ? OGR_G_Length(OGRGeometry::ToHandle(poGeom.get()))
8837 2 : : OGR_G_GeodesicLength(OGRGeometry::ToHandle(poGeom.get())));
8838 : }
8839 :
8840 : /************************************************************************/
8841 : /* OGRGeoPackageTransform() */
8842 : /************************************************************************/
8843 :
8844 : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
8845 : sqlite3_value **argv);
8846 :
8847 32 : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
8848 : sqlite3_value **argv)
8849 : {
8850 63 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB ||
8851 31 : sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
8852 : {
8853 2 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8854 32 : return;
8855 : }
8856 :
8857 30 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8858 : const GByte *pabyBLOB =
8859 30 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8860 : GPkgHeader sHeader;
8861 30 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8862 : {
8863 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8864 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8865 1 : return;
8866 : }
8867 :
8868 29 : const int nDestSRID = sqlite3_value_int(argv[1]);
8869 29 : if (sHeader.iSrsId == nDestSRID)
8870 : {
8871 : // Return blob unmodified
8872 3 : sqlite3_result_blob(pContext, pabyBLOB, nBLOBLen, SQLITE_TRANSIENT);
8873 3 : return;
8874 : }
8875 :
8876 : GDALGeoPackageDataset *poDS =
8877 26 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8878 :
8879 : // Try to get the cached coordinate transformation
8880 : OGRCoordinateTransformation *poCT;
8881 26 : if (poDS->m_nLastCachedCTSrcSRId == sHeader.iSrsId &&
8882 20 : poDS->m_nLastCachedCTDstSRId == nDestSRID)
8883 : {
8884 20 : poCT = poDS->m_poLastCachedCT.get();
8885 : }
8886 : else
8887 : {
8888 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
8889 6 : poSrcSRS(poDS->GetSpatialRef(sHeader.iSrsId, true));
8890 6 : if (poSrcSRS == nullptr)
8891 : {
8892 0 : CPLError(CE_Failure, CPLE_AppDefined,
8893 : "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8894 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8895 0 : return;
8896 : }
8897 :
8898 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
8899 6 : poDstSRS(poDS->GetSpatialRef(nDestSRID, true));
8900 6 : if (poDstSRS == nullptr)
8901 : {
8902 0 : CPLError(CE_Failure, CPLE_AppDefined, "Target SRID (%d) is invalid",
8903 : nDestSRID);
8904 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8905 0 : return;
8906 : }
8907 : poCT =
8908 6 : OGRCreateCoordinateTransformation(poSrcSRS.get(), poDstSRS.get());
8909 6 : if (poCT == nullptr)
8910 : {
8911 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8912 0 : return;
8913 : }
8914 :
8915 : // Cache coordinate transformation for potential later reuse
8916 6 : poDS->m_nLastCachedCTSrcSRId = sHeader.iSrsId;
8917 6 : poDS->m_nLastCachedCTDstSRId = nDestSRID;
8918 6 : poDS->m_poLastCachedCT.reset(poCT);
8919 6 : poCT = poDS->m_poLastCachedCT.get();
8920 : }
8921 :
8922 26 : if (sHeader.nHeaderLen >= 8)
8923 : {
8924 26 : std::vector<GByte> &abyNewBLOB = poDS->m_abyWKBTransformCache;
8925 26 : abyNewBLOB.resize(nBLOBLen);
8926 26 : memcpy(abyNewBLOB.data(), pabyBLOB, nBLOBLen);
8927 :
8928 26 : OGREnvelope3D oEnv3d;
8929 26 : if (!OGRWKBTransform(abyNewBLOB.data() + sHeader.nHeaderLen,
8930 26 : nBLOBLen - sHeader.nHeaderLen, poCT,
8931 78 : poDS->m_oWKBTransformCache, oEnv3d) ||
8932 26 : !GPkgUpdateHeader(abyNewBLOB.data(), nBLOBLen, nDestSRID,
8933 : oEnv3d.MinX, oEnv3d.MaxX, oEnv3d.MinY,
8934 : oEnv3d.MaxY, oEnv3d.MinZ, oEnv3d.MaxZ))
8935 : {
8936 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8937 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8938 0 : return;
8939 : }
8940 :
8941 26 : sqlite3_result_blob(pContext, abyNewBLOB.data(), nBLOBLen,
8942 : SQLITE_TRANSIENT);
8943 26 : return;
8944 : }
8945 :
8946 : // Try also spatialite geometry blobs
8947 0 : OGRGeometry *poGeomSpatialite = nullptr;
8948 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8949 0 : &poGeomSpatialite) != OGRERR_NONE)
8950 : {
8951 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8952 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8953 0 : return;
8954 : }
8955 0 : auto poGeom = std::unique_ptr<OGRGeometry>(poGeomSpatialite);
8956 :
8957 0 : if (poGeom->transform(poCT) != OGRERR_NONE)
8958 : {
8959 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8960 0 : return;
8961 : }
8962 :
8963 0 : size_t nBLOBDestLen = 0;
8964 : GByte *pabyDestBLOB =
8965 0 : GPkgGeometryFromOGR(poGeom.get(), nDestSRID, nullptr, &nBLOBDestLen);
8966 0 : if (!pabyDestBLOB)
8967 : {
8968 0 : sqlite3_result_null(pContext);
8969 0 : return;
8970 : }
8971 0 : sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
8972 : VSIFree);
8973 : }
8974 :
8975 : /************************************************************************/
8976 : /* OGRGeoPackageSridFromAuthCRS() */
8977 : /************************************************************************/
8978 :
8979 4 : static void OGRGeoPackageSridFromAuthCRS(sqlite3_context *pContext,
8980 : int /*argc*/, sqlite3_value **argv)
8981 : {
8982 7 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8983 3 : sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
8984 : {
8985 2 : sqlite3_result_int(pContext, -1);
8986 2 : return;
8987 : }
8988 :
8989 : GDALGeoPackageDataset *poDS =
8990 2 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8991 :
8992 2 : char *pszSQL = sqlite3_mprintf(
8993 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
8994 : "lower(organization) = lower('%q') AND organization_coordsys_id = %d",
8995 2 : sqlite3_value_text(argv[0]), sqlite3_value_int(argv[1]));
8996 2 : OGRErr err = OGRERR_NONE;
8997 2 : int nSRSId = SQLGetInteger(poDS->GetDB(), pszSQL, &err);
8998 2 : sqlite3_free(pszSQL);
8999 2 : if (err != OGRERR_NONE)
9000 1 : nSRSId = -1;
9001 2 : sqlite3_result_int(pContext, nSRSId);
9002 : }
9003 :
9004 : /************************************************************************/
9005 : /* OGRGeoPackageImportFromEPSG() */
9006 : /************************************************************************/
9007 :
9008 4 : static void OGRGeoPackageImportFromEPSG(sqlite3_context *pContext, int /*argc*/,
9009 : sqlite3_value **argv)
9010 : {
9011 4 : if (sqlite3_value_type(argv[0]) != SQLITE_INTEGER)
9012 : {
9013 1 : sqlite3_result_int(pContext, -1);
9014 2 : return;
9015 : }
9016 :
9017 : GDALGeoPackageDataset *poDS =
9018 3 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9019 3 : OGRSpatialReference oSRS;
9020 3 : if (oSRS.importFromEPSG(sqlite3_value_int(argv[0])) != OGRERR_NONE)
9021 : {
9022 1 : sqlite3_result_int(pContext, -1);
9023 1 : return;
9024 : }
9025 :
9026 2 : sqlite3_result_int(pContext, poDS->GetSrsId(&oSRS));
9027 : }
9028 :
9029 : /************************************************************************/
9030 : /* OGRGeoPackageRegisterGeometryExtension() */
9031 : /************************************************************************/
9032 :
9033 1 : static void OGRGeoPackageRegisterGeometryExtension(sqlite3_context *pContext,
9034 : int /*argc*/,
9035 : sqlite3_value **argv)
9036 : {
9037 1 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9038 2 : sqlite3_value_type(argv[1]) != SQLITE_TEXT ||
9039 1 : sqlite3_value_type(argv[2]) != SQLITE_TEXT)
9040 : {
9041 0 : sqlite3_result_int(pContext, 0);
9042 0 : return;
9043 : }
9044 :
9045 : const char *pszTableName =
9046 1 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9047 : const char *pszGeomName =
9048 1 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9049 : const char *pszGeomType =
9050 1 : reinterpret_cast<const char *>(sqlite3_value_text(argv[2]));
9051 :
9052 : GDALGeoPackageDataset *poDS =
9053 1 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9054 :
9055 1 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
9056 1 : poDS->GetLayerByName(pszTableName));
9057 1 : if (poLyr == nullptr)
9058 : {
9059 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
9060 0 : sqlite3_result_int(pContext, 0);
9061 0 : return;
9062 : }
9063 1 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
9064 : {
9065 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
9066 0 : sqlite3_result_int(pContext, 0);
9067 0 : return;
9068 : }
9069 1 : const OGRwkbGeometryType eGeomType = OGRFromOGCGeomType(pszGeomType);
9070 1 : if (eGeomType == wkbUnknown)
9071 : {
9072 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry type name");
9073 0 : sqlite3_result_int(pContext, 0);
9074 0 : return;
9075 : }
9076 :
9077 1 : sqlite3_result_int(
9078 : pContext,
9079 1 : static_cast<int>(poLyr->CreateGeometryExtensionIfNecessary(eGeomType)));
9080 : }
9081 :
9082 : /************************************************************************/
9083 : /* OGRGeoPackageCreateSpatialIndex() */
9084 : /************************************************************************/
9085 :
9086 14 : static void OGRGeoPackageCreateSpatialIndex(sqlite3_context *pContext,
9087 : int /*argc*/, sqlite3_value **argv)
9088 : {
9089 27 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9090 13 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9091 : {
9092 2 : sqlite3_result_int(pContext, 0);
9093 2 : return;
9094 : }
9095 :
9096 : const char *pszTableName =
9097 12 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9098 : const char *pszGeomName =
9099 12 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9100 : GDALGeoPackageDataset *poDS =
9101 12 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9102 :
9103 12 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
9104 12 : poDS->GetLayerByName(pszTableName));
9105 12 : if (poLyr == nullptr)
9106 : {
9107 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
9108 1 : sqlite3_result_int(pContext, 0);
9109 1 : return;
9110 : }
9111 11 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
9112 : {
9113 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
9114 1 : sqlite3_result_int(pContext, 0);
9115 1 : return;
9116 : }
9117 :
9118 10 : sqlite3_result_int(pContext, poLyr->CreateSpatialIndex());
9119 : }
9120 :
9121 : /************************************************************************/
9122 : /* OGRGeoPackageDisableSpatialIndex() */
9123 : /************************************************************************/
9124 :
9125 12 : static void OGRGeoPackageDisableSpatialIndex(sqlite3_context *pContext,
9126 : int /*argc*/, sqlite3_value **argv)
9127 : {
9128 23 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9129 11 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9130 : {
9131 2 : sqlite3_result_int(pContext, 0);
9132 2 : return;
9133 : }
9134 :
9135 : const char *pszTableName =
9136 10 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9137 : const char *pszGeomName =
9138 10 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9139 : GDALGeoPackageDataset *poDS =
9140 10 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9141 :
9142 10 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
9143 10 : poDS->GetLayerByName(pszTableName));
9144 10 : if (poLyr == nullptr)
9145 : {
9146 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
9147 1 : sqlite3_result_int(pContext, 0);
9148 1 : return;
9149 : }
9150 9 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
9151 : {
9152 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
9153 1 : sqlite3_result_int(pContext, 0);
9154 1 : return;
9155 : }
9156 :
9157 8 : sqlite3_result_int(pContext, poLyr->DropSpatialIndex(true));
9158 : }
9159 :
9160 : /************************************************************************/
9161 : /* OGRGeoPackageHasSpatialIndex() */
9162 : /************************************************************************/
9163 :
9164 29 : static void OGRGeoPackageHasSpatialIndex(sqlite3_context *pContext,
9165 : int /*argc*/, sqlite3_value **argv)
9166 : {
9167 57 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9168 28 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9169 : {
9170 2 : sqlite3_result_int(pContext, 0);
9171 2 : return;
9172 : }
9173 :
9174 : const char *pszTableName =
9175 27 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9176 : const char *pszGeomName =
9177 27 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9178 : GDALGeoPackageDataset *poDS =
9179 27 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9180 :
9181 27 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
9182 27 : poDS->GetLayerByName(pszTableName));
9183 27 : if (poLyr == nullptr)
9184 : {
9185 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
9186 1 : sqlite3_result_int(pContext, 0);
9187 1 : return;
9188 : }
9189 26 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
9190 : {
9191 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
9192 1 : sqlite3_result_int(pContext, 0);
9193 1 : return;
9194 : }
9195 :
9196 25 : poLyr->RunDeferredCreationIfNecessary();
9197 25 : poLyr->CreateSpatialIndexIfNecessary();
9198 :
9199 25 : sqlite3_result_int(pContext, poLyr->HasSpatialIndex());
9200 : }
9201 :
9202 : /************************************************************************/
9203 : /* GPKG_hstore_get_value() */
9204 : /************************************************************************/
9205 :
9206 4 : static void GPKG_hstore_get_value(sqlite3_context *pContext, int /*argc*/,
9207 : sqlite3_value **argv)
9208 : {
9209 7 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9210 3 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9211 : {
9212 2 : sqlite3_result_null(pContext);
9213 2 : return;
9214 : }
9215 :
9216 : const char *pszHStore =
9217 2 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9218 : const char *pszSearchedKey =
9219 2 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9220 2 : char *pszValue = OGRHStoreGetValue(pszHStore, pszSearchedKey);
9221 2 : if (pszValue != nullptr)
9222 1 : sqlite3_result_text(pContext, pszValue, -1, CPLFree);
9223 : else
9224 1 : sqlite3_result_null(pContext);
9225 : }
9226 :
9227 : /************************************************************************/
9228 : /* GPKG_GDAL_GetMemFileFromBlob() */
9229 : /************************************************************************/
9230 :
9231 105 : static CPLString GPKG_GDAL_GetMemFileFromBlob(sqlite3_value **argv)
9232 : {
9233 105 : int nBytes = sqlite3_value_bytes(argv[0]);
9234 : const GByte *pabyBLOB =
9235 105 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
9236 : const CPLString osMemFileName(
9237 105 : VSIMemGenerateHiddenFilename("GPKG_GDAL_GetMemFileFromBlob"));
9238 105 : VSILFILE *fp = VSIFileFromMemBuffer(
9239 : osMemFileName.c_str(), const_cast<GByte *>(pabyBLOB), nBytes, FALSE);
9240 105 : VSIFCloseL(fp);
9241 105 : return osMemFileName;
9242 : }
9243 :
9244 : /************************************************************************/
9245 : /* GPKG_GDAL_GetMimeType() */
9246 : /************************************************************************/
9247 :
9248 35 : static void GPKG_GDAL_GetMimeType(sqlite3_context *pContext, int /*argc*/,
9249 : sqlite3_value **argv)
9250 : {
9251 35 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
9252 : {
9253 0 : sqlite3_result_null(pContext);
9254 0 : return;
9255 : }
9256 :
9257 70 : CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
9258 : GDALDriver *poDriver =
9259 35 : GDALDriver::FromHandle(GDALIdentifyDriver(osMemFileName, nullptr));
9260 35 : if (poDriver != nullptr)
9261 : {
9262 35 : const char *pszRes = nullptr;
9263 35 : if (EQUAL(poDriver->GetDescription(), "PNG"))
9264 23 : pszRes = "image/png";
9265 12 : else if (EQUAL(poDriver->GetDescription(), "JPEG"))
9266 6 : pszRes = "image/jpeg";
9267 6 : else if (EQUAL(poDriver->GetDescription(), "WEBP"))
9268 6 : pszRes = "image/x-webp";
9269 0 : else if (EQUAL(poDriver->GetDescription(), "GTIFF"))
9270 0 : pszRes = "image/tiff";
9271 : else
9272 0 : pszRes = CPLSPrintf("gdal/%s", poDriver->GetDescription());
9273 35 : sqlite3_result_text(pContext, pszRes, -1, SQLITE_TRANSIENT);
9274 : }
9275 : else
9276 0 : sqlite3_result_null(pContext);
9277 35 : VSIUnlink(osMemFileName);
9278 : }
9279 :
9280 : /************************************************************************/
9281 : /* GPKG_GDAL_GetBandCount() */
9282 : /************************************************************************/
9283 :
9284 35 : static void GPKG_GDAL_GetBandCount(sqlite3_context *pContext, int /*argc*/,
9285 : sqlite3_value **argv)
9286 : {
9287 35 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
9288 : {
9289 0 : sqlite3_result_null(pContext);
9290 0 : return;
9291 : }
9292 :
9293 70 : CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
9294 : auto poDS = std::unique_ptr<GDALDataset>(
9295 : GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
9296 70 : nullptr, nullptr, nullptr));
9297 35 : if (poDS != nullptr)
9298 : {
9299 35 : sqlite3_result_int(pContext, poDS->GetRasterCount());
9300 : }
9301 : else
9302 0 : sqlite3_result_null(pContext);
9303 35 : VSIUnlink(osMemFileName);
9304 : }
9305 :
9306 : /************************************************************************/
9307 : /* GPKG_GDAL_HasColorTable() */
9308 : /************************************************************************/
9309 :
9310 35 : static void GPKG_GDAL_HasColorTable(sqlite3_context *pContext, int /*argc*/,
9311 : sqlite3_value **argv)
9312 : {
9313 35 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
9314 : {
9315 0 : sqlite3_result_null(pContext);
9316 0 : return;
9317 : }
9318 :
9319 70 : CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
9320 : auto poDS = std::unique_ptr<GDALDataset>(
9321 : GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
9322 70 : nullptr, nullptr, nullptr));
9323 35 : if (poDS != nullptr)
9324 : {
9325 35 : sqlite3_result_int(
9326 46 : pContext, poDS->GetRasterCount() == 1 &&
9327 11 : poDS->GetRasterBand(1)->GetColorTable() != nullptr);
9328 : }
9329 : else
9330 0 : sqlite3_result_null(pContext);
9331 35 : VSIUnlink(osMemFileName);
9332 : }
9333 :
9334 : /************************************************************************/
9335 : /* GetRasterLayerDataset() */
9336 : /************************************************************************/
9337 :
9338 : GDALDataset *
9339 12 : GDALGeoPackageDataset::GetRasterLayerDataset(const char *pszLayerName)
9340 : {
9341 12 : const auto oIter = m_oCachedRasterDS.find(pszLayerName);
9342 12 : if (oIter != m_oCachedRasterDS.end())
9343 10 : return oIter->second.get();
9344 :
9345 : auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
9346 4 : (std::string("GPKG:\"") + m_pszFilename + "\":" + pszLayerName).c_str(),
9347 4 : GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
9348 2 : if (!poDS)
9349 : {
9350 0 : return nullptr;
9351 : }
9352 2 : m_oCachedRasterDS[pszLayerName] = std::move(poDS);
9353 2 : return m_oCachedRasterDS[pszLayerName].get();
9354 : }
9355 :
9356 : /************************************************************************/
9357 : /* GPKG_gdal_get_layer_pixel_value() */
9358 : /************************************************************************/
9359 :
9360 : // NOTE: keep in sync implementations in ogrsqlitesqlfunctionscommon.cpp
9361 : // and ogrgeopackagedatasource.cpp
9362 13 : static void GPKG_gdal_get_layer_pixel_value(sqlite3_context *pContext, int argc,
9363 : sqlite3_value **argv)
9364 : {
9365 13 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
9366 : {
9367 1 : CPLError(CE_Failure, CPLE_AppDefined,
9368 : "Invalid arguments to gdal_get_layer_pixel_value()");
9369 1 : sqlite3_result_null(pContext);
9370 1 : return;
9371 : }
9372 :
9373 : const char *pszLayerName =
9374 12 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9375 :
9376 : GDALGeoPackageDataset *poGlobalDS =
9377 12 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9378 12 : auto poDS = poGlobalDS->GetRasterLayerDataset(pszLayerName);
9379 12 : if (!poDS)
9380 : {
9381 0 : sqlite3_result_null(pContext);
9382 0 : return;
9383 : }
9384 :
9385 12 : OGRSQLite_gdal_get_pixel_value_common("gdal_get_layer_pixel_value",
9386 : pContext, argc, argv, poDS);
9387 : }
9388 :
9389 : /************************************************************************/
9390 : /* GPKG_ogr_layer_Extent() */
9391 : /************************************************************************/
9392 :
9393 3 : static void GPKG_ogr_layer_Extent(sqlite3_context *pContext, int /*argc*/,
9394 : sqlite3_value **argv)
9395 : {
9396 3 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
9397 : {
9398 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s: Invalid argument type",
9399 : "ogr_layer_Extent");
9400 1 : sqlite3_result_null(pContext);
9401 2 : return;
9402 : }
9403 :
9404 : const char *pszLayerName =
9405 2 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9406 : GDALGeoPackageDataset *poDS =
9407 2 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9408 2 : OGRLayer *poLayer = poDS->GetLayerByName(pszLayerName);
9409 2 : if (!poLayer)
9410 : {
9411 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s: unknown layer",
9412 : "ogr_layer_Extent");
9413 1 : sqlite3_result_null(pContext);
9414 1 : return;
9415 : }
9416 :
9417 1 : if (poLayer->GetGeomType() == wkbNone)
9418 : {
9419 0 : sqlite3_result_null(pContext);
9420 0 : return;
9421 : }
9422 :
9423 1 : OGREnvelope sExtent;
9424 1 : if (poLayer->GetExtent(&sExtent) != OGRERR_NONE)
9425 : {
9426 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s: Cannot fetch layer extent",
9427 : "ogr_layer_Extent");
9428 0 : sqlite3_result_null(pContext);
9429 0 : return;
9430 : }
9431 :
9432 1 : OGRPolygon oPoly;
9433 1 : OGRLinearRing *poRing = new OGRLinearRing();
9434 1 : oPoly.addRingDirectly(poRing);
9435 1 : poRing->addPoint(sExtent.MinX, sExtent.MinY);
9436 1 : poRing->addPoint(sExtent.MaxX, sExtent.MinY);
9437 1 : poRing->addPoint(sExtent.MaxX, sExtent.MaxY);
9438 1 : poRing->addPoint(sExtent.MinX, sExtent.MaxY);
9439 1 : poRing->addPoint(sExtent.MinX, sExtent.MinY);
9440 :
9441 1 : const auto poSRS = poLayer->GetSpatialRef();
9442 1 : const int nSRID = poDS->GetSrsId(poSRS);
9443 1 : size_t nBLOBDestLen = 0;
9444 : GByte *pabyDestBLOB =
9445 1 : GPkgGeometryFromOGR(&oPoly, nSRID, nullptr, &nBLOBDestLen);
9446 1 : if (!pabyDestBLOB)
9447 : {
9448 0 : sqlite3_result_null(pContext);
9449 0 : return;
9450 : }
9451 1 : sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
9452 : VSIFree);
9453 : }
9454 :
9455 : /************************************************************************/
9456 : /* InstallSQLFunctions() */
9457 : /************************************************************************/
9458 :
9459 : #ifndef SQLITE_DETERMINISTIC
9460 : #define SQLITE_DETERMINISTIC 0
9461 : #endif
9462 :
9463 : #ifndef SQLITE_INNOCUOUS
9464 : #define SQLITE_INNOCUOUS 0
9465 : #endif
9466 :
9467 : #ifndef UTF8_INNOCUOUS
9468 : #define UTF8_INNOCUOUS (SQLITE_UTF8 | SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS)
9469 : #endif
9470 :
9471 1899 : void GDALGeoPackageDataset::InstallSQLFunctions()
9472 : {
9473 1899 : InitSpatialite();
9474 :
9475 : // Enable SpatiaLite 4.3 "amphibious" mode, i.e. that SpatiaLite functions
9476 : // that take geometries will accept GPKG encoded geometries without
9477 : // explicit conversion.
9478 : // Use sqlite3_exec() instead of SQLCommand() since we don't want verbose
9479 : // error.
9480 1899 : sqlite3_exec(hDB, "SELECT EnableGpkgAmphibiousMode()", nullptr, nullptr,
9481 : nullptr);
9482 :
9483 : /* Used by RTree Spatial Index Extension */
9484 1899 : sqlite3_create_function(hDB, "ST_MinX", 1, UTF8_INNOCUOUS, nullptr,
9485 : OGRGeoPackageSTMinX, nullptr, nullptr);
9486 1899 : sqlite3_create_function(hDB, "ST_MinY", 1, UTF8_INNOCUOUS, nullptr,
9487 : OGRGeoPackageSTMinY, nullptr, nullptr);
9488 1899 : sqlite3_create_function(hDB, "ST_MaxX", 1, UTF8_INNOCUOUS, nullptr,
9489 : OGRGeoPackageSTMaxX, nullptr, nullptr);
9490 1899 : sqlite3_create_function(hDB, "ST_MaxY", 1, UTF8_INNOCUOUS, nullptr,
9491 : OGRGeoPackageSTMaxY, nullptr, nullptr);
9492 1899 : sqlite3_create_function(hDB, "ST_IsEmpty", 1, UTF8_INNOCUOUS, nullptr,
9493 : OGRGeoPackageSTIsEmpty, nullptr, nullptr);
9494 :
9495 : /* Used by Geometry Type Triggers Extension */
9496 1899 : sqlite3_create_function(hDB, "ST_GeometryType", 1, UTF8_INNOCUOUS, nullptr,
9497 : OGRGeoPackageSTGeometryType, nullptr, nullptr);
9498 1899 : sqlite3_create_function(hDB, "GPKG_IsAssignable", 2, UTF8_INNOCUOUS,
9499 : nullptr, OGRGeoPackageGPKGIsAssignable, nullptr,
9500 : nullptr);
9501 :
9502 : /* Used by Geometry SRS ID Triggers Extension */
9503 1899 : sqlite3_create_function(hDB, "ST_SRID", 1, UTF8_INNOCUOUS, nullptr,
9504 : OGRGeoPackageSTSRID, nullptr, nullptr);
9505 :
9506 : /* Spatialite-like functions */
9507 1899 : sqlite3_create_function(hDB, "CreateSpatialIndex", 2, SQLITE_UTF8, this,
9508 : OGRGeoPackageCreateSpatialIndex, nullptr, nullptr);
9509 1899 : sqlite3_create_function(hDB, "DisableSpatialIndex", 2, SQLITE_UTF8, this,
9510 : OGRGeoPackageDisableSpatialIndex, nullptr, nullptr);
9511 1899 : sqlite3_create_function(hDB, "HasSpatialIndex", 2, SQLITE_UTF8, this,
9512 : OGRGeoPackageHasSpatialIndex, nullptr, nullptr);
9513 :
9514 : // HSTORE functions
9515 1899 : sqlite3_create_function(hDB, "hstore_get_value", 2, UTF8_INNOCUOUS, nullptr,
9516 : GPKG_hstore_get_value, nullptr, nullptr);
9517 :
9518 : // Override a few Spatialite functions to work with gpkg_spatial_ref_sys
9519 1899 : sqlite3_create_function(hDB, "ST_Transform", 2, UTF8_INNOCUOUS, this,
9520 : OGRGeoPackageTransform, nullptr, nullptr);
9521 1899 : sqlite3_create_function(hDB, "Transform", 2, UTF8_INNOCUOUS, this,
9522 : OGRGeoPackageTransform, nullptr, nullptr);
9523 1899 : sqlite3_create_function(hDB, "SridFromAuthCRS", 2, SQLITE_UTF8, this,
9524 : OGRGeoPackageSridFromAuthCRS, nullptr, nullptr);
9525 :
9526 1899 : sqlite3_create_function(hDB, "ST_EnvIntersects", 2, UTF8_INNOCUOUS, nullptr,
9527 : OGRGeoPackageSTEnvelopesIntersectsTwoParams,
9528 : nullptr, nullptr);
9529 1899 : sqlite3_create_function(
9530 : hDB, "ST_EnvelopesIntersects", 2, UTF8_INNOCUOUS, nullptr,
9531 : OGRGeoPackageSTEnvelopesIntersectsTwoParams, nullptr, nullptr);
9532 :
9533 1899 : sqlite3_create_function(hDB, "ST_EnvIntersects", 5, UTF8_INNOCUOUS, nullptr,
9534 : OGRGeoPackageSTEnvelopesIntersects, nullptr,
9535 : nullptr);
9536 1899 : sqlite3_create_function(hDB, "ST_EnvelopesIntersects", 5, UTF8_INNOCUOUS,
9537 : nullptr, OGRGeoPackageSTEnvelopesIntersects,
9538 : nullptr, nullptr);
9539 :
9540 : // Implementation that directly hacks the GeoPackage geometry blob header
9541 1899 : sqlite3_create_function(hDB, "SetSRID", 2, UTF8_INNOCUOUS, nullptr,
9542 : OGRGeoPackageSetSRID, nullptr, nullptr);
9543 :
9544 : // GDAL specific function
9545 1899 : sqlite3_create_function(hDB, "ImportFromEPSG", 1, SQLITE_UTF8, this,
9546 : OGRGeoPackageImportFromEPSG, nullptr, nullptr);
9547 :
9548 : // May be used by ogrmerge.py
9549 1899 : sqlite3_create_function(hDB, "RegisterGeometryExtension", 3, SQLITE_UTF8,
9550 : this, OGRGeoPackageRegisterGeometryExtension,
9551 : nullptr, nullptr);
9552 :
9553 1899 : if (OGRGeometryFactory::haveGEOS())
9554 : {
9555 1899 : sqlite3_create_function(hDB, "ST_MakeValid", 1, UTF8_INNOCUOUS, nullptr,
9556 : OGRGeoPackageSTMakeValid, nullptr, nullptr);
9557 : }
9558 :
9559 1899 : sqlite3_create_function(hDB, "ST_Length", 1, UTF8_INNOCUOUS, nullptr,
9560 : OGRGeoPackageLengthOrGeodesicLength, nullptr,
9561 : nullptr);
9562 1899 : sqlite3_create_function(hDB, "ST_Length", 2, UTF8_INNOCUOUS, this,
9563 : OGRGeoPackageLengthOrGeodesicLength, nullptr,
9564 : nullptr);
9565 :
9566 1899 : sqlite3_create_function(hDB, "ST_Area", 1, UTF8_INNOCUOUS, nullptr,
9567 : OGRGeoPackageSTArea, nullptr, nullptr);
9568 1899 : sqlite3_create_function(hDB, "ST_Area", 2, UTF8_INNOCUOUS, this,
9569 : OGRGeoPackageGeodesicArea, nullptr, nullptr);
9570 :
9571 : // Debug functions
9572 1899 : if (CPLTestBool(CPLGetConfigOption("GPKG_DEBUG", "FALSE")))
9573 : {
9574 417 : sqlite3_create_function(hDB, "GDAL_GetMimeType", 1,
9575 : SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9576 : GPKG_GDAL_GetMimeType, nullptr, nullptr);
9577 417 : sqlite3_create_function(hDB, "GDAL_GetBandCount", 1,
9578 : SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9579 : GPKG_GDAL_GetBandCount, nullptr, nullptr);
9580 417 : sqlite3_create_function(hDB, "GDAL_HasColorTable", 1,
9581 : SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9582 : GPKG_GDAL_HasColorTable, nullptr, nullptr);
9583 : }
9584 :
9585 1899 : sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 5, SQLITE_UTF8,
9586 : this, GPKG_gdal_get_layer_pixel_value, nullptr,
9587 : nullptr);
9588 1899 : sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 6, SQLITE_UTF8,
9589 : this, GPKG_gdal_get_layer_pixel_value, nullptr,
9590 : nullptr);
9591 :
9592 : // Function from VirtualOGR
9593 1899 : sqlite3_create_function(hDB, "ogr_layer_Extent", 1, SQLITE_UTF8, this,
9594 : GPKG_ogr_layer_Extent, nullptr, nullptr);
9595 :
9596 1899 : m_pSQLFunctionData = OGRSQLiteRegisterSQLFunctionsCommon(hDB);
9597 1899 : }
9598 :
9599 : /************************************************************************/
9600 : /* OpenOrCreateDB() */
9601 : /************************************************************************/
9602 :
9603 1900 : bool GDALGeoPackageDataset::OpenOrCreateDB(int flags)
9604 : {
9605 1900 : const bool bSuccess = OGRSQLiteBaseDataSource::OpenOrCreateDB(
9606 : flags, /*bRegisterOGR2SQLiteExtensions=*/false,
9607 : /*bLoadExtensions=*/true);
9608 1900 : if (!bSuccess)
9609 6 : return false;
9610 :
9611 : // Turning on recursive_triggers is needed so that DELETE triggers fire
9612 : // in a INSERT OR REPLACE statement. In particular this is needed to
9613 : // make sure gpkg_ogr_contents.feature_count is properly updated.
9614 1894 : SQLCommand(hDB, "PRAGMA recursive_triggers = 1");
9615 :
9616 1894 : InstallSQLFunctions();
9617 :
9618 : const char *pszSqlitePragma =
9619 1894 : CPLGetConfigOption("OGR_SQLITE_PRAGMA", nullptr);
9620 1894 : OGRErr eErr = OGRERR_NONE;
9621 6 : if ((!pszSqlitePragma || !strstr(pszSqlitePragma, "trusted_schema")) &&
9622 : // Older sqlite versions don't have this pragma
9623 3794 : SQLGetInteger(hDB, "PRAGMA trusted_schema", &eErr) == 0 &&
9624 1894 : eErr == OGRERR_NONE)
9625 : {
9626 1894 : bool bNeedsTrustedSchema = false;
9627 :
9628 : // Current SQLite versions require PRAGMA trusted_schema = 1 to be
9629 : // able to use the RTree from triggers, which is only needed when
9630 : // modifying the RTree.
9631 4683 : if (((flags & SQLITE_OPEN_READWRITE) != 0 ||
9632 2893 : (flags & SQLITE_OPEN_CREATE) != 0) &&
9633 999 : OGRSQLiteRTreeRequiresTrustedSchemaOn())
9634 : {
9635 999 : bNeedsTrustedSchema = true;
9636 : }
9637 :
9638 : #ifdef HAVE_SPATIALITE
9639 : // Spatialite <= 5.1.0 doesn't declare its functions as SQLITE_INNOCUOUS
9640 895 : if (!bNeedsTrustedSchema && HasExtensionsTable() &&
9641 817 : SQLGetInteger(
9642 : hDB,
9643 : "SELECT 1 FROM gpkg_extensions WHERE "
9644 : "extension_name ='gdal_spatialite_computed_geom_column'",
9645 1 : nullptr) == 1 &&
9646 2789 : SpatialiteRequiresTrustedSchemaOn() && AreSpatialiteTriggersSafe())
9647 : {
9648 1 : bNeedsTrustedSchema = true;
9649 : }
9650 : #endif
9651 :
9652 1894 : if (bNeedsTrustedSchema)
9653 : {
9654 1000 : CPLDebug("GPKG", "Setting PRAGMA trusted_schema = 1");
9655 1000 : SQLCommand(hDB, "PRAGMA trusted_schema = 1");
9656 : }
9657 : }
9658 :
9659 1894 : return true;
9660 : }
9661 :
9662 : /************************************************************************/
9663 : /* GetLayerWithGetSpatialWhereByName() */
9664 : /************************************************************************/
9665 :
9666 : std::pair<OGRLayer *, IOGRSQLiteGetSpatialWhere *>
9667 90 : GDALGeoPackageDataset::GetLayerWithGetSpatialWhereByName(const char *pszName)
9668 : {
9669 : OGRGeoPackageLayer *poRet =
9670 90 : cpl::down_cast<OGRGeoPackageLayer *>(GetLayerByName(pszName));
9671 90 : return std::pair(poRet, poRet);
9672 : }
9673 :
9674 : /************************************************************************/
9675 : /* CommitTransaction() */
9676 : /************************************************************************/
9677 :
9678 176 : OGRErr GDALGeoPackageDataset::CommitTransaction()
9679 :
9680 : {
9681 176 : if (nSoftTransactionLevel == 1)
9682 : {
9683 175 : FlushMetadata();
9684 397 : for (int i = 0; i < m_nLayers; i++)
9685 : {
9686 222 : m_papoLayers[i]->DoJobAtTransactionCommit();
9687 : }
9688 : }
9689 :
9690 176 : return OGRSQLiteBaseDataSource::CommitTransaction();
9691 : }
9692 :
9693 : /************************************************************************/
9694 : /* RollbackTransaction() */
9695 : /************************************************************************/
9696 :
9697 34 : OGRErr GDALGeoPackageDataset::RollbackTransaction()
9698 :
9699 : {
9700 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9701 68 : std::vector<bool> abAddTriggers;
9702 34 : std::vector<bool> abTriggersDeletedInTransaction;
9703 : #endif
9704 34 : if (nSoftTransactionLevel == 1)
9705 : {
9706 33 : FlushMetadata();
9707 68 : for (int i = 0; i < m_nLayers; i++)
9708 : {
9709 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9710 35 : abAddTriggers.push_back(
9711 35 : m_papoLayers[i]->GetAddOGRFeatureCountTriggers());
9712 35 : abTriggersDeletedInTransaction.push_back(
9713 35 : m_papoLayers[i]
9714 35 : ->GetOGRFeatureCountTriggersDeletedInTransaction());
9715 35 : m_papoLayers[i]->SetAddOGRFeatureCountTriggers(false);
9716 : #endif
9717 35 : m_papoLayers[i]->DoJobAtTransactionRollback();
9718 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9719 35 : m_papoLayers[i]->DisableFeatureCount();
9720 : #endif
9721 : }
9722 : }
9723 :
9724 34 : const OGRErr eErr = OGRSQLiteBaseDataSource::RollbackTransaction();
9725 :
9726 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9727 34 : if (!abAddTriggers.empty())
9728 : {
9729 66 : for (int i = 0; i < m_nLayers; i++)
9730 : {
9731 35 : if (abTriggersDeletedInTransaction[i])
9732 : {
9733 7 : m_papoLayers[i]->SetOGRFeatureCountTriggersEnabled(true);
9734 : }
9735 : else
9736 : {
9737 28 : m_papoLayers[i]->SetAddOGRFeatureCountTriggers(
9738 56 : abAddTriggers[i]);
9739 : }
9740 : }
9741 : }
9742 : #endif
9743 68 : return eErr;
9744 : }
9745 :
9746 : /************************************************************************/
9747 : /* GetGeometryTypeString() */
9748 : /************************************************************************/
9749 :
9750 : const char *
9751 1303 : GDALGeoPackageDataset::GetGeometryTypeString(OGRwkbGeometryType eType)
9752 : {
9753 1303 : const char *pszGPKGGeomType = OGRToOGCGeomType(eType);
9754 1315 : if (EQUAL(pszGPKGGeomType, "GEOMETRYCOLLECTION") &&
9755 12 : CPLTestBool(CPLGetConfigOption("OGR_GPKG_GEOMCOLLECTION", "NO")))
9756 : {
9757 0 : pszGPKGGeomType = "GEOMCOLLECTION";
9758 : }
9759 1303 : return pszGPKGGeomType;
9760 : }
9761 :
9762 : /************************************************************************/
9763 : /* GetFieldDomainNames() */
9764 : /************************************************************************/
9765 :
9766 : std::vector<std::string>
9767 10 : GDALGeoPackageDataset::GetFieldDomainNames(CSLConstList) const
9768 : {
9769 10 : if (!HasDataColumnConstraintsTable())
9770 3 : return std::vector<std::string>();
9771 :
9772 14 : std::vector<std::string> oDomainNamesList;
9773 :
9774 7 : std::unique_ptr<SQLResult> oResultTable;
9775 : {
9776 : std::string osSQL =
9777 : "SELECT DISTINCT constraint_name "
9778 : "FROM gpkg_data_column_constraints "
9779 : "WHERE constraint_name NOT LIKE '_%_domain_description' "
9780 : "ORDER BY constraint_name "
9781 7 : "LIMIT 10000" // to avoid denial of service
9782 : ;
9783 7 : oResultTable = SQLQuery(hDB, osSQL.c_str());
9784 7 : if (!oResultTable)
9785 0 : return oDomainNamesList;
9786 : }
9787 :
9788 7 : if (oResultTable->RowCount() == 10000)
9789 : {
9790 0 : CPLError(CE_Warning, CPLE_AppDefined,
9791 : "Number of rows returned for field domain names has been "
9792 : "truncated.");
9793 : }
9794 7 : else if (oResultTable->RowCount() > 0)
9795 : {
9796 7 : oDomainNamesList.reserve(oResultTable->RowCount());
9797 89 : for (int i = 0; i < oResultTable->RowCount(); i++)
9798 : {
9799 82 : const char *pszConstraintName = oResultTable->GetValue(0, i);
9800 82 : if (!pszConstraintName)
9801 0 : continue;
9802 :
9803 82 : oDomainNamesList.emplace_back(pszConstraintName);
9804 : }
9805 : }
9806 :
9807 7 : return oDomainNamesList;
9808 : }
9809 :
9810 : /************************************************************************/
9811 : /* GetFieldDomain() */
9812 : /************************************************************************/
9813 :
9814 : const OGRFieldDomain *
9815 102 : GDALGeoPackageDataset::GetFieldDomain(const std::string &name) const
9816 : {
9817 102 : const auto baseRet = GDALDataset::GetFieldDomain(name);
9818 102 : if (baseRet)
9819 42 : return baseRet;
9820 :
9821 60 : if (!HasDataColumnConstraintsTable())
9822 4 : return nullptr;
9823 :
9824 56 : const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
9825 56 : const char *min_is_inclusive =
9826 56 : bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
9827 56 : const char *max_is_inclusive =
9828 56 : bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
9829 :
9830 56 : std::unique_ptr<SQLResult> oResultTable;
9831 : // Note: for coded domains, we use a little trick by using a dummy
9832 : // _{domainname}_domain_description enum that has a single entry whose
9833 : // description is the description of the main domain.
9834 : {
9835 56 : char *pszSQL = sqlite3_mprintf(
9836 : "SELECT constraint_type, value, min, %s, "
9837 : "max, %s, description, constraint_name "
9838 : "FROM gpkg_data_column_constraints "
9839 : "WHERE constraint_name IN ('%q', "
9840 : "'_%q_domain_description') "
9841 : "AND length(constraint_type) < 100 " // to
9842 : // avoid
9843 : // denial
9844 : // of
9845 : // service
9846 : "AND (value IS NULL OR length(value) < "
9847 : "10000) " // to avoid denial
9848 : // of service
9849 : "AND (description IS NULL OR "
9850 : "length(description) < 10000) " // to
9851 : // avoid
9852 : // denial
9853 : // of
9854 : // service
9855 : "ORDER BY value "
9856 : "LIMIT 10000", // to avoid denial of
9857 : // service
9858 : min_is_inclusive, max_is_inclusive, name.c_str(), name.c_str());
9859 56 : oResultTable = SQLQuery(hDB, pszSQL);
9860 56 : sqlite3_free(pszSQL);
9861 56 : if (!oResultTable)
9862 0 : return nullptr;
9863 : }
9864 56 : if (oResultTable->RowCount() == 0)
9865 : {
9866 15 : return nullptr;
9867 : }
9868 41 : if (oResultTable->RowCount() == 10000)
9869 : {
9870 0 : CPLError(CE_Warning, CPLE_AppDefined,
9871 : "Number of rows returned for field domain %s has been "
9872 : "truncated.",
9873 : name.c_str());
9874 : }
9875 :
9876 : // Try to find the field domain data type from fields that implement it
9877 41 : int nFieldType = -1;
9878 41 : OGRFieldSubType eSubType = OFSTNone;
9879 41 : if (HasDataColumnsTable())
9880 : {
9881 36 : char *pszSQL = sqlite3_mprintf(
9882 : "SELECT table_name, column_name FROM gpkg_data_columns WHERE "
9883 : "constraint_name = '%q' LIMIT 10",
9884 : name.c_str());
9885 72 : auto oResultTable2 = SQLQuery(hDB, pszSQL);
9886 36 : sqlite3_free(pszSQL);
9887 36 : if (oResultTable2 && oResultTable2->RowCount() >= 1)
9888 : {
9889 46 : for (int iRecord = 0; iRecord < oResultTable2->RowCount();
9890 : iRecord++)
9891 : {
9892 23 : const char *pszTableName = oResultTable2->GetValue(0, iRecord);
9893 23 : const char *pszColumnName = oResultTable2->GetValue(1, iRecord);
9894 23 : if (pszTableName == nullptr || pszColumnName == nullptr)
9895 0 : continue;
9896 : OGRLayer *poLayer =
9897 46 : const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
9898 23 : pszTableName);
9899 23 : if (poLayer)
9900 : {
9901 23 : const auto poFDefn = poLayer->GetLayerDefn();
9902 23 : int nIdx = poFDefn->GetFieldIndex(pszColumnName);
9903 23 : if (nIdx >= 0)
9904 : {
9905 23 : const auto poFieldDefn = poFDefn->GetFieldDefn(nIdx);
9906 23 : const auto eType = poFieldDefn->GetType();
9907 23 : if (nFieldType < 0)
9908 : {
9909 23 : nFieldType = eType;
9910 23 : eSubType = poFieldDefn->GetSubType();
9911 : }
9912 0 : else if ((eType == OFTInteger64 || eType == OFTReal) &&
9913 : nFieldType == OFTInteger)
9914 : {
9915 : // ok
9916 : }
9917 0 : else if (eType == OFTInteger &&
9918 0 : (nFieldType == OFTInteger64 ||
9919 : nFieldType == OFTReal))
9920 : {
9921 0 : nFieldType = OFTInteger;
9922 0 : eSubType = OFSTNone;
9923 : }
9924 0 : else if (nFieldType != eType)
9925 : {
9926 0 : nFieldType = -1;
9927 0 : eSubType = OFSTNone;
9928 0 : break;
9929 : }
9930 : }
9931 : }
9932 : }
9933 : }
9934 : }
9935 :
9936 41 : std::unique_ptr<OGRFieldDomain> poDomain;
9937 82 : std::vector<OGRCodedValue> asValues;
9938 41 : bool error = false;
9939 82 : CPLString osLastConstraintType;
9940 41 : int nFieldTypeFromEnumCode = -1;
9941 82 : std::string osConstraintDescription;
9942 82 : std::string osDescrConstraintName("_");
9943 41 : osDescrConstraintName += name;
9944 41 : osDescrConstraintName += "_domain_description";
9945 100 : for (int iRecord = 0; iRecord < oResultTable->RowCount(); iRecord++)
9946 : {
9947 63 : const char *pszConstraintType = oResultTable->GetValue(0, iRecord);
9948 63 : if (pszConstraintType == nullptr)
9949 0 : continue;
9950 63 : const char *pszValue = oResultTable->GetValue(1, iRecord);
9951 63 : const char *pszMin = oResultTable->GetValue(2, iRecord);
9952 : const bool bIsMinIncluded =
9953 63 : oResultTable->GetValueAsInteger(3, iRecord) == 1;
9954 63 : const char *pszMax = oResultTable->GetValue(4, iRecord);
9955 : const bool bIsMaxIncluded =
9956 63 : oResultTable->GetValueAsInteger(5, iRecord) == 1;
9957 63 : const char *pszDescription = oResultTable->GetValue(6, iRecord);
9958 63 : const char *pszConstraintName = oResultTable->GetValue(7, iRecord);
9959 :
9960 63 : if (!osLastConstraintType.empty() && osLastConstraintType != "enum")
9961 : {
9962 1 : CPLError(CE_Failure, CPLE_AppDefined,
9963 : "Only constraint of type 'enum' can have multiple rows");
9964 1 : error = true;
9965 1 : break;
9966 : }
9967 :
9968 62 : if (strcmp(pszConstraintType, "enum") == 0)
9969 : {
9970 42 : if (pszValue == nullptr)
9971 : {
9972 1 : CPLError(CE_Failure, CPLE_AppDefined,
9973 : "NULL in 'value' column of enumeration");
9974 1 : error = true;
9975 1 : break;
9976 : }
9977 41 : if (osDescrConstraintName == pszConstraintName)
9978 : {
9979 1 : if (pszDescription)
9980 : {
9981 1 : osConstraintDescription = pszDescription;
9982 : }
9983 1 : continue;
9984 : }
9985 40 : if (asValues.empty())
9986 : {
9987 20 : asValues.reserve(oResultTable->RowCount() + 1);
9988 : }
9989 : OGRCodedValue cv;
9990 : // intended: the 'value' column in GPKG is actually the code
9991 40 : cv.pszCode = VSI_STRDUP_VERBOSE(pszValue);
9992 40 : if (cv.pszCode == nullptr)
9993 : {
9994 0 : error = true;
9995 0 : break;
9996 : }
9997 40 : if (pszDescription)
9998 : {
9999 29 : cv.pszValue = VSI_STRDUP_VERBOSE(pszDescription);
10000 29 : if (cv.pszValue == nullptr)
10001 : {
10002 0 : VSIFree(cv.pszCode);
10003 0 : error = true;
10004 0 : break;
10005 : }
10006 : }
10007 : else
10008 : {
10009 11 : cv.pszValue = nullptr;
10010 : }
10011 :
10012 : // If we can't get the data type from field definition, guess it
10013 : // from code.
10014 40 : if (nFieldType < 0 && nFieldTypeFromEnumCode != OFTString)
10015 : {
10016 18 : switch (CPLGetValueType(cv.pszCode))
10017 : {
10018 13 : case CPL_VALUE_INTEGER:
10019 : {
10020 13 : if (nFieldTypeFromEnumCode != OFTReal &&
10021 : nFieldTypeFromEnumCode != OFTInteger64)
10022 : {
10023 9 : const auto nVal = CPLAtoGIntBig(cv.pszCode);
10024 17 : if (nVal < std::numeric_limits<int>::min() ||
10025 8 : nVal > std::numeric_limits<int>::max())
10026 : {
10027 3 : nFieldTypeFromEnumCode = OFTInteger64;
10028 : }
10029 : else
10030 : {
10031 6 : nFieldTypeFromEnumCode = OFTInteger;
10032 : }
10033 : }
10034 13 : break;
10035 : }
10036 :
10037 3 : case CPL_VALUE_REAL:
10038 3 : nFieldTypeFromEnumCode = OFTReal;
10039 3 : break;
10040 :
10041 2 : case CPL_VALUE_STRING:
10042 2 : nFieldTypeFromEnumCode = OFTString;
10043 2 : break;
10044 : }
10045 : }
10046 :
10047 40 : asValues.emplace_back(cv);
10048 : }
10049 20 : else if (strcmp(pszConstraintType, "range") == 0)
10050 : {
10051 : OGRField sMin;
10052 : OGRField sMax;
10053 14 : OGR_RawField_SetUnset(&sMin);
10054 14 : OGR_RawField_SetUnset(&sMax);
10055 14 : if (nFieldType != OFTInteger && nFieldType != OFTInteger64)
10056 8 : nFieldType = OFTReal;
10057 27 : if (pszMin != nullptr &&
10058 13 : CPLAtof(pszMin) != -std::numeric_limits<double>::infinity())
10059 : {
10060 10 : if (nFieldType == OFTInteger)
10061 3 : sMin.Integer = atoi(pszMin);
10062 7 : else if (nFieldType == OFTInteger64)
10063 3 : sMin.Integer64 = CPLAtoGIntBig(pszMin);
10064 : else /* if( nFieldType == OFTReal ) */
10065 4 : sMin.Real = CPLAtof(pszMin);
10066 : }
10067 27 : if (pszMax != nullptr &&
10068 13 : CPLAtof(pszMax) != std::numeric_limits<double>::infinity())
10069 : {
10070 10 : if (nFieldType == OFTInteger)
10071 3 : sMax.Integer = atoi(pszMax);
10072 7 : else if (nFieldType == OFTInteger64)
10073 3 : sMax.Integer64 = CPLAtoGIntBig(pszMax);
10074 : else /* if( nFieldType == OFTReal ) */
10075 4 : sMax.Real = CPLAtof(pszMax);
10076 : }
10077 28 : poDomain.reset(new OGRRangeFieldDomain(
10078 : name, pszDescription ? pszDescription : "",
10079 : static_cast<OGRFieldType>(nFieldType), eSubType, sMin,
10080 14 : bIsMinIncluded, sMax, bIsMaxIncluded));
10081 : }
10082 6 : else if (strcmp(pszConstraintType, "glob") == 0)
10083 : {
10084 5 : if (pszValue == nullptr)
10085 : {
10086 1 : CPLError(CE_Failure, CPLE_AppDefined,
10087 : "NULL in 'value' column of glob");
10088 1 : error = true;
10089 1 : break;
10090 : }
10091 4 : if (nFieldType < 0)
10092 1 : nFieldType = OFTString;
10093 8 : poDomain.reset(new OGRGlobFieldDomain(
10094 : name, pszDescription ? pszDescription : "",
10095 4 : static_cast<OGRFieldType>(nFieldType), eSubType, pszValue));
10096 : }
10097 : else
10098 : {
10099 1 : CPLError(CE_Failure, CPLE_AppDefined,
10100 : "Unhandled constraint_type: %s", pszConstraintType);
10101 1 : error = true;
10102 1 : break;
10103 : }
10104 :
10105 58 : osLastConstraintType = pszConstraintType;
10106 : }
10107 :
10108 41 : if (!asValues.empty())
10109 : {
10110 20 : if (nFieldType < 0)
10111 9 : nFieldType = nFieldTypeFromEnumCode;
10112 20 : poDomain.reset(
10113 : new OGRCodedFieldDomain(name, osConstraintDescription,
10114 : static_cast<OGRFieldType>(nFieldType),
10115 20 : eSubType, std::move(asValues)));
10116 : }
10117 :
10118 41 : if (error)
10119 : {
10120 4 : return nullptr;
10121 : }
10122 :
10123 37 : m_oMapFieldDomains[name] = std::move(poDomain);
10124 37 : return GDALDataset::GetFieldDomain(name);
10125 : }
10126 :
10127 : /************************************************************************/
10128 : /* AddFieldDomain() */
10129 : /************************************************************************/
10130 :
10131 18 : bool GDALGeoPackageDataset::AddFieldDomain(
10132 : std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
10133 : {
10134 36 : const std::string domainName(domain->GetName());
10135 18 : if (!GetUpdate())
10136 : {
10137 0 : CPLError(CE_Failure, CPLE_NotSupported,
10138 : "AddFieldDomain() not supported on read-only dataset");
10139 0 : return false;
10140 : }
10141 18 : if (GetFieldDomain(domainName) != nullptr)
10142 : {
10143 1 : failureReason = "A domain of identical name already exists";
10144 1 : return false;
10145 : }
10146 17 : if (!CreateColumnsTableAndColumnConstraintsTablesIfNecessary())
10147 0 : return false;
10148 :
10149 17 : const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
10150 17 : const char *min_is_inclusive =
10151 17 : bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
10152 17 : const char *max_is_inclusive =
10153 17 : bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
10154 :
10155 17 : const auto &osDescription = domain->GetDescription();
10156 17 : switch (domain->GetDomainType())
10157 : {
10158 11 : case OFDT_CODED:
10159 : {
10160 : const auto poCodedDomain =
10161 11 : cpl::down_cast<const OGRCodedFieldDomain *>(domain.get());
10162 11 : if (!osDescription.empty())
10163 : {
10164 : // We use a little trick by using a dummy
10165 : // _{domainname}_domain_description enum that has a single
10166 : // entry whose description is the description of the main
10167 : // domain.
10168 1 : char *pszSQL = sqlite3_mprintf(
10169 : "INSERT INTO gpkg_data_column_constraints ("
10170 : "constraint_name, constraint_type, value, "
10171 : "min, %s, max, %s, "
10172 : "description) VALUES ("
10173 : "'_%q_domain_description', 'enum', '', NULL, NULL, NULL, "
10174 : "NULL, %Q)",
10175 : min_is_inclusive, max_is_inclusive, domainName.c_str(),
10176 : osDescription.c_str());
10177 1 : CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
10178 1 : sqlite3_free(pszSQL);
10179 : }
10180 11 : const auto &enumeration = poCodedDomain->GetEnumeration();
10181 33 : for (int i = 0; enumeration[i].pszCode != nullptr; ++i)
10182 : {
10183 22 : char *pszSQL = sqlite3_mprintf(
10184 : "INSERT INTO gpkg_data_column_constraints ("
10185 : "constraint_name, constraint_type, value, "
10186 : "min, %s, max, %s, "
10187 : "description) VALUES ("
10188 : "'%q', 'enum', '%q', NULL, NULL, NULL, NULL, %Q)",
10189 : min_is_inclusive, max_is_inclusive, domainName.c_str(),
10190 22 : enumeration[i].pszCode, enumeration[i].pszValue);
10191 22 : bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
10192 22 : sqlite3_free(pszSQL);
10193 22 : if (!ok)
10194 0 : return false;
10195 : }
10196 11 : break;
10197 : }
10198 :
10199 5 : case OFDT_RANGE:
10200 : {
10201 : const auto poRangeDomain =
10202 5 : cpl::down_cast<const OGRRangeFieldDomain *>(domain.get());
10203 5 : const auto eFieldType = poRangeDomain->GetFieldType();
10204 5 : if (eFieldType != OFTInteger && eFieldType != OFTInteger64 &&
10205 : eFieldType != OFTReal)
10206 : {
10207 : failureReason = "Only range domains of numeric type are "
10208 0 : "supported in GeoPackage";
10209 0 : return false;
10210 : }
10211 :
10212 5 : double dfMin = -std::numeric_limits<double>::infinity();
10213 5 : double dfMax = std::numeric_limits<double>::infinity();
10214 5 : bool bMinIsInclusive = true;
10215 5 : const auto &sMin = poRangeDomain->GetMin(bMinIsInclusive);
10216 5 : bool bMaxIsInclusive = true;
10217 5 : const auto &sMax = poRangeDomain->GetMax(bMaxIsInclusive);
10218 5 : if (eFieldType == OFTInteger)
10219 : {
10220 1 : if (!OGR_RawField_IsUnset(&sMin))
10221 1 : dfMin = sMin.Integer;
10222 1 : if (!OGR_RawField_IsUnset(&sMax))
10223 1 : dfMax = sMax.Integer;
10224 : }
10225 4 : else if (eFieldType == OFTInteger64)
10226 : {
10227 1 : if (!OGR_RawField_IsUnset(&sMin))
10228 1 : dfMin = static_cast<double>(sMin.Integer64);
10229 1 : if (!OGR_RawField_IsUnset(&sMax))
10230 1 : dfMax = static_cast<double>(sMax.Integer64);
10231 : }
10232 : else /* if( eFieldType == OFTReal ) */
10233 : {
10234 3 : if (!OGR_RawField_IsUnset(&sMin))
10235 3 : dfMin = sMin.Real;
10236 3 : if (!OGR_RawField_IsUnset(&sMax))
10237 3 : dfMax = sMax.Real;
10238 : }
10239 :
10240 5 : sqlite3_stmt *hInsertStmt = nullptr;
10241 : const char *pszSQL =
10242 5 : CPLSPrintf("INSERT INTO gpkg_data_column_constraints ("
10243 : "constraint_name, constraint_type, value, "
10244 : "min, %s, max, %s, "
10245 : "description) VALUES ("
10246 : "?, 'range', NULL, ?, ?, ?, ?, ?)",
10247 : min_is_inclusive, max_is_inclusive);
10248 5 : if (sqlite3_prepare_v2(hDB, pszSQL, -1, &hInsertStmt, nullptr) !=
10249 : SQLITE_OK)
10250 : {
10251 0 : CPLError(CE_Failure, CPLE_AppDefined,
10252 : "failed to prepare SQL: %s", pszSQL);
10253 0 : return false;
10254 : }
10255 5 : sqlite3_bind_text(hInsertStmt, 1, domainName.c_str(),
10256 5 : static_cast<int>(domainName.size()),
10257 : SQLITE_TRANSIENT);
10258 5 : sqlite3_bind_double(hInsertStmt, 2, dfMin);
10259 5 : sqlite3_bind_int(hInsertStmt, 3, bMinIsInclusive ? 1 : 0);
10260 5 : sqlite3_bind_double(hInsertStmt, 4, dfMax);
10261 5 : sqlite3_bind_int(hInsertStmt, 5, bMaxIsInclusive ? 1 : 0);
10262 5 : if (osDescription.empty())
10263 : {
10264 3 : sqlite3_bind_null(hInsertStmt, 6);
10265 : }
10266 : else
10267 : {
10268 2 : sqlite3_bind_text(hInsertStmt, 6, osDescription.c_str(),
10269 2 : static_cast<int>(osDescription.size()),
10270 : SQLITE_TRANSIENT);
10271 : }
10272 5 : const int sqlite_err = sqlite3_step(hInsertStmt);
10273 5 : sqlite3_finalize(hInsertStmt);
10274 5 : if (sqlite_err != SQLITE_OK && sqlite_err != SQLITE_DONE)
10275 : {
10276 0 : CPLError(CE_Failure, CPLE_AppDefined,
10277 : "failed to execute insertion: %s",
10278 : sqlite3_errmsg(hDB));
10279 0 : return false;
10280 : }
10281 :
10282 5 : break;
10283 : }
10284 :
10285 1 : case OFDT_GLOB:
10286 : {
10287 : const auto poGlobDomain =
10288 1 : cpl::down_cast<const OGRGlobFieldDomain *>(domain.get());
10289 2 : char *pszSQL = sqlite3_mprintf(
10290 : "INSERT INTO gpkg_data_column_constraints ("
10291 : "constraint_name, constraint_type, value, "
10292 : "min, %s, max, %s, "
10293 : "description) VALUES ("
10294 : "'%q', 'glob', '%q', NULL, NULL, NULL, NULL, %Q)",
10295 : min_is_inclusive, max_is_inclusive, domainName.c_str(),
10296 1 : poGlobDomain->GetGlob().c_str(),
10297 2 : osDescription.empty() ? nullptr : osDescription.c_str());
10298 1 : bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
10299 1 : sqlite3_free(pszSQL);
10300 1 : if (!ok)
10301 0 : return false;
10302 :
10303 1 : break;
10304 : }
10305 : }
10306 :
10307 17 : m_oMapFieldDomains[domainName] = std::move(domain);
10308 17 : return true;
10309 : }
10310 :
10311 : /************************************************************************/
10312 : /* AddRelationship() */
10313 : /************************************************************************/
10314 :
10315 24 : bool GDALGeoPackageDataset::AddRelationship(
10316 : std::unique_ptr<GDALRelationship> &&relationship,
10317 : std::string &failureReason)
10318 : {
10319 24 : if (!GetUpdate())
10320 : {
10321 0 : CPLError(CE_Failure, CPLE_NotSupported,
10322 : "AddRelationship() not supported on read-only dataset");
10323 0 : return false;
10324 : }
10325 :
10326 : const std::string osRelationshipName = GenerateNameForRelationship(
10327 24 : relationship->GetLeftTableName().c_str(),
10328 24 : relationship->GetRightTableName().c_str(),
10329 96 : relationship->GetRelatedTableType().c_str());
10330 : // sanity checks
10331 24 : if (GetRelationship(osRelationshipName) != nullptr)
10332 : {
10333 1 : failureReason = "A relationship of identical name already exists";
10334 1 : return false;
10335 : }
10336 :
10337 23 : if (!ValidateRelationship(relationship.get(), failureReason))
10338 : {
10339 14 : return false;
10340 : }
10341 :
10342 9 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
10343 : {
10344 0 : return false;
10345 : }
10346 9 : if (!CreateRelationsTableIfNecessary())
10347 : {
10348 0 : failureReason = "Could not create gpkgext_relations table";
10349 0 : return false;
10350 : }
10351 9 : if (SQLGetInteger(GetDB(),
10352 : "SELECT 1 FROM gpkg_extensions WHERE "
10353 : "table_name = 'gpkgext_relations'",
10354 9 : nullptr) != 1)
10355 : {
10356 4 : if (OGRERR_NONE !=
10357 4 : SQLCommand(
10358 : GetDB(),
10359 : "INSERT INTO gpkg_extensions "
10360 : "(table_name,column_name,extension_name,definition,scope) "
10361 : "VALUES ('gpkgext_relations', NULL, 'gpkg_related_tables', "
10362 : "'http://www.geopackage.org/18-000.html', "
10363 : "'read-write')"))
10364 : {
10365 : failureReason =
10366 0 : "Could not create gpkg_extensions entry for gpkgext_relations";
10367 0 : return false;
10368 : }
10369 : }
10370 :
10371 9 : const std::string &osLeftTableName = relationship->GetLeftTableName();
10372 9 : const std::string &osRightTableName = relationship->GetRightTableName();
10373 9 : const auto &aosLeftTableFields = relationship->GetLeftTableFields();
10374 9 : const auto &aosRightTableFields = relationship->GetRightTableFields();
10375 :
10376 18 : std::string osRelatedTableType = relationship->GetRelatedTableType();
10377 9 : if (osRelatedTableType.empty())
10378 : {
10379 5 : osRelatedTableType = "features";
10380 : }
10381 :
10382 : // generate mapping table if not set
10383 18 : CPLString osMappingTableName = relationship->GetMappingTableName();
10384 9 : if (osMappingTableName.empty())
10385 : {
10386 3 : int nIndex = 1;
10387 3 : osMappingTableName = osLeftTableName + "_" + osRightTableName;
10388 3 : while (FindLayerIndex(osMappingTableName.c_str()) >= 0)
10389 : {
10390 0 : nIndex += 1;
10391 : osMappingTableName.Printf("%s_%s_%d", osLeftTableName.c_str(),
10392 0 : osRightTableName.c_str(), nIndex);
10393 : }
10394 :
10395 : // determine whether base/related keys are unique
10396 3 : bool bBaseKeyIsUnique = false;
10397 : {
10398 : const std::set<std::string> uniqueBaseFieldsUC =
10399 : SQLGetUniqueFieldUCConstraints(GetDB(),
10400 6 : osLeftTableName.c_str());
10401 6 : if (uniqueBaseFieldsUC.find(
10402 3 : CPLString(aosLeftTableFields[0]).toupper()) !=
10403 6 : uniqueBaseFieldsUC.end())
10404 : {
10405 2 : bBaseKeyIsUnique = true;
10406 : }
10407 : }
10408 3 : bool bRelatedKeyIsUnique = false;
10409 : {
10410 : const std::set<std::string> uniqueRelatedFieldsUC =
10411 : SQLGetUniqueFieldUCConstraints(GetDB(),
10412 6 : osRightTableName.c_str());
10413 6 : if (uniqueRelatedFieldsUC.find(
10414 3 : CPLString(aosRightTableFields[0]).toupper()) !=
10415 6 : uniqueRelatedFieldsUC.end())
10416 : {
10417 2 : bRelatedKeyIsUnique = true;
10418 : }
10419 : }
10420 :
10421 : // create mapping table
10422 :
10423 3 : std::string osBaseIdDefinition = "base_id INTEGER";
10424 3 : if (bBaseKeyIsUnique)
10425 : {
10426 2 : char *pszSQL = sqlite3_mprintf(
10427 : " CONSTRAINT 'fk_base_id_%q' REFERENCES \"%w\"(\"%w\") ON "
10428 : "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
10429 : "DEFERRED",
10430 : osMappingTableName.c_str(), osLeftTableName.c_str(),
10431 2 : aosLeftTableFields[0].c_str());
10432 2 : osBaseIdDefinition += pszSQL;
10433 2 : sqlite3_free(pszSQL);
10434 : }
10435 :
10436 3 : std::string osRelatedIdDefinition = "related_id INTEGER";
10437 3 : if (bRelatedKeyIsUnique)
10438 : {
10439 2 : char *pszSQL = sqlite3_mprintf(
10440 : " CONSTRAINT 'fk_related_id_%q' REFERENCES \"%w\"(\"%w\") ON "
10441 : "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
10442 : "DEFERRED",
10443 : osMappingTableName.c_str(), osRightTableName.c_str(),
10444 2 : aosRightTableFields[0].c_str());
10445 2 : osRelatedIdDefinition += pszSQL;
10446 2 : sqlite3_free(pszSQL);
10447 : }
10448 :
10449 3 : char *pszSQL = sqlite3_mprintf("CREATE TABLE \"%w\" ("
10450 : "id INTEGER PRIMARY KEY AUTOINCREMENT, "
10451 : "%s, %s);",
10452 : osMappingTableName.c_str(),
10453 : osBaseIdDefinition.c_str(),
10454 : osRelatedIdDefinition.c_str());
10455 3 : OGRErr eErr = SQLCommand(hDB, pszSQL);
10456 3 : sqlite3_free(pszSQL);
10457 3 : if (eErr != OGRERR_NONE)
10458 : {
10459 : failureReason =
10460 0 : ("Could not create mapping table " + osMappingTableName)
10461 0 : .c_str();
10462 0 : return false;
10463 : }
10464 :
10465 : /*
10466 : * Strictly speaking we should NOT be inserting the mapping table into gpkg_contents.
10467 : * The related tables extension explicitly states that the mapping table should only be
10468 : * in the gpkgext_relations table and not in gpkg_contents. (See also discussion at
10469 : * https://github.com/opengeospatial/geopackage/issues/679).
10470 : *
10471 : * However, if we don't insert the mapping table into gpkg_contents then it is no longer
10472 : * visible to some clients (eg ESRI software only allows opening tables that are present
10473 : * in gpkg_contents). So we'll do this anyway, for maximum compatibility and flexibility.
10474 : *
10475 : * More related discussion is at https://github.com/OSGeo/gdal/pull/9258
10476 : */
10477 3 : pszSQL = sqlite3_mprintf(
10478 : "INSERT INTO gpkg_contents "
10479 : "(table_name,data_type,identifier,description,last_change,srs_id) "
10480 : "VALUES "
10481 : "('%q','attributes','%q','Mapping table for relationship between "
10482 : "%q and %q',%s,0)",
10483 : osMappingTableName.c_str(), /*table_name*/
10484 : osMappingTableName.c_str(), /*identifier*/
10485 : osLeftTableName.c_str(), /*description left table name*/
10486 : osRightTableName.c_str(), /*description right table name*/
10487 6 : GDALGeoPackageDataset::GetCurrentDateEscapedSQL().c_str());
10488 :
10489 : // Note -- we explicitly ignore failures here, because hey, we aren't really
10490 : // supposed to be adding this table to gpkg_contents anyway!
10491 3 : (void)SQLCommand(hDB, pszSQL);
10492 3 : sqlite3_free(pszSQL);
10493 :
10494 3 : pszSQL = sqlite3_mprintf(
10495 : "CREATE INDEX \"idx_%w_base_id\" ON \"%w\" (base_id);",
10496 : osMappingTableName.c_str(), osMappingTableName.c_str());
10497 3 : eErr = SQLCommand(hDB, pszSQL);
10498 3 : sqlite3_free(pszSQL);
10499 3 : if (eErr != OGRERR_NONE)
10500 : {
10501 0 : failureReason = ("Could not create index for " +
10502 0 : osMappingTableName + " (base_id)")
10503 0 : .c_str();
10504 0 : return false;
10505 : }
10506 :
10507 3 : pszSQL = sqlite3_mprintf(
10508 : "CREATE INDEX \"idx_%qw_related_id\" ON \"%w\" (related_id);",
10509 : osMappingTableName.c_str(), osMappingTableName.c_str());
10510 3 : eErr = SQLCommand(hDB, pszSQL);
10511 3 : sqlite3_free(pszSQL);
10512 3 : if (eErr != OGRERR_NONE)
10513 : {
10514 0 : failureReason = ("Could not create index for " +
10515 0 : osMappingTableName + " (related_id)")
10516 0 : .c_str();
10517 0 : return false;
10518 : }
10519 : }
10520 : else
10521 : {
10522 : // validate mapping table structure
10523 6 : if (OGRGeoPackageTableLayer *poLayer =
10524 6 : cpl::down_cast<OGRGeoPackageTableLayer *>(
10525 6 : GetLayerByName(osMappingTableName)))
10526 : {
10527 4 : if (poLayer->GetLayerDefn()->GetFieldIndex("base_id") < 0)
10528 : {
10529 : failureReason =
10530 2 : ("Field base_id must exist in " + osMappingTableName)
10531 1 : .c_str();
10532 1 : return false;
10533 : }
10534 3 : if (poLayer->GetLayerDefn()->GetFieldIndex("related_id") < 0)
10535 : {
10536 : failureReason =
10537 2 : ("Field related_id must exist in " + osMappingTableName)
10538 1 : .c_str();
10539 1 : return false;
10540 : }
10541 : }
10542 : else
10543 : {
10544 : failureReason =
10545 2 : ("Could not retrieve table " + osMappingTableName).c_str();
10546 2 : return false;
10547 : }
10548 : }
10549 :
10550 5 : char *pszSQL = sqlite3_mprintf(
10551 : "INSERT INTO gpkg_extensions "
10552 : "(table_name,column_name,extension_name,definition,scope) "
10553 : "VALUES ('%q', NULL, 'gpkg_related_tables', "
10554 : "'http://www.geopackage.org/18-000.html', "
10555 : "'read-write')",
10556 : osMappingTableName.c_str());
10557 5 : OGRErr eErr = SQLCommand(hDB, pszSQL);
10558 5 : sqlite3_free(pszSQL);
10559 5 : if (eErr != OGRERR_NONE)
10560 : {
10561 0 : failureReason = ("Could not insert mapping table " +
10562 0 : osMappingTableName + " into gpkg_extensions")
10563 0 : .c_str();
10564 0 : return false;
10565 : }
10566 :
10567 15 : pszSQL = sqlite3_mprintf(
10568 : "INSERT INTO gpkgext_relations "
10569 : "(base_table_name,base_primary_column,related_table_name,related_"
10570 : "primary_column,relation_name,mapping_table_name) "
10571 : "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
10572 5 : osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
10573 5 : osRightTableName.c_str(), aosRightTableFields[0].c_str(),
10574 : osRelatedTableType.c_str(), osMappingTableName.c_str());
10575 5 : eErr = SQLCommand(hDB, pszSQL);
10576 5 : sqlite3_free(pszSQL);
10577 5 : if (eErr != OGRERR_NONE)
10578 : {
10579 0 : failureReason = "Could not insert relationship into gpkgext_relations";
10580 0 : return false;
10581 : }
10582 :
10583 5 : ClearCachedRelationships();
10584 5 : LoadRelationships();
10585 5 : return true;
10586 : }
10587 :
10588 : /************************************************************************/
10589 : /* DeleteRelationship() */
10590 : /************************************************************************/
10591 :
10592 4 : bool GDALGeoPackageDataset::DeleteRelationship(const std::string &name,
10593 : std::string &failureReason)
10594 : {
10595 4 : if (eAccess != GA_Update)
10596 : {
10597 0 : CPLError(CE_Failure, CPLE_NotSupported,
10598 : "DeleteRelationship() not supported on read-only dataset");
10599 0 : return false;
10600 : }
10601 :
10602 : // ensure relationships are up to date before we try to remove one
10603 4 : ClearCachedRelationships();
10604 4 : LoadRelationships();
10605 :
10606 8 : std::string osMappingTableName;
10607 : {
10608 4 : const GDALRelationship *poRelationship = GetRelationship(name);
10609 4 : if (poRelationship == nullptr)
10610 : {
10611 1 : failureReason = "Could not find relationship with name " + name;
10612 1 : return false;
10613 : }
10614 :
10615 3 : osMappingTableName = poRelationship->GetMappingTableName();
10616 : }
10617 :
10618 : // DeleteLayerCommon will delete existing relationship objects, so we can't
10619 : // refer to poRelationship or any of its members previously obtained here
10620 3 : if (DeleteLayerCommon(osMappingTableName.c_str()) != OGRERR_NONE)
10621 : {
10622 : failureReason =
10623 0 : "Could not remove mapping layer name " + osMappingTableName;
10624 :
10625 : // relationships may have been left in an inconsistent state -- reload
10626 : // them now
10627 0 : ClearCachedRelationships();
10628 0 : LoadRelationships();
10629 0 : return false;
10630 : }
10631 :
10632 3 : ClearCachedRelationships();
10633 3 : LoadRelationships();
10634 3 : return true;
10635 : }
10636 :
10637 : /************************************************************************/
10638 : /* UpdateRelationship() */
10639 : /************************************************************************/
10640 :
10641 6 : bool GDALGeoPackageDataset::UpdateRelationship(
10642 : std::unique_ptr<GDALRelationship> &&relationship,
10643 : std::string &failureReason)
10644 : {
10645 6 : if (eAccess != GA_Update)
10646 : {
10647 0 : CPLError(CE_Failure, CPLE_NotSupported,
10648 : "UpdateRelationship() not supported on read-only dataset");
10649 0 : return false;
10650 : }
10651 :
10652 : // ensure relationships are up to date before we try to update one
10653 6 : ClearCachedRelationships();
10654 6 : LoadRelationships();
10655 :
10656 6 : const std::string &osRelationshipName = relationship->GetName();
10657 6 : const std::string &osLeftTableName = relationship->GetLeftTableName();
10658 6 : const std::string &osRightTableName = relationship->GetRightTableName();
10659 6 : const std::string &osMappingTableName = relationship->GetMappingTableName();
10660 6 : const auto &aosLeftTableFields = relationship->GetLeftTableFields();
10661 6 : const auto &aosRightTableFields = relationship->GetRightTableFields();
10662 :
10663 : // sanity checks
10664 : {
10665 : const GDALRelationship *poExistingRelationship =
10666 6 : GetRelationship(osRelationshipName);
10667 6 : if (poExistingRelationship == nullptr)
10668 : {
10669 : failureReason =
10670 1 : "The relationship should already exist to be updated";
10671 1 : return false;
10672 : }
10673 :
10674 5 : if (!ValidateRelationship(relationship.get(), failureReason))
10675 : {
10676 2 : return false;
10677 : }
10678 :
10679 : // we don't permit changes to the participating tables
10680 3 : if (osLeftTableName != poExistingRelationship->GetLeftTableName())
10681 : {
10682 0 : failureReason = ("Cannot change base table from " +
10683 0 : poExistingRelationship->GetLeftTableName() +
10684 0 : " to " + osLeftTableName)
10685 0 : .c_str();
10686 0 : return false;
10687 : }
10688 3 : if (osRightTableName != poExistingRelationship->GetRightTableName())
10689 : {
10690 0 : failureReason = ("Cannot change related table from " +
10691 0 : poExistingRelationship->GetRightTableName() +
10692 0 : " to " + osRightTableName)
10693 0 : .c_str();
10694 0 : return false;
10695 : }
10696 3 : if (osMappingTableName != poExistingRelationship->GetMappingTableName())
10697 : {
10698 0 : failureReason = ("Cannot change mapping table from " +
10699 0 : poExistingRelationship->GetMappingTableName() +
10700 0 : " to " + osMappingTableName)
10701 0 : .c_str();
10702 0 : return false;
10703 : }
10704 : }
10705 :
10706 6 : std::string osRelatedTableType = relationship->GetRelatedTableType();
10707 3 : if (osRelatedTableType.empty())
10708 : {
10709 0 : osRelatedTableType = "features";
10710 : }
10711 :
10712 3 : char *pszSQL = sqlite3_mprintf(
10713 : "DELETE FROM gpkgext_relations WHERE mapping_table_name='%q'",
10714 : osMappingTableName.c_str());
10715 3 : OGRErr eErr = SQLCommand(hDB, pszSQL);
10716 3 : sqlite3_free(pszSQL);
10717 3 : if (eErr != OGRERR_NONE)
10718 : {
10719 : failureReason =
10720 0 : "Could not delete old relationship from gpkgext_relations";
10721 0 : return false;
10722 : }
10723 :
10724 9 : pszSQL = sqlite3_mprintf(
10725 : "INSERT INTO gpkgext_relations "
10726 : "(base_table_name,base_primary_column,related_table_name,related_"
10727 : "primary_column,relation_name,mapping_table_name) "
10728 : "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
10729 3 : osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
10730 3 : osRightTableName.c_str(), aosRightTableFields[0].c_str(),
10731 : osRelatedTableType.c_str(), osMappingTableName.c_str());
10732 3 : eErr = SQLCommand(hDB, pszSQL);
10733 3 : sqlite3_free(pszSQL);
10734 3 : if (eErr != OGRERR_NONE)
10735 : {
10736 : failureReason =
10737 0 : "Could not insert updated relationship into gpkgext_relations";
10738 0 : return false;
10739 : }
10740 :
10741 3 : ClearCachedRelationships();
10742 3 : LoadRelationships();
10743 3 : return true;
10744 : }
10745 :
10746 : /************************************************************************/
10747 : /* GetSqliteMasterContent() */
10748 : /************************************************************************/
10749 :
10750 : const std::vector<SQLSqliteMasterContent> &
10751 2 : GDALGeoPackageDataset::GetSqliteMasterContent()
10752 : {
10753 2 : if (m_aoSqliteMasterContent.empty())
10754 : {
10755 : auto oResultTable =
10756 2 : SQLQuery(hDB, "SELECT sql, type, tbl_name FROM sqlite_master");
10757 1 : if (oResultTable)
10758 : {
10759 58 : for (int rowCnt = 0; rowCnt < oResultTable->RowCount(); ++rowCnt)
10760 : {
10761 114 : SQLSqliteMasterContent row;
10762 57 : const char *pszSQL = oResultTable->GetValue(0, rowCnt);
10763 57 : row.osSQL = pszSQL ? pszSQL : "";
10764 57 : const char *pszType = oResultTable->GetValue(1, rowCnt);
10765 57 : row.osType = pszType ? pszType : "";
10766 57 : const char *pszTableName = oResultTable->GetValue(2, rowCnt);
10767 57 : row.osTableName = pszTableName ? pszTableName : "";
10768 57 : m_aoSqliteMasterContent.emplace_back(std::move(row));
10769 : }
10770 : }
10771 : }
10772 2 : return m_aoSqliteMasterContent;
10773 : }
|