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 508 : GetTilingScheme(const char *pszName)
82 : {
83 508 : if (EQUAL(pszName, "CUSTOM"))
84 380 : 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 : /* Only recent versions of SQLite will let us muck with application_id */
192 : /* via a PRAGMA statement, so we have to write directly into the */
193 : /* file header here. */
194 : /* We do this at the *end* of initialization so that there is */
195 : /* data to write down to a file, and we will have a writable file */
196 : /* once we close the SQLite connection */
197 751 : OGRErr GDALGeoPackageDataset::SetApplicationAndUserVersionId()
198 : {
199 751 : CPLAssert(hDB != nullptr);
200 :
201 751 : const CPLString osPragma(CPLString().Printf("PRAGMA application_id = %u;"
202 : "PRAGMA user_version = %u",
203 : m_nApplicationId,
204 1502 : m_nUserVersion));
205 1502 : return SQLCommand(hDB, osPragma.c_str());
206 : }
207 :
208 2182 : bool GDALGeoPackageDataset::CloseDB()
209 : {
210 2182 : OGRSQLiteUnregisterSQLFunctions(m_pSQLFunctionData);
211 2182 : m_pSQLFunctionData = nullptr;
212 2182 : return OGRSQLiteBaseDataSource::CloseDB();
213 : }
214 :
215 11 : bool GDALGeoPackageDataset::ReOpenDB()
216 : {
217 11 : CPLAssert(hDB != nullptr);
218 11 : CPLAssert(m_pszFilename != nullptr);
219 :
220 11 : FinishSpatialite();
221 :
222 11 : CloseDB();
223 :
224 : /* And re-open the file */
225 11 : return OpenOrCreateDB(SQLITE_OPEN_READWRITE);
226 : }
227 :
228 700 : static OGRErr GDALGPKGImportFromEPSG(OGRSpatialReference *poSpatialRef,
229 : int nEPSGCode)
230 : {
231 700 : CPLPushErrorHandler(CPLQuietErrorHandler);
232 700 : const OGRErr eErr = poSpatialRef->importFromEPSG(nEPSGCode);
233 700 : CPLPopErrorHandler();
234 700 : CPLErrorReset();
235 700 : return eErr;
236 : }
237 :
238 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
239 1074 : GDALGeoPackageDataset::GetSpatialRef(int iSrsId, bool bFallbackToEPSG,
240 : bool bEmitErrorIfNotFound)
241 : {
242 1074 : const auto oIter = m_oMapSrsIdToSrs.find(iSrsId);
243 1074 : if (oIter != m_oMapSrsIdToSrs.end())
244 : {
245 65 : if (oIter->second == nullptr)
246 29 : return nullptr;
247 36 : oIter->second->Reference();
248 : return std::unique_ptr<OGRSpatialReference,
249 36 : OGRSpatialReferenceReleaser>(oIter->second);
250 : }
251 :
252 1009 : if (iSrsId == 0 || iSrsId == -1)
253 : {
254 119 : OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
255 119 : poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
256 :
257 : // See corresponding tests in GDALGeoPackageDataset::GetSrsId
258 119 : if (iSrsId == 0)
259 : {
260 29 : poSpatialRef->SetGeogCS("Undefined geographic SRS", "unknown",
261 : "unknown", SRS_WGS84_SEMIMAJOR,
262 : SRS_WGS84_INVFLATTENING);
263 : }
264 90 : else if (iSrsId == -1)
265 : {
266 90 : poSpatialRef->SetLocalCS("Undefined Cartesian SRS");
267 90 : poSpatialRef->SetLinearUnits(SRS_UL_METER, 1.0);
268 : }
269 :
270 119 : m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
271 119 : poSpatialRef->Reference();
272 : return std::unique_ptr<OGRSpatialReference,
273 119 : OGRSpatialReferenceReleaser>(poSpatialRef);
274 : }
275 :
276 1780 : CPLString oSQL;
277 890 : oSQL.Printf("SELECT srs_name, definition, organization, "
278 : "organization_coordsys_id%s%s "
279 : "FROM gpkg_spatial_ref_sys WHERE "
280 : "srs_id = %d LIMIT 2",
281 890 : m_bHasDefinition12_063 ? ", definition_12_063" : "",
282 890 : m_bHasEpochColumn ? ", epoch" : "", iSrsId);
283 :
284 1780 : auto oResult = SQLQuery(hDB, oSQL.c_str());
285 :
286 890 : if (!oResult || oResult->RowCount() != 1)
287 : {
288 12 : if (bFallbackToEPSG)
289 : {
290 7 : CPLDebug("GPKG",
291 : "unable to read srs_id '%d' from gpkg_spatial_ref_sys",
292 : iSrsId);
293 7 : OGRSpatialReference *poSRS = new OGRSpatialReference();
294 7 : if (poSRS->importFromEPSG(iSrsId) == OGRERR_NONE)
295 : {
296 5 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
297 : return std::unique_ptr<OGRSpatialReference,
298 5 : OGRSpatialReferenceReleaser>(poSRS);
299 : }
300 2 : poSRS->Release();
301 : }
302 5 : else if (bEmitErrorIfNotFound)
303 : {
304 2 : CPLError(CE_Warning, CPLE_AppDefined,
305 : "unable to read srs_id '%d' from gpkg_spatial_ref_sys",
306 : iSrsId);
307 2 : m_oMapSrsIdToSrs[iSrsId] = nullptr;
308 : }
309 7 : return nullptr;
310 : }
311 :
312 878 : const char *pszName = oResult->GetValue(0, 0);
313 878 : if (pszName && EQUAL(pszName, "Undefined SRS"))
314 : {
315 361 : m_oMapSrsIdToSrs[iSrsId] = nullptr;
316 361 : return nullptr;
317 : }
318 517 : const char *pszWkt = oResult->GetValue(1, 0);
319 517 : if (pszWkt == nullptr)
320 0 : return nullptr;
321 517 : const char *pszOrganization = oResult->GetValue(2, 0);
322 517 : const char *pszOrganizationCoordsysID = oResult->GetValue(3, 0);
323 : const char *pszWkt2 =
324 517 : m_bHasDefinition12_063 ? oResult->GetValue(4, 0) : nullptr;
325 517 : if (pszWkt2 && !EQUAL(pszWkt2, "undefined"))
326 73 : pszWkt = pszWkt2;
327 : const char *pszCoordinateEpoch =
328 517 : m_bHasEpochColumn ? oResult->GetValue(5, 0) : nullptr;
329 : const double dfCoordinateEpoch =
330 517 : pszCoordinateEpoch ? CPLAtof(pszCoordinateEpoch) : 0.0;
331 :
332 517 : OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
333 517 : poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
334 : // Try to import first from EPSG code, and then from WKT
335 517 : if (!(pszOrganization && pszOrganizationCoordsysID &&
336 517 : EQUAL(pszOrganization, "EPSG") &&
337 498 : (atoi(pszOrganizationCoordsysID) == iSrsId ||
338 4 : (dfCoordinateEpoch > 0 && strstr(pszWkt, "DYNAMIC[") == nullptr)) &&
339 498 : GDALGPKGImportFromEPSG(
340 1034 : poSpatialRef, atoi(pszOrganizationCoordsysID)) == OGRERR_NONE) &&
341 19 : poSpatialRef->importFromWkt(pszWkt) != OGRERR_NONE)
342 : {
343 0 : CPLError(CE_Warning, CPLE_AppDefined,
344 : "Unable to parse srs_id '%d' well-known text '%s'", iSrsId,
345 : pszWkt);
346 0 : delete poSpatialRef;
347 0 : m_oMapSrsIdToSrs[iSrsId] = nullptr;
348 0 : return nullptr;
349 : }
350 :
351 517 : poSpatialRef->StripTOWGS84IfKnownDatumAndAllowed();
352 517 : poSpatialRef->SetCoordinateEpoch(dfCoordinateEpoch);
353 517 : m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
354 517 : poSpatialRef->Reference();
355 : return std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>(
356 517 : poSpatialRef);
357 : }
358 :
359 229 : const char *GDALGeoPackageDataset::GetSrsName(const OGRSpatialReference &oSRS)
360 : {
361 229 : const char *pszName = oSRS.GetName();
362 229 : if (pszName)
363 229 : return pszName;
364 :
365 : // Something odd. Return empty.
366 0 : return "Unnamed SRS";
367 : }
368 :
369 : /* Add the definition_12_063 column to an existing gpkg_spatial_ref_sys table */
370 6 : bool GDALGeoPackageDataset::ConvertGpkgSpatialRefSysToExtensionWkt2(
371 : bool bForceEpoch)
372 : {
373 6 : const bool bAddEpoch = (m_nUserVersion >= GPKG_1_4_VERSION || bForceEpoch);
374 : auto oResultTable = SQLQuery(
375 : hDB, "SELECT srs_name, srs_id, organization, organization_coordsys_id, "
376 12 : "definition, description FROM gpkg_spatial_ref_sys LIMIT 100000");
377 6 : if (!oResultTable)
378 0 : return false;
379 :
380 : // Temporary remove foreign key checks
381 : const GPKGTemporaryForeignKeyCheckDisabler
382 6 : oGPKGTemporaryForeignKeyCheckDisabler(this);
383 :
384 6 : bool bRet = SoftStartTransaction() == OGRERR_NONE;
385 :
386 6 : if (bRet)
387 : {
388 : std::string osSQL("CREATE TABLE gpkg_spatial_ref_sys_temp ("
389 : "srs_name TEXT NOT NULL,"
390 : "srs_id INTEGER NOT NULL PRIMARY KEY,"
391 : "organization TEXT NOT NULL,"
392 : "organization_coordsys_id INTEGER NOT NULL,"
393 : "definition TEXT NOT NULL,"
394 : "description TEXT, "
395 6 : "definition_12_063 TEXT NOT NULL");
396 6 : if (bAddEpoch)
397 3 : osSQL += ", epoch DOUBLE";
398 6 : osSQL += ")";
399 6 : bRet = SQLCommand(hDB, osSQL.c_str()) == OGRERR_NONE;
400 : }
401 :
402 6 : if (bRet)
403 : {
404 28 : for (int i = 0; bRet && i < oResultTable->RowCount(); i++)
405 : {
406 22 : const char *pszSrsName = oResultTable->GetValue(0, i);
407 22 : const char *pszSrsId = oResultTable->GetValue(1, i);
408 22 : const char *pszOrganization = oResultTable->GetValue(2, i);
409 : const char *pszOrganizationCoordsysID =
410 22 : oResultTable->GetValue(3, i);
411 22 : const char *pszDefinition = oResultTable->GetValue(4, i);
412 : if (pszSrsName == nullptr || pszSrsId == nullptr ||
413 : pszOrganization == nullptr ||
414 : pszOrganizationCoordsysID == nullptr)
415 : {
416 : // should not happen as there are NOT NULL constraints
417 : // But a database could lack such NOT NULL constraints or have
418 : // large values that would cause a memory allocation failure.
419 : }
420 22 : const char *pszDescription = oResultTable->GetValue(5, i);
421 : char *pszSQL;
422 :
423 44 : OGRSpatialReference oSRS;
424 22 : if (pszOrganization && pszOrganizationCoordsysID &&
425 22 : EQUAL(pszOrganization, "EPSG"))
426 : {
427 8 : oSRS.importFromEPSG(atoi(pszOrganizationCoordsysID));
428 : }
429 30 : if (!oSRS.IsEmpty() && pszDefinition &&
430 8 : !EQUAL(pszDefinition, "undefined"))
431 : {
432 8 : oSRS.SetFromUserInput(pszDefinition);
433 : }
434 22 : char *pszWKT2 = nullptr;
435 22 : if (!oSRS.IsEmpty())
436 : {
437 8 : const char *const apszOptionsWkt2[] = {"FORMAT=WKT2_2015",
438 : nullptr};
439 8 : oSRS.exportToWkt(&pszWKT2, apszOptionsWkt2);
440 8 : if (pszWKT2 && pszWKT2[0] == '\0')
441 : {
442 0 : CPLFree(pszWKT2);
443 0 : pszWKT2 = nullptr;
444 : }
445 : }
446 22 : if (pszWKT2 == nullptr)
447 : {
448 14 : pszWKT2 = CPLStrdup("undefined");
449 : }
450 :
451 22 : if (pszDescription)
452 : {
453 19 : pszSQL = sqlite3_mprintf(
454 : "INSERT INTO gpkg_spatial_ref_sys_temp(srs_name, srs_id, "
455 : "organization, organization_coordsys_id, definition, "
456 : "description, definition_12_063) VALUES ('%q', '%q', '%q', "
457 : "'%q', '%q', '%q', '%q')",
458 : pszSrsName, pszSrsId, pszOrganization,
459 : pszOrganizationCoordsysID, pszDefinition, pszDescription,
460 : pszWKT2);
461 : }
462 : else
463 : {
464 3 : pszSQL = sqlite3_mprintf(
465 : "INSERT INTO gpkg_spatial_ref_sys_temp(srs_name, srs_id, "
466 : "organization, organization_coordsys_id, definition, "
467 : "description, definition_12_063) VALUES ('%q', '%q', '%q', "
468 : "'%q', '%q', NULL, '%q')",
469 : pszSrsName, pszSrsId, pszOrganization,
470 : pszOrganizationCoordsysID, pszDefinition, pszWKT2);
471 : }
472 :
473 22 : CPLFree(pszWKT2);
474 22 : bRet &= SQLCommand(hDB, pszSQL) == OGRERR_NONE;
475 22 : sqlite3_free(pszSQL);
476 : }
477 : }
478 :
479 6 : if (bRet)
480 : {
481 6 : bRet =
482 6 : SQLCommand(hDB, "DROP TABLE gpkg_spatial_ref_sys") == OGRERR_NONE;
483 : }
484 6 : if (bRet)
485 : {
486 6 : bRet = SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys_temp RENAME "
487 : "TO gpkg_spatial_ref_sys") == OGRERR_NONE;
488 : }
489 6 : if (bRet)
490 : {
491 12 : bRet = OGRERR_NONE == CreateExtensionsTableIfNecessary() &&
492 6 : OGRERR_NONE == SQLCommand(hDB,
493 : "INSERT INTO gpkg_extensions "
494 : "(table_name, column_name, "
495 : "extension_name, definition, scope) "
496 : "VALUES "
497 : "('gpkg_spatial_ref_sys', "
498 : "'definition_12_063', 'gpkg_crs_wkt', "
499 : "'http://www.geopackage.org/spec120/"
500 : "#extension_crs_wkt', 'read-write')");
501 : }
502 6 : if (bRet && bAddEpoch)
503 : {
504 3 : bRet =
505 : OGRERR_NONE ==
506 3 : SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
507 : "'gpkg_crs_wkt_1_1' "
508 6 : "WHERE extension_name = 'gpkg_crs_wkt'") &&
509 : OGRERR_NONE ==
510 3 : SQLCommand(
511 : hDB,
512 : "INSERT INTO gpkg_extensions "
513 : "(table_name, column_name, extension_name, definition, "
514 : "scope) "
515 : "VALUES "
516 : "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
517 : "'http://www.geopackage.org/spec/#extension_crs_wkt', "
518 : "'read-write')");
519 : }
520 6 : if (bRet)
521 : {
522 6 : SoftCommitTransaction();
523 6 : m_bHasDefinition12_063 = true;
524 6 : if (bAddEpoch)
525 3 : m_bHasEpochColumn = true;
526 : }
527 : else
528 : {
529 0 : SoftRollbackTransaction();
530 : }
531 :
532 6 : return bRet;
533 : }
534 :
535 738 : int GDALGeoPackageDataset::GetSrsId(const OGRSpatialReference *poSRSIn)
536 : {
537 738 : const char *pszName = poSRSIn ? poSRSIn->GetName() : nullptr;
538 1080 : if (!poSRSIn || poSRSIn->IsEmpty() ||
539 342 : (pszName && EQUAL(pszName, "Undefined SRS")))
540 : {
541 398 : OGRErr err = OGRERR_NONE;
542 398 : const int nSRSId = SQLGetInteger(
543 : hDB,
544 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE srs_name = "
545 : "'Undefined SRS' AND organization = 'GDAL'",
546 : &err);
547 398 : if (err == OGRERR_NONE)
548 52 : return nSRSId;
549 :
550 : // The below WKT definitions are somehow questionable (using a unknown
551 : // unit). For GDAL >= 3.9, they won't be used. They will only be used
552 : // for earlier versions.
553 : const char *pszSQL;
554 : #define UNDEFINED_CRS_SRS_ID 99999
555 : static_assert(UNDEFINED_CRS_SRS_ID == FIRST_CUSTOM_SRSID - 1);
556 : #define STRINGIFY(x) #x
557 : #define XSTRINGIFY(x) STRINGIFY(x)
558 346 : if (m_bHasDefinition12_063)
559 : {
560 : /* clang-format off */
561 1 : pszSQL =
562 : "INSERT INTO gpkg_spatial_ref_sys "
563 : "(srs_name,srs_id,organization,organization_coordsys_id,"
564 : "definition, definition_12_063, description) VALUES "
565 : "('Undefined SRS'," XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ",'GDAL',"
566 : XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ","
567 : "'LOCAL_CS[\"Undefined SRS\",LOCAL_DATUM[\"unknown\",32767],"
568 : "UNIT[\"unknown\",0],AXIS[\"Easting\",EAST],"
569 : "AXIS[\"Northing\",NORTH]]',"
570 : "'ENGCRS[\"Undefined SRS\",EDATUM[\"unknown\"],CS[Cartesian,2],"
571 : "AXIS[\"easting\",east,ORDER[1],LENGTHUNIT[\"unknown\",0]],"
572 : "AXIS[\"northing\",north,ORDER[2],LENGTHUNIT[\"unknown\",0]]]',"
573 : "'Custom undefined coordinate reference system')";
574 : /* clang-format on */
575 : }
576 : else
577 : {
578 : /* clang-format off */
579 345 : pszSQL =
580 : "INSERT INTO gpkg_spatial_ref_sys "
581 : "(srs_name,srs_id,organization,organization_coordsys_id,"
582 : "definition, description) VALUES "
583 : "('Undefined SRS'," XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ",'GDAL',"
584 : XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ","
585 : "'LOCAL_CS[\"Undefined SRS\",LOCAL_DATUM[\"unknown\",32767],"
586 : "UNIT[\"unknown\",0],AXIS[\"Easting\",EAST],"
587 : "AXIS[\"Northing\",NORTH]]',"
588 : "'Custom undefined coordinate reference system')";
589 : /* clang-format on */
590 : }
591 346 : if (SQLCommand(hDB, pszSQL) == OGRERR_NONE)
592 346 : return UNDEFINED_CRS_SRS_ID;
593 : #undef UNDEFINED_CRS_SRS_ID
594 : #undef XSTRINGIFY
595 : #undef STRINGIFY
596 0 : return -1;
597 : }
598 :
599 680 : std::unique_ptr<OGRSpatialReference> poSRS(poSRSIn->Clone());
600 :
601 340 : if (poSRS->IsGeographic() || poSRS->IsLocal())
602 : {
603 : // See corresponding tests in GDALGeoPackageDataset::GetSpatialRef
604 134 : if (pszName != nullptr && strlen(pszName) > 0)
605 : {
606 134 : if (EQUAL(pszName, "Undefined geographic SRS"))
607 2 : return 0;
608 :
609 132 : if (EQUAL(pszName, "Undefined Cartesian SRS"))
610 1 : return -1;
611 : }
612 : }
613 :
614 337 : const char *pszAuthorityName = poSRS->GetAuthorityName(nullptr);
615 :
616 337 : if (pszAuthorityName == nullptr || strlen(pszAuthorityName) == 0)
617 : {
618 : // Try to force identify an EPSG code.
619 24 : poSRS->AutoIdentifyEPSG();
620 :
621 24 : pszAuthorityName = poSRS->GetAuthorityName(nullptr);
622 24 : if (pszAuthorityName != nullptr && EQUAL(pszAuthorityName, "EPSG"))
623 : {
624 0 : const char *pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
625 0 : if (pszAuthorityCode != nullptr && strlen(pszAuthorityCode) > 0)
626 : {
627 : /* Import 'clean' SRS */
628 0 : poSRS->importFromEPSG(atoi(pszAuthorityCode));
629 :
630 0 : pszAuthorityName = poSRS->GetAuthorityName(nullptr);
631 : }
632 : }
633 :
634 24 : poSRS->SetCoordinateEpoch(poSRSIn->GetCoordinateEpoch());
635 : }
636 :
637 : // Check whether the EPSG authority code is already mapped to a
638 : // SRS ID.
639 337 : char *pszSQL = nullptr;
640 337 : int nSRSId = DEFAULT_SRID;
641 337 : int nAuthorityCode = 0;
642 337 : OGRErr err = OGRERR_NONE;
643 337 : bool bCanUseAuthorityCode = false;
644 337 : const char *const apszIsSameOptions[] = {
645 : "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES",
646 : "IGNORE_COORDINATE_EPOCH=YES", nullptr};
647 337 : if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0)
648 : {
649 313 : const char *pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
650 313 : if (pszAuthorityCode)
651 : {
652 313 : if (CPLGetValueType(pszAuthorityCode) == CPL_VALUE_INTEGER)
653 : {
654 313 : nAuthorityCode = atoi(pszAuthorityCode);
655 : }
656 : else
657 : {
658 0 : CPLDebug("GPKG",
659 : "SRS has %s:%s identification, but the code not "
660 : "being an integer value cannot be stored as such "
661 : "in the database.",
662 : pszAuthorityName, pszAuthorityCode);
663 0 : pszAuthorityName = nullptr;
664 : }
665 : }
666 : }
667 :
668 650 : if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
669 313 : poSRSIn->GetCoordinateEpoch() == 0)
670 : {
671 : pszSQL =
672 308 : sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
673 : "upper(organization) = upper('%q') AND "
674 : "organization_coordsys_id = %d",
675 : pszAuthorityName, nAuthorityCode);
676 :
677 308 : nSRSId = SQLGetInteger(hDB, pszSQL, &err);
678 308 : sqlite3_free(pszSQL);
679 :
680 : // Got a match? Return it!
681 308 : if (OGRERR_NONE == err)
682 : {
683 104 : auto poRefSRS = GetSpatialRef(nSRSId);
684 : bool bOK =
685 104 : (poRefSRS == nullptr ||
686 105 : poSRS->IsSame(poRefSRS.get(), apszIsSameOptions) ||
687 1 : !CPLTestBool(CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")));
688 104 : if (bOK)
689 : {
690 103 : return nSRSId;
691 : }
692 : else
693 : {
694 1 : CPLError(CE_Warning, CPLE_AppDefined,
695 : "Passed SRS uses %s:%d identification, but its "
696 : "definition is not compatible with the "
697 : "definition of that object already in the database. "
698 : "Registering it as a new entry into the database.",
699 : pszAuthorityName, nAuthorityCode);
700 1 : pszAuthorityName = nullptr;
701 1 : nAuthorityCode = 0;
702 : }
703 : }
704 : }
705 :
706 : // Translate SRS to WKT.
707 234 : CPLCharUniquePtr pszWKT1;
708 234 : CPLCharUniquePtr pszWKT2_2015;
709 234 : CPLCharUniquePtr pszWKT2_2019;
710 234 : const char *const apszOptionsWkt1[] = {"FORMAT=WKT1_GDAL", nullptr};
711 234 : const char *const apszOptionsWkt2_2015[] = {"FORMAT=WKT2_2015", nullptr};
712 234 : const char *const apszOptionsWkt2_2019[] = {"FORMAT=WKT2_2019", nullptr};
713 :
714 468 : std::string osEpochTest;
715 234 : if (poSRSIn->GetCoordinateEpoch() > 0 && m_bHasEpochColumn)
716 : {
717 : osEpochTest =
718 3 : CPLSPrintf(" AND epoch = %.17g", poSRSIn->GetCoordinateEpoch());
719 : }
720 :
721 234 : if (!(poSRS->IsGeographic() && poSRS->GetAxesCount() == 3))
722 : {
723 227 : char *pszTmp = nullptr;
724 227 : poSRS->exportToWkt(&pszTmp, apszOptionsWkt1);
725 227 : pszWKT1.reset(pszTmp);
726 227 : if (pszWKT1 && pszWKT1.get()[0] == '\0')
727 : {
728 0 : pszWKT1.reset();
729 : }
730 : }
731 : {
732 234 : char *pszTmp = nullptr;
733 234 : poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2015);
734 234 : pszWKT2_2015.reset(pszTmp);
735 234 : if (pszWKT2_2015 && pszWKT2_2015.get()[0] == '\0')
736 : {
737 0 : pszWKT2_2015.reset();
738 : }
739 : }
740 : {
741 234 : char *pszTmp = nullptr;
742 234 : poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2019);
743 234 : pszWKT2_2019.reset(pszTmp);
744 234 : if (pszWKT2_2019 && pszWKT2_2019.get()[0] == '\0')
745 : {
746 0 : pszWKT2_2019.reset();
747 : }
748 : }
749 :
750 234 : if (!pszWKT1 && !pszWKT2_2015 && !pszWKT2_2019)
751 : {
752 0 : return DEFAULT_SRID;
753 : }
754 :
755 234 : if (poSRSIn->GetCoordinateEpoch() == 0 || m_bHasEpochColumn)
756 : {
757 : // Search if there is already an existing entry with this WKT
758 231 : if (m_bHasDefinition12_063 && (pszWKT2_2015 || pszWKT2_2019))
759 : {
760 40 : if (pszWKT1)
761 : {
762 140 : pszSQL = sqlite3_mprintf(
763 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
764 : "(definition = '%q' OR definition_12_063 IN ('%q','%q'))%s",
765 : pszWKT1.get(),
766 70 : pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
767 70 : pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
768 : osEpochTest.c_str());
769 : }
770 : else
771 : {
772 20 : pszSQL = sqlite3_mprintf(
773 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
774 : "definition_12_063 IN ('%q', '%q')%s",
775 10 : pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
776 10 : pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
777 : osEpochTest.c_str());
778 : }
779 : }
780 191 : else if (pszWKT1)
781 : {
782 : pszSQL =
783 189 : sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
784 : "definition = '%q'%s",
785 : pszWKT1.get(), osEpochTest.c_str());
786 : }
787 : else
788 : {
789 2 : pszSQL = nullptr;
790 : }
791 231 : if (pszSQL)
792 : {
793 229 : nSRSId = SQLGetInteger(hDB, pszSQL, &err);
794 229 : sqlite3_free(pszSQL);
795 229 : if (OGRERR_NONE == err)
796 : {
797 5 : return nSRSId;
798 : }
799 : }
800 : }
801 :
802 436 : if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
803 207 : poSRSIn->GetCoordinateEpoch() == 0)
804 : {
805 203 : bool bTryToReuseSRSId = true;
806 203 : if (EQUAL(pszAuthorityName, "EPSG"))
807 : {
808 404 : OGRSpatialReference oSRS_EPSG;
809 202 : if (GDALGPKGImportFromEPSG(&oSRS_EPSG, nAuthorityCode) ==
810 : OGRERR_NONE)
811 : {
812 203 : if (!poSRS->IsSame(&oSRS_EPSG, apszIsSameOptions) &&
813 1 : CPLTestBool(
814 : CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")))
815 : {
816 1 : bTryToReuseSRSId = false;
817 1 : CPLError(
818 : CE_Warning, CPLE_AppDefined,
819 : "Passed SRS uses %s:%d identification, but its "
820 : "definition is not compatible with the "
821 : "official definition of the object. "
822 : "Registering it as a non-%s entry into the database.",
823 : pszAuthorityName, nAuthorityCode, pszAuthorityName);
824 1 : pszAuthorityName = nullptr;
825 1 : nAuthorityCode = 0;
826 : }
827 : }
828 : }
829 203 : if (bTryToReuseSRSId)
830 : {
831 : // No match, but maybe we can use the nAuthorityCode as the nSRSId?
832 202 : pszSQL = sqlite3_mprintf(
833 : "SELECT Count(*) FROM gpkg_spatial_ref_sys WHERE "
834 : "srs_id = %d",
835 : nAuthorityCode);
836 :
837 : // Yep, we can!
838 202 : if (SQLGetInteger(hDB, pszSQL, nullptr) == 0)
839 201 : bCanUseAuthorityCode = true;
840 202 : sqlite3_free(pszSQL);
841 : }
842 : }
843 :
844 229 : bool bConvertGpkgSpatialRefSysToExtensionWkt2 = false;
845 229 : bool bForceEpoch = false;
846 231 : if (!m_bHasDefinition12_063 && pszWKT1 == nullptr &&
847 2 : (pszWKT2_2015 != nullptr || pszWKT2_2019 != nullptr))
848 : {
849 2 : bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
850 : }
851 :
852 : // Add epoch column if needed
853 229 : if (poSRSIn->GetCoordinateEpoch() > 0 && !m_bHasEpochColumn)
854 : {
855 3 : if (m_bHasDefinition12_063)
856 : {
857 0 : if (SoftStartTransaction() != OGRERR_NONE)
858 0 : return DEFAULT_SRID;
859 0 : if (SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys "
860 0 : "ADD COLUMN epoch DOUBLE") != OGRERR_NONE ||
861 0 : SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
862 : "'gpkg_crs_wkt_1_1' "
863 : "WHERE extension_name = 'gpkg_crs_wkt'") !=
864 0 : OGRERR_NONE ||
865 0 : SQLCommand(
866 : hDB,
867 : "INSERT INTO gpkg_extensions "
868 : "(table_name, column_name, extension_name, definition, "
869 : "scope) "
870 : "VALUES "
871 : "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
872 : "'http://www.geopackage.org/spec/#extension_crs_wkt', "
873 : "'read-write')") != OGRERR_NONE)
874 : {
875 0 : SoftRollbackTransaction();
876 0 : return DEFAULT_SRID;
877 : }
878 :
879 0 : if (SoftCommitTransaction() != OGRERR_NONE)
880 0 : return DEFAULT_SRID;
881 :
882 0 : m_bHasEpochColumn = true;
883 : }
884 : else
885 : {
886 3 : bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
887 3 : bForceEpoch = true;
888 : }
889 : }
890 :
891 234 : if (bConvertGpkgSpatialRefSysToExtensionWkt2 &&
892 5 : !ConvertGpkgSpatialRefSysToExtensionWkt2(bForceEpoch))
893 : {
894 0 : return DEFAULT_SRID;
895 : }
896 :
897 : // Reuse the authority code number as SRS_ID if we can
898 229 : if (bCanUseAuthorityCode)
899 : {
900 201 : nSRSId = nAuthorityCode;
901 : }
902 : // Otherwise, generate a new SRS_ID number (max + 1)
903 : else
904 : {
905 : // Get the current maximum srid in the srs table.
906 28 : const int nMaxSRSId = SQLGetInteger(
907 : hDB, "SELECT MAX(srs_id) FROM gpkg_spatial_ref_sys", nullptr);
908 28 : nSRSId = std::max(FIRST_CUSTOM_SRSID, nMaxSRSId + 1);
909 : }
910 :
911 458 : std::string osEpochColumn;
912 229 : std::string osEpochVal;
913 229 : if (poSRSIn->GetCoordinateEpoch() > 0)
914 : {
915 5 : osEpochColumn = ", epoch";
916 5 : osEpochVal = CPLSPrintf(", %.17g", poSRSIn->GetCoordinateEpoch());
917 : }
918 :
919 : // Add new SRS row to gpkg_spatial_ref_sys.
920 229 : if (m_bHasDefinition12_063)
921 : {
922 : // Force WKT2_2019 when we have a dynamic CRS and coordinate epoch
923 42 : const char *pszWKT2 = poSRSIn->IsDynamic() &&
924 8 : poSRSIn->GetCoordinateEpoch() > 0 &&
925 1 : pszWKT2_2019
926 1 : ? pszWKT2_2019.get()
927 41 : : pszWKT2_2015 ? pszWKT2_2015.get()
928 89 : : pszWKT2_2019.get();
929 :
930 42 : if (pszAuthorityName != nullptr && nAuthorityCode > 0)
931 : {
932 93 : pszSQL = sqlite3_mprintf(
933 : "INSERT INTO gpkg_spatial_ref_sys "
934 : "(srs_name,srs_id,organization,organization_coordsys_id,"
935 : "definition, definition_12_063%s) VALUES "
936 : "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
937 31 : osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId,
938 : pszAuthorityName, nAuthorityCode,
939 59 : pszWKT1 ? pszWKT1.get() : "undefined",
940 : pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
941 : }
942 : else
943 : {
944 33 : pszSQL = sqlite3_mprintf(
945 : "INSERT INTO gpkg_spatial_ref_sys "
946 : "(srs_name,srs_id,organization,organization_coordsys_id,"
947 : "definition, definition_12_063%s) VALUES "
948 : "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
949 11 : osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId, "NONE",
950 20 : nSRSId, pszWKT1 ? pszWKT1.get() : "undefined",
951 : pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
952 : }
953 : }
954 : else
955 : {
956 187 : if (pszAuthorityName != nullptr && nAuthorityCode > 0)
957 : {
958 350 : pszSQL = sqlite3_mprintf(
959 : "INSERT INTO gpkg_spatial_ref_sys "
960 : "(srs_name,srs_id,organization,organization_coordsys_id,"
961 : "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
962 175 : GetSrsName(*poSRS), nSRSId, pszAuthorityName, nAuthorityCode,
963 350 : pszWKT1 ? pszWKT1.get() : "undefined");
964 : }
965 : else
966 : {
967 24 : pszSQL = sqlite3_mprintf(
968 : "INSERT INTO gpkg_spatial_ref_sys "
969 : "(srs_name,srs_id,organization,organization_coordsys_id,"
970 : "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
971 12 : GetSrsName(*poSRS), nSRSId, "NONE", nSRSId,
972 24 : pszWKT1 ? pszWKT1.get() : "undefined");
973 : }
974 : }
975 :
976 : // Add new row to gpkg_spatial_ref_sys.
977 229 : CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
978 :
979 : // Free everything that was allocated.
980 229 : sqlite3_free(pszSQL);
981 :
982 229 : return nSRSId;
983 : }
984 :
985 : /************************************************************************/
986 : /* GDALGeoPackageDataset() */
987 : /************************************************************************/
988 :
989 2171 : GDALGeoPackageDataset::GDALGeoPackageDataset()
990 : : m_nApplicationId(GPKG_APPLICATION_ID), m_nUserVersion(GPKG_1_2_VERSION),
991 : m_papoLayers(nullptr), m_nLayers(0),
992 : #ifdef ENABLE_GPKG_OGR_CONTENTS
993 : m_bHasGPKGOGRContents(false),
994 : #endif
995 : m_bHasGPKGGeometryColumns(false), m_bHasDefinition12_063(false),
996 : m_bIdentifierAsCO(false), m_bDescriptionAsCO(false),
997 : m_bHasReadMetadataFromStorage(false), m_bMetadataDirty(false),
998 : m_bRecordInsertedInGPKGContent(false), m_bGeoTransformValid(false),
999 : m_nSRID(-1), // Unknown Cartesian.
1000 : m_dfTMSMinX(0.0), m_dfTMSMaxY(0.0), m_nOverviewCount(0),
1001 : m_papoOverviewDS(nullptr), m_bZoomOther(false), m_bInFlushCache(false),
1002 : m_osTilingScheme("CUSTOM"), m_bMapTableToExtensionsBuilt(false),
1003 2171 : m_bMapTableToContentsBuilt(false)
1004 : {
1005 2171 : m_adfGeoTransform[0] = 0.0;
1006 2171 : m_adfGeoTransform[1] = 1.0;
1007 2171 : m_adfGeoTransform[2] = 0.0;
1008 2171 : m_adfGeoTransform[3] = 0.0;
1009 2171 : m_adfGeoTransform[4] = 0.0;
1010 2171 : m_adfGeoTransform[5] = 1.0;
1011 2171 : }
1012 :
1013 : /************************************************************************/
1014 : /* ~GDALGeoPackageDataset() */
1015 : /************************************************************************/
1016 :
1017 4342 : GDALGeoPackageDataset::~GDALGeoPackageDataset()
1018 : {
1019 2171 : GDALGeoPackageDataset::Close();
1020 4342 : }
1021 :
1022 : /************************************************************************/
1023 : /* Close() */
1024 : /************************************************************************/
1025 :
1026 3601 : CPLErr GDALGeoPackageDataset::Close()
1027 : {
1028 3601 : CPLErr eErr = CE_None;
1029 3601 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
1030 : {
1031 1264 : if (eAccess == GA_Update && m_poParentDS == nullptr &&
1032 3435 : !m_osRasterTable.empty() && !m_bGeoTransformValid)
1033 : {
1034 3 : CPLError(CE_Failure, CPLE_AppDefined,
1035 : "Raster table %s not correctly initialized due to missing "
1036 : "call to SetGeoTransform()",
1037 : m_osRasterTable.c_str());
1038 : }
1039 :
1040 2171 : if (GDALGeoPackageDataset::FlushCache(true) != CE_None)
1041 7 : eErr = CE_Failure;
1042 :
1043 : // Destroy bands now since we don't want
1044 : // GDALGPKGMBTilesLikeRasterBand::FlushCache() to run after dataset
1045 : // destruction
1046 3944 : for (int i = 0; i < nBands; i++)
1047 1773 : delete papoBands[i];
1048 2171 : nBands = 0;
1049 2171 : CPLFree(papoBands);
1050 2171 : papoBands = nullptr;
1051 :
1052 : // Destroy overviews before cleaning m_hTempDB as they could still
1053 : // need it
1054 2496 : for (int i = 0; i < m_nOverviewCount; i++)
1055 325 : delete m_papoOverviewDS[i];
1056 :
1057 2171 : if (m_poParentDS)
1058 : {
1059 325 : hDB = nullptr;
1060 : }
1061 :
1062 5720 : for (int i = 0; i < m_nLayers; i++)
1063 3549 : delete m_papoLayers[i];
1064 :
1065 2171 : CPLFree(m_papoLayers);
1066 2171 : CPLFree(m_papoOverviewDS);
1067 :
1068 : std::map<int, OGRSpatialReference *>::iterator oIter =
1069 2171 : m_oMapSrsIdToSrs.begin();
1070 3170 : for (; oIter != m_oMapSrsIdToSrs.end(); ++oIter)
1071 : {
1072 999 : OGRSpatialReference *poSRS = oIter->second;
1073 999 : if (poSRS)
1074 636 : poSRS->Release();
1075 : }
1076 :
1077 2171 : if (!CloseDB())
1078 0 : eErr = CE_Failure;
1079 :
1080 2171 : if (OGRSQLiteBaseDataSource::Close() != CE_None)
1081 0 : eErr = CE_Failure;
1082 : }
1083 3601 : return eErr;
1084 : }
1085 :
1086 : /************************************************************************/
1087 : /* ICanIWriteBlock() */
1088 : /************************************************************************/
1089 :
1090 5677 : bool GDALGeoPackageDataset::ICanIWriteBlock()
1091 : {
1092 5677 : if (!GetUpdate())
1093 : {
1094 0 : CPLError(
1095 : CE_Failure, CPLE_NotSupported,
1096 : "IWriteBlock() not supported on dataset opened in read-only mode");
1097 0 : return false;
1098 : }
1099 :
1100 5677 : if (m_pabyCachedTiles == nullptr)
1101 : {
1102 0 : return false;
1103 : }
1104 :
1105 5677 : if (!m_bGeoTransformValid || m_nSRID == UNKNOWN_SRID)
1106 : {
1107 0 : CPLError(CE_Failure, CPLE_NotSupported,
1108 : "IWriteBlock() not supported if georeferencing not set");
1109 0 : return false;
1110 : }
1111 5677 : return true;
1112 : }
1113 :
1114 : /************************************************************************/
1115 : /* IRasterIO() */
1116 : /************************************************************************/
1117 :
1118 114 : CPLErr GDALGeoPackageDataset::IRasterIO(
1119 : GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
1120 : void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
1121 : int nBandCount, BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
1122 : GSpacing nLineSpace, GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
1123 :
1124 : {
1125 114 : CPLErr eErr = OGRSQLiteBaseDataSource::IRasterIO(
1126 : eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
1127 : eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace, nBandSpace,
1128 : psExtraArg);
1129 :
1130 : // If writing all bands, in non-shifted mode, flush all entirely written
1131 : // tiles This can avoid "stressing" the block cache with too many dirty
1132 : // blocks. Note: this logic would be useless with a per-dataset block cache.
1133 114 : if (eErr == CE_None && eRWFlag == GF_Write && nXSize == nBufXSize &&
1134 105 : nYSize == nBufYSize && nBandCount == nBands &&
1135 102 : m_nShiftXPixelsMod == 0 && m_nShiftYPixelsMod == 0)
1136 : {
1137 : auto poBand =
1138 98 : cpl::down_cast<GDALGPKGMBTilesLikeRasterBand *>(GetRasterBand(1));
1139 : int nBlockXSize, nBlockYSize;
1140 98 : poBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
1141 98 : const int nBlockXStart = DIV_ROUND_UP(nXOff, nBlockXSize);
1142 98 : const int nBlockYStart = DIV_ROUND_UP(nYOff, nBlockYSize);
1143 98 : const int nBlockXEnd = (nXOff + nXSize) / nBlockXSize;
1144 98 : const int nBlockYEnd = (nYOff + nYSize) / nBlockYSize;
1145 252 : for (int nBlockY = nBlockXStart; nBlockY < nBlockYEnd; nBlockY++)
1146 : {
1147 4371 : for (int nBlockX = nBlockYStart; nBlockX < nBlockXEnd; nBlockX++)
1148 : {
1149 : GDALRasterBlock *poBlock =
1150 4217 : poBand->AccessibleTryGetLockedBlockRef(nBlockX, nBlockY);
1151 4217 : if (poBlock)
1152 : {
1153 : // GetDirty() should be true in most situation (otherwise
1154 : // it means the block cache is under extreme pressure!)
1155 4215 : if (poBlock->GetDirty())
1156 : {
1157 : // IWriteBlock() on one band will check the dirty state
1158 : // of the corresponding blocks in other bands, to decide
1159 : // if it can call WriteTile(), so we have only to do
1160 : // that on one of the bands
1161 4215 : if (poBlock->Write() != CE_None)
1162 250 : eErr = CE_Failure;
1163 : }
1164 4215 : poBlock->DropLock();
1165 : }
1166 : }
1167 : }
1168 : }
1169 :
1170 114 : return eErr;
1171 : }
1172 :
1173 : /************************************************************************/
1174 : /* GetOGRTableLimit() */
1175 : /************************************************************************/
1176 :
1177 3441 : static int GetOGRTableLimit()
1178 : {
1179 3441 : return atoi(CPLGetConfigOption("OGR_TABLE_LIMIT", "10000"));
1180 : }
1181 :
1182 : /************************************************************************/
1183 : /* GetNameTypeMapFromSQliteMaster() */
1184 : /************************************************************************/
1185 :
1186 : const std::map<CPLString, CPLString> &
1187 1056 : GDALGeoPackageDataset::GetNameTypeMapFromSQliteMaster()
1188 : {
1189 1056 : if (!m_oMapNameToType.empty())
1190 294 : return m_oMapNameToType;
1191 :
1192 : CPLString osSQL(
1193 : "SELECT name, type FROM sqlite_master WHERE "
1194 : "type IN ('view', 'table') OR "
1195 1524 : "(name LIKE 'trigger_%_feature_count_%' AND type = 'trigger')");
1196 762 : const int nTableLimit = GetOGRTableLimit();
1197 762 : if (nTableLimit > 0)
1198 : {
1199 762 : osSQL += " LIMIT ";
1200 762 : osSQL += CPLSPrintf("%d", 1 + 3 * nTableLimit);
1201 : }
1202 :
1203 762 : auto oResult = SQLQuery(hDB, osSQL);
1204 762 : if (oResult)
1205 : {
1206 12686 : for (int i = 0; i < oResult->RowCount(); i++)
1207 : {
1208 11924 : const char *pszName = oResult->GetValue(0, i);
1209 11924 : const char *pszType = oResult->GetValue(1, i);
1210 11924 : m_oMapNameToType[CPLString(pszName).toupper()] = pszType;
1211 : }
1212 : }
1213 :
1214 762 : return m_oMapNameToType;
1215 : }
1216 :
1217 : /************************************************************************/
1218 : /* RemoveTableFromSQLiteMasterCache() */
1219 : /************************************************************************/
1220 :
1221 51 : void GDALGeoPackageDataset::RemoveTableFromSQLiteMasterCache(
1222 : const char *pszTableName)
1223 : {
1224 51 : m_oMapNameToType.erase(CPLString(pszTableName).toupper());
1225 51 : }
1226 :
1227 : /************************************************************************/
1228 : /* GetUnknownExtensionsTableSpecific() */
1229 : /************************************************************************/
1230 :
1231 : const std::map<CPLString, std::vector<GPKGExtensionDesc>> &
1232 728 : GDALGeoPackageDataset::GetUnknownExtensionsTableSpecific()
1233 : {
1234 728 : if (m_bMapTableToExtensionsBuilt)
1235 68 : return m_oMapTableToExtensions;
1236 660 : m_bMapTableToExtensionsBuilt = true;
1237 :
1238 660 : if (!HasExtensionsTable())
1239 40 : return m_oMapTableToExtensions;
1240 :
1241 : CPLString osSQL(
1242 : "SELECT table_name, extension_name, definition, scope "
1243 : "FROM gpkg_extensions WHERE "
1244 : "table_name IS NOT NULL "
1245 : "AND extension_name IS NOT NULL "
1246 : "AND definition IS NOT NULL "
1247 : "AND scope IS NOT NULL "
1248 : "AND extension_name NOT IN ('gpkg_geom_CIRCULARSTRING', "
1249 : "'gpkg_geom_COMPOUNDCURVE', 'gpkg_geom_CURVEPOLYGON', "
1250 : "'gpkg_geom_MULTICURVE', "
1251 : "'gpkg_geom_MULTISURFACE', 'gpkg_geom_CURVE', 'gpkg_geom_SURFACE', "
1252 : "'gpkg_geom_POLYHEDRALSURFACE', 'gpkg_geom_TIN', 'gpkg_geom_TRIANGLE', "
1253 : "'gpkg_rtree_index', 'gpkg_geometry_type_trigger', "
1254 : "'gpkg_srs_id_trigger', "
1255 : "'gpkg_crs_wkt', 'gpkg_crs_wkt_1_1', 'gpkg_schema', "
1256 : "'gpkg_related_tables', 'related_tables'"
1257 : #ifdef HAVE_SPATIALITE
1258 : ", 'gdal_spatialite_computed_geom_column'"
1259 : #endif
1260 1240 : ")");
1261 620 : const int nTableLimit = GetOGRTableLimit();
1262 620 : if (nTableLimit > 0)
1263 : {
1264 620 : osSQL += " LIMIT ";
1265 620 : osSQL += CPLSPrintf("%d", 1 + 10 * nTableLimit);
1266 : }
1267 :
1268 620 : auto oResult = SQLQuery(hDB, osSQL);
1269 620 : if (oResult)
1270 : {
1271 1199 : for (int i = 0; i < oResult->RowCount(); i++)
1272 : {
1273 579 : const char *pszTableName = oResult->GetValue(0, i);
1274 579 : const char *pszExtensionName = oResult->GetValue(1, i);
1275 579 : const char *pszDefinition = oResult->GetValue(2, i);
1276 579 : const char *pszScope = oResult->GetValue(3, i);
1277 579 : if (pszTableName && pszExtensionName && pszDefinition && pszScope)
1278 : {
1279 579 : GPKGExtensionDesc oDesc;
1280 579 : oDesc.osExtensionName = pszExtensionName;
1281 579 : oDesc.osDefinition = pszDefinition;
1282 579 : oDesc.osScope = pszScope;
1283 1158 : m_oMapTableToExtensions[CPLString(pszTableName).toupper()]
1284 579 : .push_back(oDesc);
1285 : }
1286 : }
1287 : }
1288 :
1289 620 : return m_oMapTableToExtensions;
1290 : }
1291 :
1292 : /************************************************************************/
1293 : /* GetContents() */
1294 : /************************************************************************/
1295 :
1296 : const std::map<CPLString, GPKGContentsDesc> &
1297 714 : GDALGeoPackageDataset::GetContents()
1298 : {
1299 714 : if (m_bMapTableToContentsBuilt)
1300 56 : return m_oMapTableToContents;
1301 658 : m_bMapTableToContentsBuilt = true;
1302 :
1303 : CPLString osSQL("SELECT table_name, data_type, identifier, "
1304 : "description, min_x, min_y, max_x, max_y "
1305 1316 : "FROM gpkg_contents");
1306 658 : const int nTableLimit = GetOGRTableLimit();
1307 658 : if (nTableLimit > 0)
1308 : {
1309 658 : osSQL += " LIMIT ";
1310 658 : osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1311 : }
1312 :
1313 658 : auto oResult = SQLQuery(hDB, osSQL);
1314 658 : if (oResult)
1315 : {
1316 1411 : for (int i = 0; i < oResult->RowCount(); i++)
1317 : {
1318 753 : const char *pszTableName = oResult->GetValue(0, i);
1319 753 : if (pszTableName == nullptr)
1320 0 : continue;
1321 753 : const char *pszDataType = oResult->GetValue(1, i);
1322 753 : const char *pszIdentifier = oResult->GetValue(2, i);
1323 753 : const char *pszDescription = oResult->GetValue(3, i);
1324 753 : const char *pszMinX = oResult->GetValue(4, i);
1325 753 : const char *pszMinY = oResult->GetValue(5, i);
1326 753 : const char *pszMaxX = oResult->GetValue(6, i);
1327 753 : const char *pszMaxY = oResult->GetValue(7, i);
1328 753 : GPKGContentsDesc oDesc;
1329 753 : if (pszDataType)
1330 753 : oDesc.osDataType = pszDataType;
1331 753 : if (pszIdentifier)
1332 753 : oDesc.osIdentifier = pszIdentifier;
1333 753 : if (pszDescription)
1334 752 : oDesc.osDescription = pszDescription;
1335 753 : if (pszMinX)
1336 527 : oDesc.osMinX = pszMinX;
1337 753 : if (pszMinY)
1338 527 : oDesc.osMinY = pszMinY;
1339 753 : if (pszMaxX)
1340 527 : oDesc.osMaxX = pszMaxX;
1341 753 : if (pszMaxY)
1342 527 : oDesc.osMaxY = pszMaxY;
1343 1506 : m_oMapTableToContents[CPLString(pszTableName).toupper()] =
1344 1506 : std::move(oDesc);
1345 : }
1346 : }
1347 :
1348 658 : return m_oMapTableToContents;
1349 : }
1350 :
1351 : /************************************************************************/
1352 : /* Open() */
1353 : /************************************************************************/
1354 :
1355 1071 : int GDALGeoPackageDataset::Open(GDALOpenInfo *poOpenInfo,
1356 : const std::string &osFilenameInZip)
1357 : {
1358 1071 : m_osFilenameInZip = osFilenameInZip;
1359 1071 : CPLAssert(m_nLayers == 0);
1360 1071 : CPLAssert(hDB == nullptr);
1361 :
1362 1071 : SetDescription(poOpenInfo->pszFilename);
1363 2142 : CPLString osFilename(poOpenInfo->pszFilename);
1364 2142 : CPLString osSubdatasetTableName;
1365 : GByte abyHeaderLetMeHerePlease[100];
1366 1071 : const GByte *pabyHeader = poOpenInfo->pabyHeader;
1367 1071 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GPKG:"))
1368 : {
1369 240 : char **papszTokens = CSLTokenizeString2(poOpenInfo->pszFilename, ":",
1370 : CSLT_HONOURSTRINGS);
1371 240 : int nCount = CSLCount(papszTokens);
1372 240 : if (nCount < 2)
1373 : {
1374 0 : CSLDestroy(papszTokens);
1375 0 : return FALSE;
1376 : }
1377 :
1378 240 : if (nCount <= 3)
1379 : {
1380 238 : osFilename = papszTokens[1];
1381 : }
1382 : /* GPKG:C:\BLA.GPKG:foo */
1383 2 : else if (nCount == 4 && strlen(papszTokens[1]) == 1 &&
1384 2 : (papszTokens[2][0] == '/' || papszTokens[2][0] == '\\'))
1385 : {
1386 2 : osFilename = CPLString(papszTokens[1]) + ":" + papszTokens[2];
1387 : }
1388 : // GPKG:/vsicurl/http[s]://[user:passwd@]example.com[:8080]/foo.gpkg:bar
1389 0 : else if (/*nCount >= 4 && */
1390 0 : (EQUAL(papszTokens[1], "/vsicurl/http") ||
1391 0 : EQUAL(papszTokens[1], "/vsicurl/https")))
1392 : {
1393 0 : osFilename = CPLString(papszTokens[1]);
1394 0 : for (int i = 2; i < nCount - 1; i++)
1395 : {
1396 0 : osFilename += ':';
1397 0 : osFilename += papszTokens[i];
1398 : }
1399 : }
1400 240 : if (nCount >= 3)
1401 12 : osSubdatasetTableName = papszTokens[nCount - 1];
1402 :
1403 240 : CSLDestroy(papszTokens);
1404 240 : VSILFILE *fp = VSIFOpenL(osFilename, "rb");
1405 240 : if (fp != nullptr)
1406 : {
1407 240 : VSIFReadL(abyHeaderLetMeHerePlease, 1, 100, fp);
1408 240 : VSIFCloseL(fp);
1409 : }
1410 240 : pabyHeader = abyHeaderLetMeHerePlease;
1411 : }
1412 831 : else if (poOpenInfo->pabyHeader &&
1413 831 : STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
1414 : "SQLite format 3"))
1415 : {
1416 825 : m_bCallUndeclareFileNotToOpen = true;
1417 825 : GDALOpenInfoDeclareFileNotToOpen(osFilename, poOpenInfo->pabyHeader,
1418 : poOpenInfo->nHeaderBytes);
1419 : }
1420 :
1421 1071 : eAccess = poOpenInfo->eAccess;
1422 1071 : if (!m_osFilenameInZip.empty())
1423 : {
1424 1 : m_pszFilename = CPLStrdup(CPLSPrintf(
1425 : "/vsizip/{%s}/%s", osFilename.c_str(), m_osFilenameInZip.c_str()));
1426 : }
1427 : else
1428 : {
1429 1070 : m_pszFilename = CPLStrdup(osFilename);
1430 : }
1431 :
1432 1071 : if (poOpenInfo->papszOpenOptions)
1433 : {
1434 98 : CSLDestroy(papszOpenOptions);
1435 98 : papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
1436 : }
1437 :
1438 : #ifdef ENABLE_SQL_GPKG_FORMAT
1439 1071 : if (poOpenInfo->pabyHeader &&
1440 831 : STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
1441 5 : "-- SQL GPKG") &&
1442 5 : poOpenInfo->fpL != nullptr)
1443 : {
1444 5 : if (sqlite3_open_v2(":memory:", &hDB, SQLITE_OPEN_READWRITE, nullptr) !=
1445 : SQLITE_OK)
1446 : {
1447 0 : return FALSE;
1448 : }
1449 :
1450 5 : InstallSQLFunctions();
1451 :
1452 : // Ingest the lines of the dump
1453 5 : VSIFSeekL(poOpenInfo->fpL, 0, SEEK_SET);
1454 : const char *pszLine;
1455 76 : while ((pszLine = CPLReadLineL(poOpenInfo->fpL)) != nullptr)
1456 : {
1457 71 : if (STARTS_WITH(pszLine, "--"))
1458 5 : continue;
1459 :
1460 : // Reject a few words tat might have security implications
1461 : // Basically we just want to allow CREATE TABLE and INSERT INTO
1462 66 : if (CPLString(pszLine).ifind("ATTACH") != std::string::npos ||
1463 132 : CPLString(pszLine).ifind("DETACH") != std::string::npos ||
1464 132 : CPLString(pszLine).ifind("PRAGMA") != std::string::npos ||
1465 132 : CPLString(pszLine).ifind("SELECT") != std::string::npos ||
1466 128 : CPLString(pszLine).ifind("UPDATE") != std::string::npos ||
1467 128 : CPLString(pszLine).ifind("REPLACE") != std::string::npos ||
1468 128 : CPLString(pszLine).ifind("DELETE") != std::string::npos ||
1469 128 : CPLString(pszLine).ifind("DROP") != std::string::npos ||
1470 260 : CPLString(pszLine).ifind("ALTER") != std::string::npos ||
1471 128 : CPLString(pszLine).ifind("VIRTUAL") != std::string::npos)
1472 : {
1473 8 : bool bOK = false;
1474 : // Accept creation of spatial index
1475 8 : if (STARTS_WITH_CI(pszLine, "CREATE VIRTUAL TABLE "))
1476 : {
1477 4 : const char *pszStr =
1478 : pszLine + strlen("CREATE VIRTUAL TABLE ");
1479 4 : if (*pszStr == '"')
1480 0 : pszStr++;
1481 52 : while ((*pszStr >= 'a' && *pszStr <= 'z') ||
1482 64 : (*pszStr >= 'A' && *pszStr <= 'Z') || *pszStr == '_')
1483 : {
1484 60 : pszStr++;
1485 : }
1486 4 : if (*pszStr == '"')
1487 0 : pszStr++;
1488 4 : if (EQUAL(pszStr,
1489 : " USING rtree(id, minx, maxx, miny, maxy);"))
1490 : {
1491 4 : bOK = true;
1492 : }
1493 : }
1494 : // Accept INSERT INTO rtree_poly_geom SELECT fid, ST_MinX(geom),
1495 : // ST_MaxX(geom), ST_MinY(geom), ST_MaxY(geom) FROM poly;
1496 8 : else if (STARTS_WITH_CI(pszLine, "INSERT INTO rtree_") &&
1497 8 : CPLString(pszLine).ifind("SELECT") !=
1498 : std::string::npos)
1499 : {
1500 : char **papszTokens =
1501 4 : CSLTokenizeString2(pszLine, " (),,", 0);
1502 4 : if (CSLCount(papszTokens) == 15 &&
1503 4 : EQUAL(papszTokens[3], "SELECT") &&
1504 4 : EQUAL(papszTokens[5], "ST_MinX") &&
1505 4 : EQUAL(papszTokens[7], "ST_MaxX") &&
1506 4 : EQUAL(papszTokens[9], "ST_MinY") &&
1507 12 : EQUAL(papszTokens[11], "ST_MaxY") &&
1508 4 : EQUAL(papszTokens[13], "FROM"))
1509 : {
1510 4 : bOK = TRUE;
1511 : }
1512 4 : CSLDestroy(papszTokens);
1513 : }
1514 :
1515 8 : if (!bOK)
1516 : {
1517 0 : CPLError(CE_Failure, CPLE_NotSupported,
1518 : "Rejected statement: %s", pszLine);
1519 0 : return FALSE;
1520 : }
1521 : }
1522 66 : char *pszErrMsg = nullptr;
1523 66 : if (sqlite3_exec(hDB, pszLine, nullptr, nullptr, &pszErrMsg) !=
1524 : SQLITE_OK)
1525 : {
1526 0 : if (pszErrMsg)
1527 0 : CPLDebug("SQLITE", "Error %s", pszErrMsg);
1528 : }
1529 66 : sqlite3_free(pszErrMsg);
1530 5 : }
1531 : }
1532 :
1533 1066 : else if (pabyHeader != nullptr)
1534 : #endif
1535 : {
1536 1066 : if (poOpenInfo->fpL)
1537 : {
1538 : // See above comment about -wal locking for the importance of
1539 : // closing that file, prior to calling sqlite3_open()
1540 726 : VSIFCloseL(poOpenInfo->fpL);
1541 726 : poOpenInfo->fpL = nullptr;
1542 : }
1543 :
1544 : /* See if we can open the SQLite database */
1545 1066 : if (!OpenOrCreateDB(GetUpdate() ? SQLITE_OPEN_READWRITE
1546 : : SQLITE_OPEN_READONLY))
1547 2 : return FALSE;
1548 :
1549 1064 : memcpy(&m_nApplicationId, pabyHeader + knApplicationIdPos, 4);
1550 1064 : m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
1551 1064 : memcpy(&m_nUserVersion, pabyHeader + knUserVersionPos, 4);
1552 1064 : m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
1553 1064 : if (m_nApplicationId == GP10_APPLICATION_ID)
1554 : {
1555 7 : CPLDebug("GPKG", "GeoPackage v1.0");
1556 : }
1557 1057 : else if (m_nApplicationId == GP11_APPLICATION_ID)
1558 : {
1559 2 : CPLDebug("GPKG", "GeoPackage v1.1");
1560 : }
1561 1055 : else if (m_nApplicationId == GPKG_APPLICATION_ID &&
1562 1052 : m_nUserVersion >= GPKG_1_2_VERSION)
1563 : {
1564 1050 : CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
1565 1050 : (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
1566 : }
1567 : }
1568 :
1569 : /* Requirement 6: The SQLite PRAGMA integrity_check SQL command SHALL return
1570 : * “ok” */
1571 : /* http://opengis.github.io/geopackage/#_file_integrity */
1572 : /* Disable integrity check by default, since it is expensive on big files */
1573 1069 : if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_INTEGRITY_CHECK", "NO")) &&
1574 0 : OGRERR_NONE != PragmaCheck("integrity_check", "ok", 1))
1575 : {
1576 0 : CPLError(CE_Failure, CPLE_AppDefined,
1577 : "pragma integrity_check on '%s' failed", m_pszFilename);
1578 0 : return FALSE;
1579 : }
1580 :
1581 : /* Requirement 7: The SQLite PRAGMA foreign_key_check() SQL with no */
1582 : /* parameter value SHALL return an empty result set */
1583 : /* http://opengis.github.io/geopackage/#_file_integrity */
1584 : /* Disable the check by default, since it is to corrupt databases, and */
1585 : /* that causes issues to downstream software that can't open them. */
1586 1069 : if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_FOREIGN_KEY_CHECK", "NO")) &&
1587 0 : OGRERR_NONE != PragmaCheck("foreign_key_check", "", 0))
1588 : {
1589 0 : CPLError(CE_Failure, CPLE_AppDefined,
1590 : "pragma foreign_key_check on '%s' failed.", m_pszFilename);
1591 0 : return FALSE;
1592 : }
1593 :
1594 : /* Check for requirement metadata tables */
1595 : /* Requirement 10: gpkg_spatial_ref_sys must exist */
1596 : /* Requirement 13: gpkg_contents must exist */
1597 1069 : if (SQLGetInteger(hDB,
1598 : "SELECT COUNT(*) FROM sqlite_master WHERE "
1599 : "name IN ('gpkg_spatial_ref_sys', 'gpkg_contents') AND "
1600 : "type IN ('table', 'view')",
1601 1069 : nullptr) != 2)
1602 : {
1603 0 : CPLError(CE_Failure, CPLE_AppDefined,
1604 : "At least one of the required GeoPackage tables, "
1605 : "gpkg_spatial_ref_sys or gpkg_contents, is missing");
1606 0 : return FALSE;
1607 : }
1608 :
1609 1069 : DetectSpatialRefSysColumns();
1610 :
1611 : #ifdef ENABLE_GPKG_OGR_CONTENTS
1612 1069 : if (SQLGetInteger(hDB,
1613 : "SELECT 1 FROM sqlite_master WHERE "
1614 : "name = 'gpkg_ogr_contents' AND type = 'table'",
1615 1069 : nullptr) == 1)
1616 : {
1617 1064 : m_bHasGPKGOGRContents = true;
1618 : }
1619 : #endif
1620 :
1621 1069 : CheckUnknownExtensions();
1622 :
1623 1069 : int bRet = FALSE;
1624 1069 : bool bHasGPKGExtRelations = false;
1625 1069 : if (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR)
1626 : {
1627 886 : m_bHasGPKGGeometryColumns =
1628 886 : SQLGetInteger(hDB,
1629 : "SELECT 1 FROM sqlite_master WHERE "
1630 : "name = 'gpkg_geometry_columns' AND "
1631 : "type IN ('table', 'view')",
1632 886 : nullptr) == 1;
1633 886 : bHasGPKGExtRelations = HasGpkgextRelationsTable();
1634 : }
1635 1069 : if (m_bHasGPKGGeometryColumns)
1636 : {
1637 : /* Load layer definitions for all tables in gpkg_contents &
1638 : * gpkg_geometry_columns */
1639 : /* and non-spatial tables as well */
1640 : std::string osSQL =
1641 : "SELECT c.table_name, c.identifier, 1 as is_spatial, "
1642 : "g.column_name, g.geometry_type_name, g.z, g.m, c.min_x, c.min_y, "
1643 : "c.max_x, c.max_y, 1 AS is_in_gpkg_contents, "
1644 : "(SELECT type FROM sqlite_master WHERE lower(name) = "
1645 : "lower(c.table_name) AND type IN ('table', 'view')) AS object_type "
1646 : " FROM gpkg_geometry_columns g "
1647 : " JOIN gpkg_contents c ON (g.table_name = c.table_name)"
1648 : " WHERE "
1649 : " c.table_name <> 'ogr_empty_table' AND"
1650 : " c.data_type = 'features' "
1651 : // aspatial: Was the only method available in OGR 2.0 and 2.1
1652 : // attributes: GPKG 1.2 or later
1653 : "UNION ALL "
1654 : "SELECT table_name, identifier, 0 as is_spatial, NULL, NULL, 0, 0, "
1655 : "0 AS xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 1 AS "
1656 : "is_in_gpkg_contents, "
1657 : "(SELECT type FROM sqlite_master WHERE lower(name) = "
1658 : "lower(table_name) AND type IN ('table', 'view')) AS object_type "
1659 : " FROM gpkg_contents"
1660 885 : " WHERE data_type IN ('aspatial', 'attributes') ";
1661 :
1662 1770 : const char *pszListAllTables = CSLFetchNameValueDef(
1663 885 : poOpenInfo->papszOpenOptions, "LIST_ALL_TABLES", "AUTO");
1664 885 : bool bHasASpatialOrAttributes = HasGDALAspatialExtension();
1665 885 : if (!bHasASpatialOrAttributes)
1666 : {
1667 : auto oResultTable =
1668 : SQLQuery(hDB, "SELECT * FROM gpkg_contents WHERE "
1669 884 : "data_type = 'attributes' LIMIT 1");
1670 884 : bHasASpatialOrAttributes =
1671 884 : (oResultTable && oResultTable->RowCount() == 1);
1672 : }
1673 885 : if (bHasGPKGExtRelations)
1674 : {
1675 : osSQL += "UNION ALL "
1676 : "SELECT mapping_table_name, mapping_table_name, 0 as "
1677 : "is_spatial, NULL, NULL, 0, 0, 0 AS "
1678 : "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
1679 : "is_in_gpkg_contents, 'table' AS object_type "
1680 : "FROM gpkgext_relations WHERE "
1681 : "lower(mapping_table_name) NOT IN (SELECT "
1682 : "lower(table_name) FROM "
1683 12 : "gpkg_contents)";
1684 : }
1685 885 : if (EQUAL(pszListAllTables, "YES") ||
1686 885 : (!bHasASpatialOrAttributes && EQUAL(pszListAllTables, "AUTO")))
1687 : {
1688 : // vgpkg_ is Spatialite virtual table
1689 : osSQL +=
1690 : "UNION ALL "
1691 : "SELECT name, name, 0 as is_spatial, NULL, NULL, 0, 0, 0 AS "
1692 : "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
1693 : "is_in_gpkg_contents, type AS object_type "
1694 : "FROM sqlite_master WHERE type IN ('table', 'view') "
1695 : "AND name NOT LIKE 'gpkg_%' "
1696 : "AND name NOT LIKE 'vgpkg_%' "
1697 : "AND name NOT LIKE 'rtree_%' AND name NOT LIKE 'sqlite_%' "
1698 : // Avoid reading those views from simple_sewer_features.gpkg
1699 : "AND name NOT IN ('st_spatial_ref_sys', 'spatial_ref_sys', "
1700 : "'st_geometry_columns', 'geometry_columns') "
1701 : "AND lower(name) NOT IN (SELECT lower(table_name) FROM "
1702 832 : "gpkg_contents)";
1703 832 : if (bHasGPKGExtRelations)
1704 : {
1705 : osSQL += " AND lower(name) NOT IN (SELECT "
1706 : "lower(mapping_table_name) FROM "
1707 8 : "gpkgext_relations)";
1708 : }
1709 : }
1710 885 : const int nTableLimit = GetOGRTableLimit();
1711 885 : if (nTableLimit > 0)
1712 : {
1713 885 : osSQL += " LIMIT ";
1714 885 : osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1715 : }
1716 :
1717 885 : auto oResult = SQLQuery(hDB, osSQL.c_str());
1718 885 : if (!oResult)
1719 : {
1720 0 : return FALSE;
1721 : }
1722 :
1723 885 : if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
1724 : {
1725 1 : CPLError(CE_Warning, CPLE_AppDefined,
1726 : "File has more than %d vector tables. "
1727 : "Limiting to first %d (can be overridden with "
1728 : "OGR_TABLE_LIMIT config option)",
1729 : nTableLimit, nTableLimit);
1730 1 : oResult->LimitRowCount(nTableLimit);
1731 : }
1732 :
1733 885 : if (oResult->RowCount() > 0)
1734 : {
1735 776 : bRet = TRUE;
1736 :
1737 1552 : m_papoLayers = static_cast<OGRGeoPackageTableLayer **>(CPLMalloc(
1738 776 : sizeof(OGRGeoPackageTableLayer *) * oResult->RowCount()));
1739 :
1740 1552 : std::map<std::string, int> oMapTableRefCount;
1741 3713 : for (int i = 0; i < oResult->RowCount(); i++)
1742 : {
1743 2937 : const char *pszTableName = oResult->GetValue(0, i);
1744 2937 : if (pszTableName == nullptr)
1745 0 : continue;
1746 2937 : if (++oMapTableRefCount[pszTableName] == 2)
1747 : {
1748 : // This should normally not happen if all constraints are
1749 : // properly set
1750 2 : CPLError(CE_Warning, CPLE_AppDefined,
1751 : "Table %s appearing several times in "
1752 : "gpkg_contents and/or gpkg_geometry_columns",
1753 : pszTableName);
1754 : }
1755 : }
1756 :
1757 1552 : std::set<std::string> oExistingLayers;
1758 3713 : for (int i = 0; i < oResult->RowCount(); i++)
1759 : {
1760 2937 : const char *pszTableName = oResult->GetValue(0, i);
1761 2937 : if (pszTableName == nullptr)
1762 2 : continue;
1763 : const bool bTableHasSeveralGeomColumns =
1764 2937 : oMapTableRefCount[pszTableName] > 1;
1765 2937 : bool bIsSpatial = CPL_TO_BOOL(oResult->GetValueAsInteger(2, i));
1766 2937 : const char *pszGeomColName = oResult->GetValue(3, i);
1767 2937 : const char *pszGeomType = oResult->GetValue(4, i);
1768 2937 : const char *pszZ = oResult->GetValue(5, i);
1769 2937 : const char *pszM = oResult->GetValue(6, i);
1770 : bool bIsInGpkgContents =
1771 2937 : CPL_TO_BOOL(oResult->GetValueAsInteger(11, i));
1772 2937 : if (!bIsInGpkgContents)
1773 41 : m_bNonSpatialTablesNonRegisteredInGpkgContentsFound = true;
1774 2937 : const char *pszObjectType = oResult->GetValue(12, i);
1775 2937 : if (pszObjectType == nullptr ||
1776 2936 : !(EQUAL(pszObjectType, "table") ||
1777 21 : EQUAL(pszObjectType, "view")))
1778 : {
1779 1 : CPLError(CE_Warning, CPLE_AppDefined,
1780 : "Table/view %s is referenced in gpkg_contents, "
1781 : "but does not exist",
1782 : pszTableName);
1783 1 : continue;
1784 : }
1785 : // Non-standard and undocumented behavior:
1786 : // if the same table appears to have several geometry columns,
1787 : // handle it for now as multiple layers named
1788 : // "table_name (geom_col_name)"
1789 : // The way we handle that might change in the future (e.g
1790 : // could be a single layer with multiple geometry columns)
1791 : const std::string osLayerNameWithGeomColName =
1792 5453 : pszGeomColName ? std::string(pszTableName) + " (" +
1793 : pszGeomColName + ')'
1794 5872 : : std::string(pszTableName);
1795 2936 : if (cpl::contains(oExistingLayers, osLayerNameWithGeomColName))
1796 1 : continue;
1797 2935 : oExistingLayers.insert(osLayerNameWithGeomColName);
1798 : const std::string osLayerName = bTableHasSeveralGeomColumns
1799 : ? osLayerNameWithGeomColName
1800 2935 : : std::string(pszTableName);
1801 : OGRGeoPackageTableLayer *poLayer =
1802 2935 : new OGRGeoPackageTableLayer(this, osLayerName.c_str());
1803 2935 : bool bHasZ = pszZ && atoi(pszZ) > 0;
1804 2935 : bool bHasM = pszM && atoi(pszM) > 0;
1805 2935 : if (pszGeomType && EQUAL(pszGeomType, "GEOMETRY"))
1806 : {
1807 550 : if (pszZ && atoi(pszZ) == 2)
1808 7 : bHasZ = false;
1809 550 : if (pszM && atoi(pszM) == 2)
1810 6 : bHasM = false;
1811 : }
1812 2935 : poLayer->SetOpeningParameters(
1813 : pszTableName, pszObjectType, bIsInGpkgContents, bIsSpatial,
1814 : pszGeomColName, pszGeomType, bHasZ, bHasM);
1815 2935 : m_papoLayers[m_nLayers++] = poLayer;
1816 : }
1817 : }
1818 : }
1819 :
1820 1069 : bool bHasTileMatrixSet = false;
1821 1069 : if (poOpenInfo->nOpenFlags & GDAL_OF_RASTER)
1822 : {
1823 517 : bHasTileMatrixSet = SQLGetInteger(hDB,
1824 : "SELECT 1 FROM sqlite_master WHERE "
1825 : "name = 'gpkg_tile_matrix_set' AND "
1826 : "type IN ('table', 'view')",
1827 : nullptr) == 1;
1828 : }
1829 1069 : if (bHasTileMatrixSet)
1830 : {
1831 : std::string osSQL =
1832 : "SELECT c.table_name, c.identifier, c.description, c.srs_id, "
1833 : "c.min_x, c.min_y, c.max_x, c.max_y, "
1834 : "tms.min_x, tms.min_y, tms.max_x, tms.max_y, c.data_type "
1835 : "FROM gpkg_contents c JOIN gpkg_tile_matrix_set tms ON "
1836 : "c.table_name = tms.table_name WHERE "
1837 516 : "data_type IN ('tiles', '2d-gridded-coverage')";
1838 516 : if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE"))
1839 : osSubdatasetTableName =
1840 2 : CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE");
1841 516 : if (!osSubdatasetTableName.empty())
1842 : {
1843 14 : char *pszTmp = sqlite3_mprintf(" AND c.table_name='%q'",
1844 : osSubdatasetTableName.c_str());
1845 14 : osSQL += pszTmp;
1846 14 : sqlite3_free(pszTmp);
1847 14 : SetPhysicalFilename(osFilename.c_str());
1848 : }
1849 516 : const int nTableLimit = GetOGRTableLimit();
1850 516 : if (nTableLimit > 0)
1851 : {
1852 516 : osSQL += " LIMIT ";
1853 516 : osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1854 : }
1855 :
1856 516 : auto oResult = SQLQuery(hDB, osSQL.c_str());
1857 516 : if (!oResult)
1858 : {
1859 0 : return FALSE;
1860 : }
1861 :
1862 516 : if (oResult->RowCount() == 0 && !osSubdatasetTableName.empty())
1863 : {
1864 1 : CPLError(CE_Failure, CPLE_AppDefined,
1865 : "Cannot find table '%s' in GeoPackage dataset",
1866 : osSubdatasetTableName.c_str());
1867 : }
1868 515 : else if (oResult->RowCount() == 1)
1869 : {
1870 263 : const char *pszTableName = oResult->GetValue(0, 0);
1871 263 : const char *pszIdentifier = oResult->GetValue(1, 0);
1872 263 : const char *pszDescription = oResult->GetValue(2, 0);
1873 263 : const char *pszSRSId = oResult->GetValue(3, 0);
1874 263 : const char *pszMinX = oResult->GetValue(4, 0);
1875 263 : const char *pszMinY = oResult->GetValue(5, 0);
1876 263 : const char *pszMaxX = oResult->GetValue(6, 0);
1877 263 : const char *pszMaxY = oResult->GetValue(7, 0);
1878 263 : const char *pszTMSMinX = oResult->GetValue(8, 0);
1879 263 : const char *pszTMSMinY = oResult->GetValue(9, 0);
1880 263 : const char *pszTMSMaxX = oResult->GetValue(10, 0);
1881 263 : const char *pszTMSMaxY = oResult->GetValue(11, 0);
1882 263 : const char *pszDataType = oResult->GetValue(12, 0);
1883 263 : if (pszTableName && pszTMSMinX && pszTMSMinY && pszTMSMaxX &&
1884 : pszTMSMaxY)
1885 : {
1886 526 : bRet = OpenRaster(
1887 : pszTableName, pszIdentifier, pszDescription,
1888 263 : pszSRSId ? atoi(pszSRSId) : 0, CPLAtof(pszTMSMinX),
1889 : CPLAtof(pszTMSMinY), CPLAtof(pszTMSMaxX),
1890 : CPLAtof(pszTMSMaxY), pszMinX, pszMinY, pszMaxX, pszMaxY,
1891 263 : EQUAL(pszDataType, "tiles"), poOpenInfo->papszOpenOptions);
1892 : }
1893 : }
1894 252 : else if (oResult->RowCount() >= 1)
1895 : {
1896 4 : bRet = TRUE;
1897 :
1898 4 : if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
1899 : {
1900 1 : CPLError(CE_Warning, CPLE_AppDefined,
1901 : "File has more than %d raster tables. "
1902 : "Limiting to first %d (can be overridden with "
1903 : "OGR_TABLE_LIMIT config option)",
1904 : nTableLimit, nTableLimit);
1905 1 : oResult->LimitRowCount(nTableLimit);
1906 : }
1907 :
1908 4 : int nSDSCount = 0;
1909 2010 : for (int i = 0; i < oResult->RowCount(); i++)
1910 : {
1911 2006 : const char *pszTableName = oResult->GetValue(0, i);
1912 2006 : const char *pszIdentifier = oResult->GetValue(1, i);
1913 2006 : if (pszTableName == nullptr)
1914 0 : continue;
1915 : m_aosSubDatasets.AddNameValue(
1916 : CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
1917 2006 : CPLSPrintf("GPKG:%s:%s", m_pszFilename, pszTableName));
1918 : m_aosSubDatasets.AddNameValue(
1919 : CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
1920 : pszIdentifier
1921 2006 : ? CPLSPrintf("%s - %s", pszTableName, pszIdentifier)
1922 4012 : : pszTableName);
1923 2006 : nSDSCount++;
1924 : }
1925 : }
1926 : }
1927 :
1928 1069 : if (!bRet && (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR))
1929 : {
1930 30 : if ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE))
1931 : {
1932 20 : bRet = TRUE;
1933 : }
1934 : else
1935 : {
1936 10 : CPLDebug("GPKG",
1937 : "This GeoPackage has no vector content and is opened "
1938 : "in read-only mode. If you open it in update mode, "
1939 : "opening will be successful.");
1940 : }
1941 : }
1942 :
1943 1069 : if (eAccess == GA_Update)
1944 : {
1945 206 : FixupWrongRTreeTrigger();
1946 206 : FixupWrongMedataReferenceColumnNameUpdate();
1947 : }
1948 :
1949 1069 : SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
1950 :
1951 1069 : return bRet;
1952 : }
1953 :
1954 : /************************************************************************/
1955 : /* DetectSpatialRefSysColumns() */
1956 : /************************************************************************/
1957 :
1958 1076 : void GDALGeoPackageDataset::DetectSpatialRefSysColumns()
1959 : {
1960 : // Detect definition_12_063 column
1961 : {
1962 1076 : sqlite3_stmt *hSQLStmt = nullptr;
1963 1076 : int rc = sqlite3_prepare_v2(
1964 : hDB, "SELECT definition_12_063 FROM gpkg_spatial_ref_sys ", -1,
1965 : &hSQLStmt, nullptr);
1966 1076 : if (rc == SQLITE_OK)
1967 : {
1968 80 : m_bHasDefinition12_063 = true;
1969 80 : sqlite3_finalize(hSQLStmt);
1970 : }
1971 : }
1972 :
1973 : // Detect epoch column
1974 1076 : if (m_bHasDefinition12_063)
1975 : {
1976 80 : sqlite3_stmt *hSQLStmt = nullptr;
1977 : int rc =
1978 80 : sqlite3_prepare_v2(hDB, "SELECT epoch FROM gpkg_spatial_ref_sys ",
1979 : -1, &hSQLStmt, nullptr);
1980 80 : if (rc == SQLITE_OK)
1981 : {
1982 5 : m_bHasEpochColumn = true;
1983 5 : sqlite3_finalize(hSQLStmt);
1984 : }
1985 : }
1986 1076 : }
1987 :
1988 : /************************************************************************/
1989 : /* FixupWrongRTreeTrigger() */
1990 : /************************************************************************/
1991 :
1992 206 : void GDALGeoPackageDataset::FixupWrongRTreeTrigger()
1993 : {
1994 : auto oResult = SQLQuery(
1995 : hDB,
1996 : "SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND "
1997 206 : "NAME LIKE 'rtree_%_update3' AND sql LIKE '% AFTER UPDATE OF % ON %'");
1998 206 : if (oResult == nullptr)
1999 0 : return;
2000 206 : if (oResult->RowCount() > 0)
2001 : {
2002 1 : CPLDebug("GPKG", "Fixing incorrect trigger(s) related to RTree");
2003 : }
2004 208 : for (int i = 0; i < oResult->RowCount(); i++)
2005 : {
2006 2 : const char *pszName = oResult->GetValue(0, i);
2007 2 : const char *pszSQL = oResult->GetValue(1, i);
2008 2 : const char *pszPtr1 = strstr(pszSQL, " AFTER UPDATE OF ");
2009 2 : if (pszPtr1)
2010 : {
2011 2 : const char *pszPtr = pszPtr1 + strlen(" AFTER UPDATE OF ");
2012 : // Skipping over geometry column name
2013 4 : while (*pszPtr == ' ')
2014 2 : pszPtr++;
2015 2 : if (pszPtr[0] == '"' || pszPtr[0] == '\'')
2016 : {
2017 1 : char chStringDelim = pszPtr[0];
2018 1 : pszPtr++;
2019 9 : while (*pszPtr != '\0' && *pszPtr != chStringDelim)
2020 : {
2021 8 : if (*pszPtr == '\\' && pszPtr[1] == chStringDelim)
2022 0 : pszPtr += 2;
2023 : else
2024 8 : pszPtr += 1;
2025 : }
2026 1 : if (*pszPtr == chStringDelim)
2027 1 : pszPtr++;
2028 : }
2029 : else
2030 : {
2031 1 : pszPtr++;
2032 8 : while (*pszPtr != ' ')
2033 7 : pszPtr++;
2034 : }
2035 2 : if (*pszPtr == ' ')
2036 : {
2037 2 : SQLCommand(hDB,
2038 4 : ("DROP TRIGGER \"" + SQLEscapeName(pszName) + "\"")
2039 : .c_str());
2040 4 : CPLString newSQL;
2041 2 : newSQL.assign(pszSQL, pszPtr1 - pszSQL);
2042 2 : newSQL += " AFTER UPDATE";
2043 2 : newSQL += pszPtr;
2044 2 : SQLCommand(hDB, newSQL);
2045 : }
2046 : }
2047 : }
2048 : }
2049 :
2050 : /************************************************************************/
2051 : /* FixupWrongMedataReferenceColumnNameUpdate() */
2052 : /************************************************************************/
2053 :
2054 206 : void GDALGeoPackageDataset::FixupWrongMedataReferenceColumnNameUpdate()
2055 : {
2056 : // Fix wrong trigger that was generated by GDAL < 2.4.0
2057 : // See https://github.com/qgis/QGIS/issues/42768
2058 : auto oResult = SQLQuery(
2059 : hDB, "SELECT sql FROM sqlite_master WHERE type = 'trigger' AND "
2060 : "NAME ='gpkg_metadata_reference_column_name_update' AND "
2061 206 : "sql LIKE '%column_nameIS%'");
2062 206 : if (oResult == nullptr)
2063 0 : return;
2064 206 : if (oResult->RowCount() == 1)
2065 : {
2066 1 : CPLDebug("GPKG", "Fixing incorrect trigger "
2067 : "gpkg_metadata_reference_column_name_update");
2068 1 : const char *pszSQL = oResult->GetValue(0, 0);
2069 : std::string osNewSQL(
2070 3 : CPLString(pszSQL).replaceAll("column_nameIS", "column_name IS"));
2071 :
2072 1 : SQLCommand(hDB,
2073 : "DROP TRIGGER gpkg_metadata_reference_column_name_update");
2074 1 : SQLCommand(hDB, osNewSQL.c_str());
2075 : }
2076 : }
2077 :
2078 : /************************************************************************/
2079 : /* ClearCachedRelationships() */
2080 : /************************************************************************/
2081 :
2082 35 : void GDALGeoPackageDataset::ClearCachedRelationships()
2083 : {
2084 35 : m_bHasPopulatedRelationships = false;
2085 35 : m_osMapRelationships.clear();
2086 35 : }
2087 :
2088 : /************************************************************************/
2089 : /* LoadRelationships() */
2090 : /************************************************************************/
2091 :
2092 49 : void GDALGeoPackageDataset::LoadRelationships() const
2093 : {
2094 49 : m_osMapRelationships.clear();
2095 :
2096 49 : std::vector<std::string> oExcludedTables;
2097 49 : if (HasGpkgextRelationsTable())
2098 : {
2099 30 : LoadRelationshipsUsingRelatedTablesExtension();
2100 :
2101 76 : for (const auto &oRelationship : m_osMapRelationships)
2102 : {
2103 : oExcludedTables.emplace_back(
2104 46 : oRelationship.second->GetMappingTableName());
2105 : }
2106 : }
2107 :
2108 : // Also load relationships defined using foreign keys (i.e. one-to-many
2109 : // relationships). Here we must exclude any relationships defined from the
2110 : // related tables extension, we don't want them included twice.
2111 49 : LoadRelationshipsFromForeignKeys(oExcludedTables);
2112 49 : m_bHasPopulatedRelationships = true;
2113 49 : }
2114 :
2115 : /************************************************************************/
2116 : /* LoadRelationshipsUsingRelatedTablesExtension() */
2117 : /************************************************************************/
2118 :
2119 30 : void GDALGeoPackageDataset::LoadRelationshipsUsingRelatedTablesExtension() const
2120 : {
2121 30 : m_osMapRelationships.clear();
2122 :
2123 : auto oResultTable = SQLQuery(
2124 30 : hDB, "SELECT base_table_name, base_primary_column, "
2125 : "related_table_name, related_primary_column, relation_name, "
2126 60 : "mapping_table_name FROM gpkgext_relations");
2127 30 : if (oResultTable && oResultTable->RowCount() > 0)
2128 : {
2129 74 : for (int i = 0; i < oResultTable->RowCount(); i++)
2130 : {
2131 47 : const char *pszBaseTableName = oResultTable->GetValue(0, i);
2132 47 : if (!pszBaseTableName)
2133 : {
2134 0 : CPLError(CE_Warning, CPLE_AppDefined,
2135 : "Could not retrieve base_table_name from "
2136 : "gpkgext_relations");
2137 1 : continue;
2138 : }
2139 47 : const char *pszBasePrimaryColumn = oResultTable->GetValue(1, i);
2140 47 : if (!pszBasePrimaryColumn)
2141 : {
2142 0 : CPLError(CE_Warning, CPLE_AppDefined,
2143 : "Could not retrieve base_primary_column from "
2144 : "gpkgext_relations");
2145 0 : continue;
2146 : }
2147 47 : const char *pszRelatedTableName = oResultTable->GetValue(2, i);
2148 47 : if (!pszRelatedTableName)
2149 : {
2150 0 : CPLError(CE_Warning, CPLE_AppDefined,
2151 : "Could not retrieve related_table_name from "
2152 : "gpkgext_relations");
2153 0 : continue;
2154 : }
2155 47 : const char *pszRelatedPrimaryColumn = oResultTable->GetValue(3, i);
2156 47 : if (!pszRelatedPrimaryColumn)
2157 : {
2158 0 : CPLError(CE_Warning, CPLE_AppDefined,
2159 : "Could not retrieve related_primary_column from "
2160 : "gpkgext_relations");
2161 0 : continue;
2162 : }
2163 47 : const char *pszRelationName = oResultTable->GetValue(4, i);
2164 47 : if (!pszRelationName)
2165 : {
2166 0 : CPLError(
2167 : CE_Warning, CPLE_AppDefined,
2168 : "Could not retrieve relation_name from gpkgext_relations");
2169 0 : continue;
2170 : }
2171 47 : const char *pszMappingTableName = oResultTable->GetValue(5, i);
2172 47 : if (!pszMappingTableName)
2173 : {
2174 0 : CPLError(CE_Warning, CPLE_AppDefined,
2175 : "Could not retrieve mapping_table_name from "
2176 : "gpkgext_relations");
2177 0 : continue;
2178 : }
2179 :
2180 : // confirm that mapping table exists
2181 : char *pszSQL =
2182 47 : sqlite3_mprintf("SELECT 1 FROM sqlite_master WHERE "
2183 : "name='%q' AND type IN ('table', 'view')",
2184 : pszMappingTableName);
2185 47 : const int nMappingTableCount = SQLGetInteger(hDB, pszSQL, nullptr);
2186 47 : sqlite3_free(pszSQL);
2187 :
2188 47 : if (nMappingTableCount < 1)
2189 : {
2190 1 : CPLError(CE_Warning, CPLE_AppDefined,
2191 : "Relationship mapping table %s does not exist",
2192 : pszMappingTableName);
2193 1 : continue;
2194 : }
2195 :
2196 : const std::string osRelationName = GenerateNameForRelationship(
2197 92 : pszBaseTableName, pszRelatedTableName, pszRelationName);
2198 :
2199 92 : std::string osType{};
2200 : // defined requirement classes -- for these types the relation name
2201 : // will be specific string value from the related tables extension.
2202 : // In this case we need to construct a unique relationship name
2203 : // based on the related tables
2204 46 : if (EQUAL(pszRelationName, "media") ||
2205 34 : EQUAL(pszRelationName, "simple_attributes") ||
2206 34 : EQUAL(pszRelationName, "features") ||
2207 12 : EQUAL(pszRelationName, "attributes") ||
2208 2 : EQUAL(pszRelationName, "tiles"))
2209 : {
2210 44 : osType = pszRelationName;
2211 : }
2212 : else
2213 : {
2214 : // user defined types default to features
2215 2 : osType = "features";
2216 : }
2217 :
2218 : std::unique_ptr<GDALRelationship> poRelationship(
2219 : new GDALRelationship(osRelationName, pszBaseTableName,
2220 138 : pszRelatedTableName, GRC_MANY_TO_MANY));
2221 :
2222 92 : poRelationship->SetLeftTableFields({pszBasePrimaryColumn});
2223 92 : poRelationship->SetRightTableFields({pszRelatedPrimaryColumn});
2224 92 : poRelationship->SetLeftMappingTableFields({"base_id"});
2225 92 : poRelationship->SetRightMappingTableFields({"related_id"});
2226 46 : poRelationship->SetMappingTableName(pszMappingTableName);
2227 46 : poRelationship->SetRelatedTableType(osType);
2228 :
2229 46 : m_osMapRelationships[osRelationName] = std::move(poRelationship);
2230 : }
2231 : }
2232 30 : }
2233 :
2234 : /************************************************************************/
2235 : /* GenerateNameForRelationship() */
2236 : /************************************************************************/
2237 :
2238 66 : std::string GDALGeoPackageDataset::GenerateNameForRelationship(
2239 : const char *pszBaseTableName, const char *pszRelatedTableName,
2240 : const char *pszType)
2241 : {
2242 : // defined requirement classes -- for these types the relation name will be
2243 : // specific string value from the related tables extension. In this case we
2244 : // need to construct a unique relationship name based on the related tables
2245 66 : if (EQUAL(pszType, "media") || EQUAL(pszType, "simple_attributes") ||
2246 43 : EQUAL(pszType, "features") || EQUAL(pszType, "attributes") ||
2247 8 : EQUAL(pszType, "tiles"))
2248 : {
2249 116 : std::ostringstream stream;
2250 : stream << pszBaseTableName << '_' << pszRelatedTableName << '_'
2251 58 : << pszType;
2252 58 : return stream.str();
2253 : }
2254 : else
2255 : {
2256 : // user defined types default to features
2257 8 : return pszType;
2258 : }
2259 : }
2260 :
2261 : /************************************************************************/
2262 : /* ValidateRelationship() */
2263 : /************************************************************************/
2264 :
2265 24 : bool GDALGeoPackageDataset::ValidateRelationship(
2266 : const GDALRelationship *poRelationship, std::string &failureReason)
2267 : {
2268 :
2269 24 : if (poRelationship->GetCardinality() !=
2270 : GDALRelationshipCardinality::GRC_MANY_TO_MANY)
2271 : {
2272 3 : failureReason = "Only many to many relationships are supported";
2273 3 : return false;
2274 : }
2275 :
2276 42 : std::string osRelatedTableType = poRelationship->GetRelatedTableType();
2277 53 : if (!osRelatedTableType.empty() && osRelatedTableType != "features" &&
2278 22 : osRelatedTableType != "media" &&
2279 12 : osRelatedTableType != "simple_attributes" &&
2280 43 : osRelatedTableType != "attributes" && osRelatedTableType != "tiles")
2281 : {
2282 : failureReason =
2283 4 : ("Related table type " + osRelatedTableType +
2284 : " is not a valid value for the GeoPackage specification. "
2285 : "Valid values are: features, media, simple_attributes, "
2286 : "attributes, tiles.")
2287 2 : .c_str();
2288 2 : return false;
2289 : }
2290 :
2291 19 : const std::string &osLeftTableName = poRelationship->GetLeftTableName();
2292 19 : OGRGeoPackageLayer *poLeftTable = cpl::down_cast<OGRGeoPackageLayer *>(
2293 19 : GetLayerByName(osLeftTableName.c_str()));
2294 19 : if (!poLeftTable)
2295 : {
2296 2 : failureReason = ("Left table " + osLeftTableName +
2297 : " is not an existing layer in the dataset")
2298 1 : .c_str();
2299 1 : return false;
2300 : }
2301 18 : const std::string &osRightTableName = poRelationship->GetRightTableName();
2302 18 : OGRGeoPackageLayer *poRightTable = cpl::down_cast<OGRGeoPackageLayer *>(
2303 18 : GetLayerByName(osRightTableName.c_str()));
2304 18 : if (!poRightTable)
2305 : {
2306 2 : failureReason = ("Right table " + osRightTableName +
2307 : " is not an existing layer in the dataset")
2308 1 : .c_str();
2309 1 : return false;
2310 : }
2311 :
2312 17 : const auto &aosLeftTableFields = poRelationship->GetLeftTableFields();
2313 17 : if (aosLeftTableFields.empty())
2314 : {
2315 1 : failureReason = "No left table fields were specified";
2316 1 : return false;
2317 : }
2318 16 : else if (aosLeftTableFields.size() > 1)
2319 : {
2320 : failureReason = "Only a single left table field is permitted for the "
2321 1 : "GeoPackage specification";
2322 1 : return false;
2323 : }
2324 : else
2325 : {
2326 : // validate left field exists
2327 30 : if (poLeftTable->GetLayerDefn()->GetFieldIndex(
2328 33 : aosLeftTableFields[0].c_str()) < 0 &&
2329 3 : !EQUAL(poLeftTable->GetFIDColumn(), aosLeftTableFields[0].c_str()))
2330 : {
2331 2 : failureReason = ("Left table field " + aosLeftTableFields[0] +
2332 2 : " does not exist in " + osLeftTableName)
2333 1 : .c_str();
2334 1 : return false;
2335 : }
2336 : }
2337 :
2338 14 : const auto &aosRightTableFields = poRelationship->GetRightTableFields();
2339 14 : if (aosRightTableFields.empty())
2340 : {
2341 1 : failureReason = "No right table fields were specified";
2342 1 : return false;
2343 : }
2344 13 : else if (aosRightTableFields.size() > 1)
2345 : {
2346 : failureReason = "Only a single right table field is permitted for the "
2347 1 : "GeoPackage specification";
2348 1 : return false;
2349 : }
2350 : else
2351 : {
2352 : // validate right field exists
2353 24 : if (poRightTable->GetLayerDefn()->GetFieldIndex(
2354 28 : aosRightTableFields[0].c_str()) < 0 &&
2355 4 : !EQUAL(poRightTable->GetFIDColumn(),
2356 : aosRightTableFields[0].c_str()))
2357 : {
2358 4 : failureReason = ("Right table field " + aosRightTableFields[0] +
2359 4 : " does not exist in " + osRightTableName)
2360 2 : .c_str();
2361 2 : return false;
2362 : }
2363 : }
2364 :
2365 10 : return true;
2366 : }
2367 :
2368 : /************************************************************************/
2369 : /* InitRaster() */
2370 : /************************************************************************/
2371 :
2372 347 : bool GDALGeoPackageDataset::InitRaster(
2373 : GDALGeoPackageDataset *poParentDS, const char *pszTableName, double dfMinX,
2374 : double dfMinY, double dfMaxX, double dfMaxY, const char *pszContentsMinX,
2375 : const char *pszContentsMinY, const char *pszContentsMaxX,
2376 : const char *pszContentsMaxY, char **papszOpenOptionsIn,
2377 : const SQLResult &oResult, int nIdxInResult)
2378 : {
2379 347 : m_osRasterTable = pszTableName;
2380 347 : m_dfTMSMinX = dfMinX;
2381 347 : m_dfTMSMaxY = dfMaxY;
2382 :
2383 : // Despite prior checking, the type might be Binary and
2384 : // SQLResultGetValue() not working properly on it
2385 347 : int nZoomLevel = atoi(oResult.GetValue(0, nIdxInResult));
2386 347 : if (nZoomLevel < 0 || nZoomLevel > 65536)
2387 : {
2388 0 : return false;
2389 : }
2390 347 : double dfPixelXSize = CPLAtof(oResult.GetValue(1, nIdxInResult));
2391 347 : double dfPixelYSize = CPLAtof(oResult.GetValue(2, nIdxInResult));
2392 347 : if (dfPixelXSize <= 0 || dfPixelYSize <= 0)
2393 : {
2394 0 : return false;
2395 : }
2396 347 : int nTileWidth = atoi(oResult.GetValue(3, nIdxInResult));
2397 347 : int nTileHeight = atoi(oResult.GetValue(4, nIdxInResult));
2398 347 : if (nTileWidth <= 0 || nTileWidth > 65536 || nTileHeight <= 0 ||
2399 : nTileHeight > 65536)
2400 : {
2401 0 : return false;
2402 : }
2403 : int nTileMatrixWidth = static_cast<int>(
2404 694 : std::min(static_cast<GIntBig>(INT_MAX),
2405 347 : CPLAtoGIntBig(oResult.GetValue(5, nIdxInResult))));
2406 : int nTileMatrixHeight = static_cast<int>(
2407 694 : std::min(static_cast<GIntBig>(INT_MAX),
2408 347 : CPLAtoGIntBig(oResult.GetValue(6, nIdxInResult))));
2409 347 : if (nTileMatrixWidth <= 0 || nTileMatrixHeight <= 0)
2410 : {
2411 0 : return false;
2412 : }
2413 :
2414 : /* Use content bounds in priority over tile_matrix_set bounds */
2415 347 : double dfGDALMinX = dfMinX;
2416 347 : double dfGDALMinY = dfMinY;
2417 347 : double dfGDALMaxX = dfMaxX;
2418 347 : double dfGDALMaxY = dfMaxY;
2419 : pszContentsMinX =
2420 347 : CSLFetchNameValueDef(papszOpenOptionsIn, "MINX", pszContentsMinX);
2421 : pszContentsMinY =
2422 347 : CSLFetchNameValueDef(papszOpenOptionsIn, "MINY", pszContentsMinY);
2423 : pszContentsMaxX =
2424 347 : CSLFetchNameValueDef(papszOpenOptionsIn, "MAXX", pszContentsMaxX);
2425 : pszContentsMaxY =
2426 347 : CSLFetchNameValueDef(papszOpenOptionsIn, "MAXY", pszContentsMaxY);
2427 347 : if (pszContentsMinX != nullptr && pszContentsMinY != nullptr &&
2428 347 : pszContentsMaxX != nullptr && pszContentsMaxY != nullptr)
2429 : {
2430 693 : if (CPLAtof(pszContentsMinX) < CPLAtof(pszContentsMaxX) &&
2431 346 : CPLAtof(pszContentsMinY) < CPLAtof(pszContentsMaxY))
2432 : {
2433 346 : dfGDALMinX = CPLAtof(pszContentsMinX);
2434 346 : dfGDALMinY = CPLAtof(pszContentsMinY);
2435 346 : dfGDALMaxX = CPLAtof(pszContentsMaxX);
2436 346 : dfGDALMaxY = CPLAtof(pszContentsMaxY);
2437 : }
2438 : else
2439 : {
2440 1 : CPLError(CE_Warning, CPLE_AppDefined,
2441 : "Illegal min_x/min_y/max_x/max_y values for %s in open "
2442 : "options and/or gpkg_contents. Using bounds of "
2443 : "gpkg_tile_matrix_set instead",
2444 : pszTableName);
2445 : }
2446 : }
2447 347 : if (dfGDALMinX >= dfGDALMaxX || dfGDALMinY >= dfGDALMaxY)
2448 : {
2449 0 : CPLError(CE_Failure, CPLE_AppDefined,
2450 : "Illegal min_x/min_y/max_x/max_y values for %s", pszTableName);
2451 0 : return false;
2452 : }
2453 :
2454 347 : int nBandCount = 0;
2455 : const char *pszBAND_COUNT =
2456 347 : CSLFetchNameValue(papszOpenOptionsIn, "BAND_COUNT");
2457 347 : if (poParentDS)
2458 : {
2459 86 : nBandCount = poParentDS->GetRasterCount();
2460 : }
2461 261 : else if (m_eDT != GDT_Byte)
2462 : {
2463 65 : if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO") &&
2464 0 : !EQUAL(pszBAND_COUNT, "1"))
2465 : {
2466 0 : CPLError(CE_Warning, CPLE_AppDefined,
2467 : "BAND_COUNT ignored for non-Byte data");
2468 : }
2469 65 : nBandCount = 1;
2470 : }
2471 : else
2472 : {
2473 196 : if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO"))
2474 : {
2475 69 : nBandCount = atoi(pszBAND_COUNT);
2476 69 : if (nBandCount == 1)
2477 5 : GetMetadata("IMAGE_STRUCTURE");
2478 : }
2479 : else
2480 : {
2481 127 : GetMetadata("IMAGE_STRUCTURE");
2482 127 : nBandCount = m_nBandCountFromMetadata;
2483 127 : if (nBandCount == 1)
2484 31 : m_eTF = GPKG_TF_PNG;
2485 : }
2486 196 : if (nBandCount == 1 && !m_osTFFromMetadata.empty())
2487 : {
2488 2 : m_eTF = GDALGPKGMBTilesGetTileFormat(m_osTFFromMetadata.c_str());
2489 : }
2490 196 : if (nBandCount <= 0 || nBandCount > 4)
2491 82 : nBandCount = 4;
2492 : }
2493 :
2494 347 : return InitRaster(poParentDS, pszTableName, nZoomLevel, nBandCount, dfMinX,
2495 : dfMaxY, dfPixelXSize, dfPixelYSize, nTileWidth,
2496 : nTileHeight, nTileMatrixWidth, nTileMatrixHeight,
2497 347 : dfGDALMinX, dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
2498 : }
2499 :
2500 : /************************************************************************/
2501 : /* ComputeTileAndPixelShifts() */
2502 : /************************************************************************/
2503 :
2504 749 : bool GDALGeoPackageDataset::ComputeTileAndPixelShifts()
2505 : {
2506 : int nTileWidth, nTileHeight;
2507 749 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
2508 :
2509 : // Compute shift between GDAL origin and TileMatrixSet origin
2510 749 : const double dfShiftXPixels =
2511 749 : (m_adfGeoTransform[0] - m_dfTMSMinX) / m_adfGeoTransform[1];
2512 749 : if (dfShiftXPixels / nTileWidth <= INT_MIN ||
2513 747 : dfShiftXPixels / nTileWidth > INT_MAX)
2514 2 : return false;
2515 747 : const int64_t nShiftXPixels =
2516 747 : static_cast<int64_t>(floor(0.5 + dfShiftXPixels));
2517 747 : m_nShiftXTiles = static_cast<int>(nShiftXPixels / nTileWidth);
2518 747 : if (nShiftXPixels < 0 && (nShiftXPixels % nTileWidth) != 0)
2519 11 : m_nShiftXTiles--;
2520 747 : m_nShiftXPixelsMod =
2521 747 : (static_cast<int>(nShiftXPixels % nTileWidth) + nTileWidth) %
2522 : nTileWidth;
2523 :
2524 747 : const double dfShiftYPixels =
2525 747 : (m_adfGeoTransform[3] - m_dfTMSMaxY) / m_adfGeoTransform[5];
2526 747 : if (dfShiftYPixels / nTileHeight <= INT_MIN ||
2527 747 : dfShiftYPixels / nTileHeight > INT_MAX)
2528 1 : return false;
2529 746 : const int64_t nShiftYPixels =
2530 746 : static_cast<int64_t>(floor(0.5 + dfShiftYPixels));
2531 746 : m_nShiftYTiles = static_cast<int>(nShiftYPixels / nTileHeight);
2532 746 : if (nShiftYPixels < 0 && (nShiftYPixels % nTileHeight) != 0)
2533 11 : m_nShiftYTiles--;
2534 746 : m_nShiftYPixelsMod =
2535 746 : (static_cast<int>(nShiftYPixels % nTileHeight) + nTileHeight) %
2536 : nTileHeight;
2537 746 : return true;
2538 : }
2539 :
2540 : /************************************************************************/
2541 : /* AllocCachedTiles() */
2542 : /************************************************************************/
2543 :
2544 746 : bool GDALGeoPackageDataset::AllocCachedTiles()
2545 : {
2546 : int nTileWidth, nTileHeight;
2547 746 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
2548 :
2549 : // We currently need 4 caches because of
2550 : // GDALGPKGMBTilesLikePseudoDataset::ReadTile(int nRow, int nCol)
2551 746 : const int nCacheCount = 4;
2552 : /*
2553 : (m_nShiftXPixelsMod != 0 || m_nShiftYPixelsMod != 0) ? 4 :
2554 : (GetUpdate() && m_eDT == GDT_Byte) ? 2 : 1;
2555 : */
2556 746 : m_pabyCachedTiles = static_cast<GByte *>(VSI_MALLOC3_VERBOSE(
2557 : cpl::fits_on<int>(nCacheCount * (m_eDT == GDT_Byte ? 4 : 1) *
2558 : m_nDTSize),
2559 : nTileWidth, nTileHeight));
2560 746 : if (m_pabyCachedTiles == nullptr)
2561 : {
2562 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too big tiles: %d x %d",
2563 : nTileWidth, nTileHeight);
2564 0 : return false;
2565 : }
2566 :
2567 746 : return true;
2568 : }
2569 :
2570 : /************************************************************************/
2571 : /* InitRaster() */
2572 : /************************************************************************/
2573 :
2574 586 : bool GDALGeoPackageDataset::InitRaster(
2575 : GDALGeoPackageDataset *poParentDS, const char *pszTableName, int nZoomLevel,
2576 : int nBandCount, double dfTMSMinX, double dfTMSMaxY, double dfPixelXSize,
2577 : double dfPixelYSize, int nTileWidth, int nTileHeight, int nTileMatrixWidth,
2578 : int nTileMatrixHeight, double dfGDALMinX, double dfGDALMinY,
2579 : double dfGDALMaxX, double dfGDALMaxY)
2580 : {
2581 586 : m_osRasterTable = pszTableName;
2582 586 : m_dfTMSMinX = dfTMSMinX;
2583 586 : m_dfTMSMaxY = dfTMSMaxY;
2584 586 : m_nZoomLevel = nZoomLevel;
2585 586 : m_nTileMatrixWidth = nTileMatrixWidth;
2586 586 : m_nTileMatrixHeight = nTileMatrixHeight;
2587 :
2588 586 : m_bGeoTransformValid = true;
2589 586 : m_adfGeoTransform[0] = dfGDALMinX;
2590 586 : m_adfGeoTransform[1] = dfPixelXSize;
2591 586 : m_adfGeoTransform[3] = dfGDALMaxY;
2592 586 : m_adfGeoTransform[5] = -dfPixelYSize;
2593 586 : double dfRasterXSize = 0.5 + (dfGDALMaxX - dfGDALMinX) / dfPixelXSize;
2594 586 : double dfRasterYSize = 0.5 + (dfGDALMaxY - dfGDALMinY) / dfPixelYSize;
2595 586 : if (dfRasterXSize > INT_MAX || dfRasterYSize > INT_MAX)
2596 : {
2597 0 : CPLError(CE_Failure, CPLE_NotSupported, "Too big raster: %f x %f",
2598 : dfRasterXSize, dfRasterYSize);
2599 0 : return false;
2600 : }
2601 586 : nRasterXSize = std::max(1, static_cast<int>(dfRasterXSize));
2602 586 : nRasterYSize = std::max(1, static_cast<int>(dfRasterYSize));
2603 :
2604 586 : if (poParentDS)
2605 : {
2606 325 : m_poParentDS = poParentDS;
2607 325 : eAccess = poParentDS->eAccess;
2608 325 : hDB = poParentDS->hDB;
2609 325 : m_eTF = poParentDS->m_eTF;
2610 325 : m_eDT = poParentDS->m_eDT;
2611 325 : m_nDTSize = poParentDS->m_nDTSize;
2612 325 : m_dfScale = poParentDS->m_dfScale;
2613 325 : m_dfOffset = poParentDS->m_dfOffset;
2614 325 : m_dfPrecision = poParentDS->m_dfPrecision;
2615 325 : m_usGPKGNull = poParentDS->m_usGPKGNull;
2616 325 : m_nQuality = poParentDS->m_nQuality;
2617 325 : m_nZLevel = poParentDS->m_nZLevel;
2618 325 : m_bDither = poParentDS->m_bDither;
2619 : /*m_nSRID = poParentDS->m_nSRID;*/
2620 325 : m_osWHERE = poParentDS->m_osWHERE;
2621 325 : SetDescription(CPLSPrintf("%s - zoom_level=%d",
2622 325 : poParentDS->GetDescription(), m_nZoomLevel));
2623 : }
2624 :
2625 2060 : for (int i = 1; i <= nBandCount; i++)
2626 : {
2627 : GDALGeoPackageRasterBand *poNewBand =
2628 1474 : new GDALGeoPackageRasterBand(this, nTileWidth, nTileHeight);
2629 1474 : if (poParentDS)
2630 : {
2631 761 : int bHasNoData = FALSE;
2632 : double dfNoDataValue =
2633 761 : poParentDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
2634 761 : if (bHasNoData)
2635 24 : poNewBand->SetNoDataValueInternal(dfNoDataValue);
2636 : }
2637 1474 : SetBand(i, poNewBand);
2638 :
2639 1474 : if (nBandCount == 1 && m_poCTFromMetadata)
2640 : {
2641 3 : poNewBand->AssignColorTable(m_poCTFromMetadata.get());
2642 : }
2643 1474 : if (!m_osNodataValueFromMetadata.empty())
2644 : {
2645 8 : poNewBand->SetNoDataValueInternal(
2646 : CPLAtof(m_osNodataValueFromMetadata.c_str()));
2647 : }
2648 : }
2649 :
2650 586 : if (!ComputeTileAndPixelShifts())
2651 : {
2652 3 : CPLError(CE_Failure, CPLE_AppDefined,
2653 : "Overflow occurred in ComputeTileAndPixelShifts()");
2654 3 : return false;
2655 : }
2656 :
2657 583 : GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
2658 583 : GDALPamDataset::SetMetadataItem("ZOOM_LEVEL",
2659 : CPLSPrintf("%d", m_nZoomLevel));
2660 :
2661 583 : return AllocCachedTiles();
2662 : }
2663 :
2664 : /************************************************************************/
2665 : /* GDALGPKGMBTilesGetTileFormat() */
2666 : /************************************************************************/
2667 :
2668 80 : GPKGTileFormat GDALGPKGMBTilesGetTileFormat(const char *pszTF)
2669 : {
2670 80 : GPKGTileFormat eTF = GPKG_TF_PNG_JPEG;
2671 80 : if (pszTF)
2672 : {
2673 80 : if (EQUAL(pszTF, "PNG_JPEG") || EQUAL(pszTF, "AUTO"))
2674 1 : eTF = GPKG_TF_PNG_JPEG;
2675 79 : else if (EQUAL(pszTF, "PNG"))
2676 46 : eTF = GPKG_TF_PNG;
2677 33 : else if (EQUAL(pszTF, "PNG8"))
2678 6 : eTF = GPKG_TF_PNG8;
2679 27 : else if (EQUAL(pszTF, "JPEG"))
2680 14 : eTF = GPKG_TF_JPEG;
2681 13 : else if (EQUAL(pszTF, "WEBP"))
2682 13 : eTF = GPKG_TF_WEBP;
2683 : else
2684 : {
2685 0 : CPLError(CE_Failure, CPLE_NotSupported,
2686 : "Unsuppoted value for TILE_FORMAT: %s", pszTF);
2687 : }
2688 : }
2689 80 : return eTF;
2690 : }
2691 :
2692 28 : const char *GDALMBTilesGetTileFormatName(GPKGTileFormat eTF)
2693 : {
2694 28 : switch (eTF)
2695 : {
2696 26 : case GPKG_TF_PNG:
2697 : case GPKG_TF_PNG8:
2698 26 : return "png";
2699 1 : case GPKG_TF_JPEG:
2700 1 : return "jpg";
2701 1 : case GPKG_TF_WEBP:
2702 1 : return "webp";
2703 0 : default:
2704 0 : break;
2705 : }
2706 0 : CPLError(CE_Failure, CPLE_NotSupported,
2707 : "Unsuppoted value for TILE_FORMAT: %d", static_cast<int>(eTF));
2708 0 : return nullptr;
2709 : }
2710 :
2711 : /************************************************************************/
2712 : /* OpenRaster() */
2713 : /************************************************************************/
2714 :
2715 263 : bool GDALGeoPackageDataset::OpenRaster(
2716 : const char *pszTableName, const char *pszIdentifier,
2717 : const char *pszDescription, int nSRSId, double dfMinX, double dfMinY,
2718 : double dfMaxX, double dfMaxY, const char *pszContentsMinX,
2719 : const char *pszContentsMinY, const char *pszContentsMaxX,
2720 : const char *pszContentsMaxY, bool bIsTiles, char **papszOpenOptionsIn)
2721 : {
2722 263 : if (dfMinX >= dfMaxX || dfMinY >= dfMaxY)
2723 0 : return false;
2724 :
2725 : // Config option just for debug, and for example force set to NaN
2726 : // which is not supported
2727 526 : CPLString osDataNull = CPLGetConfigOption("GPKG_NODATA", "");
2728 526 : CPLString osUom;
2729 526 : CPLString osFieldName;
2730 526 : CPLString osGridCellEncoding;
2731 263 : if (!bIsTiles)
2732 : {
2733 65 : char *pszSQL = sqlite3_mprintf(
2734 : "SELECT datatype, scale, offset, data_null, precision FROM "
2735 : "gpkg_2d_gridded_coverage_ancillary "
2736 : "WHERE tile_matrix_set_name = '%q' "
2737 : "AND datatype IN ('integer', 'float')"
2738 : "AND (scale > 0 OR scale IS NULL)",
2739 : pszTableName);
2740 65 : auto oResult = SQLQuery(hDB, pszSQL);
2741 65 : sqlite3_free(pszSQL);
2742 65 : if (!oResult || oResult->RowCount() == 0)
2743 : {
2744 0 : return false;
2745 : }
2746 65 : const char *pszDataType = oResult->GetValue(0, 0);
2747 65 : const char *pszScale = oResult->GetValue(1, 0);
2748 65 : const char *pszOffset = oResult->GetValue(2, 0);
2749 65 : const char *pszDataNull = oResult->GetValue(3, 0);
2750 65 : const char *pszPrecision = oResult->GetValue(4, 0);
2751 65 : if (pszDataNull)
2752 23 : osDataNull = pszDataNull;
2753 65 : if (EQUAL(pszDataType, "float"))
2754 : {
2755 6 : SetDataType(GDT_Float32);
2756 6 : m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
2757 : }
2758 : else
2759 : {
2760 59 : SetDataType(GDT_Float32);
2761 59 : m_eTF = GPKG_TF_PNG_16BIT;
2762 59 : const double dfScale = pszScale ? CPLAtof(pszScale) : 1.0;
2763 59 : const double dfOffset = pszOffset ? CPLAtof(pszOffset) : 0.0;
2764 59 : if (dfScale == 1.0)
2765 : {
2766 59 : if (dfOffset == 0.0)
2767 : {
2768 24 : SetDataType(GDT_UInt16);
2769 : }
2770 35 : else if (dfOffset == -32768.0)
2771 : {
2772 35 : SetDataType(GDT_Int16);
2773 : }
2774 : // coverity[tainted_data]
2775 0 : else if (dfOffset == -32767.0 && !osDataNull.empty() &&
2776 0 : CPLAtof(osDataNull) == 65535.0)
2777 : // Given that we will map the nodata value to -32768
2778 : {
2779 0 : SetDataType(GDT_Int16);
2780 : }
2781 : }
2782 :
2783 : // Check that the tile offset and scales are compatible of a
2784 : // final integer result.
2785 59 : if (m_eDT != GDT_Float32)
2786 : {
2787 : // coverity[tainted_data]
2788 59 : if (dfScale == 1.0 && dfOffset == -32768.0 &&
2789 118 : !osDataNull.empty() && CPLAtof(osDataNull) == 65535.0)
2790 : {
2791 : // Given that we will map the nodata value to -32768
2792 9 : pszSQL = sqlite3_mprintf(
2793 : "SELECT 1 FROM "
2794 : "gpkg_2d_gridded_tile_ancillary WHERE "
2795 : "tpudt_name = '%q' "
2796 : "AND NOT ((offset = 0.0 or offset = 1.0) "
2797 : "AND scale = 1.0) "
2798 : "LIMIT 1",
2799 : pszTableName);
2800 : }
2801 : else
2802 : {
2803 50 : pszSQL = sqlite3_mprintf(
2804 : "SELECT 1 FROM "
2805 : "gpkg_2d_gridded_tile_ancillary WHERE "
2806 : "tpudt_name = '%q' "
2807 : "AND NOT (offset = 0.0 AND scale = 1.0) LIMIT 1",
2808 : pszTableName);
2809 : }
2810 59 : sqlite3_stmt *hSQLStmt = nullptr;
2811 : int rc =
2812 59 : sqlite3_prepare_v2(hDB, pszSQL, -1, &hSQLStmt, nullptr);
2813 :
2814 59 : if (rc == SQLITE_OK)
2815 : {
2816 59 : if (sqlite3_step(hSQLStmt) == SQLITE_ROW)
2817 : {
2818 8 : SetDataType(GDT_Float32);
2819 : }
2820 59 : sqlite3_finalize(hSQLStmt);
2821 : }
2822 : else
2823 : {
2824 0 : CPLError(CE_Failure, CPLE_AppDefined,
2825 : "Error when running %s", pszSQL);
2826 : }
2827 59 : sqlite3_free(pszSQL);
2828 : }
2829 :
2830 59 : SetGlobalOffsetScale(dfOffset, dfScale);
2831 : }
2832 65 : if (pszPrecision)
2833 65 : m_dfPrecision = CPLAtof(pszPrecision);
2834 :
2835 : // Request those columns in a separate query, so as to keep
2836 : // compatibility with pre OGC 17-066r1 databases
2837 : pszSQL =
2838 65 : sqlite3_mprintf("SELECT uom, field_name, grid_cell_encoding FROM "
2839 : "gpkg_2d_gridded_coverage_ancillary "
2840 : "WHERE tile_matrix_set_name = '%q'",
2841 : pszTableName);
2842 65 : CPLPushErrorHandler(CPLQuietErrorHandler);
2843 65 : oResult = SQLQuery(hDB, pszSQL);
2844 65 : CPLPopErrorHandler();
2845 65 : sqlite3_free(pszSQL);
2846 65 : if (oResult && oResult->RowCount() == 1)
2847 : {
2848 64 : const char *pszUom = oResult->GetValue(0, 0);
2849 64 : if (pszUom)
2850 2 : osUom = pszUom;
2851 64 : const char *pszFieldName = oResult->GetValue(1, 0);
2852 64 : if (pszFieldName)
2853 64 : osFieldName = pszFieldName;
2854 64 : const char *pszGridCellEncoding = oResult->GetValue(2, 0);
2855 64 : if (pszGridCellEncoding)
2856 64 : osGridCellEncoding = pszGridCellEncoding;
2857 : }
2858 : }
2859 :
2860 263 : m_bRecordInsertedInGPKGContent = true;
2861 263 : m_nSRID = nSRSId;
2862 :
2863 525 : if (auto poSRS = GetSpatialRef(nSRSId))
2864 : {
2865 262 : m_oSRS = *(poSRS.get());
2866 : }
2867 :
2868 : /* Various sanity checks added in the SELECT */
2869 263 : char *pszQuotedTableName = sqlite3_mprintf("'%q'", pszTableName);
2870 526 : CPLString osQuotedTableName(pszQuotedTableName);
2871 263 : sqlite3_free(pszQuotedTableName);
2872 263 : char *pszSQL = sqlite3_mprintf(
2873 : "SELECT zoom_level, pixel_x_size, pixel_y_size, tile_width, "
2874 : "tile_height, matrix_width, matrix_height "
2875 : "FROM gpkg_tile_matrix tm "
2876 : "WHERE table_name = %s "
2877 : // INT_MAX would be the theoretical maximum value to avoid
2878 : // overflows, but that's already a insane value.
2879 : "AND zoom_level >= 0 AND zoom_level <= 65536 "
2880 : "AND pixel_x_size > 0 AND pixel_y_size > 0 "
2881 : "AND tile_width >= 1 AND tile_width <= 65536 "
2882 : "AND tile_height >= 1 AND tile_height <= 65536 "
2883 : "AND matrix_width >= 1 AND matrix_height >= 1",
2884 : osQuotedTableName.c_str());
2885 526 : CPLString osSQL(pszSQL);
2886 : const char *pszZoomLevel =
2887 263 : CSLFetchNameValue(papszOpenOptionsIn, "ZOOM_LEVEL");
2888 263 : if (pszZoomLevel)
2889 : {
2890 5 : if (GetUpdate())
2891 1 : osSQL += CPLSPrintf(" AND zoom_level <= %d", atoi(pszZoomLevel));
2892 : else
2893 : {
2894 : osSQL += CPLSPrintf(
2895 : " AND (zoom_level = %d OR (zoom_level < %d AND EXISTS(SELECT 1 "
2896 : "FROM %s WHERE zoom_level = tm.zoom_level LIMIT 1)))",
2897 : atoi(pszZoomLevel), atoi(pszZoomLevel),
2898 4 : osQuotedTableName.c_str());
2899 : }
2900 : }
2901 : // In read-only mode, only lists non empty zoom levels
2902 258 : else if (!GetUpdate())
2903 : {
2904 : osSQL += CPLSPrintf(" AND EXISTS(SELECT 1 FROM %s WHERE zoom_level = "
2905 : "tm.zoom_level LIMIT 1)",
2906 207 : osQuotedTableName.c_str());
2907 : }
2908 : else // if( pszZoomLevel == nullptr )
2909 : {
2910 : osSQL +=
2911 : CPLSPrintf(" AND zoom_level <= (SELECT MAX(zoom_level) FROM %s)",
2912 51 : osQuotedTableName.c_str());
2913 : }
2914 263 : osSQL += " ORDER BY zoom_level DESC";
2915 : // To avoid denial of service.
2916 263 : osSQL += " LIMIT 100";
2917 :
2918 526 : auto oResult = SQLQuery(hDB, osSQL.c_str());
2919 263 : if (!oResult || oResult->RowCount() == 0)
2920 : {
2921 106 : if (oResult && oResult->RowCount() == 0 && pszContentsMinX != nullptr &&
2922 106 : pszContentsMinY != nullptr && pszContentsMaxX != nullptr &&
2923 : pszContentsMaxY != nullptr)
2924 : {
2925 52 : osSQL = pszSQL;
2926 52 : osSQL += " ORDER BY zoom_level DESC";
2927 52 : if (!GetUpdate())
2928 26 : osSQL += " LIMIT 1";
2929 52 : oResult = SQLQuery(hDB, osSQL.c_str());
2930 : }
2931 53 : if (!oResult || oResult->RowCount() == 0)
2932 : {
2933 1 : if (oResult && pszZoomLevel != nullptr)
2934 : {
2935 1 : CPLError(CE_Failure, CPLE_AppDefined,
2936 : "ZOOM_LEVEL is probably not valid w.r.t tile "
2937 : "table content");
2938 : }
2939 1 : sqlite3_free(pszSQL);
2940 1 : return false;
2941 : }
2942 : }
2943 262 : sqlite3_free(pszSQL);
2944 :
2945 : // If USE_TILE_EXTENT=YES, then query the tile table to find which tiles
2946 : // actually exist.
2947 :
2948 : // CAUTION: Do not move those variables inside inner scope !
2949 524 : CPLString osContentsMinX, osContentsMinY, osContentsMaxX, osContentsMaxY;
2950 :
2951 262 : if (CPLTestBool(
2952 : CSLFetchNameValueDef(papszOpenOptionsIn, "USE_TILE_EXTENT", "NO")))
2953 : {
2954 13 : pszSQL = sqlite3_mprintf(
2955 : "SELECT MIN(tile_column), MIN(tile_row), MAX(tile_column), "
2956 : "MAX(tile_row) FROM \"%w\" WHERE zoom_level = %d",
2957 : pszTableName, atoi(oResult->GetValue(0, 0)));
2958 13 : auto oResult2 = SQLQuery(hDB, pszSQL);
2959 13 : sqlite3_free(pszSQL);
2960 26 : if (!oResult2 || oResult2->RowCount() == 0 ||
2961 : // Can happen if table is empty
2962 38 : oResult2->GetValue(0, 0) == nullptr ||
2963 : // Can happen if table has no NOT NULL constraint on tile_row
2964 : // and that all tile_row are NULL
2965 12 : oResult2->GetValue(1, 0) == nullptr)
2966 : {
2967 1 : return false;
2968 : }
2969 12 : const double dfPixelXSize = CPLAtof(oResult->GetValue(1, 0));
2970 12 : const double dfPixelYSize = CPLAtof(oResult->GetValue(2, 0));
2971 12 : const int nTileWidth = atoi(oResult->GetValue(3, 0));
2972 12 : const int nTileHeight = atoi(oResult->GetValue(4, 0));
2973 : osContentsMinX =
2974 24 : CPLSPrintf("%.17g", dfMinX + dfPixelXSize * nTileWidth *
2975 12 : atoi(oResult2->GetValue(0, 0)));
2976 : osContentsMaxY =
2977 24 : CPLSPrintf("%.17g", dfMaxY - dfPixelYSize * nTileHeight *
2978 12 : atoi(oResult2->GetValue(1, 0)));
2979 : osContentsMaxX = CPLSPrintf(
2980 24 : "%.17g", dfMinX + dfPixelXSize * nTileWidth *
2981 12 : (1 + atoi(oResult2->GetValue(2, 0))));
2982 : osContentsMinY = CPLSPrintf(
2983 24 : "%.17g", dfMaxY - dfPixelYSize * nTileHeight *
2984 12 : (1 + atoi(oResult2->GetValue(3, 0))));
2985 12 : pszContentsMinX = osContentsMinX.c_str();
2986 12 : pszContentsMinY = osContentsMinY.c_str();
2987 12 : pszContentsMaxX = osContentsMaxX.c_str();
2988 12 : pszContentsMaxY = osContentsMaxY.c_str();
2989 : }
2990 :
2991 261 : if (!InitRaster(nullptr, pszTableName, dfMinX, dfMinY, dfMaxX, dfMaxY,
2992 : pszContentsMinX, pszContentsMinY, pszContentsMaxX,
2993 261 : pszContentsMaxY, papszOpenOptionsIn, *oResult, 0))
2994 : {
2995 3 : return false;
2996 : }
2997 :
2998 : auto poBand =
2999 258 : reinterpret_cast<GDALGeoPackageRasterBand *>(GetRasterBand(1));
3000 258 : if (!osDataNull.empty())
3001 : {
3002 23 : double dfGPKGNoDataValue = CPLAtof(osDataNull);
3003 23 : if (m_eTF == GPKG_TF_PNG_16BIT)
3004 : {
3005 21 : if (dfGPKGNoDataValue < 0 || dfGPKGNoDataValue > 65535 ||
3006 21 : static_cast<int>(dfGPKGNoDataValue) != dfGPKGNoDataValue)
3007 : {
3008 0 : CPLError(CE_Warning, CPLE_AppDefined,
3009 : "data_null = %.17g is invalid for integer data_type",
3010 : dfGPKGNoDataValue);
3011 : }
3012 : else
3013 : {
3014 21 : m_usGPKGNull = static_cast<GUInt16>(dfGPKGNoDataValue);
3015 21 : if (m_eDT == GDT_Int16 && m_usGPKGNull > 32767)
3016 9 : dfGPKGNoDataValue = -32768.0;
3017 12 : else if (m_eDT == GDT_Float32)
3018 : {
3019 : // Pick a value that is unlikely to be hit with offset &
3020 : // scale
3021 4 : dfGPKGNoDataValue = -std::numeric_limits<float>::max();
3022 : }
3023 21 : poBand->SetNoDataValueInternal(dfGPKGNoDataValue);
3024 : }
3025 : }
3026 : else
3027 : {
3028 2 : poBand->SetNoDataValueInternal(
3029 2 : static_cast<float>(dfGPKGNoDataValue));
3030 : }
3031 : }
3032 258 : if (!osUom.empty())
3033 : {
3034 2 : poBand->SetUnitTypeInternal(osUom);
3035 : }
3036 258 : if (!osFieldName.empty())
3037 : {
3038 64 : GetRasterBand(1)->GDALRasterBand::SetDescription(osFieldName);
3039 : }
3040 258 : if (!osGridCellEncoding.empty())
3041 : {
3042 64 : if (osGridCellEncoding == "grid-value-is-center")
3043 : {
3044 15 : GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
3045 : GDALMD_AOP_POINT);
3046 : }
3047 49 : else if (osGridCellEncoding == "grid-value-is-area")
3048 : {
3049 45 : GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
3050 : GDALMD_AOP_AREA);
3051 : }
3052 : else
3053 : {
3054 4 : GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
3055 : GDALMD_AOP_POINT);
3056 4 : GetRasterBand(1)->GDALRasterBand::SetMetadataItem(
3057 : "GRID_CELL_ENCODING", osGridCellEncoding);
3058 : }
3059 : }
3060 :
3061 258 : CheckUnknownExtensions(true);
3062 :
3063 : // Do this after CheckUnknownExtensions() so that m_eTF is set to
3064 : // GPKG_TF_WEBP if the table already registers the gpkg_webp extension
3065 258 : const char *pszTF = CSLFetchNameValue(papszOpenOptionsIn, "TILE_FORMAT");
3066 258 : if (pszTF)
3067 : {
3068 4 : if (!GetUpdate())
3069 : {
3070 0 : CPLError(CE_Warning, CPLE_AppDefined,
3071 : "TILE_FORMAT open option ignored in read-only mode");
3072 : }
3073 4 : else if (m_eTF == GPKG_TF_PNG_16BIT ||
3074 4 : m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
3075 : {
3076 0 : CPLError(CE_Warning, CPLE_AppDefined,
3077 : "TILE_FORMAT open option ignored on gridded coverages");
3078 : }
3079 : else
3080 : {
3081 4 : GPKGTileFormat eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
3082 4 : if (eTF == GPKG_TF_WEBP && m_eTF != eTF)
3083 : {
3084 1 : if (!RegisterWebPExtension())
3085 0 : return false;
3086 : }
3087 4 : m_eTF = eTF;
3088 : }
3089 : }
3090 :
3091 258 : ParseCompressionOptions(papszOpenOptionsIn);
3092 :
3093 258 : m_osWHERE = CSLFetchNameValueDef(papszOpenOptionsIn, "WHERE", "");
3094 :
3095 : // Set metadata
3096 258 : if (pszIdentifier && pszIdentifier[0])
3097 258 : GDALPamDataset::SetMetadataItem("IDENTIFIER", pszIdentifier);
3098 258 : if (pszDescription && pszDescription[0])
3099 21 : GDALPamDataset::SetMetadataItem("DESCRIPTION", pszDescription);
3100 :
3101 : // Add overviews
3102 343 : for (int i = 1; i < oResult->RowCount(); i++)
3103 : {
3104 86 : GDALGeoPackageDataset *poOvrDS = new GDALGeoPackageDataset();
3105 86 : poOvrDS->ShareLockWithParentDataset(this);
3106 86 : if (!poOvrDS->InitRaster(this, pszTableName, dfMinX, dfMinY, dfMaxX,
3107 : dfMaxY, pszContentsMinX, pszContentsMinY,
3108 : pszContentsMaxX, pszContentsMaxY,
3109 86 : papszOpenOptionsIn, *oResult, i))
3110 : {
3111 0 : delete poOvrDS;
3112 1 : break;
3113 : }
3114 :
3115 86 : m_papoOverviewDS = static_cast<GDALGeoPackageDataset **>(
3116 172 : CPLRealloc(m_papoOverviewDS, sizeof(GDALGeoPackageDataset *) *
3117 86 : (m_nOverviewCount + 1)));
3118 86 : m_papoOverviewDS[m_nOverviewCount++] = poOvrDS;
3119 :
3120 : int nTileWidth, nTileHeight;
3121 86 : poOvrDS->GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3122 87 : if (eAccess == GA_ReadOnly && poOvrDS->GetRasterXSize() < nTileWidth &&
3123 1 : poOvrDS->GetRasterYSize() < nTileHeight)
3124 : {
3125 1 : break;
3126 : }
3127 : }
3128 :
3129 258 : return true;
3130 : }
3131 :
3132 : /************************************************************************/
3133 : /* GetSpatialRef() */
3134 : /************************************************************************/
3135 :
3136 14 : const OGRSpatialReference *GDALGeoPackageDataset::GetSpatialRef() const
3137 : {
3138 14 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
3139 : }
3140 :
3141 : /************************************************************************/
3142 : /* SetSpatialRef() */
3143 : /************************************************************************/
3144 :
3145 138 : CPLErr GDALGeoPackageDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
3146 : {
3147 138 : if (nBands == 0)
3148 : {
3149 1 : CPLError(CE_Failure, CPLE_NotSupported,
3150 : "SetProjection() not supported on a dataset with 0 band");
3151 1 : return CE_Failure;
3152 : }
3153 137 : if (eAccess != GA_Update)
3154 : {
3155 1 : CPLError(CE_Failure, CPLE_NotSupported,
3156 : "SetProjection() not supported on read-only dataset");
3157 1 : return CE_Failure;
3158 : }
3159 :
3160 136 : const int nSRID = GetSrsId(poSRS);
3161 272 : const auto poTS = GetTilingScheme(m_osTilingScheme);
3162 136 : if (poTS && nSRID != poTS->nEPSGCode)
3163 : {
3164 2 : CPLError(CE_Failure, CPLE_NotSupported,
3165 : "Projection should be EPSG:%d for %s tiling scheme",
3166 1 : poTS->nEPSGCode, m_osTilingScheme.c_str());
3167 1 : return CE_Failure;
3168 : }
3169 :
3170 135 : m_nSRID = nSRID;
3171 135 : m_oSRS.Clear();
3172 135 : if (poSRS)
3173 134 : m_oSRS = *poSRS;
3174 :
3175 135 : if (m_bRecordInsertedInGPKGContent)
3176 : {
3177 110 : char *pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET srs_id = %d "
3178 : "WHERE lower(table_name) = lower('%q')",
3179 : m_nSRID, m_osRasterTable.c_str());
3180 110 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3181 110 : sqlite3_free(pszSQL);
3182 110 : if (eErr != OGRERR_NONE)
3183 0 : return CE_Failure;
3184 :
3185 110 : pszSQL = sqlite3_mprintf("UPDATE gpkg_tile_matrix_set SET srs_id = %d "
3186 : "WHERE lower(table_name) = lower('%q')",
3187 : m_nSRID, m_osRasterTable.c_str());
3188 110 : eErr = SQLCommand(hDB, pszSQL);
3189 110 : sqlite3_free(pszSQL);
3190 110 : if (eErr != OGRERR_NONE)
3191 0 : return CE_Failure;
3192 : }
3193 :
3194 135 : return CE_None;
3195 : }
3196 :
3197 : /************************************************************************/
3198 : /* GetGeoTransform() */
3199 : /************************************************************************/
3200 :
3201 33 : CPLErr GDALGeoPackageDataset::GetGeoTransform(double *padfGeoTransform)
3202 : {
3203 33 : memcpy(padfGeoTransform, m_adfGeoTransform, 6 * sizeof(double));
3204 33 : if (!m_bGeoTransformValid)
3205 2 : return CE_Failure;
3206 : else
3207 31 : return CE_None;
3208 : }
3209 :
3210 : /************************************************************************/
3211 : /* SetGeoTransform() */
3212 : /************************************************************************/
3213 :
3214 168 : CPLErr GDALGeoPackageDataset::SetGeoTransform(double *padfGeoTransform)
3215 : {
3216 168 : if (nBands == 0)
3217 : {
3218 2 : CPLError(CE_Failure, CPLE_NotSupported,
3219 : "SetGeoTransform() not supported on a dataset with 0 band");
3220 2 : return CE_Failure;
3221 : }
3222 166 : if (eAccess != GA_Update)
3223 : {
3224 1 : CPLError(CE_Failure, CPLE_NotSupported,
3225 : "SetGeoTransform() not supported on read-only dataset");
3226 1 : return CE_Failure;
3227 : }
3228 165 : if (m_bGeoTransformValid)
3229 : {
3230 1 : CPLError(CE_Failure, CPLE_NotSupported,
3231 : "Cannot modify geotransform once set");
3232 1 : return CE_Failure;
3233 : }
3234 164 : if (padfGeoTransform[2] != 0.0 || padfGeoTransform[4] != 0 ||
3235 164 : padfGeoTransform[5] > 0.0)
3236 : {
3237 0 : CPLError(CE_Failure, CPLE_NotSupported,
3238 : "Only north-up non rotated geotransform supported");
3239 0 : return CE_Failure;
3240 : }
3241 :
3242 164 : if (m_nZoomLevel < 0)
3243 : {
3244 163 : const auto poTS = GetTilingScheme(m_osTilingScheme);
3245 163 : if (poTS)
3246 : {
3247 20 : double dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
3248 20 : double dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
3249 199 : for (m_nZoomLevel = 0; m_nZoomLevel < MAX_ZOOM_LEVEL;
3250 179 : m_nZoomLevel++)
3251 : {
3252 198 : double dfExpectedPixelXSize =
3253 198 : dfPixelXSizeZoomLevel0 / (1 << m_nZoomLevel);
3254 198 : double dfExpectedPixelYSize =
3255 198 : dfPixelYSizeZoomLevel0 / (1 << m_nZoomLevel);
3256 198 : if (fabs(padfGeoTransform[1] - dfExpectedPixelXSize) <
3257 198 : 1e-8 * dfExpectedPixelXSize &&
3258 19 : fabs(fabs(padfGeoTransform[5]) - dfExpectedPixelYSize) <
3259 19 : 1e-8 * dfExpectedPixelYSize)
3260 : {
3261 19 : break;
3262 : }
3263 : }
3264 20 : if (m_nZoomLevel == MAX_ZOOM_LEVEL)
3265 : {
3266 1 : m_nZoomLevel = -1;
3267 1 : CPLError(
3268 : CE_Failure, CPLE_NotSupported,
3269 : "Could not find an appropriate zoom level of %s tiling "
3270 : "scheme that matches raster pixel size",
3271 : m_osTilingScheme.c_str());
3272 1 : return CE_Failure;
3273 : }
3274 : }
3275 : }
3276 :
3277 163 : memcpy(m_adfGeoTransform, padfGeoTransform, 6 * sizeof(double));
3278 163 : m_bGeoTransformValid = true;
3279 :
3280 163 : return FinalizeRasterRegistration();
3281 : }
3282 :
3283 : /************************************************************************/
3284 : /* FinalizeRasterRegistration() */
3285 : /************************************************************************/
3286 :
3287 163 : CPLErr GDALGeoPackageDataset::FinalizeRasterRegistration()
3288 : {
3289 : OGRErr eErr;
3290 :
3291 163 : m_dfTMSMinX = m_adfGeoTransform[0];
3292 163 : m_dfTMSMaxY = m_adfGeoTransform[3];
3293 :
3294 : int nTileWidth, nTileHeight;
3295 163 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3296 :
3297 163 : if (m_nZoomLevel < 0)
3298 : {
3299 143 : m_nZoomLevel = 0;
3300 217 : while ((nRasterXSize >> m_nZoomLevel) > nTileWidth ||
3301 143 : (nRasterYSize >> m_nZoomLevel) > nTileHeight)
3302 74 : m_nZoomLevel++;
3303 : }
3304 :
3305 163 : double dfPixelXSizeZoomLevel0 = m_adfGeoTransform[1] * (1 << m_nZoomLevel);
3306 163 : double dfPixelYSizeZoomLevel0 =
3307 163 : fabs(m_adfGeoTransform[5]) * (1 << m_nZoomLevel);
3308 : int nTileXCountZoomLevel0 =
3309 163 : std::max(1, DIV_ROUND_UP((nRasterXSize >> m_nZoomLevel), nTileWidth));
3310 : int nTileYCountZoomLevel0 =
3311 163 : std::max(1, DIV_ROUND_UP((nRasterYSize >> m_nZoomLevel), nTileHeight));
3312 :
3313 326 : const auto poTS = GetTilingScheme(m_osTilingScheme);
3314 163 : if (poTS)
3315 : {
3316 20 : CPLAssert(m_nZoomLevel >= 0);
3317 20 : m_dfTMSMinX = poTS->dfMinX;
3318 20 : m_dfTMSMaxY = poTS->dfMaxY;
3319 20 : dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
3320 20 : dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
3321 20 : nTileXCountZoomLevel0 = poTS->nTileXCountZoomLevel0;
3322 20 : nTileYCountZoomLevel0 = poTS->nTileYCountZoomLevel0;
3323 : }
3324 163 : m_nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << m_nZoomLevel);
3325 163 : m_nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << m_nZoomLevel);
3326 :
3327 163 : if (!ComputeTileAndPixelShifts())
3328 : {
3329 0 : CPLError(CE_Failure, CPLE_AppDefined,
3330 : "Overflow occurred in ComputeTileAndPixelShifts()");
3331 0 : return CE_Failure;
3332 : }
3333 :
3334 163 : if (!AllocCachedTiles())
3335 : {
3336 0 : return CE_Failure;
3337 : }
3338 :
3339 163 : double dfGDALMinX = m_adfGeoTransform[0];
3340 163 : double dfGDALMinY =
3341 163 : m_adfGeoTransform[3] + nRasterYSize * m_adfGeoTransform[5];
3342 163 : double dfGDALMaxX =
3343 163 : m_adfGeoTransform[0] + nRasterXSize * m_adfGeoTransform[1];
3344 163 : double dfGDALMaxY = m_adfGeoTransform[3];
3345 :
3346 163 : if (SoftStartTransaction() != OGRERR_NONE)
3347 0 : return CE_Failure;
3348 :
3349 : const char *pszCurrentDate =
3350 163 : CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
3351 : CPLString osInsertGpkgContentsFormatting(
3352 : "INSERT INTO gpkg_contents "
3353 : "(table_name,data_type,identifier,description,min_x,min_y,max_x,max_y,"
3354 : "last_change,srs_id) VALUES "
3355 326 : "('%q','%q','%q','%q',%.17g,%.17g,%.17g,%.17g,");
3356 163 : osInsertGpkgContentsFormatting += (pszCurrentDate) ? "'%q'" : "%s";
3357 163 : osInsertGpkgContentsFormatting += ",%d)";
3358 326 : char *pszSQL = sqlite3_mprintf(
3359 : osInsertGpkgContentsFormatting.c_str(), m_osRasterTable.c_str(),
3360 163 : (m_eDT == GDT_Byte) ? "tiles" : "2d-gridded-coverage",
3361 : m_osIdentifier.c_str(), m_osDescription.c_str(), dfGDALMinX, dfGDALMinY,
3362 : dfGDALMaxX, dfGDALMaxY,
3363 : pszCurrentDate ? pszCurrentDate
3364 : : "strftime('%Y-%m-%dT%H:%M:%fZ','now')",
3365 : m_nSRID);
3366 :
3367 163 : eErr = SQLCommand(hDB, pszSQL);
3368 163 : sqlite3_free(pszSQL);
3369 163 : if (eErr != OGRERR_NONE)
3370 : {
3371 0 : SoftRollbackTransaction();
3372 0 : return CE_Failure;
3373 : }
3374 :
3375 163 : double dfTMSMaxX = m_dfTMSMinX + nTileXCountZoomLevel0 * nTileWidth *
3376 : dfPixelXSizeZoomLevel0;
3377 163 : double dfTMSMinY = m_dfTMSMaxY - nTileYCountZoomLevel0 * nTileHeight *
3378 : dfPixelYSizeZoomLevel0;
3379 :
3380 : pszSQL =
3381 163 : sqlite3_mprintf("INSERT INTO gpkg_tile_matrix_set "
3382 : "(table_name,srs_id,min_x,min_y,max_x,max_y) VALUES "
3383 : "('%q',%d,%.17g,%.17g,%.17g,%.17g)",
3384 : m_osRasterTable.c_str(), m_nSRID, m_dfTMSMinX,
3385 : dfTMSMinY, dfTMSMaxX, m_dfTMSMaxY);
3386 163 : eErr = SQLCommand(hDB, pszSQL);
3387 163 : sqlite3_free(pszSQL);
3388 163 : if (eErr != OGRERR_NONE)
3389 : {
3390 0 : SoftRollbackTransaction();
3391 0 : return CE_Failure;
3392 : }
3393 :
3394 163 : m_papoOverviewDS = static_cast<GDALGeoPackageDataset **>(
3395 163 : CPLCalloc(sizeof(GDALGeoPackageDataset *), m_nZoomLevel));
3396 :
3397 561 : for (int i = 0; i <= m_nZoomLevel; i++)
3398 : {
3399 398 : double dfPixelXSizeZoomLevel = 0.0;
3400 398 : double dfPixelYSizeZoomLevel = 0.0;
3401 398 : int nTileMatrixWidth = 0;
3402 398 : int nTileMatrixHeight = 0;
3403 398 : if (EQUAL(m_osTilingScheme, "CUSTOM"))
3404 : {
3405 217 : dfPixelXSizeZoomLevel =
3406 217 : m_adfGeoTransform[1] * (1 << (m_nZoomLevel - i));
3407 217 : dfPixelYSizeZoomLevel =
3408 217 : fabs(m_adfGeoTransform[5]) * (1 << (m_nZoomLevel - i));
3409 : }
3410 : else
3411 : {
3412 181 : dfPixelXSizeZoomLevel = dfPixelXSizeZoomLevel0 / (1 << i);
3413 181 : dfPixelYSizeZoomLevel = dfPixelYSizeZoomLevel0 / (1 << i);
3414 : }
3415 398 : nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << i);
3416 398 : nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << i);
3417 :
3418 398 : pszSQL = sqlite3_mprintf(
3419 : "INSERT INTO gpkg_tile_matrix "
3420 : "(table_name,zoom_level,matrix_width,matrix_height,tile_width,tile_"
3421 : "height,pixel_x_size,pixel_y_size) VALUES "
3422 : "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
3423 : m_osRasterTable.c_str(), i, nTileMatrixWidth, nTileMatrixHeight,
3424 : nTileWidth, nTileHeight, dfPixelXSizeZoomLevel,
3425 : dfPixelYSizeZoomLevel);
3426 398 : eErr = SQLCommand(hDB, pszSQL);
3427 398 : sqlite3_free(pszSQL);
3428 398 : if (eErr != OGRERR_NONE)
3429 : {
3430 0 : SoftRollbackTransaction();
3431 0 : return CE_Failure;
3432 : }
3433 :
3434 398 : if (i < m_nZoomLevel)
3435 : {
3436 235 : GDALGeoPackageDataset *poOvrDS = new GDALGeoPackageDataset();
3437 235 : poOvrDS->ShareLockWithParentDataset(this);
3438 235 : poOvrDS->InitRaster(this, m_osRasterTable, i, nBands, m_dfTMSMinX,
3439 : m_dfTMSMaxY, dfPixelXSizeZoomLevel,
3440 : dfPixelYSizeZoomLevel, nTileWidth, nTileHeight,
3441 : nTileMatrixWidth, nTileMatrixHeight, dfGDALMinX,
3442 : dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
3443 :
3444 235 : m_papoOverviewDS[m_nZoomLevel - 1 - i] = poOvrDS;
3445 : }
3446 : }
3447 :
3448 163 : if (!m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.empty())
3449 : {
3450 40 : eErr = SQLCommand(
3451 : hDB, m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.c_str());
3452 40 : m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.clear();
3453 40 : if (eErr != OGRERR_NONE)
3454 : {
3455 0 : SoftRollbackTransaction();
3456 0 : return CE_Failure;
3457 : }
3458 : }
3459 :
3460 163 : SoftCommitTransaction();
3461 :
3462 163 : m_nOverviewCount = m_nZoomLevel;
3463 163 : m_bRecordInsertedInGPKGContent = true;
3464 :
3465 163 : return CE_None;
3466 : }
3467 :
3468 : /************************************************************************/
3469 : /* FlushCache() */
3470 : /************************************************************************/
3471 :
3472 2307 : CPLErr GDALGeoPackageDataset::FlushCache(bool bAtClosing)
3473 : {
3474 2307 : if (m_bInFlushCache)
3475 0 : return CE_None;
3476 :
3477 2307 : if (eAccess == GA_Update || !m_bMetadataDirty)
3478 : {
3479 2304 : SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
3480 : }
3481 :
3482 2307 : if (m_bRemoveOGREmptyTable)
3483 : {
3484 556 : m_bRemoveOGREmptyTable = false;
3485 556 : RemoveOGREmptyTable();
3486 : }
3487 :
3488 2307 : CPLErr eErr = IFlushCacheWithErrCode(bAtClosing);
3489 :
3490 2307 : FlushMetadata();
3491 :
3492 2307 : if (eAccess == GA_Update || !m_bMetadataDirty)
3493 : {
3494 : // Needed again as above IFlushCacheWithErrCode()
3495 : // may have call GDALGeoPackageRasterBand::InvalidateStatistics()
3496 : // which modifies metadata
3497 2307 : SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
3498 : }
3499 :
3500 2307 : return eErr;
3501 : }
3502 :
3503 4489 : CPLErr GDALGeoPackageDataset::IFlushCacheWithErrCode(bool bAtClosing)
3504 :
3505 : {
3506 4489 : if (m_bInFlushCache)
3507 2115 : return CE_None;
3508 2374 : m_bInFlushCache = true;
3509 2374 : if (hDB && eAccess == GA_ReadOnly && bAtClosing)
3510 : {
3511 : // Clean-up metadata that will go to PAM by removing items that
3512 : // are reconstructed.
3513 1752 : CPLStringList aosMD;
3514 1478 : for (CSLConstList papszIter = GetMetadata(); papszIter && *papszIter;
3515 : ++papszIter)
3516 : {
3517 602 : char *pszKey = nullptr;
3518 602 : CPLParseNameValue(*papszIter, &pszKey);
3519 1204 : if (pszKey &&
3520 602 : (EQUAL(pszKey, "AREA_OR_POINT") ||
3521 461 : EQUAL(pszKey, "IDENTIFIER") || EQUAL(pszKey, "DESCRIPTION") ||
3522 248 : EQUAL(pszKey, "ZOOM_LEVEL") ||
3523 632 : STARTS_WITH(pszKey, "GPKG_METADATA_ITEM_")))
3524 : {
3525 : // remove it
3526 : }
3527 : else
3528 : {
3529 30 : aosMD.AddString(*papszIter);
3530 : }
3531 602 : CPLFree(pszKey);
3532 : }
3533 876 : oMDMD.SetMetadata(aosMD.List());
3534 876 : oMDMD.SetMetadata(nullptr, "IMAGE_STRUCTURE");
3535 :
3536 1752 : GDALPamDataset::FlushCache(bAtClosing);
3537 : }
3538 : else
3539 : {
3540 : // Short circuit GDALPamDataset to avoid serialization to .aux.xml
3541 1498 : GDALDataset::FlushCache(bAtClosing);
3542 : }
3543 :
3544 5997 : for (int i = 0; i < m_nLayers; i++)
3545 : {
3546 3623 : m_papoLayers[i]->RunDeferredCreationIfNecessary();
3547 3623 : m_papoLayers[i]->CreateSpatialIndexIfNecessary();
3548 : }
3549 :
3550 : // Update raster table last_change column in gpkg_contents if needed
3551 2374 : if (m_bHasModifiedTiles)
3552 : {
3553 496 : for (int i = 1; i <= nBands; ++i)
3554 : {
3555 : auto poBand =
3556 334 : cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
3557 334 : if (!poBand->HaveStatsMetadataBeenSetInThisSession())
3558 : {
3559 331 : poBand->InvalidateStatistics();
3560 331 : if (psPam && psPam->pszPamFilename)
3561 331 : VSIUnlink(psPam->pszPamFilename);
3562 : }
3563 : }
3564 :
3565 162 : UpdateGpkgContentsLastChange(m_osRasterTable);
3566 :
3567 162 : m_bHasModifiedTiles = false;
3568 : }
3569 :
3570 2374 : CPLErr eErr = FlushTiles();
3571 :
3572 2374 : m_bInFlushCache = false;
3573 2374 : return eErr;
3574 : }
3575 :
3576 : /************************************************************************/
3577 : /* GetCurrentDateEscapedSQL() */
3578 : /************************************************************************/
3579 :
3580 1643 : std::string GDALGeoPackageDataset::GetCurrentDateEscapedSQL()
3581 : {
3582 : const char *pszCurrentDate =
3583 1643 : CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
3584 1643 : if (pszCurrentDate)
3585 6 : return '\'' + SQLEscapeLiteral(pszCurrentDate) + '\'';
3586 1640 : return "strftime('%Y-%m-%dT%H:%M:%fZ','now')";
3587 : }
3588 :
3589 : /************************************************************************/
3590 : /* UpdateGpkgContentsLastChange() */
3591 : /************************************************************************/
3592 :
3593 : OGRErr
3594 715 : GDALGeoPackageDataset::UpdateGpkgContentsLastChange(const char *pszTableName)
3595 : {
3596 : char *pszSQL =
3597 715 : sqlite3_mprintf("UPDATE gpkg_contents SET "
3598 : "last_change = %s "
3599 : "WHERE lower(table_name) = lower('%q')",
3600 1430 : GetCurrentDateEscapedSQL().c_str(), pszTableName);
3601 715 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3602 715 : sqlite3_free(pszSQL);
3603 715 : return eErr;
3604 : }
3605 :
3606 : /************************************************************************/
3607 : /* IBuildOverviews() */
3608 : /************************************************************************/
3609 :
3610 20 : CPLErr GDALGeoPackageDataset::IBuildOverviews(
3611 : const char *pszResampling, int nOverviews, const int *panOverviewList,
3612 : int nBandsIn, const int * /*panBandList*/, GDALProgressFunc pfnProgress,
3613 : void *pProgressData, CSLConstList papszOptions)
3614 : {
3615 20 : if (GetAccess() != GA_Update)
3616 : {
3617 1 : CPLError(CE_Failure, CPLE_NotSupported,
3618 : "Overview building not supported on a database opened in "
3619 : "read-only mode");
3620 1 : return CE_Failure;
3621 : }
3622 19 : if (m_poParentDS != nullptr)
3623 : {
3624 1 : CPLError(CE_Failure, CPLE_NotSupported,
3625 : "Overview building not supported on overview dataset");
3626 1 : return CE_Failure;
3627 : }
3628 :
3629 18 : if (nOverviews == 0)
3630 : {
3631 5 : for (int i = 0; i < m_nOverviewCount; i++)
3632 3 : m_papoOverviewDS[i]->FlushCache(false);
3633 :
3634 2 : SoftStartTransaction();
3635 :
3636 2 : if (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
3637 : {
3638 1 : char *pszSQL = sqlite3_mprintf(
3639 : "DELETE FROM gpkg_2d_gridded_tile_ancillary WHERE id IN "
3640 : "(SELECT y.id FROM \"%w\" x "
3641 : "JOIN gpkg_2d_gridded_tile_ancillary y "
3642 : "ON x.id = y.tpudt_id AND y.tpudt_name = '%q' AND "
3643 : "x.zoom_level < %d)",
3644 : m_osRasterTable.c_str(), m_osRasterTable.c_str(), m_nZoomLevel);
3645 1 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3646 1 : sqlite3_free(pszSQL);
3647 1 : if (eErr != OGRERR_NONE)
3648 : {
3649 0 : SoftRollbackTransaction();
3650 0 : return CE_Failure;
3651 : }
3652 : }
3653 :
3654 : char *pszSQL =
3655 2 : sqlite3_mprintf("DELETE FROM \"%w\" WHERE zoom_level < %d",
3656 : m_osRasterTable.c_str(), m_nZoomLevel);
3657 2 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3658 2 : sqlite3_free(pszSQL);
3659 2 : if (eErr != OGRERR_NONE)
3660 : {
3661 0 : SoftRollbackTransaction();
3662 0 : return CE_Failure;
3663 : }
3664 :
3665 2 : SoftCommitTransaction();
3666 :
3667 2 : return CE_None;
3668 : }
3669 :
3670 16 : if (nBandsIn != nBands)
3671 : {
3672 0 : CPLError(CE_Failure, CPLE_NotSupported,
3673 : "Generation of overviews in GPKG only"
3674 : "supported when operating on all bands.");
3675 0 : return CE_Failure;
3676 : }
3677 :
3678 16 : if (m_nOverviewCount == 0)
3679 : {
3680 0 : CPLError(CE_Failure, CPLE_AppDefined,
3681 : "Image too small to support overviews");
3682 0 : return CE_Failure;
3683 : }
3684 :
3685 16 : FlushCache(false);
3686 60 : for (int i = 0; i < nOverviews; i++)
3687 : {
3688 47 : if (panOverviewList[i] < 2)
3689 : {
3690 1 : CPLError(CE_Failure, CPLE_IllegalArg,
3691 : "Overview factor must be >= 2");
3692 1 : return CE_Failure;
3693 : }
3694 :
3695 46 : bool bFound = false;
3696 46 : int jCandidate = -1;
3697 46 : int nMaxOvFactor = 0;
3698 196 : for (int j = 0; j < m_nOverviewCount; j++)
3699 : {
3700 190 : auto poODS = m_papoOverviewDS[j];
3701 190 : const int nOvFactor = static_cast<int>(
3702 190 : 0.5 + poODS->m_adfGeoTransform[1] / m_adfGeoTransform[1]);
3703 :
3704 190 : nMaxOvFactor = nOvFactor;
3705 :
3706 190 : if (nOvFactor == panOverviewList[i])
3707 : {
3708 40 : bFound = true;
3709 40 : break;
3710 : }
3711 :
3712 150 : if (jCandidate < 0 && nOvFactor > panOverviewList[i])
3713 1 : jCandidate = j;
3714 : }
3715 :
3716 46 : if (!bFound)
3717 : {
3718 : /* Mostly for debug */
3719 6 : if (!CPLTestBool(CPLGetConfigOption(
3720 : "ALLOW_GPKG_ZOOM_OTHER_EXTENSION", "YES")))
3721 : {
3722 2 : CPLString osOvrList;
3723 4 : for (int j = 0; j < m_nOverviewCount; j++)
3724 : {
3725 2 : auto poODS = m_papoOverviewDS[j];
3726 2 : const int nOvFactor =
3727 2 : static_cast<int>(0.5 + poODS->m_adfGeoTransform[1] /
3728 2 : m_adfGeoTransform[1]);
3729 :
3730 2 : if (j != 0)
3731 0 : osOvrList += " ";
3732 2 : osOvrList += CPLSPrintf("%d", nOvFactor);
3733 : }
3734 2 : CPLError(CE_Failure, CPLE_NotSupported,
3735 : "Only overviews %s can be computed",
3736 : osOvrList.c_str());
3737 2 : return CE_Failure;
3738 : }
3739 : else
3740 : {
3741 4 : int nOvFactor = panOverviewList[i];
3742 4 : if (jCandidate < 0)
3743 3 : jCandidate = m_nOverviewCount;
3744 :
3745 4 : int nOvXSize = std::max(1, GetRasterXSize() / nOvFactor);
3746 4 : int nOvYSize = std::max(1, GetRasterYSize() / nOvFactor);
3747 4 : if (!(jCandidate == m_nOverviewCount &&
3748 3 : nOvFactor == 2 * nMaxOvFactor) &&
3749 1 : !m_bZoomOther)
3750 : {
3751 1 : CPLError(CE_Warning, CPLE_AppDefined,
3752 : "Use of overview factor %d causes gpkg_zoom_other "
3753 : "extension to be needed",
3754 : nOvFactor);
3755 1 : RegisterZoomOtherExtension();
3756 1 : m_bZoomOther = true;
3757 : }
3758 :
3759 4 : SoftStartTransaction();
3760 :
3761 4 : CPLAssert(jCandidate > 0);
3762 4 : int nNewZoomLevel =
3763 4 : m_papoOverviewDS[jCandidate - 1]->m_nZoomLevel;
3764 :
3765 : char *pszSQL;
3766 : OGRErr eErr;
3767 24 : for (int k = 0; k <= jCandidate; k++)
3768 : {
3769 60 : pszSQL = sqlite3_mprintf(
3770 : "UPDATE gpkg_tile_matrix SET zoom_level = %d "
3771 : "WHERE lower(table_name) = lower('%q') AND zoom_level "
3772 : "= %d",
3773 20 : m_nZoomLevel - k + 1, m_osRasterTable.c_str(),
3774 20 : m_nZoomLevel - k);
3775 20 : eErr = SQLCommand(hDB, pszSQL);
3776 20 : sqlite3_free(pszSQL);
3777 20 : if (eErr != OGRERR_NONE)
3778 : {
3779 0 : SoftRollbackTransaction();
3780 0 : return CE_Failure;
3781 : }
3782 :
3783 : pszSQL =
3784 20 : sqlite3_mprintf("UPDATE \"%w\" SET zoom_level = %d "
3785 : "WHERE zoom_level = %d",
3786 : m_osRasterTable.c_str(),
3787 20 : m_nZoomLevel - k + 1, m_nZoomLevel - k);
3788 20 : eErr = SQLCommand(hDB, pszSQL);
3789 20 : sqlite3_free(pszSQL);
3790 20 : if (eErr != OGRERR_NONE)
3791 : {
3792 0 : SoftRollbackTransaction();
3793 0 : return CE_Failure;
3794 : }
3795 : }
3796 :
3797 4 : double dfGDALMinX = m_adfGeoTransform[0];
3798 4 : double dfGDALMinY =
3799 4 : m_adfGeoTransform[3] + nRasterYSize * m_adfGeoTransform[5];
3800 4 : double dfGDALMaxX =
3801 4 : m_adfGeoTransform[0] + nRasterXSize * m_adfGeoTransform[1];
3802 4 : double dfGDALMaxY = m_adfGeoTransform[3];
3803 4 : double dfPixelXSizeZoomLevel = m_adfGeoTransform[1] * nOvFactor;
3804 4 : double dfPixelYSizeZoomLevel =
3805 4 : fabs(m_adfGeoTransform[5]) * nOvFactor;
3806 : int nTileWidth, nTileHeight;
3807 4 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3808 4 : int nTileMatrixWidth = (nOvXSize + nTileWidth - 1) / nTileWidth;
3809 4 : int nTileMatrixHeight =
3810 4 : (nOvYSize + nTileHeight - 1) / nTileHeight;
3811 4 : pszSQL = sqlite3_mprintf(
3812 : "INSERT INTO gpkg_tile_matrix "
3813 : "(table_name,zoom_level,matrix_width,matrix_height,tile_"
3814 : "width,tile_height,pixel_x_size,pixel_y_size) VALUES "
3815 : "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
3816 : m_osRasterTable.c_str(), nNewZoomLevel, nTileMatrixWidth,
3817 : nTileMatrixHeight, nTileWidth, nTileHeight,
3818 : dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel);
3819 4 : eErr = SQLCommand(hDB, pszSQL);
3820 4 : sqlite3_free(pszSQL);
3821 4 : if (eErr != OGRERR_NONE)
3822 : {
3823 0 : SoftRollbackTransaction();
3824 0 : return CE_Failure;
3825 : }
3826 :
3827 4 : SoftCommitTransaction();
3828 :
3829 4 : m_nZoomLevel++; /* this change our zoom level as well as
3830 : previous overviews */
3831 20 : for (int k = 0; k < jCandidate; k++)
3832 16 : m_papoOverviewDS[k]->m_nZoomLevel++;
3833 :
3834 4 : GDALGeoPackageDataset *poOvrDS = new GDALGeoPackageDataset();
3835 4 : poOvrDS->ShareLockWithParentDataset(this);
3836 4 : poOvrDS->InitRaster(
3837 : this, m_osRasterTable, nNewZoomLevel, nBands, m_dfTMSMinX,
3838 : m_dfTMSMaxY, dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel,
3839 : nTileWidth, nTileHeight, nTileMatrixWidth,
3840 : nTileMatrixHeight, dfGDALMinX, dfGDALMinY, dfGDALMaxX,
3841 : dfGDALMaxY);
3842 4 : m_papoOverviewDS =
3843 8 : static_cast<GDALGeoPackageDataset **>(CPLRealloc(
3844 4 : m_papoOverviewDS, sizeof(GDALGeoPackageDataset *) *
3845 4 : (m_nOverviewCount + 1)));
3846 :
3847 4 : if (jCandidate < m_nOverviewCount)
3848 : {
3849 1 : memmove(m_papoOverviewDS + jCandidate + 1,
3850 1 : m_papoOverviewDS + jCandidate,
3851 : sizeof(GDALGeoPackageDataset *) *
3852 1 : (m_nOverviewCount - jCandidate));
3853 : }
3854 4 : m_papoOverviewDS[jCandidate] = poOvrDS;
3855 4 : m_nOverviewCount++;
3856 : }
3857 : }
3858 : }
3859 :
3860 : GDALRasterBand ***papapoOverviewBands = static_cast<GDALRasterBand ***>(
3861 13 : CPLCalloc(sizeof(GDALRasterBand **), nBands));
3862 13 : CPLErr eErr = CE_None;
3863 49 : for (int iBand = 0; eErr == CE_None && iBand < nBands; iBand++)
3864 : {
3865 72 : papapoOverviewBands[iBand] = static_cast<GDALRasterBand **>(
3866 36 : CPLCalloc(sizeof(GDALRasterBand *), nOverviews));
3867 36 : int iCurOverview = 0;
3868 185 : for (int i = 0; i < nOverviews; i++)
3869 : {
3870 149 : int j = 0; // Used after for.
3871 724 : for (; j < m_nOverviewCount; j++)
3872 : {
3873 724 : auto poODS = m_papoOverviewDS[j];
3874 724 : const int nOvFactor = static_cast<int>(
3875 724 : 0.5 + poODS->m_adfGeoTransform[1] / m_adfGeoTransform[1]);
3876 :
3877 724 : if (nOvFactor == panOverviewList[i])
3878 : {
3879 298 : papapoOverviewBands[iBand][iCurOverview] =
3880 149 : poODS->GetRasterBand(iBand + 1);
3881 149 : iCurOverview++;
3882 149 : break;
3883 : }
3884 : }
3885 149 : if (j == m_nOverviewCount)
3886 : {
3887 0 : CPLError(CE_Failure, CPLE_AppDefined,
3888 : "Could not find dataset corresponding to ov factor %d",
3889 0 : panOverviewList[i]);
3890 0 : eErr = CE_Failure;
3891 : }
3892 : }
3893 36 : if (eErr == CE_None)
3894 : {
3895 36 : CPLAssert(iCurOverview == nOverviews);
3896 : }
3897 : }
3898 :
3899 13 : if (eErr == CE_None)
3900 13 : eErr = GDALRegenerateOverviewsMultiBand(
3901 13 : nBands, papoBands, nOverviews, papapoOverviewBands, pszResampling,
3902 : pfnProgress, pProgressData, papszOptions);
3903 :
3904 49 : for (int iBand = 0; iBand < nBands; iBand++)
3905 : {
3906 36 : CPLFree(papapoOverviewBands[iBand]);
3907 : }
3908 13 : CPLFree(papapoOverviewBands);
3909 :
3910 13 : return eErr;
3911 : }
3912 :
3913 : /************************************************************************/
3914 : /* GetFileList() */
3915 : /************************************************************************/
3916 :
3917 36 : char **GDALGeoPackageDataset::GetFileList()
3918 : {
3919 36 : TryLoadXML();
3920 36 : return GDALPamDataset::GetFileList();
3921 : }
3922 :
3923 : /************************************************************************/
3924 : /* GetMetadataDomainList() */
3925 : /************************************************************************/
3926 :
3927 32 : char **GDALGeoPackageDataset::GetMetadataDomainList()
3928 : {
3929 32 : GetMetadata();
3930 32 : if (!m_osRasterTable.empty())
3931 5 : GetMetadata("GEOPACKAGE");
3932 32 : return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
3933 32 : TRUE, "SUBDATASETS", nullptr);
3934 : }
3935 :
3936 : /************************************************************************/
3937 : /* CheckMetadataDomain() */
3938 : /************************************************************************/
3939 :
3940 4603 : const char *GDALGeoPackageDataset::CheckMetadataDomain(const char *pszDomain)
3941 : {
3942 4768 : if (pszDomain != nullptr && EQUAL(pszDomain, "GEOPACKAGE") &&
3943 165 : m_osRasterTable.empty())
3944 : {
3945 4 : CPLError(
3946 : CE_Warning, CPLE_IllegalArg,
3947 : "Using GEOPACKAGE for a non-raster geopackage is not supported. "
3948 : "Using default domain instead");
3949 4 : return nullptr;
3950 : }
3951 4599 : return pszDomain;
3952 : }
3953 :
3954 : /************************************************************************/
3955 : /* HasMetadataTables() */
3956 : /************************************************************************/
3957 :
3958 4544 : bool GDALGeoPackageDataset::HasMetadataTables() const
3959 : {
3960 4544 : if (m_nHasMetadataTables < 0)
3961 : {
3962 : const int nCount =
3963 1746 : SQLGetInteger(hDB,
3964 : "SELECT COUNT(*) FROM sqlite_master WHERE name IN "
3965 : "('gpkg_metadata', 'gpkg_metadata_reference') "
3966 : "AND type IN ('table', 'view')",
3967 : nullptr);
3968 1746 : m_nHasMetadataTables = nCount == 2;
3969 : }
3970 4544 : return CPL_TO_BOOL(m_nHasMetadataTables);
3971 : }
3972 :
3973 : /************************************************************************/
3974 : /* HasDataColumnsTable() */
3975 : /************************************************************************/
3976 :
3977 896 : bool GDALGeoPackageDataset::HasDataColumnsTable() const
3978 : {
3979 1792 : const int nCount = SQLGetInteger(
3980 896 : hDB,
3981 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_data_columns'"
3982 : "AND type IN ('table', 'view')",
3983 : nullptr);
3984 896 : return nCount == 1;
3985 : }
3986 :
3987 : /************************************************************************/
3988 : /* HasDataColumnConstraintsTable() */
3989 : /************************************************************************/
3990 :
3991 119 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTable() const
3992 : {
3993 119 : const int nCount = SQLGetInteger(hDB,
3994 : "SELECT 1 FROM sqlite_master WHERE name = "
3995 : "'gpkg_data_column_constraints'"
3996 : "AND type IN ('table', 'view')",
3997 : nullptr);
3998 119 : return nCount == 1;
3999 : }
4000 :
4001 : /************************************************************************/
4002 : /* HasDataColumnConstraintsTableGPKG_1_0() */
4003 : /************************************************************************/
4004 :
4005 73 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTableGPKG_1_0() const
4006 : {
4007 73 : if (m_nApplicationId != GP10_APPLICATION_ID)
4008 71 : return false;
4009 : // In GPKG 1.0, the columns were named minIsInclusive, maxIsInclusive
4010 : // They were changed in 1.1 to min_is_inclusive, max_is_inclusive
4011 2 : bool bRet = false;
4012 2 : sqlite3_stmt *hSQLStmt = nullptr;
4013 2 : int rc = sqlite3_prepare_v2(hDB,
4014 : "SELECT minIsInclusive, maxIsInclusive FROM "
4015 : "gpkg_data_column_constraints",
4016 : -1, &hSQLStmt, nullptr);
4017 2 : if (rc == SQLITE_OK)
4018 : {
4019 2 : bRet = true;
4020 2 : sqlite3_finalize(hSQLStmt);
4021 : }
4022 2 : return bRet;
4023 : }
4024 :
4025 : /************************************************************************/
4026 : /* CreateColumnsTableAndColumnConstraintsTablesIfNecessary() */
4027 : /************************************************************************/
4028 :
4029 49 : bool GDALGeoPackageDataset::
4030 : CreateColumnsTableAndColumnConstraintsTablesIfNecessary()
4031 : {
4032 49 : if (!HasDataColumnsTable())
4033 : {
4034 : // Geopackage < 1.3 had
4035 : // CONSTRAINT fk_gdc_tn FOREIGN KEY (table_name) REFERENCES
4036 : // gpkg_contents(table_name) instead of the unique constraint.
4037 10 : if (OGRERR_NONE !=
4038 10 : SQLCommand(
4039 : GetDB(),
4040 : "CREATE TABLE gpkg_data_columns ("
4041 : "table_name TEXT NOT NULL,"
4042 : "column_name TEXT NOT NULL,"
4043 : "name TEXT,"
4044 : "title TEXT,"
4045 : "description TEXT,"
4046 : "mime_type TEXT,"
4047 : "constraint_name TEXT,"
4048 : "CONSTRAINT pk_gdc PRIMARY KEY (table_name, column_name),"
4049 : "CONSTRAINT gdc_tn UNIQUE (table_name, name));"))
4050 : {
4051 0 : return false;
4052 : }
4053 : }
4054 49 : if (!HasDataColumnConstraintsTable())
4055 : {
4056 22 : const char *min_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
4057 11 : ? "min_is_inclusive"
4058 : : "minIsInclusive";
4059 22 : const char *max_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
4060 11 : ? "max_is_inclusive"
4061 : : "maxIsInclusive";
4062 :
4063 : const std::string osSQL(
4064 : CPLSPrintf("CREATE TABLE gpkg_data_column_constraints ("
4065 : "constraint_name TEXT NOT NULL,"
4066 : "constraint_type TEXT NOT NULL,"
4067 : "value TEXT,"
4068 : "min NUMERIC,"
4069 : "%s BOOLEAN,"
4070 : "max NUMERIC,"
4071 : "%s BOOLEAN,"
4072 : "description TEXT,"
4073 : "CONSTRAINT gdcc_ntv UNIQUE (constraint_name, "
4074 : "constraint_type, value));",
4075 11 : min_is_inclusive, max_is_inclusive));
4076 11 : if (OGRERR_NONE != SQLCommand(GetDB(), osSQL.c_str()))
4077 : {
4078 0 : return false;
4079 : }
4080 : }
4081 49 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
4082 : {
4083 0 : return false;
4084 : }
4085 49 : if (SQLGetInteger(GetDB(),
4086 : "SELECT 1 FROM gpkg_extensions WHERE "
4087 : "table_name = 'gpkg_data_columns'",
4088 49 : nullptr) != 1)
4089 : {
4090 11 : if (OGRERR_NONE !=
4091 11 : SQLCommand(
4092 : GetDB(),
4093 : "INSERT INTO gpkg_extensions "
4094 : "(table_name,column_name,extension_name,definition,scope) "
4095 : "VALUES ('gpkg_data_columns', NULL, 'gpkg_schema', "
4096 : "'http://www.geopackage.org/spec121/#extension_schema', "
4097 : "'read-write')"))
4098 : {
4099 0 : return false;
4100 : }
4101 : }
4102 49 : if (SQLGetInteger(GetDB(),
4103 : "SELECT 1 FROM gpkg_extensions WHERE "
4104 : "table_name = 'gpkg_data_column_constraints'",
4105 49 : nullptr) != 1)
4106 : {
4107 11 : if (OGRERR_NONE !=
4108 11 : SQLCommand(
4109 : GetDB(),
4110 : "INSERT INTO gpkg_extensions "
4111 : "(table_name,column_name,extension_name,definition,scope) "
4112 : "VALUES ('gpkg_data_column_constraints', NULL, 'gpkg_schema', "
4113 : "'http://www.geopackage.org/spec121/#extension_schema', "
4114 : "'read-write')"))
4115 : {
4116 0 : return false;
4117 : }
4118 : }
4119 :
4120 49 : return true;
4121 : }
4122 :
4123 : /************************************************************************/
4124 : /* HasGpkgextRelationsTable() */
4125 : /************************************************************************/
4126 :
4127 997 : bool GDALGeoPackageDataset::HasGpkgextRelationsTable() const
4128 : {
4129 1994 : const int nCount = SQLGetInteger(
4130 997 : hDB,
4131 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkgext_relations'"
4132 : "AND type IN ('table', 'view')",
4133 : nullptr);
4134 997 : return nCount == 1;
4135 : }
4136 :
4137 : /************************************************************************/
4138 : /* CreateRelationsTableIfNecessary() */
4139 : /************************************************************************/
4140 :
4141 7 : bool GDALGeoPackageDataset::CreateRelationsTableIfNecessary()
4142 : {
4143 7 : if (HasGpkgextRelationsTable())
4144 : {
4145 5 : return true;
4146 : }
4147 :
4148 2 : if (OGRERR_NONE !=
4149 2 : SQLCommand(GetDB(), "CREATE TABLE gpkgext_relations ("
4150 : "id INTEGER PRIMARY KEY AUTOINCREMENT,"
4151 : "base_table_name TEXT NOT NULL,"
4152 : "base_primary_column TEXT NOT NULL DEFAULT 'id',"
4153 : "related_table_name TEXT NOT NULL,"
4154 : "related_primary_column TEXT NOT NULL DEFAULT 'id',"
4155 : "relation_name TEXT NOT NULL,"
4156 : "mapping_table_name TEXT NOT NULL UNIQUE);"))
4157 : {
4158 0 : return false;
4159 : }
4160 :
4161 2 : return true;
4162 : }
4163 :
4164 : /************************************************************************/
4165 : /* HasQGISLayerStyles() */
4166 : /************************************************************************/
4167 :
4168 11 : bool GDALGeoPackageDataset::HasQGISLayerStyles() const
4169 : {
4170 : // QGIS layer_styles extension:
4171 : // https://github.com/pka/qgpkg/blob/master/qgis_geopackage_extension.md
4172 11 : bool bRet = false;
4173 : const int nCount =
4174 11 : SQLGetInteger(hDB,
4175 : "SELECT 1 FROM sqlite_master WHERE name = 'layer_styles'"
4176 : "AND type = 'table'",
4177 : nullptr);
4178 11 : if (nCount == 1)
4179 : {
4180 1 : sqlite3_stmt *hSQLStmt = nullptr;
4181 2 : int rc = sqlite3_prepare_v2(
4182 1 : hDB, "SELECT f_table_name, f_geometry_column FROM layer_styles", -1,
4183 : &hSQLStmt, nullptr);
4184 1 : if (rc == SQLITE_OK)
4185 : {
4186 1 : bRet = true;
4187 1 : sqlite3_finalize(hSQLStmt);
4188 : }
4189 : }
4190 11 : return bRet;
4191 : }
4192 :
4193 : /************************************************************************/
4194 : /* GetMetadata() */
4195 : /************************************************************************/
4196 :
4197 3090 : char **GDALGeoPackageDataset::GetMetadata(const char *pszDomain)
4198 :
4199 : {
4200 3090 : pszDomain = CheckMetadataDomain(pszDomain);
4201 3090 : if (pszDomain != nullptr && EQUAL(pszDomain, "SUBDATASETS"))
4202 50 : return m_aosSubDatasets.List();
4203 :
4204 3040 : if (m_bHasReadMetadataFromStorage)
4205 1342 : return GDALPamDataset::GetMetadata(pszDomain);
4206 :
4207 1698 : m_bHasReadMetadataFromStorage = true;
4208 :
4209 1698 : TryLoadXML();
4210 :
4211 1698 : if (!HasMetadataTables())
4212 1244 : return GDALPamDataset::GetMetadata(pszDomain);
4213 :
4214 454 : char *pszSQL = nullptr;
4215 454 : if (!m_osRasterTable.empty())
4216 : {
4217 160 : pszSQL = sqlite3_mprintf(
4218 : "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
4219 : "mdr.reference_scope FROM gpkg_metadata md "
4220 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4221 : "WHERE "
4222 : "(mdr.reference_scope = 'geopackage' OR "
4223 : "(mdr.reference_scope = 'table' AND lower(mdr.table_name) = "
4224 : "lower('%q'))) ORDER BY md.id "
4225 : "LIMIT 1000", // to avoid denial of service
4226 : m_osRasterTable.c_str());
4227 : }
4228 : else
4229 : {
4230 294 : pszSQL = sqlite3_mprintf(
4231 : "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
4232 : "mdr.reference_scope FROM gpkg_metadata md "
4233 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4234 : "WHERE "
4235 : "mdr.reference_scope = 'geopackage' ORDER BY md.id "
4236 : "LIMIT 1000" // to avoid denial of service
4237 : );
4238 : }
4239 :
4240 908 : auto oResult = SQLQuery(hDB, pszSQL);
4241 454 : sqlite3_free(pszSQL);
4242 454 : if (!oResult)
4243 : {
4244 0 : return GDALPamDataset::GetMetadata(pszDomain);
4245 : }
4246 :
4247 454 : char **papszMetadata = CSLDuplicate(GDALPamDataset::GetMetadata());
4248 :
4249 : /* GDAL metadata */
4250 635 : for (int i = 0; i < oResult->RowCount(); i++)
4251 : {
4252 181 : const char *pszMetadata = oResult->GetValue(0, i);
4253 181 : const char *pszMDStandardURI = oResult->GetValue(1, i);
4254 181 : const char *pszMimeType = oResult->GetValue(2, i);
4255 181 : const char *pszReferenceScope = oResult->GetValue(3, i);
4256 181 : if (pszMetadata && pszMDStandardURI && pszMimeType &&
4257 181 : pszReferenceScope && EQUAL(pszMDStandardURI, "http://gdal.org") &&
4258 165 : EQUAL(pszMimeType, "text/xml"))
4259 : {
4260 165 : CPLXMLNode *psXMLNode = CPLParseXMLString(pszMetadata);
4261 165 : if (psXMLNode)
4262 : {
4263 330 : GDALMultiDomainMetadata oLocalMDMD;
4264 165 : oLocalMDMD.XMLInit(psXMLNode, FALSE);
4265 315 : if (!m_osRasterTable.empty() &&
4266 150 : EQUAL(pszReferenceScope, "geopackage"))
4267 : {
4268 6 : oMDMD.SetMetadata(oLocalMDMD.GetMetadata(), "GEOPACKAGE");
4269 : }
4270 : else
4271 : {
4272 : papszMetadata =
4273 159 : CSLMerge(papszMetadata, oLocalMDMD.GetMetadata());
4274 159 : CSLConstList papszDomainList = oLocalMDMD.GetDomainList();
4275 159 : CSLConstList papszIter = papszDomainList;
4276 417 : while (papszIter && *papszIter)
4277 : {
4278 258 : if (EQUAL(*papszIter, "IMAGE_STRUCTURE"))
4279 : {
4280 : CSLConstList papszMD =
4281 117 : oLocalMDMD.GetMetadata(*papszIter);
4282 : const char *pszBAND_COUNT =
4283 117 : CSLFetchNameValue(papszMD, "BAND_COUNT");
4284 117 : if (pszBAND_COUNT)
4285 115 : m_nBandCountFromMetadata = atoi(pszBAND_COUNT);
4286 :
4287 : const char *pszCOLOR_TABLE =
4288 117 : CSLFetchNameValue(papszMD, "COLOR_TABLE");
4289 117 : if (pszCOLOR_TABLE)
4290 : {
4291 : const CPLStringList aosTokens(
4292 : CSLTokenizeString2(pszCOLOR_TABLE, "{,",
4293 26 : 0));
4294 13 : if ((aosTokens.size() % 4) == 0)
4295 : {
4296 13 : const int nColors = aosTokens.size() / 4;
4297 : m_poCTFromMetadata =
4298 13 : std::make_unique<GDALColorTable>();
4299 3341 : for (int iColor = 0; iColor < nColors;
4300 : ++iColor)
4301 : {
4302 : GDALColorEntry sEntry;
4303 3328 : sEntry.c1 = static_cast<short>(
4304 3328 : atoi(aosTokens[4 * iColor + 0]));
4305 3328 : sEntry.c2 = static_cast<short>(
4306 3328 : atoi(aosTokens[4 * iColor + 1]));
4307 3328 : sEntry.c3 = static_cast<short>(
4308 3328 : atoi(aosTokens[4 * iColor + 2]));
4309 3328 : sEntry.c4 = static_cast<short>(
4310 3328 : atoi(aosTokens[4 * iColor + 3]));
4311 3328 : m_poCTFromMetadata->SetColorEntry(
4312 : iColor, &sEntry);
4313 : }
4314 : }
4315 : }
4316 :
4317 : const char *pszTILE_FORMAT =
4318 117 : CSLFetchNameValue(papszMD, "TILE_FORMAT");
4319 117 : if (pszTILE_FORMAT)
4320 : {
4321 8 : m_osTFFromMetadata = pszTILE_FORMAT;
4322 8 : oMDMD.SetMetadataItem("TILE_FORMAT",
4323 : pszTILE_FORMAT,
4324 : "IMAGE_STRUCTURE");
4325 : }
4326 :
4327 : const char *pszNodataValue =
4328 117 : CSLFetchNameValue(papszMD, "NODATA_VALUE");
4329 117 : if (pszNodataValue)
4330 : {
4331 2 : m_osNodataValueFromMetadata = pszNodataValue;
4332 : }
4333 : }
4334 :
4335 141 : else if (!EQUAL(*papszIter, "") &&
4336 12 : !STARTS_WITH(*papszIter, "BAND_"))
4337 : {
4338 12 : oMDMD.SetMetadata(
4339 6 : oLocalMDMD.GetMetadata(*papszIter), *papszIter);
4340 : }
4341 258 : papszIter++;
4342 : }
4343 : }
4344 165 : CPLDestroyXMLNode(psXMLNode);
4345 : }
4346 : }
4347 : }
4348 :
4349 454 : GDALPamDataset::SetMetadata(papszMetadata);
4350 454 : CSLDestroy(papszMetadata);
4351 454 : papszMetadata = nullptr;
4352 :
4353 : /* Add non-GDAL metadata now */
4354 454 : int nNonGDALMDILocal = 1;
4355 454 : int nNonGDALMDIGeopackage = 1;
4356 635 : for (int i = 0; i < oResult->RowCount(); i++)
4357 : {
4358 181 : const char *pszMetadata = oResult->GetValue(0, i);
4359 181 : const char *pszMDStandardURI = oResult->GetValue(1, i);
4360 181 : const char *pszMimeType = oResult->GetValue(2, i);
4361 181 : const char *pszReferenceScope = oResult->GetValue(3, i);
4362 181 : if (pszMetadata == nullptr || pszMDStandardURI == nullptr ||
4363 181 : pszMimeType == nullptr || pszReferenceScope == nullptr)
4364 : {
4365 : // should not happen as there are NOT NULL constraints
4366 : // But a database could lack such NOT NULL constraints or have
4367 : // large values that would cause a memory allocation failure.
4368 0 : continue;
4369 : }
4370 181 : int bIsGPKGScope = EQUAL(pszReferenceScope, "geopackage");
4371 181 : if (EQUAL(pszMDStandardURI, "http://gdal.org") &&
4372 165 : EQUAL(pszMimeType, "text/xml"))
4373 165 : continue;
4374 :
4375 16 : if (!m_osRasterTable.empty() && bIsGPKGScope)
4376 : {
4377 8 : oMDMD.SetMetadataItem(
4378 : CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDIGeopackage),
4379 : pszMetadata, "GEOPACKAGE");
4380 8 : nNonGDALMDIGeopackage++;
4381 : }
4382 : /*else if( strcmp( pszMDStandardURI, "http://www.isotc211.org/2005/gmd"
4383 : ) == 0 && strcmp( pszMimeType, "text/xml" ) == 0 )
4384 : {
4385 : char* apszMD[2];
4386 : apszMD[0] = (char*)pszMetadata;
4387 : apszMD[1] = NULL;
4388 : oMDMD.SetMetadata(apszMD, "xml:MD_Metadata");
4389 : }*/
4390 : else
4391 : {
4392 8 : oMDMD.SetMetadataItem(
4393 : CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDILocal),
4394 : pszMetadata);
4395 8 : nNonGDALMDILocal++;
4396 : }
4397 : }
4398 :
4399 454 : return GDALPamDataset::GetMetadata(pszDomain);
4400 : }
4401 :
4402 : /************************************************************************/
4403 : /* WriteMetadata() */
4404 : /************************************************************************/
4405 :
4406 622 : void GDALGeoPackageDataset::WriteMetadata(
4407 : CPLXMLNode *psXMLNode, /* will be destroyed by the method */
4408 : const char *pszTableName)
4409 : {
4410 622 : const bool bIsEmpty = (psXMLNode == nullptr);
4411 622 : if (!HasMetadataTables())
4412 : {
4413 459 : if (bIsEmpty || !CreateMetadataTables())
4414 : {
4415 211 : CPLDestroyXMLNode(psXMLNode);
4416 211 : return;
4417 : }
4418 : }
4419 :
4420 411 : char *pszXML = nullptr;
4421 411 : if (!bIsEmpty)
4422 : {
4423 : CPLXMLNode *psMasterXMLNode =
4424 281 : CPLCreateXMLNode(nullptr, CXT_Element, "GDALMultiDomainMetadata");
4425 281 : psMasterXMLNode->psChild = psXMLNode;
4426 281 : pszXML = CPLSerializeXMLTree(psMasterXMLNode);
4427 281 : CPLDestroyXMLNode(psMasterXMLNode);
4428 : }
4429 : // cppcheck-suppress uselessAssignmentPtrArg
4430 411 : psXMLNode = nullptr;
4431 :
4432 411 : char *pszSQL = nullptr;
4433 411 : if (pszTableName && pszTableName[0] != '\0')
4434 : {
4435 289 : pszSQL = sqlite3_mprintf(
4436 : "SELECT md.id FROM gpkg_metadata md "
4437 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4438 : "WHERE md.md_scope = 'dataset' AND "
4439 : "md.md_standard_uri='http://gdal.org' "
4440 : "AND md.mime_type='text/xml' AND mdr.reference_scope = 'table' AND "
4441 : "lower(mdr.table_name) = lower('%q')",
4442 : pszTableName);
4443 : }
4444 : else
4445 : {
4446 122 : pszSQL = sqlite3_mprintf(
4447 : "SELECT md.id FROM gpkg_metadata md "
4448 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4449 : "WHERE md.md_scope = 'dataset' AND "
4450 : "md.md_standard_uri='http://gdal.org' "
4451 : "AND md.mime_type='text/xml' AND mdr.reference_scope = "
4452 : "'geopackage'");
4453 : }
4454 : OGRErr err;
4455 411 : int mdId = SQLGetInteger(hDB, pszSQL, &err);
4456 411 : if (err != OGRERR_NONE)
4457 386 : mdId = -1;
4458 411 : sqlite3_free(pszSQL);
4459 :
4460 411 : if (bIsEmpty)
4461 : {
4462 130 : if (mdId >= 0)
4463 : {
4464 6 : SQLCommand(
4465 : hDB,
4466 : CPLSPrintf(
4467 : "DELETE FROM gpkg_metadata_reference WHERE md_file_id = %d",
4468 : mdId));
4469 6 : SQLCommand(
4470 : hDB,
4471 : CPLSPrintf("DELETE FROM gpkg_metadata WHERE id = %d", mdId));
4472 : }
4473 : }
4474 : else
4475 : {
4476 281 : if (mdId >= 0)
4477 : {
4478 19 : pszSQL = sqlite3_mprintf(
4479 : "UPDATE gpkg_metadata SET metadata = '%q' WHERE id = %d",
4480 : pszXML, mdId);
4481 : }
4482 : else
4483 : {
4484 : pszSQL =
4485 262 : sqlite3_mprintf("INSERT INTO gpkg_metadata (md_scope, "
4486 : "md_standard_uri, mime_type, metadata) VALUES "
4487 : "('dataset','http://gdal.org','text/xml','%q')",
4488 : pszXML);
4489 : }
4490 281 : SQLCommand(hDB, pszSQL);
4491 281 : sqlite3_free(pszSQL);
4492 :
4493 281 : CPLFree(pszXML);
4494 :
4495 281 : if (mdId < 0)
4496 : {
4497 262 : const sqlite_int64 nFID = sqlite3_last_insert_rowid(hDB);
4498 262 : if (pszTableName != nullptr && pszTableName[0] != '\0')
4499 : {
4500 250 : pszSQL = sqlite3_mprintf(
4501 : "INSERT INTO gpkg_metadata_reference (reference_scope, "
4502 : "table_name, timestamp, md_file_id) VALUES "
4503 : "('table', '%q', %s, %d)",
4504 500 : pszTableName, GetCurrentDateEscapedSQL().c_str(),
4505 : static_cast<int>(nFID));
4506 : }
4507 : else
4508 : {
4509 12 : pszSQL = sqlite3_mprintf(
4510 : "INSERT INTO gpkg_metadata_reference (reference_scope, "
4511 : "timestamp, md_file_id) VALUES "
4512 : "('geopackage', %s, %d)",
4513 24 : GetCurrentDateEscapedSQL().c_str(), static_cast<int>(nFID));
4514 : }
4515 : }
4516 : else
4517 : {
4518 19 : pszSQL = sqlite3_mprintf("UPDATE gpkg_metadata_reference SET "
4519 : "timestamp = %s WHERE md_file_id = %d",
4520 38 : GetCurrentDateEscapedSQL().c_str(), mdId);
4521 : }
4522 281 : SQLCommand(hDB, pszSQL);
4523 281 : sqlite3_free(pszSQL);
4524 : }
4525 : }
4526 :
4527 : /************************************************************************/
4528 : /* CreateMetadataTables() */
4529 : /************************************************************************/
4530 :
4531 260 : bool GDALGeoPackageDataset::CreateMetadataTables()
4532 : {
4533 : const bool bCreateTriggers =
4534 260 : CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "NO"));
4535 :
4536 : /* From C.10. gpkg_metadata Table 35. gpkg_metadata Table Definition SQL */
4537 : CPLString osSQL = "CREATE TABLE gpkg_metadata ("
4538 : "id INTEGER CONSTRAINT m_pk PRIMARY KEY ASC NOT NULL,"
4539 : "md_scope TEXT NOT NULL DEFAULT 'dataset',"
4540 : "md_standard_uri TEXT NOT NULL,"
4541 : "mime_type TEXT NOT NULL DEFAULT 'text/xml',"
4542 : "metadata TEXT NOT NULL DEFAULT ''"
4543 520 : ")";
4544 :
4545 : /* From D.2. metadata Table 40. metadata Trigger Definition SQL */
4546 260 : const char *pszMetadataTriggers =
4547 : "CREATE TRIGGER 'gpkg_metadata_md_scope_insert' "
4548 : "BEFORE INSERT ON 'gpkg_metadata' "
4549 : "FOR EACH ROW BEGIN "
4550 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata violates "
4551 : "constraint: md_scope must be one of undefined | fieldSession | "
4552 : "collectionSession | series | dataset | featureType | feature | "
4553 : "attributeType | attribute | tile | model | catalogue | schema | "
4554 : "taxonomy software | service | collectionHardware | "
4555 : "nonGeographicDataset | dimensionGroup') "
4556 : "WHERE NOT(NEW.md_scope IN "
4557 : "('undefined','fieldSession','collectionSession','series','dataset', "
4558 : "'featureType','feature','attributeType','attribute','tile','model', "
4559 : "'catalogue','schema','taxonomy','software','service', "
4560 : "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
4561 : "END; "
4562 : "CREATE TRIGGER 'gpkg_metadata_md_scope_update' "
4563 : "BEFORE UPDATE OF 'md_scope' ON 'gpkg_metadata' "
4564 : "FOR EACH ROW BEGIN "
4565 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata violates "
4566 : "constraint: md_scope must be one of undefined | fieldSession | "
4567 : "collectionSession | series | dataset | featureType | feature | "
4568 : "attributeType | attribute | tile | model | catalogue | schema | "
4569 : "taxonomy software | service | collectionHardware | "
4570 : "nonGeographicDataset | dimensionGroup') "
4571 : "WHERE NOT(NEW.md_scope IN "
4572 : "('undefined','fieldSession','collectionSession','series','dataset', "
4573 : "'featureType','feature','attributeType','attribute','tile','model', "
4574 : "'catalogue','schema','taxonomy','software','service', "
4575 : "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
4576 : "END";
4577 260 : if (bCreateTriggers)
4578 : {
4579 0 : osSQL += ";";
4580 0 : osSQL += pszMetadataTriggers;
4581 : }
4582 :
4583 : /* From C.11. gpkg_metadata_reference Table 36. gpkg_metadata_reference
4584 : * Table Definition SQL */
4585 : osSQL += ";"
4586 : "CREATE TABLE gpkg_metadata_reference ("
4587 : "reference_scope TEXT NOT NULL,"
4588 : "table_name TEXT,"
4589 : "column_name TEXT,"
4590 : "row_id_value INTEGER,"
4591 : "timestamp DATETIME NOT NULL DEFAULT "
4592 : "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
4593 : "md_file_id INTEGER NOT NULL,"
4594 : "md_parent_id INTEGER,"
4595 : "CONSTRAINT crmr_mfi_fk FOREIGN KEY (md_file_id) REFERENCES "
4596 : "gpkg_metadata(id),"
4597 : "CONSTRAINT crmr_mpi_fk FOREIGN KEY (md_parent_id) REFERENCES "
4598 : "gpkg_metadata(id)"
4599 260 : ")";
4600 :
4601 : /* From D.3. metadata_reference Table 41. gpkg_metadata_reference Trigger
4602 : * Definition SQL */
4603 260 : const char *pszMetadataReferenceTriggers =
4604 : "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_insert' "
4605 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4606 : "FOR EACH ROW BEGIN "
4607 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4608 : "violates constraint: reference_scope must be one of \"geopackage\", "
4609 : "table\", \"column\", \"row\", \"row/col\"') "
4610 : "WHERE NOT NEW.reference_scope IN "
4611 : "('geopackage','table','column','row','row/col'); "
4612 : "END; "
4613 : "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_update' "
4614 : "BEFORE UPDATE OF 'reference_scope' ON 'gpkg_metadata_reference' "
4615 : "FOR EACH ROW BEGIN "
4616 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4617 : "violates constraint: reference_scope must be one of \"geopackage\", "
4618 : "\"table\", \"column\", \"row\", \"row/col\"') "
4619 : "WHERE NOT NEW.reference_scope IN "
4620 : "('geopackage','table','column','row','row/col'); "
4621 : "END; "
4622 : "CREATE TRIGGER 'gpkg_metadata_reference_column_name_insert' "
4623 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4624 : "FOR EACH ROW BEGIN "
4625 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4626 : "violates constraint: column name must be NULL when reference_scope "
4627 : "is \"geopackage\", \"table\" or \"row\"') "
4628 : "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
4629 : "AND NEW.column_name IS NOT NULL); "
4630 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4631 : "violates constraint: column name must be defined for the specified "
4632 : "table when reference_scope is \"column\" or \"row/col\"') "
4633 : "WHERE (NEW.reference_scope IN ('column','row/col') "
4634 : "AND NOT NEW.table_name IN ( "
4635 : "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
4636 : "AND name = NEW.table_name "
4637 : "AND sql LIKE ('%' || NEW.column_name || '%'))); "
4638 : "END; "
4639 : "CREATE TRIGGER 'gpkg_metadata_reference_column_name_update' "
4640 : "BEFORE UPDATE OF column_name ON 'gpkg_metadata_reference' "
4641 : "FOR EACH ROW BEGIN "
4642 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4643 : "violates constraint: column name must be NULL when reference_scope "
4644 : "is \"geopackage\", \"table\" or \"row\"') "
4645 : "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
4646 : "AND NEW.column_name IS NOT NULL); "
4647 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4648 : "violates constraint: column name must be defined for the specified "
4649 : "table when reference_scope is \"column\" or \"row/col\"') "
4650 : "WHERE (NEW.reference_scope IN ('column','row/col') "
4651 : "AND NOT NEW.table_name IN ( "
4652 : "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
4653 : "AND name = NEW.table_name "
4654 : "AND sql LIKE ('%' || NEW.column_name || '%'))); "
4655 : "END; "
4656 : "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_insert' "
4657 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4658 : "FOR EACH ROW BEGIN "
4659 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4660 : "violates constraint: row_id_value must be NULL when reference_scope "
4661 : "is \"geopackage\", \"table\" or \"column\"') "
4662 : "WHERE NEW.reference_scope IN ('geopackage','table','column') "
4663 : "AND NEW.row_id_value IS NOT NULL; "
4664 : "END; "
4665 : "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_update' "
4666 : "BEFORE UPDATE OF 'row_id_value' ON 'gpkg_metadata_reference' "
4667 : "FOR EACH ROW BEGIN "
4668 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4669 : "violates constraint: row_id_value must be NULL when reference_scope "
4670 : "is \"geopackage\", \"table\" or \"column\"') "
4671 : "WHERE NEW.reference_scope IN ('geopackage','table','column') "
4672 : "AND NEW.row_id_value IS NOT NULL; "
4673 : "END; "
4674 : "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_insert' "
4675 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4676 : "FOR EACH ROW BEGIN "
4677 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4678 : "violates constraint: timestamp must be a valid time in ISO 8601 "
4679 : "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
4680 : "WHERE NOT (NEW.timestamp GLOB "
4681 : "'[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-"
4682 : "5][0-9].[0-9][0-9][0-9]Z' "
4683 : "AND strftime('%s',NEW.timestamp) NOT NULL); "
4684 : "END; "
4685 : "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_update' "
4686 : "BEFORE UPDATE OF 'timestamp' ON 'gpkg_metadata_reference' "
4687 : "FOR EACH ROW BEGIN "
4688 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4689 : "violates constraint: timestamp must be a valid time in ISO 8601 "
4690 : "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
4691 : "WHERE NOT (NEW.timestamp GLOB "
4692 : "'[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-"
4693 : "5][0-9].[0-9][0-9][0-9]Z' "
4694 : "AND strftime('%s',NEW.timestamp) NOT NULL); "
4695 : "END";
4696 260 : if (bCreateTriggers)
4697 : {
4698 0 : osSQL += ";";
4699 0 : osSQL += pszMetadataReferenceTriggers;
4700 : }
4701 :
4702 260 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
4703 0 : return false;
4704 :
4705 260 : osSQL += ";";
4706 : osSQL += "INSERT INTO gpkg_extensions "
4707 : "(table_name, column_name, extension_name, definition, scope) "
4708 : "VALUES "
4709 : "('gpkg_metadata', NULL, 'gpkg_metadata', "
4710 : "'http://www.geopackage.org/spec120/#extension_metadata', "
4711 260 : "'read-write')";
4712 :
4713 260 : osSQL += ";";
4714 : osSQL += "INSERT INTO gpkg_extensions "
4715 : "(table_name, column_name, extension_name, definition, scope) "
4716 : "VALUES "
4717 : "('gpkg_metadata_reference', NULL, 'gpkg_metadata', "
4718 : "'http://www.geopackage.org/spec120/#extension_metadata', "
4719 260 : "'read-write')";
4720 :
4721 260 : const bool bOK = SQLCommand(hDB, osSQL) == OGRERR_NONE;
4722 260 : m_nHasMetadataTables = bOK;
4723 260 : return bOK;
4724 : }
4725 :
4726 : /************************************************************************/
4727 : /* FlushMetadata() */
4728 : /************************************************************************/
4729 :
4730 7845 : void GDALGeoPackageDataset::FlushMetadata()
4731 : {
4732 7845 : if (!m_bMetadataDirty || m_poParentDS != nullptr ||
4733 315 : m_nCreateMetadataTables == FALSE)
4734 7536 : return;
4735 309 : m_bMetadataDirty = false;
4736 :
4737 309 : if (eAccess == GA_ReadOnly)
4738 : {
4739 3 : return;
4740 : }
4741 :
4742 306 : bool bCanWriteAreaOrPoint =
4743 610 : !m_bGridCellEncodingAsCO &&
4744 304 : (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT);
4745 306 : if (!m_osRasterTable.empty())
4746 : {
4747 : const char *pszIdentifier =
4748 124 : GDALGeoPackageDataset::GetMetadataItem("IDENTIFIER");
4749 : const char *pszDescription =
4750 124 : GDALGeoPackageDataset::GetMetadataItem("DESCRIPTION");
4751 152 : if (!m_bIdentifierAsCO && pszIdentifier != nullptr &&
4752 28 : pszIdentifier != m_osIdentifier)
4753 : {
4754 14 : m_osIdentifier = pszIdentifier;
4755 : char *pszSQL =
4756 14 : sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
4757 : "WHERE lower(table_name) = lower('%q')",
4758 : pszIdentifier, m_osRasterTable.c_str());
4759 14 : SQLCommand(hDB, pszSQL);
4760 14 : sqlite3_free(pszSQL);
4761 : }
4762 131 : if (!m_bDescriptionAsCO && pszDescription != nullptr &&
4763 7 : pszDescription != m_osDescription)
4764 : {
4765 7 : m_osDescription = pszDescription;
4766 : char *pszSQL =
4767 7 : sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
4768 : "WHERE lower(table_name) = lower('%q')",
4769 : pszDescription, m_osRasterTable.c_str());
4770 7 : SQLCommand(hDB, pszSQL);
4771 7 : sqlite3_free(pszSQL);
4772 : }
4773 124 : if (bCanWriteAreaOrPoint)
4774 : {
4775 : const char *pszAreaOrPoint =
4776 28 : GDALGeoPackageDataset::GetMetadataItem(GDALMD_AREA_OR_POINT);
4777 28 : if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_AREA))
4778 : {
4779 23 : bCanWriteAreaOrPoint = false;
4780 23 : char *pszSQL = sqlite3_mprintf(
4781 : "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
4782 : "grid_cell_encoding = 'grid-value-is-area' WHERE "
4783 : "lower(tile_matrix_set_name) = lower('%q')",
4784 : m_osRasterTable.c_str());
4785 23 : SQLCommand(hDB, pszSQL);
4786 23 : sqlite3_free(pszSQL);
4787 : }
4788 5 : else if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_POINT))
4789 : {
4790 1 : bCanWriteAreaOrPoint = false;
4791 1 : char *pszSQL = sqlite3_mprintf(
4792 : "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
4793 : "grid_cell_encoding = 'grid-value-is-center' WHERE "
4794 : "lower(tile_matrix_set_name) = lower('%q')",
4795 : m_osRasterTable.c_str());
4796 1 : SQLCommand(hDB, pszSQL);
4797 1 : sqlite3_free(pszSQL);
4798 : }
4799 : }
4800 : }
4801 :
4802 306 : char **papszMDDup = nullptr;
4803 491 : for (char **papszIter = GDALGeoPackageDataset::GetMetadata();
4804 491 : papszIter && *papszIter; ++papszIter)
4805 : {
4806 185 : if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
4807 28 : continue;
4808 157 : if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
4809 8 : continue;
4810 149 : if (STARTS_WITH_CI(*papszIter, "ZOOM_LEVEL="))
4811 14 : continue;
4812 135 : if (STARTS_WITH_CI(*papszIter, "GPKG_METADATA_ITEM_"))
4813 4 : continue;
4814 131 : if ((m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT) &&
4815 29 : !bCanWriteAreaOrPoint &&
4816 26 : STARTS_WITH_CI(*papszIter, GDALMD_AREA_OR_POINT))
4817 : {
4818 26 : continue;
4819 : }
4820 105 : papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
4821 : }
4822 :
4823 306 : CPLXMLNode *psXMLNode = nullptr;
4824 : {
4825 306 : GDALMultiDomainMetadata oLocalMDMD;
4826 306 : CSLConstList papszDomainList = oMDMD.GetDomainList();
4827 306 : CSLConstList papszIter = papszDomainList;
4828 306 : oLocalMDMD.SetMetadata(papszMDDup);
4829 589 : while (papszIter && *papszIter)
4830 : {
4831 283 : if (!EQUAL(*papszIter, "") &&
4832 139 : !EQUAL(*papszIter, "IMAGE_STRUCTURE") &&
4833 15 : !EQUAL(*papszIter, "GEOPACKAGE"))
4834 : {
4835 8 : oLocalMDMD.SetMetadata(oMDMD.GetMetadata(*papszIter),
4836 : *papszIter);
4837 : }
4838 283 : papszIter++;
4839 : }
4840 306 : if (m_nBandCountFromMetadata > 0)
4841 : {
4842 59 : oLocalMDMD.SetMetadataItem(
4843 : "BAND_COUNT", CPLSPrintf("%d", m_nBandCountFromMetadata),
4844 : "IMAGE_STRUCTURE");
4845 59 : if (nBands == 1)
4846 : {
4847 37 : const auto poCT = GetRasterBand(1)->GetColorTable();
4848 37 : if (poCT)
4849 : {
4850 16 : std::string osVal("{");
4851 8 : const int nColorCount = poCT->GetColorEntryCount();
4852 2056 : for (int i = 0; i < nColorCount; ++i)
4853 : {
4854 2048 : if (i > 0)
4855 2040 : osVal += ',';
4856 2048 : const GDALColorEntry *psEntry = poCT->GetColorEntry(i);
4857 : osVal +=
4858 2048 : CPLSPrintf("{%d,%d,%d,%d}", psEntry->c1,
4859 2048 : psEntry->c2, psEntry->c3, psEntry->c4);
4860 : }
4861 8 : osVal += '}';
4862 8 : oLocalMDMD.SetMetadataItem("COLOR_TABLE", osVal.c_str(),
4863 : "IMAGE_STRUCTURE");
4864 : }
4865 : }
4866 59 : if (nBands == 1)
4867 : {
4868 37 : const char *pszTILE_FORMAT = nullptr;
4869 37 : switch (m_eTF)
4870 : {
4871 0 : case GPKG_TF_PNG_JPEG:
4872 0 : pszTILE_FORMAT = "JPEG_PNG";
4873 0 : break;
4874 31 : case GPKG_TF_PNG:
4875 31 : break;
4876 0 : case GPKG_TF_PNG8:
4877 0 : pszTILE_FORMAT = "PNG8";
4878 0 : break;
4879 3 : case GPKG_TF_JPEG:
4880 3 : pszTILE_FORMAT = "JPEG";
4881 3 : break;
4882 3 : case GPKG_TF_WEBP:
4883 3 : pszTILE_FORMAT = "WEBP";
4884 3 : break;
4885 0 : case GPKG_TF_PNG_16BIT:
4886 0 : break;
4887 0 : case GPKG_TF_TIFF_32BIT_FLOAT:
4888 0 : break;
4889 : }
4890 37 : if (pszTILE_FORMAT)
4891 6 : oLocalMDMD.SetMetadataItem("TILE_FORMAT", pszTILE_FORMAT,
4892 : "IMAGE_STRUCTURE");
4893 : }
4894 : }
4895 430 : if (GetRasterCount() > 0 &&
4896 124 : GetRasterBand(1)->GetRasterDataType() == GDT_Byte)
4897 : {
4898 94 : int bHasNoData = FALSE;
4899 : const double dfNoDataValue =
4900 94 : GetRasterBand(1)->GetNoDataValue(&bHasNoData);
4901 94 : if (bHasNoData)
4902 : {
4903 3 : oLocalMDMD.SetMetadataItem("NODATA_VALUE",
4904 : CPLSPrintf("%.17g", dfNoDataValue),
4905 : "IMAGE_STRUCTURE");
4906 : }
4907 : }
4908 535 : for (int i = 1; i <= GetRasterCount(); ++i)
4909 : {
4910 : auto poBand =
4911 229 : cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
4912 229 : poBand->AddImplicitStatistics(false);
4913 229 : char **papszMD = GetRasterBand(i)->GetMetadata();
4914 229 : poBand->AddImplicitStatistics(true);
4915 229 : if (papszMD)
4916 : {
4917 8 : oLocalMDMD.SetMetadata(papszMD, CPLSPrintf("BAND_%d", i));
4918 : }
4919 : }
4920 306 : psXMLNode = oLocalMDMD.Serialize();
4921 : }
4922 :
4923 306 : CSLDestroy(papszMDDup);
4924 306 : papszMDDup = nullptr;
4925 :
4926 306 : WriteMetadata(psXMLNode, m_osRasterTable.c_str());
4927 :
4928 306 : if (!m_osRasterTable.empty())
4929 : {
4930 : char **papszGeopackageMD =
4931 124 : GDALGeoPackageDataset::GetMetadata("GEOPACKAGE");
4932 :
4933 124 : papszMDDup = nullptr;
4934 133 : for (char **papszIter = papszGeopackageMD; papszIter && *papszIter;
4935 : ++papszIter)
4936 : {
4937 9 : papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
4938 : }
4939 :
4940 248 : GDALMultiDomainMetadata oLocalMDMD;
4941 124 : oLocalMDMD.SetMetadata(papszMDDup);
4942 124 : CSLDestroy(papszMDDup);
4943 124 : papszMDDup = nullptr;
4944 124 : psXMLNode = oLocalMDMD.Serialize();
4945 :
4946 124 : WriteMetadata(psXMLNode, nullptr);
4947 : }
4948 :
4949 498 : for (int i = 0; i < m_nLayers; i++)
4950 : {
4951 : const char *pszIdentifier =
4952 192 : m_papoLayers[i]->GetMetadataItem("IDENTIFIER");
4953 : const char *pszDescription =
4954 192 : m_papoLayers[i]->GetMetadataItem("DESCRIPTION");
4955 192 : if (pszIdentifier != nullptr)
4956 : {
4957 : char *pszSQL =
4958 3 : sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
4959 : "WHERE lower(table_name) = lower('%q')",
4960 3 : pszIdentifier, m_papoLayers[i]->GetName());
4961 3 : SQLCommand(hDB, pszSQL);
4962 3 : sqlite3_free(pszSQL);
4963 : }
4964 192 : if (pszDescription != nullptr)
4965 : {
4966 : char *pszSQL =
4967 3 : sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
4968 : "WHERE lower(table_name) = lower('%q')",
4969 3 : pszDescription, m_papoLayers[i]->GetName());
4970 3 : SQLCommand(hDB, pszSQL);
4971 3 : sqlite3_free(pszSQL);
4972 : }
4973 :
4974 192 : papszMDDup = nullptr;
4975 548 : for (char **papszIter = m_papoLayers[i]->GetMetadata();
4976 548 : papszIter && *papszIter; ++papszIter)
4977 : {
4978 356 : if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
4979 3 : continue;
4980 353 : if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
4981 3 : continue;
4982 350 : if (STARTS_WITH_CI(*papszIter, "OLMD_FID64="))
4983 0 : continue;
4984 350 : papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
4985 : }
4986 :
4987 : {
4988 192 : GDALMultiDomainMetadata oLocalMDMD;
4989 192 : char **papszDomainList = m_papoLayers[i]->GetMetadataDomainList();
4990 192 : char **papszIter = papszDomainList;
4991 192 : oLocalMDMD.SetMetadata(papszMDDup);
4992 405 : while (papszIter && *papszIter)
4993 : {
4994 213 : if (!EQUAL(*papszIter, ""))
4995 68 : oLocalMDMD.SetMetadata(
4996 34 : m_papoLayers[i]->GetMetadata(*papszIter), *papszIter);
4997 213 : papszIter++;
4998 : }
4999 192 : CSLDestroy(papszDomainList);
5000 192 : psXMLNode = oLocalMDMD.Serialize();
5001 : }
5002 :
5003 192 : CSLDestroy(papszMDDup);
5004 192 : papszMDDup = nullptr;
5005 :
5006 192 : WriteMetadata(psXMLNode, m_papoLayers[i]->GetName());
5007 : }
5008 : }
5009 :
5010 : /************************************************************************/
5011 : /* GetMetadataItem() */
5012 : /************************************************************************/
5013 :
5014 1378 : const char *GDALGeoPackageDataset::GetMetadataItem(const char *pszName,
5015 : const char *pszDomain)
5016 : {
5017 1378 : pszDomain = CheckMetadataDomain(pszDomain);
5018 1378 : return CSLFetchNameValue(GetMetadata(pszDomain), pszName);
5019 : }
5020 :
5021 : /************************************************************************/
5022 : /* SetMetadata() */
5023 : /************************************************************************/
5024 :
5025 114 : CPLErr GDALGeoPackageDataset::SetMetadata(char **papszMetadata,
5026 : const char *pszDomain)
5027 : {
5028 114 : pszDomain = CheckMetadataDomain(pszDomain);
5029 114 : m_bMetadataDirty = true;
5030 114 : GetMetadata(); /* force loading from storage if needed */
5031 114 : return GDALPamDataset::SetMetadata(papszMetadata, pszDomain);
5032 : }
5033 :
5034 : /************************************************************************/
5035 : /* SetMetadataItem() */
5036 : /************************************************************************/
5037 :
5038 21 : CPLErr GDALGeoPackageDataset::SetMetadataItem(const char *pszName,
5039 : const char *pszValue,
5040 : const char *pszDomain)
5041 : {
5042 21 : pszDomain = CheckMetadataDomain(pszDomain);
5043 21 : m_bMetadataDirty = true;
5044 21 : GetMetadata(); /* force loading from storage if needed */
5045 21 : return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
5046 : }
5047 :
5048 : /************************************************************************/
5049 : /* Create() */
5050 : /************************************************************************/
5051 :
5052 775 : int GDALGeoPackageDataset::Create(const char *pszFilename, int nXSize,
5053 : int nYSize, int nBandsIn, GDALDataType eDT,
5054 : char **papszOptions)
5055 : {
5056 1550 : CPLString osCommand;
5057 :
5058 : /* First, ensure there isn't any such file yet. */
5059 : VSIStatBufL sStatBuf;
5060 :
5061 775 : if (nBandsIn != 0)
5062 : {
5063 200 : if (eDT == GDT_Byte)
5064 : {
5065 130 : if (nBandsIn != 1 && nBandsIn != 2 && nBandsIn != 3 &&
5066 : nBandsIn != 4)
5067 : {
5068 1 : CPLError(CE_Failure, CPLE_NotSupported,
5069 : "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), "
5070 : "3 (RGB) or 4 (RGBA) band dataset supported for "
5071 : "Byte datatype");
5072 1 : return FALSE;
5073 : }
5074 : }
5075 70 : else if (eDT == GDT_Int16 || eDT == GDT_UInt16 || eDT == GDT_Float32)
5076 : {
5077 43 : if (nBandsIn != 1)
5078 : {
5079 3 : CPLError(CE_Failure, CPLE_NotSupported,
5080 : "Only single band dataset supported for non Byte "
5081 : "datatype");
5082 3 : return FALSE;
5083 : }
5084 : }
5085 : else
5086 : {
5087 27 : CPLError(CE_Failure, CPLE_NotSupported,
5088 : "Only Byte, Int16, UInt16 or Float32 supported");
5089 27 : return FALSE;
5090 : }
5091 : }
5092 :
5093 744 : const size_t nFilenameLen = strlen(pszFilename);
5094 744 : const bool bGpkgZip =
5095 739 : (nFilenameLen > strlen(".gpkg.zip") &&
5096 1483 : !STARTS_WITH(pszFilename, "/vsizip/") &&
5097 739 : EQUAL(pszFilename + nFilenameLen - strlen(".gpkg.zip"), ".gpkg.zip"));
5098 :
5099 : const bool bUseTempFile =
5100 745 : bGpkgZip || (CPLTestBool(CPLGetConfigOption(
5101 1 : "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", "NO")) &&
5102 1 : (VSIHasOptimizedReadMultiRange(pszFilename) != FALSE ||
5103 1 : EQUAL(CPLGetConfigOption(
5104 : "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", ""),
5105 744 : "FORCED")));
5106 :
5107 744 : bool bFileExists = false;
5108 744 : if (VSIStatL(pszFilename, &sStatBuf) == 0)
5109 : {
5110 7 : bFileExists = true;
5111 14 : if (nBandsIn == 0 || bUseTempFile ||
5112 7 : !CPLTestBool(
5113 : CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")))
5114 : {
5115 0 : CPLError(CE_Failure, CPLE_AppDefined,
5116 : "A file system object called '%s' already exists.",
5117 : pszFilename);
5118 :
5119 0 : return FALSE;
5120 : }
5121 : }
5122 :
5123 744 : if (bUseTempFile)
5124 : {
5125 3 : if (bGpkgZip)
5126 : {
5127 2 : std::string osFilenameInZip(CPLGetFilename(pszFilename));
5128 2 : osFilenameInZip.resize(osFilenameInZip.size() - strlen(".zip"));
5129 : m_osFinalFilename =
5130 2 : std::string("/vsizip/{") + pszFilename + "}/" + osFilenameInZip;
5131 : }
5132 : else
5133 : {
5134 1 : m_osFinalFilename = pszFilename;
5135 : }
5136 3 : m_pszFilename =
5137 3 : CPLStrdup(CPLGenerateTempFilename(CPLGetFilename(pszFilename)));
5138 3 : CPLDebug("GPKG", "Creating temporary file %s", m_pszFilename);
5139 : }
5140 : else
5141 : {
5142 741 : m_pszFilename = CPLStrdup(pszFilename);
5143 : }
5144 744 : m_bNew = true;
5145 744 : eAccess = GA_Update;
5146 744 : m_bDateTimeWithTZ =
5147 744 : EQUAL(CSLFetchNameValueDef(papszOptions, "DATETIME_FORMAT", "WITH_TZ"),
5148 : "WITH_TZ");
5149 :
5150 : // for test/debug purposes only. true is the nominal value
5151 744 : m_bPNGSupports2Bands =
5152 744 : CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_2BANDS", "TRUE"));
5153 744 : m_bPNGSupportsCT =
5154 744 : CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_CT", "TRUE"));
5155 :
5156 744 : if (!OpenOrCreateDB(bFileExists
5157 : ? SQLITE_OPEN_READWRITE
5158 : : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE))
5159 4 : return FALSE;
5160 :
5161 : /* Default to synchronous=off for performance for new file */
5162 1473 : if (!bFileExists &&
5163 733 : CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
5164 : {
5165 289 : SQLCommand(hDB, "PRAGMA synchronous = OFF");
5166 : }
5167 :
5168 : /* OGR UTF-8 support. If we set the UTF-8 Pragma early on, it */
5169 : /* will be written into the main file and supported henceforth */
5170 740 : SQLCommand(hDB, "PRAGMA encoding = \"UTF-8\"");
5171 :
5172 740 : if (bFileExists)
5173 : {
5174 7 : VSILFILE *fp = VSIFOpenL(pszFilename, "rb");
5175 7 : if (fp)
5176 : {
5177 : GByte abyHeader[100];
5178 7 : VSIFReadL(abyHeader, 1, sizeof(abyHeader), fp);
5179 7 : VSIFCloseL(fp);
5180 :
5181 7 : memcpy(&m_nApplicationId, abyHeader + knApplicationIdPos, 4);
5182 7 : m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
5183 7 : memcpy(&m_nUserVersion, abyHeader + knUserVersionPos, 4);
5184 7 : m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
5185 :
5186 7 : if (m_nApplicationId == GP10_APPLICATION_ID)
5187 : {
5188 0 : CPLDebug("GPKG", "GeoPackage v1.0");
5189 : }
5190 7 : else if (m_nApplicationId == GP11_APPLICATION_ID)
5191 : {
5192 0 : CPLDebug("GPKG", "GeoPackage v1.1");
5193 : }
5194 7 : else if (m_nApplicationId == GPKG_APPLICATION_ID &&
5195 7 : m_nUserVersion >= GPKG_1_2_VERSION)
5196 : {
5197 7 : CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
5198 7 : (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
5199 : }
5200 : }
5201 :
5202 7 : DetectSpatialRefSysColumns();
5203 : }
5204 :
5205 740 : const char *pszVersion = CSLFetchNameValue(papszOptions, "VERSION");
5206 740 : if (pszVersion && !EQUAL(pszVersion, "AUTO"))
5207 : {
5208 33 : if (EQUAL(pszVersion, "1.0"))
5209 : {
5210 2 : m_nApplicationId = GP10_APPLICATION_ID;
5211 2 : m_nUserVersion = 0;
5212 : }
5213 31 : else if (EQUAL(pszVersion, "1.1"))
5214 : {
5215 1 : m_nApplicationId = GP11_APPLICATION_ID;
5216 1 : m_nUserVersion = 0;
5217 : }
5218 30 : else if (EQUAL(pszVersion, "1.2"))
5219 : {
5220 12 : m_nApplicationId = GPKG_APPLICATION_ID;
5221 12 : m_nUserVersion = GPKG_1_2_VERSION;
5222 : }
5223 18 : else if (EQUAL(pszVersion, "1.3"))
5224 : {
5225 3 : m_nApplicationId = GPKG_APPLICATION_ID;
5226 3 : m_nUserVersion = GPKG_1_3_VERSION;
5227 : }
5228 15 : else if (EQUAL(pszVersion, "1.4"))
5229 : {
5230 15 : m_nApplicationId = GPKG_APPLICATION_ID;
5231 15 : m_nUserVersion = GPKG_1_4_VERSION;
5232 : }
5233 : }
5234 :
5235 740 : SoftStartTransaction();
5236 :
5237 1480 : CPLString osSQL;
5238 740 : if (!bFileExists)
5239 : {
5240 : /* Requirement 10: A GeoPackage SHALL include a gpkg_spatial_ref_sys
5241 : * table */
5242 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5243 : osSQL = "CREATE TABLE gpkg_spatial_ref_sys ("
5244 : "srs_name TEXT NOT NULL,"
5245 : "srs_id INTEGER NOT NULL PRIMARY KEY,"
5246 : "organization TEXT NOT NULL,"
5247 : "organization_coordsys_id INTEGER NOT NULL,"
5248 : "definition TEXT NOT NULL,"
5249 733 : "description TEXT";
5250 733 : if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "CRS_WKT_EXTENSION",
5251 892 : "NO")) ||
5252 159 : (nBandsIn != 0 && eDT != GDT_Byte))
5253 : {
5254 42 : m_bHasDefinition12_063 = true;
5255 42 : osSQL += ", definition_12_063 TEXT NOT NULL";
5256 42 : if (m_nUserVersion >= GPKG_1_4_VERSION)
5257 : {
5258 1 : osSQL += ", epoch DOUBLE";
5259 1 : m_bHasEpochColumn = true;
5260 : }
5261 : }
5262 : osSQL += ")"
5263 : ";"
5264 : /* Requirement 11: The gpkg_spatial_ref_sys table in a
5265 : GeoPackage SHALL */
5266 : /* contain a record for EPSG:4326, the geodetic WGS84 SRS */
5267 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5268 :
5269 : "INSERT INTO gpkg_spatial_ref_sys ("
5270 : "srs_name, srs_id, organization, organization_coordsys_id, "
5271 733 : "definition, description";
5272 733 : if (m_bHasDefinition12_063)
5273 42 : osSQL += ", definition_12_063";
5274 : osSQL +=
5275 : ") VALUES ("
5276 : "'WGS 84 geodetic', 4326, 'EPSG', 4326, '"
5277 : "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS "
5278 : "84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],"
5279 : "AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY["
5280 : "\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY["
5281 : "\"EPSG\",\"9122\"]],AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\","
5282 : "EAST],AUTHORITY[\"EPSG\",\"4326\"]]"
5283 : "', 'longitude/latitude coordinates in decimal degrees on the WGS "
5284 733 : "84 spheroid'";
5285 733 : if (m_bHasDefinition12_063)
5286 : osSQL +=
5287 : ", 'GEODCRS[\"WGS 84\", DATUM[\"World Geodetic System 1984\", "
5288 : "ELLIPSOID[\"WGS 84\",6378137, 298.257223563, "
5289 : "LENGTHUNIT[\"metre\", 1.0]]], PRIMEM[\"Greenwich\", 0.0, "
5290 : "ANGLEUNIT[\"degree\",0.0174532925199433]], CS[ellipsoidal, "
5291 : "2], AXIS[\"latitude\", north, ORDER[1]], AXIS[\"longitude\", "
5292 : "east, ORDER[2]], ANGLEUNIT[\"degree\", 0.0174532925199433], "
5293 42 : "ID[\"EPSG\", 4326]]'";
5294 : osSQL +=
5295 : ")"
5296 : ";"
5297 : /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
5298 : SHALL */
5299 : /* contain a record with an srs_id of -1, an organization of “NONE”,
5300 : */
5301 : /* an organization_coordsys_id of -1, and definition “undefined” */
5302 : /* for undefined Cartesian coordinate reference systems */
5303 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5304 : "INSERT INTO gpkg_spatial_ref_sys ("
5305 : "srs_name, srs_id, organization, organization_coordsys_id, "
5306 733 : "definition, description";
5307 733 : if (m_bHasDefinition12_063)
5308 42 : osSQL += ", definition_12_063";
5309 : osSQL += ") VALUES ("
5310 : "'Undefined Cartesian SRS', -1, 'NONE', -1, 'undefined', "
5311 733 : "'undefined Cartesian coordinate reference system'";
5312 733 : if (m_bHasDefinition12_063)
5313 42 : osSQL += ", 'undefined'";
5314 : osSQL +=
5315 : ")"
5316 : ";"
5317 : /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
5318 : SHALL */
5319 : /* contain a record with an srs_id of 0, an organization of “NONE”,
5320 : */
5321 : /* an organization_coordsys_id of 0, and definition “undefined” */
5322 : /* for undefined geographic coordinate reference systems */
5323 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5324 : "INSERT INTO gpkg_spatial_ref_sys ("
5325 : "srs_name, srs_id, organization, organization_coordsys_id, "
5326 733 : "definition, description";
5327 733 : if (m_bHasDefinition12_063)
5328 42 : osSQL += ", definition_12_063";
5329 : osSQL += ") VALUES ("
5330 : "'Undefined geographic SRS', 0, 'NONE', 0, 'undefined', "
5331 733 : "'undefined geographic coordinate reference system'";
5332 733 : if (m_bHasDefinition12_063)
5333 42 : osSQL += ", 'undefined'";
5334 : osSQL += ")"
5335 : ";"
5336 : /* Requirement 13: A GeoPackage file SHALL include a
5337 : gpkg_contents table */
5338 : /* http://opengis.github.io/geopackage/#_contents */
5339 : "CREATE TABLE gpkg_contents ("
5340 : "table_name TEXT NOT NULL PRIMARY KEY,"
5341 : "data_type TEXT NOT NULL,"
5342 : "identifier TEXT UNIQUE,"
5343 : "description TEXT DEFAULT '',"
5344 : "last_change DATETIME NOT NULL DEFAULT "
5345 : "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
5346 : "min_x DOUBLE, min_y DOUBLE,"
5347 : "max_x DOUBLE, max_y DOUBLE,"
5348 : "srs_id INTEGER,"
5349 : "CONSTRAINT fk_gc_r_srs_id FOREIGN KEY (srs_id) REFERENCES "
5350 : "gpkg_spatial_ref_sys(srs_id)"
5351 733 : ")";
5352 :
5353 : #ifdef ENABLE_GPKG_OGR_CONTENTS
5354 733 : if (CPLFetchBool(papszOptions, "ADD_GPKG_OGR_CONTENTS", true))
5355 : {
5356 731 : m_bHasGPKGOGRContents = true;
5357 : osSQL += ";"
5358 : "CREATE TABLE gpkg_ogr_contents("
5359 : "table_name TEXT NOT NULL PRIMARY KEY,"
5360 : "feature_count INTEGER DEFAULT NULL"
5361 731 : ")";
5362 : }
5363 : #endif
5364 :
5365 : /* Requirement 21: A GeoPackage with a gpkg_contents table row with a
5366 : * “features” */
5367 : /* data_type SHALL contain a gpkg_geometry_columns table or updateable
5368 : * view */
5369 : /* http://opengis.github.io/geopackage/#_geometry_columns */
5370 : const bool bCreateGeometryColumns =
5371 733 : CPLTestBool(CPLGetConfigOption("CREATE_GEOMETRY_COLUMNS", "YES"));
5372 733 : if (bCreateGeometryColumns)
5373 : {
5374 732 : m_bHasGPKGGeometryColumns = true;
5375 732 : osSQL += ";";
5376 732 : osSQL += pszCREATE_GPKG_GEOMETRY_COLUMNS;
5377 : }
5378 : }
5379 :
5380 : const bool bCreateTriggers =
5381 740 : CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "YES"));
5382 7 : if ((bFileExists && nBandsIn != 0 &&
5383 7 : SQLGetInteger(
5384 : hDB,
5385 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_tile_matrix_set' "
5386 : "AND type in ('table', 'view')",
5387 1480 : nullptr) == 0) ||
5388 739 : (!bFileExists &&
5389 733 : CPLTestBool(CPLGetConfigOption("CREATE_RASTER_TABLES", "YES"))))
5390 : {
5391 733 : if (!osSQL.empty())
5392 732 : osSQL += ";";
5393 :
5394 : /* From C.5. gpkg_tile_matrix_set Table 28. gpkg_tile_matrix_set Table
5395 : * Creation SQL */
5396 : osSQL += "CREATE TABLE gpkg_tile_matrix_set ("
5397 : "table_name TEXT NOT NULL PRIMARY KEY,"
5398 : "srs_id INTEGER NOT NULL,"
5399 : "min_x DOUBLE NOT NULL,"
5400 : "min_y DOUBLE NOT NULL,"
5401 : "max_x DOUBLE NOT NULL,"
5402 : "max_y DOUBLE NOT NULL,"
5403 : "CONSTRAINT fk_gtms_table_name FOREIGN KEY (table_name) "
5404 : "REFERENCES gpkg_contents(table_name),"
5405 : "CONSTRAINT fk_gtms_srs FOREIGN KEY (srs_id) REFERENCES "
5406 : "gpkg_spatial_ref_sys (srs_id)"
5407 : ")"
5408 : ";"
5409 :
5410 : /* From C.6. gpkg_tile_matrix Table 29. gpkg_tile_matrix Table
5411 : Creation SQL */
5412 : "CREATE TABLE gpkg_tile_matrix ("
5413 : "table_name TEXT NOT NULL,"
5414 : "zoom_level INTEGER NOT NULL,"
5415 : "matrix_width INTEGER NOT NULL,"
5416 : "matrix_height INTEGER NOT NULL,"
5417 : "tile_width INTEGER NOT NULL,"
5418 : "tile_height INTEGER NOT NULL,"
5419 : "pixel_x_size DOUBLE NOT NULL,"
5420 : "pixel_y_size DOUBLE NOT NULL,"
5421 : "CONSTRAINT pk_ttm PRIMARY KEY (table_name, zoom_level),"
5422 : "CONSTRAINT fk_tmm_table_name FOREIGN KEY (table_name) "
5423 : "REFERENCES gpkg_contents(table_name)"
5424 733 : ")";
5425 :
5426 733 : if (bCreateTriggers)
5427 : {
5428 : /* From D.1. gpkg_tile_matrix Table 39. gpkg_tile_matrix Trigger
5429 : * Definition SQL */
5430 733 : const char *pszTileMatrixTrigger =
5431 : "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_insert' "
5432 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5433 : "FOR EACH ROW BEGIN "
5434 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5435 : "violates constraint: zoom_level cannot be less than 0') "
5436 : "WHERE (NEW.zoom_level < 0); "
5437 : "END; "
5438 : "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_update' "
5439 : "BEFORE UPDATE of zoom_level ON 'gpkg_tile_matrix' "
5440 : "FOR EACH ROW BEGIN "
5441 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5442 : "violates constraint: zoom_level cannot be less than 0') "
5443 : "WHERE (NEW.zoom_level < 0); "
5444 : "END; "
5445 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_insert' "
5446 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5447 : "FOR EACH ROW BEGIN "
5448 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5449 : "violates constraint: matrix_width cannot be less than 1') "
5450 : "WHERE (NEW.matrix_width < 1); "
5451 : "END; "
5452 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_update' "
5453 : "BEFORE UPDATE OF matrix_width ON 'gpkg_tile_matrix' "
5454 : "FOR EACH ROW BEGIN "
5455 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5456 : "violates constraint: matrix_width cannot be less than 1') "
5457 : "WHERE (NEW.matrix_width < 1); "
5458 : "END; "
5459 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_insert' "
5460 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5461 : "FOR EACH ROW BEGIN "
5462 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5463 : "violates constraint: matrix_height cannot be less than 1') "
5464 : "WHERE (NEW.matrix_height < 1); "
5465 : "END; "
5466 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_update' "
5467 : "BEFORE UPDATE OF matrix_height ON 'gpkg_tile_matrix' "
5468 : "FOR EACH ROW BEGIN "
5469 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5470 : "violates constraint: matrix_height cannot be less than 1') "
5471 : "WHERE (NEW.matrix_height < 1); "
5472 : "END; "
5473 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_insert' "
5474 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5475 : "FOR EACH ROW BEGIN "
5476 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5477 : "violates constraint: pixel_x_size must be greater than 0') "
5478 : "WHERE NOT (NEW.pixel_x_size > 0); "
5479 : "END; "
5480 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_update' "
5481 : "BEFORE UPDATE OF pixel_x_size ON 'gpkg_tile_matrix' "
5482 : "FOR EACH ROW BEGIN "
5483 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5484 : "violates constraint: pixel_x_size must be greater than 0') "
5485 : "WHERE NOT (NEW.pixel_x_size > 0); "
5486 : "END; "
5487 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_insert' "
5488 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5489 : "FOR EACH ROW BEGIN "
5490 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5491 : "violates constraint: pixel_y_size must be greater than 0') "
5492 : "WHERE NOT (NEW.pixel_y_size > 0); "
5493 : "END; "
5494 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_update' "
5495 : "BEFORE UPDATE OF pixel_y_size ON 'gpkg_tile_matrix' "
5496 : "FOR EACH ROW BEGIN "
5497 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5498 : "violates constraint: pixel_y_size must be greater than 0') "
5499 : "WHERE NOT (NEW.pixel_y_size > 0); "
5500 : "END;";
5501 733 : osSQL += ";";
5502 733 : osSQL += pszTileMatrixTrigger;
5503 : }
5504 : }
5505 :
5506 740 : if (!osSQL.empty() && OGRERR_NONE != SQLCommand(hDB, osSQL))
5507 0 : return FALSE;
5508 :
5509 740 : if (!bFileExists)
5510 : {
5511 : const char *pszMetadataTables =
5512 733 : CSLFetchNameValue(papszOptions, "METADATA_TABLES");
5513 733 : if (pszMetadataTables)
5514 6 : m_nCreateMetadataTables = int(CPLTestBool(pszMetadataTables));
5515 :
5516 733 : if (m_nCreateMetadataTables == TRUE && !CreateMetadataTables())
5517 0 : return FALSE;
5518 :
5519 733 : if (m_bHasDefinition12_063)
5520 : {
5521 84 : if (OGRERR_NONE != CreateExtensionsTableIfNecessary() ||
5522 : OGRERR_NONE !=
5523 42 : SQLCommand(hDB, "INSERT INTO gpkg_extensions "
5524 : "(table_name, column_name, extension_name, "
5525 : "definition, scope) "
5526 : "VALUES "
5527 : "('gpkg_spatial_ref_sys', "
5528 : "'definition_12_063', 'gpkg_crs_wkt', "
5529 : "'http://www.geopackage.org/spec120/"
5530 : "#extension_crs_wkt', 'read-write')"))
5531 : {
5532 0 : return FALSE;
5533 : }
5534 42 : if (m_bHasEpochColumn)
5535 : {
5536 1 : if (OGRERR_NONE !=
5537 1 : SQLCommand(
5538 : hDB, "UPDATE gpkg_extensions SET extension_name = "
5539 : "'gpkg_crs_wkt_1_1' "
5540 2 : "WHERE extension_name = 'gpkg_crs_wkt'") ||
5541 : OGRERR_NONE !=
5542 1 : SQLCommand(hDB, "INSERT INTO gpkg_extensions "
5543 : "(table_name, column_name, "
5544 : "extension_name, definition, scope) "
5545 : "VALUES "
5546 : "('gpkg_spatial_ref_sys', 'epoch', "
5547 : "'gpkg_crs_wkt_1_1', "
5548 : "'http://www.geopackage.org/spec/"
5549 : "#extension_crs_wkt', "
5550 : "'read-write')"))
5551 : {
5552 0 : return FALSE;
5553 : }
5554 : }
5555 : }
5556 : }
5557 :
5558 740 : if (nBandsIn != 0)
5559 : {
5560 166 : const char *pszTableName = CPLGetBasename(m_pszFilename);
5561 : m_osRasterTable =
5562 166 : CSLFetchNameValueDef(papszOptions, "RASTER_TABLE", pszTableName);
5563 166 : if (m_osRasterTable.empty())
5564 : {
5565 0 : CPLError(CE_Failure, CPLE_AppDefined,
5566 : "RASTER_TABLE must be set to a non empty value");
5567 0 : return FALSE;
5568 : }
5569 166 : m_bIdentifierAsCO =
5570 166 : CSLFetchNameValue(papszOptions, "RASTER_IDENTIFIER") != nullptr;
5571 : m_osIdentifier = CSLFetchNameValueDef(papszOptions, "RASTER_IDENTIFIER",
5572 166 : m_osRasterTable);
5573 166 : m_bDescriptionAsCO =
5574 166 : CSLFetchNameValue(papszOptions, "RASTER_DESCRIPTION") != nullptr;
5575 : m_osDescription =
5576 166 : CSLFetchNameValueDef(papszOptions, "RASTER_DESCRIPTION", "");
5577 166 : SetDataType(eDT);
5578 166 : if (eDT == GDT_Int16)
5579 16 : SetGlobalOffsetScale(-32768.0, 1.0);
5580 :
5581 : /* From C.7. sample_tile_pyramid (Informative) Table 31. EXAMPLE: tiles
5582 : * table Create Table SQL (Informative) */
5583 : char *pszSQL =
5584 166 : sqlite3_mprintf("CREATE TABLE \"%w\" ("
5585 : "id INTEGER PRIMARY KEY AUTOINCREMENT,"
5586 : "zoom_level INTEGER NOT NULL,"
5587 : "tile_column INTEGER NOT NULL,"
5588 : "tile_row INTEGER NOT NULL,"
5589 : "tile_data BLOB NOT NULL,"
5590 : "UNIQUE (zoom_level, tile_column, tile_row)"
5591 : ")",
5592 : m_osRasterTable.c_str());
5593 166 : osSQL = pszSQL;
5594 166 : sqlite3_free(pszSQL);
5595 :
5596 166 : if (bCreateTriggers)
5597 : {
5598 166 : osSQL += ";";
5599 166 : osSQL += CreateRasterTriggersSQL(m_osRasterTable);
5600 : }
5601 :
5602 166 : OGRErr eErr = SQLCommand(hDB, osSQL);
5603 166 : if (OGRERR_NONE != eErr)
5604 0 : return FALSE;
5605 :
5606 166 : const char *pszTF = CSLFetchNameValue(papszOptions, "TILE_FORMAT");
5607 166 : if (eDT == GDT_Int16 || eDT == GDT_UInt16)
5608 : {
5609 27 : m_eTF = GPKG_TF_PNG_16BIT;
5610 27 : if (pszTF)
5611 : {
5612 1 : if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "PNG"))
5613 : {
5614 0 : CPLError(CE_Warning, CPLE_NotSupported,
5615 : "Only AUTO or PNG supported "
5616 : "as tile format for Int16 / UInt16");
5617 : }
5618 : }
5619 : }
5620 139 : else if (eDT == GDT_Float32)
5621 : {
5622 13 : m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
5623 13 : if (pszTF)
5624 : {
5625 5 : if (EQUAL(pszTF, "PNG"))
5626 5 : m_eTF = GPKG_TF_PNG_16BIT;
5627 0 : else if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "TIFF"))
5628 : {
5629 0 : CPLError(CE_Warning, CPLE_NotSupported,
5630 : "Only AUTO, PNG or TIFF supported "
5631 : "as tile format for Float32");
5632 : }
5633 : }
5634 : }
5635 : else
5636 : {
5637 126 : if (pszTF)
5638 : {
5639 71 : m_eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
5640 71 : if (nBandsIn == 1 && m_eTF != GPKG_TF_PNG)
5641 7 : m_bMetadataDirty = true;
5642 : }
5643 55 : else if (nBandsIn == 1)
5644 44 : m_eTF = GPKG_TF_PNG;
5645 : }
5646 :
5647 166 : if (eDT != GDT_Byte)
5648 : {
5649 40 : if (!CreateTileGriddedTable(papszOptions))
5650 0 : return FALSE;
5651 : }
5652 :
5653 166 : nRasterXSize = nXSize;
5654 166 : nRasterYSize = nYSize;
5655 :
5656 : const char *pszTileSize =
5657 166 : CSLFetchNameValueDef(papszOptions, "BLOCKSIZE", "256");
5658 : const char *pszTileWidth =
5659 166 : CSLFetchNameValueDef(papszOptions, "BLOCKXSIZE", pszTileSize);
5660 : const char *pszTileHeight =
5661 166 : CSLFetchNameValueDef(papszOptions, "BLOCKYSIZE", pszTileSize);
5662 166 : int nTileWidth = atoi(pszTileWidth);
5663 166 : int nTileHeight = atoi(pszTileHeight);
5664 166 : if ((nTileWidth < 8 || nTileWidth > 4096 || nTileHeight < 8 ||
5665 332 : nTileHeight > 4096) &&
5666 1 : !CPLTestBool(CPLGetConfigOption("GPKG_ALLOW_CRAZY_SETTINGS", "NO")))
5667 : {
5668 0 : CPLError(CE_Failure, CPLE_AppDefined,
5669 : "Invalid block dimensions: %dx%d", nTileWidth,
5670 : nTileHeight);
5671 0 : return FALSE;
5672 : }
5673 :
5674 465 : for (int i = 1; i <= nBandsIn; i++)
5675 299 : SetBand(
5676 299 : i, new GDALGeoPackageRasterBand(this, nTileWidth, nTileHeight));
5677 :
5678 166 : GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL",
5679 : "IMAGE_STRUCTURE");
5680 166 : GDALPamDataset::SetMetadataItem("IDENTIFIER", m_osIdentifier);
5681 166 : if (!m_osDescription.empty())
5682 1 : GDALPamDataset::SetMetadataItem("DESCRIPTION", m_osDescription);
5683 :
5684 166 : ParseCompressionOptions(papszOptions);
5685 :
5686 166 : if (m_eTF == GPKG_TF_WEBP)
5687 : {
5688 10 : if (!RegisterWebPExtension())
5689 0 : return FALSE;
5690 : }
5691 :
5692 : m_osTilingScheme =
5693 166 : CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
5694 166 : if (!EQUAL(m_osTilingScheme, "CUSTOM"))
5695 : {
5696 22 : const auto poTS = GetTilingScheme(m_osTilingScheme);
5697 22 : if (!poTS)
5698 0 : return FALSE;
5699 :
5700 43 : if (nTileWidth != poTS->nTileWidth ||
5701 21 : nTileHeight != poTS->nTileHeight)
5702 : {
5703 2 : CPLError(CE_Failure, CPLE_NotSupported,
5704 : "Tile dimension should be %dx%d for %s tiling scheme",
5705 1 : poTS->nTileWidth, poTS->nTileHeight,
5706 : m_osTilingScheme.c_str());
5707 1 : return FALSE;
5708 : }
5709 :
5710 : const char *pszZoomLevel =
5711 21 : CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
5712 21 : if (pszZoomLevel)
5713 : {
5714 1 : m_nZoomLevel = atoi(pszZoomLevel);
5715 1 : int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
5716 1 : while ((1 << nMaxZoomLevelForThisTM) >
5717 2 : INT_MAX / poTS->nTileXCountZoomLevel0 ||
5718 1 : (1 << nMaxZoomLevelForThisTM) >
5719 1 : INT_MAX / poTS->nTileYCountZoomLevel0)
5720 : {
5721 0 : --nMaxZoomLevelForThisTM;
5722 : }
5723 :
5724 1 : if (m_nZoomLevel < 0 || m_nZoomLevel > nMaxZoomLevelForThisTM)
5725 : {
5726 0 : CPLError(CE_Failure, CPLE_AppDefined,
5727 : "ZOOM_LEVEL = %s is invalid. It should be in "
5728 : "[0,%d] range",
5729 : pszZoomLevel, nMaxZoomLevelForThisTM);
5730 0 : return FALSE;
5731 : }
5732 : }
5733 :
5734 : // Implicitly sets SRS.
5735 21 : OGRSpatialReference oSRS;
5736 21 : if (oSRS.importFromEPSG(poTS->nEPSGCode) != OGRERR_NONE)
5737 0 : return FALSE;
5738 21 : char *pszWKT = nullptr;
5739 21 : oSRS.exportToWkt(&pszWKT);
5740 21 : SetProjection(pszWKT);
5741 21 : CPLFree(pszWKT);
5742 : }
5743 : else
5744 : {
5745 144 : if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
5746 : {
5747 0 : CPLError(
5748 : CE_Failure, CPLE_NotSupported,
5749 : "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
5750 0 : return false;
5751 : }
5752 : }
5753 : }
5754 :
5755 739 : if (bFileExists && nBandsIn > 0 && eDT == GDT_Byte)
5756 : {
5757 : // If there was an ogr_empty_table table, we can remove it
5758 6 : RemoveOGREmptyTable();
5759 : }
5760 :
5761 739 : SoftCommitTransaction();
5762 :
5763 : /* Requirement 2 */
5764 : /* We have to do this after there's some content so the database file */
5765 : /* is not zero length */
5766 739 : SetApplicationAndUserVersionId();
5767 :
5768 : /* Default to synchronous=off for performance for new file */
5769 1471 : if (!bFileExists &&
5770 732 : CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
5771 : {
5772 289 : SQLCommand(hDB, "PRAGMA synchronous = OFF");
5773 : }
5774 :
5775 739 : return TRUE;
5776 : }
5777 :
5778 : /************************************************************************/
5779 : /* RemoveOGREmptyTable() */
5780 : /************************************************************************/
5781 :
5782 562 : void GDALGeoPackageDataset::RemoveOGREmptyTable()
5783 : {
5784 : // Run with sqlite3_exec since we don't want errors to be emitted
5785 562 : sqlite3_exec(hDB, "DROP TABLE IF EXISTS ogr_empty_table", nullptr, nullptr,
5786 : nullptr);
5787 562 : sqlite3_exec(
5788 : hDB, "DELETE FROM gpkg_contents WHERE table_name = 'ogr_empty_table'",
5789 : nullptr, nullptr, nullptr);
5790 : #ifdef ENABLE_GPKG_OGR_CONTENTS
5791 562 : if (m_bHasGPKGOGRContents)
5792 : {
5793 553 : sqlite3_exec(hDB,
5794 : "DELETE FROM gpkg_ogr_contents WHERE "
5795 : "table_name = 'ogr_empty_table'",
5796 : nullptr, nullptr, nullptr);
5797 : }
5798 : #endif
5799 562 : sqlite3_exec(hDB,
5800 : "DELETE FROM gpkg_geometry_columns WHERE "
5801 : "table_name = 'ogr_empty_table'",
5802 : nullptr, nullptr, nullptr);
5803 562 : }
5804 :
5805 : /************************************************************************/
5806 : /* CreateTileGriddedTable() */
5807 : /************************************************************************/
5808 :
5809 40 : bool GDALGeoPackageDataset::CreateTileGriddedTable(char **papszOptions)
5810 : {
5811 80 : CPLString osSQL;
5812 40 : if (!HasGriddedCoverageAncillaryTable())
5813 : {
5814 : // It doesn't exist. So create gpkg_extensions table if necessary, and
5815 : // gpkg_2d_gridded_coverage_ancillary & gpkg_2d_gridded_tile_ancillary,
5816 : // and register them as extensions.
5817 40 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
5818 0 : return false;
5819 :
5820 : // Req 1 /table-defs/coverage-ancillary
5821 : osSQL = "CREATE TABLE gpkg_2d_gridded_coverage_ancillary ("
5822 : "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
5823 : "tile_matrix_set_name TEXT NOT NULL UNIQUE,"
5824 : "datatype TEXT NOT NULL DEFAULT 'integer',"
5825 : "scale REAL NOT NULL DEFAULT 1.0,"
5826 : "offset REAL NOT NULL DEFAULT 0.0,"
5827 : "precision REAL DEFAULT 1.0,"
5828 : "data_null REAL,"
5829 : "grid_cell_encoding TEXT DEFAULT 'grid-value-is-center',"
5830 : "uom TEXT,"
5831 : "field_name TEXT DEFAULT 'Height',"
5832 : "quantity_definition TEXT DEFAULT 'Height',"
5833 : "CONSTRAINT fk_g2dgtct_name FOREIGN KEY(tile_matrix_set_name) "
5834 : "REFERENCES gpkg_tile_matrix_set ( table_name ) "
5835 : "CHECK (datatype in ('integer','float')))"
5836 : ";"
5837 : // Requirement 2 /table-defs/tile-ancillary
5838 : "CREATE TABLE gpkg_2d_gridded_tile_ancillary ("
5839 : "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
5840 : "tpudt_name TEXT NOT NULL,"
5841 : "tpudt_id INTEGER NOT NULL,"
5842 : "scale REAL NOT NULL DEFAULT 1.0,"
5843 : "offset REAL NOT NULL DEFAULT 0.0,"
5844 : "min REAL DEFAULT NULL,"
5845 : "max REAL DEFAULT NULL,"
5846 : "mean REAL DEFAULT NULL,"
5847 : "std_dev REAL DEFAULT NULL,"
5848 : "CONSTRAINT fk_g2dgtat_name FOREIGN KEY (tpudt_name) "
5849 : "REFERENCES gpkg_contents(table_name),"
5850 : "UNIQUE (tpudt_name, tpudt_id))"
5851 : ";"
5852 : // Requirement 6 /gpkg-extensions
5853 : "INSERT INTO gpkg_extensions "
5854 : "(table_name, column_name, extension_name, definition, scope) "
5855 : "VALUES ('gpkg_2d_gridded_coverage_ancillary', NULL, "
5856 : "'gpkg_2d_gridded_coverage', "
5857 : "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5858 : "'read-write')"
5859 : ";"
5860 : // Requirement 6 /gpkg-extensions
5861 : "INSERT INTO gpkg_extensions "
5862 : "(table_name, column_name, extension_name, definition, scope) "
5863 : "VALUES ('gpkg_2d_gridded_tile_ancillary', NULL, "
5864 : "'gpkg_2d_gridded_coverage', "
5865 : "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5866 : "'read-write')"
5867 40 : ";";
5868 : }
5869 :
5870 : // Requirement 6 /gpkg-extensions
5871 40 : char *pszSQL = sqlite3_mprintf(
5872 : "INSERT INTO gpkg_extensions "
5873 : "(table_name, column_name, extension_name, definition, scope) "
5874 : "VALUES ('%q', 'tile_data', "
5875 : "'gpkg_2d_gridded_coverage', "
5876 : "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5877 : "'read-write')",
5878 : m_osRasterTable.c_str());
5879 40 : osSQL += pszSQL;
5880 40 : osSQL += ";";
5881 40 : sqlite3_free(pszSQL);
5882 :
5883 : // Requirement 7 /gpkg-2d-gridded-coverage-ancillary
5884 : // Requirement 8 /gpkg-2d-gridded-coverage-ancillary-set-name
5885 : // Requirement 9 /gpkg-2d-gridded-coverage-ancillary-datatype
5886 40 : m_dfPrecision =
5887 40 : CPLAtof(CSLFetchNameValueDef(papszOptions, "PRECISION", "1"));
5888 : CPLString osGridCellEncoding(CSLFetchNameValueDef(
5889 80 : papszOptions, "GRID_CELL_ENCODING", "grid-value-is-center"));
5890 40 : m_bGridCellEncodingAsCO =
5891 40 : CSLFetchNameValue(papszOptions, "GRID_CELL_ENCODING") != nullptr;
5892 80 : CPLString osUom(CSLFetchNameValueDef(papszOptions, "UOM", ""));
5893 : CPLString osFieldName(
5894 80 : CSLFetchNameValueDef(papszOptions, "FIELD_NAME", "Height"));
5895 : CPLString osQuantityDefinition(
5896 80 : CSLFetchNameValueDef(papszOptions, "QUANTITY_DEFINITION", "Height"));
5897 :
5898 121 : pszSQL = sqlite3_mprintf(
5899 : "INSERT INTO gpkg_2d_gridded_coverage_ancillary "
5900 : "(tile_matrix_set_name, datatype, scale, offset, precision, "
5901 : "grid_cell_encoding, uom, field_name, quantity_definition) "
5902 : "VALUES (%Q, '%s', %.17g, %.17g, %.17g, %Q, %Q, %Q, %Q)",
5903 : m_osRasterTable.c_str(),
5904 40 : (m_eTF == GPKG_TF_PNG_16BIT) ? "integer" : "float", m_dfScale,
5905 : m_dfOffset, m_dfPrecision, osGridCellEncoding.c_str(),
5906 41 : osUom.empty() ? nullptr : osUom.c_str(), osFieldName.c_str(),
5907 : osQuantityDefinition.c_str());
5908 40 : m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary = pszSQL;
5909 40 : sqlite3_free(pszSQL);
5910 :
5911 : // Requirement 3 /gpkg-spatial-ref-sys-row
5912 : auto oResultTable = SQLQuery(
5913 80 : hDB, "SELECT * FROM gpkg_spatial_ref_sys WHERE srs_id = 4979 LIMIT 2");
5914 40 : bool bHasEPSG4979 = (oResultTable && oResultTable->RowCount() == 1);
5915 40 : if (!bHasEPSG4979)
5916 : {
5917 41 : if (!m_bHasDefinition12_063 &&
5918 1 : !ConvertGpkgSpatialRefSysToExtensionWkt2(/*bForceEpoch=*/false))
5919 : {
5920 0 : return false;
5921 : }
5922 :
5923 : // This is WKT 2...
5924 40 : const char *pszWKT =
5925 : "GEODCRS[\"WGS 84\","
5926 : "DATUM[\"World Geodetic System 1984\","
5927 : " ELLIPSOID[\"WGS 84\",6378137,298.257223563,"
5928 : "LENGTHUNIT[\"metre\",1.0]]],"
5929 : "CS[ellipsoidal,3],"
5930 : " AXIS[\"latitude\",north,ORDER[1],ANGLEUNIT[\"degree\","
5931 : "0.0174532925199433]],"
5932 : " AXIS[\"longitude\",east,ORDER[2],ANGLEUNIT[\"degree\","
5933 : "0.0174532925199433]],"
5934 : " AXIS[\"ellipsoidal height\",up,ORDER[3],"
5935 : "LENGTHUNIT[\"metre\",1.0]],"
5936 : "ID[\"EPSG\",4979]]";
5937 :
5938 40 : pszSQL = sqlite3_mprintf(
5939 : "INSERT INTO gpkg_spatial_ref_sys "
5940 : "(srs_name,srs_id,organization,organization_coordsys_id,"
5941 : "definition,definition_12_063) VALUES "
5942 : "('WGS 84 3D', 4979, 'EPSG', 4979, 'undefined', '%q')",
5943 : pszWKT);
5944 40 : osSQL += ";";
5945 40 : osSQL += pszSQL;
5946 40 : sqlite3_free(pszSQL);
5947 : }
5948 :
5949 40 : return SQLCommand(hDB, osSQL) == OGRERR_NONE;
5950 : }
5951 :
5952 : /************************************************************************/
5953 : /* HasGriddedCoverageAncillaryTable() */
5954 : /************************************************************************/
5955 :
5956 44 : bool GDALGeoPackageDataset::HasGriddedCoverageAncillaryTable()
5957 : {
5958 : auto oResultTable = SQLQuery(
5959 : hDB, "SELECT * FROM sqlite_master WHERE type IN ('table', 'view') AND "
5960 44 : "name = 'gpkg_2d_gridded_coverage_ancillary'");
5961 44 : bool bHasTable = (oResultTable && oResultTable->RowCount() == 1);
5962 88 : return bHasTable;
5963 : }
5964 :
5965 : /************************************************************************/
5966 : /* GetUnderlyingDataset() */
5967 : /************************************************************************/
5968 :
5969 3 : static GDALDataset *GetUnderlyingDataset(GDALDataset *poSrcDS)
5970 : {
5971 3 : if (auto poVRTDS = dynamic_cast<VRTDataset *>(poSrcDS))
5972 : {
5973 0 : auto poTmpDS = poVRTDS->GetSingleSimpleSource();
5974 0 : if (poTmpDS)
5975 0 : return poTmpDS;
5976 : }
5977 :
5978 3 : return poSrcDS;
5979 : }
5980 :
5981 : /************************************************************************/
5982 : /* CreateCopy() */
5983 : /************************************************************************/
5984 :
5985 : typedef struct
5986 : {
5987 : const char *pszName;
5988 : GDALResampleAlg eResampleAlg;
5989 : } WarpResamplingAlg;
5990 :
5991 : static const WarpResamplingAlg asResamplingAlg[] = {
5992 : {"NEAREST", GRA_NearestNeighbour},
5993 : {"BILINEAR", GRA_Bilinear},
5994 : {"CUBIC", GRA_Cubic},
5995 : {"CUBICSPLINE", GRA_CubicSpline},
5996 : {"LANCZOS", GRA_Lanczos},
5997 : {"MODE", GRA_Mode},
5998 : {"AVERAGE", GRA_Average},
5999 : {"RMS", GRA_RMS},
6000 : };
6001 :
6002 141 : GDALDataset *GDALGeoPackageDataset::CreateCopy(const char *pszFilename,
6003 : GDALDataset *poSrcDS,
6004 : int bStrict, char **papszOptions,
6005 : GDALProgressFunc pfnProgress,
6006 : void *pProgressData)
6007 : {
6008 141 : const int nBands = poSrcDS->GetRasterCount();
6009 141 : if (nBands == 0)
6010 : {
6011 2 : GDALDataset *poDS = nullptr;
6012 : GDALDriver *poThisDriver =
6013 2 : GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
6014 2 : if (poThisDriver != nullptr)
6015 : {
6016 2 : poDS = poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS,
6017 : bStrict, papszOptions,
6018 : pfnProgress, pProgressData);
6019 : }
6020 2 : return poDS;
6021 : }
6022 :
6023 : const char *pszTilingScheme =
6024 139 : CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
6025 :
6026 278 : CPLStringList apszUpdatedOptions(CSLDuplicate(papszOptions));
6027 139 : if (CPLTestBool(
6028 144 : CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")) &&
6029 5 : CSLFetchNameValue(papszOptions, "RASTER_TABLE") == nullptr)
6030 : {
6031 : CPLString osBasename(
6032 6 : CPLGetBasename(GetUnderlyingDataset(poSrcDS)->GetDescription()));
6033 3 : apszUpdatedOptions.SetNameValue("RASTER_TABLE", osBasename);
6034 : }
6035 :
6036 139 : if (nBands != 1 && nBands != 2 && nBands != 3 && nBands != 4)
6037 : {
6038 1 : CPLError(CE_Failure, CPLE_NotSupported,
6039 : "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), 3 (RGB) or "
6040 : "4 (RGBA) band dataset supported");
6041 1 : return nullptr;
6042 : }
6043 :
6044 138 : const char *pszUnitType = poSrcDS->GetRasterBand(1)->GetUnitType();
6045 276 : if (CSLFetchNameValue(papszOptions, "UOM") == nullptr && pszUnitType &&
6046 138 : !EQUAL(pszUnitType, ""))
6047 : {
6048 1 : apszUpdatedOptions.SetNameValue("UOM", pszUnitType);
6049 : }
6050 :
6051 138 : if (EQUAL(pszTilingScheme, "CUSTOM"))
6052 : {
6053 114 : if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
6054 : {
6055 0 : CPLError(CE_Failure, CPLE_NotSupported,
6056 : "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
6057 0 : return nullptr;
6058 : }
6059 :
6060 114 : GDALGeoPackageDataset *poDS = nullptr;
6061 : GDALDriver *poThisDriver =
6062 114 : GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
6063 114 : if (poThisDriver != nullptr)
6064 : {
6065 114 : apszUpdatedOptions.SetNameValue("SKIP_HOLES", "YES");
6066 114 : poDS = cpl::down_cast<GDALGeoPackageDataset *>(
6067 : poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
6068 : apszUpdatedOptions, pfnProgress,
6069 114 : pProgressData));
6070 :
6071 211 : if (poDS != nullptr &&
6072 114 : poSrcDS->GetRasterBand(1)->GetRasterDataType() == GDT_Byte &&
6073 : nBands <= 3)
6074 : {
6075 61 : poDS->m_nBandCountFromMetadata = nBands;
6076 61 : poDS->m_bMetadataDirty = true;
6077 : }
6078 : }
6079 114 : if (poDS)
6080 97 : poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
6081 114 : return poDS;
6082 : }
6083 :
6084 48 : const auto poTS = GetTilingScheme(pszTilingScheme);
6085 24 : if (!poTS)
6086 : {
6087 2 : return nullptr;
6088 : }
6089 22 : const int nEPSGCode = poTS->nEPSGCode;
6090 :
6091 44 : OGRSpatialReference oSRS;
6092 22 : if (oSRS.importFromEPSG(nEPSGCode) != OGRERR_NONE)
6093 : {
6094 0 : return nullptr;
6095 : }
6096 22 : char *pszWKT = nullptr;
6097 22 : oSRS.exportToWkt(&pszWKT);
6098 22 : char **papszTO = CSLSetNameValue(nullptr, "DST_SRS", pszWKT);
6099 :
6100 22 : void *hTransformArg = nullptr;
6101 :
6102 : // Hack to compensate for GDALSuggestedWarpOutput2() failure (or not
6103 : // ideal suggestion with PROJ 8) when reprojecting latitude = +/- 90 to
6104 : // EPSG:3857.
6105 : double adfSrcGeoTransform[6];
6106 22 : std::unique_ptr<GDALDataset> poTmpDS;
6107 22 : bool bEPSG3857Adjust = false;
6108 30 : if (nEPSGCode == 3857 &&
6109 8 : poSrcDS->GetGeoTransform(adfSrcGeoTransform) == CE_None &&
6110 38 : adfSrcGeoTransform[2] == 0 && adfSrcGeoTransform[4] == 0 &&
6111 8 : adfSrcGeoTransform[5] < 0)
6112 : {
6113 8 : const auto poSrcSRS = poSrcDS->GetSpatialRef();
6114 8 : if (poSrcSRS && poSrcSRS->IsGeographic())
6115 : {
6116 2 : double maxLat = adfSrcGeoTransform[3];
6117 2 : double minLat = adfSrcGeoTransform[3] +
6118 2 : poSrcDS->GetRasterYSize() * adfSrcGeoTransform[5];
6119 : // Corresponds to the latitude of below MAX_GM
6120 2 : constexpr double MAX_LAT = 85.0511287798066;
6121 2 : bool bModified = false;
6122 2 : if (maxLat > MAX_LAT)
6123 : {
6124 2 : maxLat = MAX_LAT;
6125 2 : bModified = true;
6126 : }
6127 2 : if (minLat < -MAX_LAT)
6128 : {
6129 2 : minLat = -MAX_LAT;
6130 2 : bModified = true;
6131 : }
6132 2 : if (bModified)
6133 : {
6134 4 : CPLStringList aosOptions;
6135 2 : aosOptions.AddString("-of");
6136 2 : aosOptions.AddString("VRT");
6137 2 : aosOptions.AddString("-projwin");
6138 : aosOptions.AddString(
6139 2 : CPLSPrintf("%.17g", adfSrcGeoTransform[0]));
6140 2 : aosOptions.AddString(CPLSPrintf("%.17g", maxLat));
6141 : aosOptions.AddString(
6142 2 : CPLSPrintf("%.17g", adfSrcGeoTransform[0] +
6143 2 : poSrcDS->GetRasterXSize() *
6144 2 : adfSrcGeoTransform[1]));
6145 2 : aosOptions.AddString(CPLSPrintf("%.17g", minLat));
6146 : auto psOptions =
6147 2 : GDALTranslateOptionsNew(aosOptions.List(), nullptr);
6148 2 : poTmpDS.reset(GDALDataset::FromHandle(GDALTranslate(
6149 : "", GDALDataset::ToHandle(poSrcDS), psOptions, nullptr)));
6150 2 : GDALTranslateOptionsFree(psOptions);
6151 2 : if (poTmpDS)
6152 : {
6153 2 : bEPSG3857Adjust = true;
6154 2 : hTransformArg = GDALCreateGenImgProjTransformer2(
6155 2 : GDALDataset::FromHandle(poTmpDS.get()), nullptr,
6156 : papszTO);
6157 : }
6158 : }
6159 : }
6160 : }
6161 22 : if (hTransformArg == nullptr)
6162 : {
6163 : hTransformArg =
6164 20 : GDALCreateGenImgProjTransformer2(poSrcDS, nullptr, papszTO);
6165 : }
6166 :
6167 22 : if (hTransformArg == nullptr)
6168 : {
6169 1 : CPLFree(pszWKT);
6170 1 : CSLDestroy(papszTO);
6171 1 : return nullptr;
6172 : }
6173 :
6174 21 : GDALTransformerInfo *psInfo =
6175 : static_cast<GDALTransformerInfo *>(hTransformArg);
6176 : double adfGeoTransform[6];
6177 : double adfExtent[4];
6178 : int nXSize, nYSize;
6179 :
6180 21 : if (GDALSuggestedWarpOutput2(poSrcDS, psInfo->pfnTransform, hTransformArg,
6181 : adfGeoTransform, &nXSize, &nYSize, adfExtent,
6182 21 : 0) != CE_None)
6183 : {
6184 0 : CPLFree(pszWKT);
6185 0 : CSLDestroy(papszTO);
6186 0 : GDALDestroyGenImgProjTransformer(hTransformArg);
6187 0 : return nullptr;
6188 : }
6189 :
6190 21 : GDALDestroyGenImgProjTransformer(hTransformArg);
6191 21 : hTransformArg = nullptr;
6192 21 : poTmpDS.reset();
6193 :
6194 21 : if (bEPSG3857Adjust)
6195 : {
6196 2 : constexpr double SPHERICAL_RADIUS = 6378137.0;
6197 2 : constexpr double MAX_GM =
6198 : SPHERICAL_RADIUS * M_PI; // 20037508.342789244
6199 2 : double maxNorthing = adfGeoTransform[3];
6200 2 : double minNorthing = adfGeoTransform[3] + adfGeoTransform[5] * nYSize;
6201 2 : bool bChanged = false;
6202 2 : if (maxNorthing > MAX_GM)
6203 : {
6204 2 : bChanged = true;
6205 2 : maxNorthing = MAX_GM;
6206 : }
6207 2 : if (minNorthing < -MAX_GM)
6208 : {
6209 2 : bChanged = true;
6210 2 : minNorthing = -MAX_GM;
6211 : }
6212 2 : if (bChanged)
6213 : {
6214 2 : adfGeoTransform[3] = maxNorthing;
6215 2 : nYSize =
6216 2 : int((maxNorthing - minNorthing) / (-adfGeoTransform[5]) + 0.5);
6217 2 : adfExtent[1] = maxNorthing + nYSize * adfGeoTransform[5];
6218 2 : adfExtent[3] = maxNorthing;
6219 : }
6220 : }
6221 :
6222 21 : double dfComputedRes = adfGeoTransform[1];
6223 21 : double dfPrevRes = 0.0;
6224 21 : double dfRes = 0.0;
6225 21 : int nZoomLevel = 0; // Used after for.
6226 21 : const char *pszZoomLevel = CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
6227 21 : if (pszZoomLevel)
6228 : {
6229 2 : nZoomLevel = atoi(pszZoomLevel);
6230 :
6231 2 : int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
6232 2 : while ((1 << nMaxZoomLevelForThisTM) >
6233 4 : INT_MAX / poTS->nTileXCountZoomLevel0 ||
6234 2 : (1 << nMaxZoomLevelForThisTM) >
6235 2 : INT_MAX / poTS->nTileYCountZoomLevel0)
6236 : {
6237 0 : --nMaxZoomLevelForThisTM;
6238 : }
6239 :
6240 2 : if (nZoomLevel < 0 || nZoomLevel > nMaxZoomLevelForThisTM)
6241 : {
6242 1 : CPLError(CE_Failure, CPLE_AppDefined,
6243 : "ZOOM_LEVEL = %s is invalid. It should be in [0,%d] range",
6244 : pszZoomLevel, nMaxZoomLevelForThisTM);
6245 1 : CPLFree(pszWKT);
6246 1 : CSLDestroy(papszTO);
6247 1 : return nullptr;
6248 : }
6249 : }
6250 : else
6251 : {
6252 171 : for (; nZoomLevel < MAX_ZOOM_LEVEL; nZoomLevel++)
6253 : {
6254 171 : dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
6255 171 : if (dfComputedRes > dfRes ||
6256 152 : fabs(dfComputedRes - dfRes) / dfRes <= 1e-8)
6257 : break;
6258 152 : dfPrevRes = dfRes;
6259 : }
6260 38 : if (nZoomLevel == MAX_ZOOM_LEVEL ||
6261 38 : (1 << nZoomLevel) > INT_MAX / poTS->nTileXCountZoomLevel0 ||
6262 19 : (1 << nZoomLevel) > INT_MAX / poTS->nTileYCountZoomLevel0)
6263 : {
6264 0 : CPLError(CE_Failure, CPLE_AppDefined,
6265 : "Could not find an appropriate zoom level");
6266 0 : CPLFree(pszWKT);
6267 0 : CSLDestroy(papszTO);
6268 0 : return nullptr;
6269 : }
6270 :
6271 19 : if (nZoomLevel > 0 && fabs(dfComputedRes - dfRes) / dfRes > 1e-8)
6272 : {
6273 17 : const char *pszZoomLevelStrategy = CSLFetchNameValueDef(
6274 : papszOptions, "ZOOM_LEVEL_STRATEGY", "AUTO");
6275 17 : if (EQUAL(pszZoomLevelStrategy, "LOWER"))
6276 : {
6277 1 : nZoomLevel--;
6278 : }
6279 16 : else if (EQUAL(pszZoomLevelStrategy, "UPPER"))
6280 : {
6281 : /* do nothing */
6282 : }
6283 : else
6284 : {
6285 15 : if (dfPrevRes / dfComputedRes < dfComputedRes / dfRes)
6286 13 : nZoomLevel--;
6287 : }
6288 : }
6289 : }
6290 :
6291 20 : dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
6292 :
6293 20 : double dfMinX = adfExtent[0];
6294 20 : double dfMinY = adfExtent[1];
6295 20 : double dfMaxX = adfExtent[2];
6296 20 : double dfMaxY = adfExtent[3];
6297 :
6298 20 : nXSize = static_cast<int>(0.5 + (dfMaxX - dfMinX) / dfRes);
6299 20 : nYSize = static_cast<int>(0.5 + (dfMaxY - dfMinY) / dfRes);
6300 20 : adfGeoTransform[1] = dfRes;
6301 20 : adfGeoTransform[5] = -dfRes;
6302 :
6303 20 : const GDALDataType eDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
6304 20 : int nTargetBands = nBands;
6305 : /* For grey level or RGB, if there's reprojection involved, add an alpha */
6306 : /* channel */
6307 37 : if (eDT == GDT_Byte &&
6308 13 : ((nBands == 1 &&
6309 17 : poSrcDS->GetRasterBand(1)->GetColorTable() == nullptr) ||
6310 : nBands == 3))
6311 : {
6312 30 : OGRSpatialReference oSrcSRS;
6313 15 : oSrcSRS.SetFromUserInput(poSrcDS->GetProjectionRef());
6314 15 : oSrcSRS.AutoIdentifyEPSG();
6315 30 : if (oSrcSRS.GetAuthorityCode(nullptr) == nullptr ||
6316 15 : atoi(oSrcSRS.GetAuthorityCode(nullptr)) != nEPSGCode)
6317 : {
6318 13 : nTargetBands++;
6319 : }
6320 : }
6321 :
6322 20 : GDALResampleAlg eResampleAlg = GRA_Bilinear;
6323 20 : const char *pszResampling = CSLFetchNameValue(papszOptions, "RESAMPLING");
6324 20 : if (pszResampling)
6325 : {
6326 6 : for (size_t iAlg = 0;
6327 6 : iAlg < sizeof(asResamplingAlg) / sizeof(asResamplingAlg[0]);
6328 : iAlg++)
6329 : {
6330 6 : if (EQUAL(pszResampling, asResamplingAlg[iAlg].pszName))
6331 : {
6332 3 : eResampleAlg = asResamplingAlg[iAlg].eResampleAlg;
6333 3 : break;
6334 : }
6335 : }
6336 : }
6337 :
6338 16 : if (nBands == 1 && poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr &&
6339 36 : eResampleAlg != GRA_NearestNeighbour && eResampleAlg != GRA_Mode)
6340 : {
6341 0 : CPLError(
6342 : CE_Warning, CPLE_AppDefined,
6343 : "Input dataset has a color table, which will likely lead to "
6344 : "bad results when using a resampling method other than "
6345 : "nearest neighbour or mode. Converting the dataset to 24/32 bit "
6346 : "(e.g. with gdal_translate -expand rgb/rgba) is advised.");
6347 : }
6348 :
6349 20 : GDALGeoPackageDataset *poDS = new GDALGeoPackageDataset();
6350 20 : if (!(poDS->Create(pszFilename, nXSize, nYSize, nTargetBands, eDT,
6351 : apszUpdatedOptions)))
6352 : {
6353 1 : delete poDS;
6354 1 : CPLFree(pszWKT);
6355 1 : CSLDestroy(papszTO);
6356 1 : return nullptr;
6357 : }
6358 :
6359 : // Assign nodata values before the SetGeoTransform call.
6360 : // SetGeoTransform will trigger creation of the overview datasets for each
6361 : // zoom level and at that point the nodata value needs to be known.
6362 19 : int bHasNoData = FALSE;
6363 : double dfNoDataValue =
6364 19 : poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
6365 19 : if (eDT != GDT_Byte && bHasNoData)
6366 : {
6367 3 : poDS->GetRasterBand(1)->SetNoDataValue(dfNoDataValue);
6368 : }
6369 :
6370 19 : poDS->SetGeoTransform(adfGeoTransform);
6371 19 : poDS->SetProjection(pszWKT);
6372 19 : CPLFree(pszWKT);
6373 19 : pszWKT = nullptr;
6374 24 : if (nTargetBands == 1 && nBands == 1 &&
6375 5 : poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr)
6376 : {
6377 2 : poDS->GetRasterBand(1)->SetColorTable(
6378 1 : poSrcDS->GetRasterBand(1)->GetColorTable());
6379 : }
6380 :
6381 19 : hTransformArg = GDALCreateGenImgProjTransformer2(poSrcDS, poDS, papszTO);
6382 19 : CSLDestroy(papszTO);
6383 19 : if (hTransformArg == nullptr)
6384 : {
6385 0 : delete poDS;
6386 0 : return nullptr;
6387 : }
6388 :
6389 19 : poDS->SetMetadata(poSrcDS->GetMetadata());
6390 :
6391 : /* -------------------------------------------------------------------- */
6392 : /* Warp the transformer with a linear approximator */
6393 : /* -------------------------------------------------------------------- */
6394 19 : hTransformArg = GDALCreateApproxTransformer(GDALGenImgProjTransform,
6395 : hTransformArg, 0.125);
6396 19 : GDALApproxTransformerOwnsSubtransformer(hTransformArg, TRUE);
6397 :
6398 : /* -------------------------------------------------------------------- */
6399 : /* Setup warp options. */
6400 : /* -------------------------------------------------------------------- */
6401 19 : GDALWarpOptions *psWO = GDALCreateWarpOptions();
6402 :
6403 19 : psWO->papszWarpOptions = CSLSetNameValue(nullptr, "OPTIMIZE_SIZE", "YES");
6404 19 : psWO->papszWarpOptions =
6405 19 : CSLSetNameValue(psWO->papszWarpOptions, "SAMPLE_GRID", "YES");
6406 19 : if (bHasNoData)
6407 : {
6408 3 : if (dfNoDataValue == 0.0)
6409 : {
6410 : // Do not initialize in the case where nodata != 0, since we
6411 : // want the GeoPackage driver to return empty tiles at the nodata
6412 : // value instead of 0 as GDAL core would
6413 0 : psWO->papszWarpOptions =
6414 0 : CSLSetNameValue(psWO->papszWarpOptions, "INIT_DEST", "0");
6415 : }
6416 :
6417 3 : psWO->padfSrcNoDataReal =
6418 3 : static_cast<double *>(CPLMalloc(sizeof(double)));
6419 3 : psWO->padfSrcNoDataReal[0] = dfNoDataValue;
6420 :
6421 3 : psWO->padfDstNoDataReal =
6422 3 : static_cast<double *>(CPLMalloc(sizeof(double)));
6423 3 : psWO->padfDstNoDataReal[0] = dfNoDataValue;
6424 : }
6425 19 : psWO->eWorkingDataType = eDT;
6426 19 : psWO->eResampleAlg = eResampleAlg;
6427 :
6428 19 : psWO->hSrcDS = poSrcDS;
6429 19 : psWO->hDstDS = poDS;
6430 :
6431 19 : psWO->pfnTransformer = GDALApproxTransform;
6432 19 : psWO->pTransformerArg = hTransformArg;
6433 :
6434 19 : psWO->pfnProgress = pfnProgress;
6435 19 : psWO->pProgressArg = pProgressData;
6436 :
6437 : /* -------------------------------------------------------------------- */
6438 : /* Setup band mapping. */
6439 : /* -------------------------------------------------------------------- */
6440 :
6441 19 : if (nBands == 2 || nBands == 4)
6442 1 : psWO->nBandCount = nBands - 1;
6443 : else
6444 18 : psWO->nBandCount = nBands;
6445 :
6446 19 : psWO->panSrcBands =
6447 19 : static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
6448 19 : psWO->panDstBands =
6449 19 : static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
6450 :
6451 46 : for (int i = 0; i < psWO->nBandCount; i++)
6452 : {
6453 27 : psWO->panSrcBands[i] = i + 1;
6454 27 : psWO->panDstBands[i] = i + 1;
6455 : }
6456 :
6457 19 : if (nBands == 2 || nBands == 4)
6458 : {
6459 1 : psWO->nSrcAlphaBand = nBands;
6460 : }
6461 19 : if (nTargetBands == 2 || nTargetBands == 4)
6462 : {
6463 13 : psWO->nDstAlphaBand = nTargetBands;
6464 : }
6465 :
6466 : /* -------------------------------------------------------------------- */
6467 : /* Initialize and execute the warp. */
6468 : /* -------------------------------------------------------------------- */
6469 19 : GDALWarpOperation oWO;
6470 :
6471 19 : CPLErr eErr = oWO.Initialize(psWO);
6472 19 : if (eErr == CE_None)
6473 : {
6474 : /*if( bMulti )
6475 : eErr = oWO.ChunkAndWarpMulti( 0, 0, nXSize, nYSize );
6476 : else*/
6477 19 : eErr = oWO.ChunkAndWarpImage(0, 0, nXSize, nYSize);
6478 : }
6479 19 : if (eErr != CE_None)
6480 : {
6481 0 : delete poDS;
6482 0 : poDS = nullptr;
6483 : }
6484 :
6485 19 : GDALDestroyTransformer(hTransformArg);
6486 19 : GDALDestroyWarpOptions(psWO);
6487 :
6488 19 : if (poDS)
6489 19 : poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
6490 :
6491 19 : return poDS;
6492 : }
6493 :
6494 : /************************************************************************/
6495 : /* ParseCompressionOptions() */
6496 : /************************************************************************/
6497 :
6498 424 : void GDALGeoPackageDataset::ParseCompressionOptions(char **papszOptions)
6499 : {
6500 424 : const char *pszZLevel = CSLFetchNameValue(papszOptions, "ZLEVEL");
6501 424 : if (pszZLevel)
6502 0 : m_nZLevel = atoi(pszZLevel);
6503 :
6504 424 : const char *pszQuality = CSLFetchNameValue(papszOptions, "QUALITY");
6505 424 : if (pszQuality)
6506 0 : m_nQuality = atoi(pszQuality);
6507 :
6508 424 : const char *pszDither = CSLFetchNameValue(papszOptions, "DITHER");
6509 424 : if (pszDither)
6510 0 : m_bDither = CPLTestBool(pszDither);
6511 424 : }
6512 :
6513 : /************************************************************************/
6514 : /* RegisterWebPExtension() */
6515 : /************************************************************************/
6516 :
6517 11 : bool GDALGeoPackageDataset::RegisterWebPExtension()
6518 : {
6519 11 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
6520 0 : return false;
6521 :
6522 11 : char *pszSQL = sqlite3_mprintf(
6523 : "INSERT INTO gpkg_extensions "
6524 : "(table_name, column_name, extension_name, definition, scope) "
6525 : "VALUES "
6526 : "('%q', 'tile_data', 'gpkg_webp', "
6527 : "'http://www.geopackage.org/spec120/#extension_tiles_webp', "
6528 : "'read-write')",
6529 : m_osRasterTable.c_str());
6530 11 : const OGRErr eErr = SQLCommand(hDB, pszSQL);
6531 11 : sqlite3_free(pszSQL);
6532 :
6533 11 : return OGRERR_NONE == eErr;
6534 : }
6535 :
6536 : /************************************************************************/
6537 : /* RegisterZoomOtherExtension() */
6538 : /************************************************************************/
6539 :
6540 1 : bool GDALGeoPackageDataset::RegisterZoomOtherExtension()
6541 : {
6542 1 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
6543 0 : return false;
6544 :
6545 1 : char *pszSQL = sqlite3_mprintf(
6546 : "INSERT INTO gpkg_extensions "
6547 : "(table_name, column_name, extension_name, definition, scope) "
6548 : "VALUES "
6549 : "('%q', 'tile_data', 'gpkg_zoom_other', "
6550 : "'http://www.geopackage.org/spec120/#extension_zoom_other_intervals', "
6551 : "'read-write')",
6552 : m_osRasterTable.c_str());
6553 1 : const OGRErr eErr = SQLCommand(hDB, pszSQL);
6554 1 : sqlite3_free(pszSQL);
6555 1 : return OGRERR_NONE == eErr;
6556 : }
6557 :
6558 : /************************************************************************/
6559 : /* GetLayer() */
6560 : /************************************************************************/
6561 :
6562 13429 : OGRLayer *GDALGeoPackageDataset::GetLayer(int iLayer)
6563 :
6564 : {
6565 13429 : if (iLayer < 0 || iLayer >= m_nLayers)
6566 6 : return nullptr;
6567 : else
6568 13423 : return m_papoLayers[iLayer];
6569 : }
6570 :
6571 : /************************************************************************/
6572 : /* LaunderName() */
6573 : /************************************************************************/
6574 :
6575 : /** Launder identifiers (table, column names) according to guidance at
6576 : * https://www.geopackage.org/guidance/getting-started.html:
6577 : * "For maximum interoperability, start your database identifiers (table names,
6578 : * column names, etc.) with a lowercase character and only use lowercase
6579 : * characters, numbers 0-9, and underscores (_)."
6580 : */
6581 :
6582 : /* static */
6583 5 : std::string GDALGeoPackageDataset::LaunderName(const std::string &osStr)
6584 : {
6585 5 : char *pszASCII = CPLUTF8ForceToASCII(osStr.c_str(), '_');
6586 10 : const std::string osStrASCII(pszASCII);
6587 5 : CPLFree(pszASCII);
6588 :
6589 10 : std::string osRet;
6590 5 : osRet.reserve(osStrASCII.size());
6591 :
6592 29 : for (size_t i = 0; i < osStrASCII.size(); ++i)
6593 : {
6594 24 : if (osRet.empty())
6595 : {
6596 5 : if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
6597 : {
6598 2 : osRet += (osStrASCII[i] - 'A' + 'a');
6599 : }
6600 3 : else if (osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z')
6601 : {
6602 2 : osRet += osStrASCII[i];
6603 : }
6604 : else
6605 : {
6606 1 : continue;
6607 : }
6608 : }
6609 19 : else if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
6610 : {
6611 11 : osRet += (osStrASCII[i] - 'A' + 'a');
6612 : }
6613 9 : else if ((osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z') ||
6614 14 : (osStrASCII[i] >= '0' && osStrASCII[i] <= '9') ||
6615 5 : osStrASCII[i] == '_')
6616 : {
6617 7 : osRet += osStrASCII[i];
6618 : }
6619 : else
6620 : {
6621 1 : osRet += '_';
6622 : }
6623 : }
6624 :
6625 5 : if (osRet.empty() && !osStrASCII.empty())
6626 2 : return LaunderName(std::string("x").append(osStrASCII));
6627 :
6628 4 : if (osRet != osStr)
6629 : {
6630 3 : CPLDebug("PG", "LaunderName('%s') -> '%s'", osStr.c_str(),
6631 : osRet.c_str());
6632 : }
6633 :
6634 4 : return osRet;
6635 : }
6636 :
6637 : /************************************************************************/
6638 : /* ICreateLayer() */
6639 : /************************************************************************/
6640 :
6641 : OGRLayer *
6642 650 : GDALGeoPackageDataset::ICreateLayer(const char *pszLayerName,
6643 : const OGRGeomFieldDefn *poSrcGeomFieldDefn,
6644 : CSLConstList papszOptions)
6645 : {
6646 : /* -------------------------------------------------------------------- */
6647 : /* Verify we are in update mode. */
6648 : /* -------------------------------------------------------------------- */
6649 650 : if (!GetUpdate())
6650 : {
6651 0 : CPLError(CE_Failure, CPLE_NoWriteAccess,
6652 : "Data source %s opened read-only.\n"
6653 : "New layer %s cannot be created.\n",
6654 : m_pszFilename, pszLayerName);
6655 :
6656 0 : return nullptr;
6657 : }
6658 :
6659 : const bool bLaunder =
6660 650 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "LAUNDER", "NO"));
6661 : const std::string osTableName(bLaunder ? LaunderName(pszLayerName)
6662 1950 : : std::string(pszLayerName));
6663 :
6664 : const auto eGType =
6665 650 : poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone;
6666 : const auto poSpatialRef =
6667 650 : poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr;
6668 :
6669 650 : if (!m_bHasGPKGGeometryColumns)
6670 : {
6671 1 : if (SQLCommand(hDB, pszCREATE_GPKG_GEOMETRY_COLUMNS) != OGRERR_NONE)
6672 : {
6673 0 : return nullptr;
6674 : }
6675 1 : m_bHasGPKGGeometryColumns = true;
6676 : }
6677 :
6678 : // Check identifier unicity
6679 650 : const char *pszIdentifier = CSLFetchNameValue(papszOptions, "IDENTIFIER");
6680 650 : if (pszIdentifier != nullptr && pszIdentifier[0] == '\0')
6681 0 : pszIdentifier = nullptr;
6682 650 : if (pszIdentifier != nullptr)
6683 : {
6684 13 : for (int i = 0; i < m_nLayers; ++i)
6685 : {
6686 : const char *pszOtherIdentifier =
6687 9 : m_papoLayers[i]->GetMetadataItem("IDENTIFIER");
6688 9 : if (pszOtherIdentifier == nullptr)
6689 6 : pszOtherIdentifier = m_papoLayers[i]->GetName();
6690 18 : if (pszOtherIdentifier != nullptr &&
6691 12 : EQUAL(pszOtherIdentifier, pszIdentifier) &&
6692 3 : !EQUAL(m_papoLayers[i]->GetName(), osTableName.c_str()))
6693 : {
6694 2 : CPLError(CE_Failure, CPLE_AppDefined,
6695 : "Identifier %s is already used by table %s",
6696 2 : pszIdentifier, m_papoLayers[i]->GetName());
6697 3 : return nullptr;
6698 : }
6699 : }
6700 :
6701 : // In case there would be table in gpkg_contents not listed as a
6702 : // vector layer
6703 4 : char *pszSQL = sqlite3_mprintf(
6704 : "SELECT table_name FROM gpkg_contents WHERE identifier = '%q' "
6705 : "LIMIT 2",
6706 : pszIdentifier);
6707 4 : auto oResult = SQLQuery(hDB, pszSQL);
6708 4 : sqlite3_free(pszSQL);
6709 8 : if (oResult && oResult->RowCount() > 0 &&
6710 9 : oResult->GetValue(0, 0) != nullptr &&
6711 1 : !EQUAL(oResult->GetValue(0, 0), osTableName.c_str()))
6712 : {
6713 1 : CPLError(CE_Failure, CPLE_AppDefined,
6714 : "Identifier %s is already used by table %s", pszIdentifier,
6715 : oResult->GetValue(0, 0));
6716 1 : return nullptr;
6717 : }
6718 : }
6719 :
6720 : /* Read GEOMETRY_NAME option */
6721 : const char *pszGeomColumnName =
6722 647 : CSLFetchNameValue(papszOptions, "GEOMETRY_NAME");
6723 647 : if (pszGeomColumnName == nullptr) /* deprecated name */
6724 611 : pszGeomColumnName = CSLFetchNameValue(papszOptions, "GEOMETRY_COLUMN");
6725 647 : if (pszGeomColumnName == nullptr && poSrcGeomFieldDefn)
6726 : {
6727 565 : pszGeomColumnName = poSrcGeomFieldDefn->GetNameRef();
6728 565 : if (pszGeomColumnName && pszGeomColumnName[0] == 0)
6729 561 : pszGeomColumnName = nullptr;
6730 : }
6731 647 : if (pszGeomColumnName == nullptr)
6732 607 : pszGeomColumnName = "geom";
6733 : const bool bGeomNullable =
6734 647 : CPLFetchBool(papszOptions, "GEOMETRY_NULLABLE", true);
6735 :
6736 : /* Read FID option */
6737 647 : const char *pszFIDColumnName = CSLFetchNameValue(papszOptions, "FID");
6738 647 : if (pszFIDColumnName == nullptr)
6739 623 : pszFIDColumnName = "fid";
6740 :
6741 647 : if (CPLTestBool(CPLGetConfigOption("GPKG_NAME_CHECK", "YES")))
6742 : {
6743 647 : if (strspn(pszFIDColumnName, "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") > 0)
6744 : {
6745 0 : CPLError(CE_Failure, CPLE_AppDefined,
6746 : "The primary key (%s) name may not contain special "
6747 : "characters or spaces",
6748 : pszFIDColumnName);
6749 0 : return nullptr;
6750 : }
6751 :
6752 : /* Avoiding gpkg prefixes is not an official requirement, but seems wise
6753 : */
6754 647 : if (STARTS_WITH(osTableName.c_str(), "gpkg"))
6755 : {
6756 0 : CPLError(CE_Failure, CPLE_AppDefined,
6757 : "The layer name may not begin with 'gpkg' as it is a "
6758 : "reserved geopackage prefix");
6759 0 : return nullptr;
6760 : }
6761 :
6762 : /* Preemptively try and avoid sqlite3 syntax errors due to */
6763 : /* illegal characters. */
6764 647 : if (strspn(osTableName.c_str(), "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") >
6765 : 0)
6766 : {
6767 0 : CPLError(
6768 : CE_Failure, CPLE_AppDefined,
6769 : "The layer name may not contain special characters or spaces");
6770 0 : return nullptr;
6771 : }
6772 : }
6773 :
6774 : /* Check for any existing layers that already use this name */
6775 836 : for (int iLayer = 0; iLayer < m_nLayers; iLayer++)
6776 : {
6777 190 : if (EQUAL(osTableName.c_str(), m_papoLayers[iLayer]->GetName()))
6778 : {
6779 : const char *pszOverwrite =
6780 2 : CSLFetchNameValue(papszOptions, "OVERWRITE");
6781 2 : if (pszOverwrite != nullptr && CPLTestBool(pszOverwrite))
6782 : {
6783 1 : DeleteLayer(iLayer);
6784 : }
6785 : else
6786 : {
6787 1 : CPLError(CE_Failure, CPLE_AppDefined,
6788 : "Layer %s already exists, CreateLayer failed.\n"
6789 : "Use the layer creation option OVERWRITE=YES to "
6790 : "replace it.",
6791 : osTableName.c_str());
6792 1 : return nullptr;
6793 : }
6794 : }
6795 : }
6796 :
6797 646 : if (m_nLayers == 1)
6798 : {
6799 : // Async RTree building doesn't play well with multiple layer:
6800 : // SQLite3 locks being hold for a long time, random failed commits,
6801 : // etc.
6802 65 : m_papoLayers[0]->FinishOrDisableThreadedRTree();
6803 : }
6804 :
6805 : /* Create a blank layer. */
6806 : auto poLayer = std::unique_ptr<OGRGeoPackageTableLayer>(
6807 1292 : new OGRGeoPackageTableLayer(this, osTableName.c_str()));
6808 :
6809 646 : OGRSpatialReference *poSRS = nullptr;
6810 646 : if (poSpatialRef)
6811 : {
6812 201 : poSRS = poSpatialRef->Clone();
6813 201 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
6814 : }
6815 1293 : poLayer->SetCreationParameters(
6816 : eGType,
6817 647 : bLaunder ? LaunderName(pszGeomColumnName).c_str() : pszGeomColumnName,
6818 : bGeomNullable, poSRS, CSLFetchNameValue(papszOptions, "SRID"),
6819 1292 : poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetCoordinatePrecision()
6820 : : OGRGeomCoordinatePrecision(),
6821 646 : CPLTestBool(
6822 : CSLFetchNameValueDef(papszOptions, "DISCARD_COORD_LSB", "NO")),
6823 646 : CPLTestBool(CSLFetchNameValueDef(
6824 : papszOptions, "UNDO_DISCARD_COORD_LSB_ON_READING", "NO")),
6825 647 : bLaunder ? LaunderName(pszFIDColumnName).c_str() : pszFIDColumnName,
6826 : pszIdentifier, CSLFetchNameValue(papszOptions, "DESCRIPTION"));
6827 646 : if (poSRS)
6828 : {
6829 201 : poSRS->Release();
6830 : }
6831 :
6832 646 : poLayer->SetLaunder(bLaunder);
6833 :
6834 : /* Should we create a spatial index ? */
6835 646 : const char *pszSI = CSLFetchNameValue(papszOptions, "SPATIAL_INDEX");
6836 646 : int bCreateSpatialIndex = (pszSI == nullptr || CPLTestBool(pszSI));
6837 646 : if (eGType != wkbNone && bCreateSpatialIndex)
6838 : {
6839 578 : poLayer->SetDeferredSpatialIndexCreation(true);
6840 : }
6841 :
6842 646 : poLayer->SetPrecisionFlag(CPLFetchBool(papszOptions, "PRECISION", true));
6843 646 : poLayer->SetTruncateFieldsFlag(
6844 646 : CPLFetchBool(papszOptions, "TRUNCATE_FIELDS", false));
6845 646 : if (eGType == wkbNone)
6846 : {
6847 46 : const char *pszASpatialVariant = CSLFetchNameValueDef(
6848 : papszOptions, "ASPATIAL_VARIANT",
6849 46 : m_bNonSpatialTablesNonRegisteredInGpkgContentsFound
6850 : ? "NOT_REGISTERED"
6851 : : "GPKG_ATTRIBUTES");
6852 46 : GPKGASpatialVariant eASpatialVariant = GPKG_ATTRIBUTES;
6853 46 : if (EQUAL(pszASpatialVariant, "GPKG_ATTRIBUTES"))
6854 34 : eASpatialVariant = GPKG_ATTRIBUTES;
6855 12 : else if (EQUAL(pszASpatialVariant, "OGR_ASPATIAL"))
6856 : {
6857 0 : CPLError(CE_Failure, CPLE_NotSupported,
6858 : "ASPATIAL_VARIANT=OGR_ASPATIAL is no longer supported");
6859 0 : return nullptr;
6860 : }
6861 12 : else if (EQUAL(pszASpatialVariant, "NOT_REGISTERED"))
6862 12 : eASpatialVariant = NOT_REGISTERED;
6863 : else
6864 : {
6865 0 : CPLError(CE_Failure, CPLE_NotSupported,
6866 : "Unsupported value for ASPATIAL_VARIANT: %s",
6867 : pszASpatialVariant);
6868 0 : return nullptr;
6869 : }
6870 46 : poLayer->SetASpatialVariant(eASpatialVariant);
6871 : }
6872 :
6873 : const char *pszDateTimePrecision =
6874 646 : CSLFetchNameValueDef(papszOptions, "DATETIME_PRECISION", "AUTO");
6875 646 : if (EQUAL(pszDateTimePrecision, "MILLISECOND"))
6876 : {
6877 2 : poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
6878 : }
6879 644 : else if (EQUAL(pszDateTimePrecision, "SECOND"))
6880 : {
6881 1 : if (m_nUserVersion < GPKG_1_4_VERSION)
6882 0 : CPLError(
6883 : CE_Warning, CPLE_AppDefined,
6884 : "DATETIME_PRECISION=SECOND is only valid since GeoPackage 1.4");
6885 1 : poLayer->SetDateTimePrecision(OGRISO8601Precision::SECOND);
6886 : }
6887 643 : else if (EQUAL(pszDateTimePrecision, "MINUTE"))
6888 : {
6889 1 : if (m_nUserVersion < GPKG_1_4_VERSION)
6890 0 : CPLError(
6891 : CE_Warning, CPLE_AppDefined,
6892 : "DATETIME_PRECISION=MINUTE is only valid since GeoPackage 1.4");
6893 1 : poLayer->SetDateTimePrecision(OGRISO8601Precision::MINUTE);
6894 : }
6895 642 : else if (EQUAL(pszDateTimePrecision, "AUTO"))
6896 : {
6897 641 : if (m_nUserVersion < GPKG_1_4_VERSION)
6898 630 : poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
6899 : }
6900 : else
6901 : {
6902 1 : CPLError(CE_Failure, CPLE_NotSupported,
6903 : "Unsupported value for DATETIME_PRECISION: %s",
6904 : pszDateTimePrecision);
6905 1 : return nullptr;
6906 : }
6907 :
6908 : // If there was an ogr_empty_table table, we can remove it
6909 : // But do it at dataset closing, otherwise locking performance issues
6910 : // can arise (probably when transactions are used).
6911 645 : m_bRemoveOGREmptyTable = true;
6912 :
6913 1290 : m_papoLayers = static_cast<OGRGeoPackageTableLayer **>(CPLRealloc(
6914 645 : m_papoLayers, sizeof(OGRGeoPackageTableLayer *) * (m_nLayers + 1)));
6915 645 : auto poRet = poLayer.release();
6916 645 : m_papoLayers[m_nLayers] = poRet;
6917 645 : m_nLayers++;
6918 645 : return poRet;
6919 : }
6920 :
6921 : /************************************************************************/
6922 : /* FindLayerIndex() */
6923 : /************************************************************************/
6924 :
6925 27 : int GDALGeoPackageDataset::FindLayerIndex(const char *pszLayerName)
6926 :
6927 : {
6928 42 : for (int iLayer = 0; iLayer < m_nLayers; iLayer++)
6929 : {
6930 28 : if (EQUAL(pszLayerName, m_papoLayers[iLayer]->GetName()))
6931 13 : return iLayer;
6932 : }
6933 14 : return -1;
6934 : }
6935 :
6936 : /************************************************************************/
6937 : /* DeleteLayerCommon() */
6938 : /************************************************************************/
6939 :
6940 36 : OGRErr GDALGeoPackageDataset::DeleteLayerCommon(const char *pszLayerName)
6941 : {
6942 : // Temporary remove foreign key checks
6943 : const GPKGTemporaryForeignKeyCheckDisabler
6944 36 : oGPKGTemporaryForeignKeyCheckDisabler(this);
6945 :
6946 36 : char *pszSQL = sqlite3_mprintf(
6947 : "DELETE FROM gpkg_contents WHERE lower(table_name) = lower('%q')",
6948 : pszLayerName);
6949 36 : OGRErr eErr = SQLCommand(hDB, pszSQL);
6950 36 : sqlite3_free(pszSQL);
6951 :
6952 36 : if (eErr == OGRERR_NONE && HasExtensionsTable())
6953 : {
6954 34 : pszSQL = sqlite3_mprintf(
6955 : "DELETE FROM gpkg_extensions WHERE lower(table_name) = lower('%q')",
6956 : pszLayerName);
6957 34 : eErr = SQLCommand(hDB, pszSQL);
6958 34 : sqlite3_free(pszSQL);
6959 : }
6960 :
6961 36 : if (eErr == OGRERR_NONE && HasMetadataTables())
6962 : {
6963 : // Delete from gpkg_metadata metadata records that are only referenced
6964 : // by the table we are about to drop
6965 8 : pszSQL = sqlite3_mprintf(
6966 : "DELETE FROM gpkg_metadata WHERE id IN ("
6967 : "SELECT DISTINCT md_file_id FROM "
6968 : "gpkg_metadata_reference WHERE "
6969 : "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
6970 : "AND id NOT IN ("
6971 : "SELECT DISTINCT md_file_id FROM gpkg_metadata_reference WHERE "
6972 : "md_file_id IN (SELECT DISTINCT md_file_id FROM "
6973 : "gpkg_metadata_reference WHERE "
6974 : "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
6975 : "AND lower(table_name) <> lower('%q'))",
6976 : pszLayerName, pszLayerName, pszLayerName);
6977 8 : eErr = SQLCommand(hDB, pszSQL);
6978 8 : sqlite3_free(pszSQL);
6979 :
6980 8 : if (eErr == OGRERR_NONE)
6981 : {
6982 : pszSQL =
6983 8 : sqlite3_mprintf("DELETE FROM gpkg_metadata_reference WHERE "
6984 : "lower(table_name) = lower('%q')",
6985 : pszLayerName);
6986 8 : eErr = SQLCommand(hDB, pszSQL);
6987 8 : sqlite3_free(pszSQL);
6988 : }
6989 : }
6990 :
6991 36 : if (eErr == OGRERR_NONE && HasGpkgextRelationsTable())
6992 : {
6993 : // Remove reference to potential corresponding mapping table in
6994 : // gpkg_extensions
6995 4 : pszSQL = sqlite3_mprintf(
6996 : "DELETE FROM gpkg_extensions WHERE "
6997 : "extension_name IN ('related_tables', "
6998 : "'gpkg_related_tables') AND lower(table_name) = "
6999 : "(SELECT lower(mapping_table_name) FROM gpkgext_relations WHERE "
7000 : "lower(base_table_name) = lower('%q') OR "
7001 : "lower(related_table_name) = lower('%q') OR "
7002 : "lower(mapping_table_name) = lower('%q'))",
7003 : pszLayerName, pszLayerName, pszLayerName);
7004 4 : eErr = SQLCommand(hDB, pszSQL);
7005 4 : sqlite3_free(pszSQL);
7006 :
7007 4 : if (eErr == OGRERR_NONE)
7008 : {
7009 : // Remove reference to potential corresponding mapping table in
7010 : // gpkgext_relations
7011 : pszSQL =
7012 4 : sqlite3_mprintf("DELETE FROM gpkgext_relations WHERE "
7013 : "lower(base_table_name) = lower('%q') OR "
7014 : "lower(related_table_name) = lower('%q') OR "
7015 : "lower(mapping_table_name) = lower('%q')",
7016 : pszLayerName, pszLayerName, pszLayerName);
7017 4 : eErr = SQLCommand(hDB, pszSQL);
7018 4 : sqlite3_free(pszSQL);
7019 : }
7020 :
7021 4 : if (eErr == OGRERR_NONE && HasExtensionsTable())
7022 : {
7023 : // If there is no longer any mapping table, then completely
7024 : // remove any reference to the extension in gpkg_extensions
7025 : // as mandated per the related table specification.
7026 : OGRErr err;
7027 4 : if (SQLGetInteger(hDB,
7028 : "SELECT COUNT(*) FROM gpkg_extensions WHERE "
7029 : "extension_name IN ('related_tables', "
7030 : "'gpkg_related_tables') AND "
7031 : "lower(table_name) != 'gpkgext_relations'",
7032 4 : &err) == 0)
7033 : {
7034 2 : eErr = SQLCommand(hDB, "DELETE FROM gpkg_extensions WHERE "
7035 : "extension_name IN ('related_tables', "
7036 : "'gpkg_related_tables')");
7037 : }
7038 :
7039 4 : ClearCachedRelationships();
7040 : }
7041 : }
7042 :
7043 36 : if (eErr == OGRERR_NONE)
7044 : {
7045 36 : pszSQL = sqlite3_mprintf("DROP TABLE \"%w\"", pszLayerName);
7046 36 : eErr = SQLCommand(hDB, pszSQL);
7047 36 : sqlite3_free(pszSQL);
7048 : }
7049 :
7050 : // Check foreign key integrity
7051 36 : if (eErr == OGRERR_NONE)
7052 : {
7053 36 : eErr = PragmaCheck("foreign_key_check", "", 0);
7054 : }
7055 :
7056 72 : return eErr;
7057 : }
7058 :
7059 : /************************************************************************/
7060 : /* DeleteLayer() */
7061 : /************************************************************************/
7062 :
7063 33 : OGRErr GDALGeoPackageDataset::DeleteLayer(int iLayer)
7064 : {
7065 33 : if (!GetUpdate() || iLayer < 0 || iLayer >= m_nLayers)
7066 2 : return OGRERR_FAILURE;
7067 :
7068 31 : m_papoLayers[iLayer]->ResetReading();
7069 31 : m_papoLayers[iLayer]->SyncToDisk();
7070 :
7071 62 : CPLString osLayerName = m_papoLayers[iLayer]->GetName();
7072 :
7073 31 : CPLDebug("GPKG", "DeleteLayer(%s)", osLayerName.c_str());
7074 :
7075 : // Temporary remove foreign key checks
7076 : const GPKGTemporaryForeignKeyCheckDisabler
7077 31 : oGPKGTemporaryForeignKeyCheckDisabler(this);
7078 :
7079 31 : OGRErr eErr = SoftStartTransaction();
7080 :
7081 31 : if (eErr == OGRERR_NONE)
7082 : {
7083 31 : if (m_papoLayers[iLayer]->HasSpatialIndex())
7084 28 : m_papoLayers[iLayer]->DropSpatialIndex();
7085 :
7086 : char *pszSQL =
7087 31 : sqlite3_mprintf("DELETE FROM gpkg_geometry_columns WHERE "
7088 : "lower(table_name) = lower('%q')",
7089 : osLayerName.c_str());
7090 31 : eErr = SQLCommand(hDB, pszSQL);
7091 31 : sqlite3_free(pszSQL);
7092 : }
7093 :
7094 31 : if (eErr == OGRERR_NONE && HasDataColumnsTable())
7095 : {
7096 1 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_data_columns WHERE "
7097 : "lower(table_name) = lower('%q')",
7098 : osLayerName.c_str());
7099 1 : eErr = SQLCommand(hDB, pszSQL);
7100 1 : sqlite3_free(pszSQL);
7101 : }
7102 :
7103 : #ifdef ENABLE_GPKG_OGR_CONTENTS
7104 31 : if (eErr == OGRERR_NONE && m_bHasGPKGOGRContents)
7105 : {
7106 31 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_ogr_contents WHERE "
7107 : "lower(table_name) = lower('%q')",
7108 : osLayerName.c_str());
7109 31 : eErr = SQLCommand(hDB, pszSQL);
7110 31 : sqlite3_free(pszSQL);
7111 : }
7112 : #endif
7113 :
7114 31 : if (eErr == OGRERR_NONE)
7115 : {
7116 31 : eErr = DeleteLayerCommon(osLayerName.c_str());
7117 : }
7118 :
7119 31 : if (eErr == OGRERR_NONE)
7120 : {
7121 31 : eErr = SoftCommitTransaction();
7122 31 : if (eErr == OGRERR_NONE)
7123 : {
7124 : /* Delete the layer object and remove the gap in the layers list */
7125 31 : delete m_papoLayers[iLayer];
7126 31 : memmove(m_papoLayers + iLayer, m_papoLayers + iLayer + 1,
7127 31 : sizeof(void *) * (m_nLayers - iLayer - 1));
7128 31 : m_nLayers--;
7129 : }
7130 : }
7131 : else
7132 : {
7133 0 : SoftRollbackTransaction();
7134 : }
7135 :
7136 31 : return eErr;
7137 : }
7138 :
7139 : /************************************************************************/
7140 : /* DeleteRasterLayer() */
7141 : /************************************************************************/
7142 :
7143 2 : OGRErr GDALGeoPackageDataset::DeleteRasterLayer(const char *pszLayerName)
7144 : {
7145 : // Temporary remove foreign key checks
7146 : const GPKGTemporaryForeignKeyCheckDisabler
7147 2 : oGPKGTemporaryForeignKeyCheckDisabler(this);
7148 :
7149 2 : OGRErr eErr = SoftStartTransaction();
7150 :
7151 2 : if (eErr == OGRERR_NONE)
7152 : {
7153 2 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix WHERE "
7154 : "lower(table_name) = lower('%q')",
7155 : pszLayerName);
7156 2 : eErr = SQLCommand(hDB, pszSQL);
7157 2 : sqlite3_free(pszSQL);
7158 : }
7159 :
7160 2 : if (eErr == OGRERR_NONE)
7161 : {
7162 2 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix_set WHERE "
7163 : "lower(table_name) = lower('%q')",
7164 : pszLayerName);
7165 2 : eErr = SQLCommand(hDB, pszSQL);
7166 2 : sqlite3_free(pszSQL);
7167 : }
7168 :
7169 2 : if (eErr == OGRERR_NONE && HasGriddedCoverageAncillaryTable())
7170 : {
7171 : char *pszSQL =
7172 1 : sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_coverage_ancillary "
7173 : "WHERE lower(tile_matrix_set_name) = lower('%q')",
7174 : pszLayerName);
7175 1 : eErr = SQLCommand(hDB, pszSQL);
7176 1 : sqlite3_free(pszSQL);
7177 :
7178 1 : if (eErr == OGRERR_NONE)
7179 : {
7180 : pszSQL =
7181 1 : sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_tile_ancillary "
7182 : "WHERE lower(tpudt_name) = lower('%q')",
7183 : pszLayerName);
7184 1 : eErr = SQLCommand(hDB, pszSQL);
7185 1 : sqlite3_free(pszSQL);
7186 : }
7187 : }
7188 :
7189 2 : if (eErr == OGRERR_NONE)
7190 : {
7191 2 : eErr = DeleteLayerCommon(pszLayerName);
7192 : }
7193 :
7194 2 : if (eErr == OGRERR_NONE)
7195 : {
7196 2 : eErr = SoftCommitTransaction();
7197 : }
7198 : else
7199 : {
7200 0 : SoftRollbackTransaction();
7201 : }
7202 :
7203 4 : return eErr;
7204 : }
7205 :
7206 : /************************************************************************/
7207 : /* DeleteVectorOrRasterLayer() */
7208 : /************************************************************************/
7209 :
7210 13 : bool GDALGeoPackageDataset::DeleteVectorOrRasterLayer(const char *pszLayerName)
7211 : {
7212 :
7213 13 : int idx = FindLayerIndex(pszLayerName);
7214 13 : if (idx >= 0)
7215 : {
7216 5 : DeleteLayer(idx);
7217 5 : return true;
7218 : }
7219 :
7220 : char *pszSQL =
7221 8 : sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
7222 : "lower(table_name) = lower('%q') "
7223 : "AND data_type IN ('tiles', '2d-gridded-coverage')",
7224 : pszLayerName);
7225 8 : bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
7226 8 : sqlite3_free(pszSQL);
7227 8 : if (bIsRasterTable)
7228 : {
7229 2 : DeleteRasterLayer(pszLayerName);
7230 2 : return true;
7231 : }
7232 6 : return false;
7233 : }
7234 :
7235 7 : bool GDALGeoPackageDataset::RenameVectorOrRasterLayer(
7236 : const char *pszLayerName, const char *pszNewLayerName)
7237 : {
7238 7 : int idx = FindLayerIndex(pszLayerName);
7239 7 : if (idx >= 0)
7240 : {
7241 4 : m_papoLayers[idx]->Rename(pszNewLayerName);
7242 4 : return true;
7243 : }
7244 :
7245 : char *pszSQL =
7246 3 : sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
7247 : "lower(table_name) = lower('%q') "
7248 : "AND data_type IN ('tiles', '2d-gridded-coverage')",
7249 : pszLayerName);
7250 3 : const bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
7251 3 : sqlite3_free(pszSQL);
7252 :
7253 3 : if (bIsRasterTable)
7254 : {
7255 2 : return RenameRasterLayer(pszLayerName, pszNewLayerName);
7256 : }
7257 :
7258 1 : return false;
7259 : }
7260 :
7261 2 : bool GDALGeoPackageDataset::RenameRasterLayer(const char *pszLayerName,
7262 : const char *pszNewLayerName)
7263 : {
7264 4 : std::string osSQL;
7265 :
7266 2 : char *pszSQL = sqlite3_mprintf(
7267 : "SELECT 1 FROM sqlite_master WHERE lower(name) = lower('%q') "
7268 : "AND type IN ('table', 'view')",
7269 : pszNewLayerName);
7270 2 : const bool bAlreadyExists = SQLGetInteger(GetDB(), pszSQL, nullptr) == 1;
7271 2 : sqlite3_free(pszSQL);
7272 2 : if (bAlreadyExists)
7273 : {
7274 0 : CPLError(CE_Failure, CPLE_AppDefined, "Table %s already exists",
7275 : pszNewLayerName);
7276 0 : return false;
7277 : }
7278 :
7279 : // Temporary remove foreign key checks
7280 : const GPKGTemporaryForeignKeyCheckDisabler
7281 4 : oGPKGTemporaryForeignKeyCheckDisabler(this);
7282 :
7283 2 : if (SoftStartTransaction() != OGRERR_NONE)
7284 : {
7285 0 : return false;
7286 : }
7287 :
7288 2 : pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET table_name = '%q' WHERE "
7289 : "lower(table_name) = lower('%q');",
7290 : pszNewLayerName, pszLayerName);
7291 2 : osSQL = pszSQL;
7292 2 : sqlite3_free(pszSQL);
7293 :
7294 2 : pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' WHERE "
7295 : "lower(identifier) = lower('%q');",
7296 : pszNewLayerName, pszLayerName);
7297 2 : osSQL += pszSQL;
7298 2 : sqlite3_free(pszSQL);
7299 :
7300 : pszSQL =
7301 2 : sqlite3_mprintf("UPDATE gpkg_tile_matrix SET table_name = '%q' WHERE "
7302 : "lower(table_name) = lower('%q');",
7303 : pszNewLayerName, pszLayerName);
7304 2 : osSQL += pszSQL;
7305 2 : sqlite3_free(pszSQL);
7306 :
7307 2 : pszSQL = sqlite3_mprintf(
7308 : "UPDATE gpkg_tile_matrix_set SET table_name = '%q' WHERE "
7309 : "lower(table_name) = lower('%q');",
7310 : pszNewLayerName, pszLayerName);
7311 2 : osSQL += pszSQL;
7312 2 : sqlite3_free(pszSQL);
7313 :
7314 2 : if (HasGriddedCoverageAncillaryTable())
7315 : {
7316 1 : pszSQL = sqlite3_mprintf("UPDATE gpkg_2d_gridded_coverage_ancillary "
7317 : "SET tile_matrix_set_name = '%q' WHERE "
7318 : "lower(tile_matrix_set_name) = lower('%q');",
7319 : pszNewLayerName, pszLayerName);
7320 1 : osSQL += pszSQL;
7321 1 : sqlite3_free(pszSQL);
7322 :
7323 1 : pszSQL = sqlite3_mprintf(
7324 : "UPDATE gpkg_2d_gridded_tile_ancillary SET tpudt_name = '%q' WHERE "
7325 : "lower(tpudt_name) = lower('%q');",
7326 : pszNewLayerName, pszLayerName);
7327 1 : osSQL += pszSQL;
7328 1 : sqlite3_free(pszSQL);
7329 : }
7330 :
7331 2 : if (HasExtensionsTable())
7332 : {
7333 2 : pszSQL = sqlite3_mprintf(
7334 : "UPDATE gpkg_extensions SET table_name = '%q' WHERE "
7335 : "lower(table_name) = lower('%q');",
7336 : pszNewLayerName, pszLayerName);
7337 2 : osSQL += pszSQL;
7338 2 : sqlite3_free(pszSQL);
7339 : }
7340 :
7341 2 : if (HasMetadataTables())
7342 : {
7343 1 : pszSQL = sqlite3_mprintf(
7344 : "UPDATE gpkg_metadata_reference SET table_name = '%q' WHERE "
7345 : "lower(table_name) = lower('%q');",
7346 : pszNewLayerName, pszLayerName);
7347 1 : osSQL += pszSQL;
7348 1 : sqlite3_free(pszSQL);
7349 : }
7350 :
7351 2 : if (HasDataColumnsTable())
7352 : {
7353 0 : pszSQL = sqlite3_mprintf(
7354 : "UPDATE gpkg_data_columns SET table_name = '%q' WHERE "
7355 : "lower(table_name) = lower('%q');",
7356 : pszNewLayerName, pszLayerName);
7357 0 : osSQL += pszSQL;
7358 0 : sqlite3_free(pszSQL);
7359 : }
7360 :
7361 2 : if (HasQGISLayerStyles())
7362 : {
7363 : // Update QGIS styles
7364 : pszSQL =
7365 0 : sqlite3_mprintf("UPDATE layer_styles SET f_table_name = '%q' WHERE "
7366 : "lower(f_table_name) = lower('%q');",
7367 : pszNewLayerName, pszLayerName);
7368 0 : osSQL += pszSQL;
7369 0 : sqlite3_free(pszSQL);
7370 : }
7371 :
7372 : #ifdef ENABLE_GPKG_OGR_CONTENTS
7373 2 : if (m_bHasGPKGOGRContents)
7374 : {
7375 2 : pszSQL = sqlite3_mprintf(
7376 : "UPDATE gpkg_ogr_contents SET table_name = '%q' WHERE "
7377 : "lower(table_name) = lower('%q');",
7378 : pszNewLayerName, pszLayerName);
7379 2 : osSQL += pszSQL;
7380 2 : sqlite3_free(pszSQL);
7381 : }
7382 : #endif
7383 :
7384 2 : if (HasGpkgextRelationsTable())
7385 : {
7386 0 : pszSQL = sqlite3_mprintf(
7387 : "UPDATE gpkgext_relations SET base_table_name = '%q' WHERE "
7388 : "lower(base_table_name) = lower('%q');",
7389 : pszNewLayerName, pszLayerName);
7390 0 : osSQL += pszSQL;
7391 0 : sqlite3_free(pszSQL);
7392 :
7393 0 : pszSQL = sqlite3_mprintf(
7394 : "UPDATE gpkgext_relations SET related_table_name = '%q' WHERE "
7395 : "lower(related_table_name) = lower('%q');",
7396 : pszNewLayerName, pszLayerName);
7397 0 : osSQL += pszSQL;
7398 0 : sqlite3_free(pszSQL);
7399 :
7400 0 : pszSQL = sqlite3_mprintf(
7401 : "UPDATE gpkgext_relations SET mapping_table_name = '%q' WHERE "
7402 : "lower(mapping_table_name) = lower('%q');",
7403 : pszNewLayerName, pszLayerName);
7404 0 : osSQL += pszSQL;
7405 0 : sqlite3_free(pszSQL);
7406 : }
7407 :
7408 : // Drop all triggers for the layer
7409 2 : pszSQL = sqlite3_mprintf("SELECT name FROM sqlite_master WHERE type = "
7410 : "'trigger' AND tbl_name = '%q'",
7411 : pszLayerName);
7412 2 : auto oTriggerResult = SQLQuery(GetDB(), pszSQL);
7413 2 : sqlite3_free(pszSQL);
7414 2 : if (oTriggerResult)
7415 : {
7416 14 : for (int i = 0; i < oTriggerResult->RowCount(); i++)
7417 : {
7418 12 : const char *pszTriggerName = oTriggerResult->GetValue(0, i);
7419 12 : pszSQL = sqlite3_mprintf("DROP TRIGGER IF EXISTS \"%w\";",
7420 : pszTriggerName);
7421 12 : osSQL += pszSQL;
7422 12 : sqlite3_free(pszSQL);
7423 : }
7424 : }
7425 :
7426 2 : pszSQL = sqlite3_mprintf("ALTER TABLE \"%w\" RENAME TO \"%w\";",
7427 : pszLayerName, pszNewLayerName);
7428 2 : osSQL += pszSQL;
7429 2 : sqlite3_free(pszSQL);
7430 :
7431 : // Recreate all zoom/tile triggers
7432 2 : if (oTriggerResult)
7433 : {
7434 2 : osSQL += CreateRasterTriggersSQL(pszNewLayerName);
7435 : }
7436 :
7437 2 : OGRErr eErr = SQLCommand(GetDB(), osSQL.c_str());
7438 :
7439 : // Check foreign key integrity
7440 2 : if (eErr == OGRERR_NONE)
7441 : {
7442 2 : eErr = PragmaCheck("foreign_key_check", "", 0);
7443 : }
7444 :
7445 2 : if (eErr == OGRERR_NONE)
7446 : {
7447 2 : eErr = SoftCommitTransaction();
7448 : }
7449 : else
7450 : {
7451 0 : SoftRollbackTransaction();
7452 : }
7453 :
7454 2 : return eErr == OGRERR_NONE;
7455 : }
7456 :
7457 : /************************************************************************/
7458 : /* TestCapability() */
7459 : /************************************************************************/
7460 :
7461 320 : int GDALGeoPackageDataset::TestCapability(const char *pszCap)
7462 : {
7463 320 : if (EQUAL(pszCap, ODsCCreateLayer) || EQUAL(pszCap, ODsCDeleteLayer) ||
7464 193 : EQUAL(pszCap, "RenameLayer"))
7465 : {
7466 127 : return GetUpdate();
7467 : }
7468 193 : else if (EQUAL(pszCap, ODsCCurveGeometries))
7469 12 : return TRUE;
7470 181 : else if (EQUAL(pszCap, ODsCMeasuredGeometries))
7471 8 : return TRUE;
7472 173 : else if (EQUAL(pszCap, ODsCZGeometries))
7473 8 : return TRUE;
7474 165 : else if (EQUAL(pszCap, ODsCRandomLayerWrite) ||
7475 165 : EQUAL(pszCap, GDsCAddRelationship) ||
7476 165 : EQUAL(pszCap, GDsCDeleteRelationship) ||
7477 165 : EQUAL(pszCap, GDsCUpdateRelationship) ||
7478 165 : EQUAL(pszCap, ODsCAddFieldDomain))
7479 1 : return GetUpdate();
7480 :
7481 164 : return OGRSQLiteBaseDataSource::TestCapability(pszCap);
7482 : }
7483 :
7484 : /************************************************************************/
7485 : /* ResetReadingAllLayers() */
7486 : /************************************************************************/
7487 :
7488 47 : void GDALGeoPackageDataset::ResetReadingAllLayers()
7489 : {
7490 99 : for (int i = 0; i < m_nLayers; i++)
7491 : {
7492 52 : m_papoLayers[i]->ResetReading();
7493 : }
7494 47 : }
7495 :
7496 : /************************************************************************/
7497 : /* ExecuteSQL() */
7498 : /************************************************************************/
7499 :
7500 : static const char *const apszFuncsWithSideEffects[] = {
7501 : "CreateSpatialIndex",
7502 : "DisableSpatialIndex",
7503 : "HasSpatialIndex",
7504 : "RegisterGeometryExtension",
7505 : };
7506 :
7507 5356 : OGRLayer *GDALGeoPackageDataset::ExecuteSQL(const char *pszSQLCommand,
7508 : OGRGeometry *poSpatialFilter,
7509 : const char *pszDialect)
7510 :
7511 : {
7512 5356 : m_bHasReadMetadataFromStorage = false;
7513 :
7514 5356 : FlushMetadata();
7515 :
7516 5374 : while (*pszSQLCommand != '\0' &&
7517 5374 : isspace(static_cast<unsigned char>(*pszSQLCommand)))
7518 18 : pszSQLCommand++;
7519 :
7520 10712 : CPLString osSQLCommand(pszSQLCommand);
7521 5356 : if (!osSQLCommand.empty() && osSQLCommand.back() == ';')
7522 48 : osSQLCommand.pop_back();
7523 :
7524 5356 : if (pszDialect == nullptr || !EQUAL(pszDialect, "DEBUG"))
7525 : {
7526 : // Some SQL commands will influence the feature count behind our
7527 : // back, so disable it in that case.
7528 : #ifdef ENABLE_GPKG_OGR_CONTENTS
7529 : const bool bInsertOrDelete =
7530 5287 : osSQLCommand.ifind("insert into ") != std::string::npos ||
7531 2175 : osSQLCommand.ifind("insert or replace into ") !=
7532 7462 : std::string::npos ||
7533 2129 : osSQLCommand.ifind("delete from ") != std::string::npos;
7534 : const bool bRollback =
7535 5287 : osSQLCommand.ifind("rollback ") != std::string::npos;
7536 : #endif
7537 :
7538 6826 : for (int i = 0; i < m_nLayers; i++)
7539 : {
7540 1539 : if (m_papoLayers[i]->SyncToDisk() != OGRERR_NONE)
7541 0 : return nullptr;
7542 : #ifdef ENABLE_GPKG_OGR_CONTENTS
7543 1740 : if (bRollback || (bInsertOrDelete &&
7544 201 : osSQLCommand.ifind(m_papoLayers[i]->GetName()) !=
7545 : std::string::npos))
7546 : {
7547 155 : m_papoLayers[i]->DisableFeatureCount();
7548 : }
7549 : #endif
7550 : }
7551 : }
7552 :
7553 5356 : if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 0") ||
7554 5355 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=0") ||
7555 5355 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =0") ||
7556 5355 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 0"))
7557 : {
7558 1 : OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, false);
7559 : }
7560 5355 : else if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 1") ||
7561 5354 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=1") ||
7562 5354 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =1") ||
7563 5354 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 1"))
7564 : {
7565 1 : OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, true);
7566 : }
7567 :
7568 : /* -------------------------------------------------------------------- */
7569 : /* DEBUG "SELECT nolock" command. */
7570 : /* -------------------------------------------------------------------- */
7571 5425 : if (pszDialect != nullptr && EQUAL(pszDialect, "DEBUG") &&
7572 69 : EQUAL(osSQLCommand, "SELECT nolock"))
7573 : {
7574 3 : return new OGRSQLiteSingleFeatureLayer(osSQLCommand, m_bNoLock ? 1 : 0);
7575 : }
7576 :
7577 : /* -------------------------------------------------------------------- */
7578 : /* Special case DELLAYER: command. */
7579 : /* -------------------------------------------------------------------- */
7580 5353 : if (STARTS_WITH_CI(osSQLCommand, "DELLAYER:"))
7581 : {
7582 4 : const char *pszLayerName = osSQLCommand.c_str() + strlen("DELLAYER:");
7583 :
7584 4 : while (*pszLayerName == ' ')
7585 0 : pszLayerName++;
7586 :
7587 4 : if (!DeleteVectorOrRasterLayer(pszLayerName))
7588 : {
7589 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
7590 : pszLayerName);
7591 : }
7592 4 : return nullptr;
7593 : }
7594 :
7595 : /* -------------------------------------------------------------------- */
7596 : /* Special case RECOMPUTE EXTENT ON command. */
7597 : /* -------------------------------------------------------------------- */
7598 5349 : if (STARTS_WITH_CI(osSQLCommand, "RECOMPUTE EXTENT ON "))
7599 : {
7600 : const char *pszLayerName =
7601 4 : osSQLCommand.c_str() + strlen("RECOMPUTE EXTENT ON ");
7602 :
7603 4 : while (*pszLayerName == ' ')
7604 0 : pszLayerName++;
7605 :
7606 4 : int idx = FindLayerIndex(pszLayerName);
7607 4 : if (idx >= 0)
7608 : {
7609 4 : m_papoLayers[idx]->RecomputeExtent();
7610 : }
7611 : else
7612 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
7613 : pszLayerName);
7614 4 : return nullptr;
7615 : }
7616 :
7617 : /* -------------------------------------------------------------------- */
7618 : /* Intercept DROP TABLE */
7619 : /* -------------------------------------------------------------------- */
7620 5345 : if (STARTS_WITH_CI(osSQLCommand, "DROP TABLE "))
7621 : {
7622 9 : const char *pszLayerName = osSQLCommand.c_str() + strlen("DROP TABLE ");
7623 :
7624 9 : while (*pszLayerName == ' ')
7625 0 : pszLayerName++;
7626 :
7627 9 : if (DeleteVectorOrRasterLayer(SQLUnescape(pszLayerName)))
7628 4 : return nullptr;
7629 : }
7630 :
7631 : /* -------------------------------------------------------------------- */
7632 : /* Intercept ALTER TABLE src_table RENAME TO dst_table */
7633 : /* and ALTER TABLE table RENAME COLUMN src_name TO dst_name */
7634 : /* and ALTER TABLE table DROP COLUMN col_name */
7635 : /* */
7636 : /* We do this because SQLite mechanisms can't deal with updating */
7637 : /* literal values in gpkg_ tables that refer to table and column */
7638 : /* names. */
7639 : /* -------------------------------------------------------------------- */
7640 5341 : if (STARTS_WITH_CI(osSQLCommand, "ALTER TABLE "))
7641 : {
7642 9 : char **papszTokens = SQLTokenize(osSQLCommand);
7643 : /* ALTER TABLE src_table RENAME TO dst_table */
7644 16 : if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "RENAME") &&
7645 7 : EQUAL(papszTokens[4], "TO"))
7646 : {
7647 7 : const char *pszSrcTableName = papszTokens[2];
7648 7 : const char *pszDstTableName = papszTokens[5];
7649 7 : if (RenameVectorOrRasterLayer(SQLUnescape(pszSrcTableName),
7650 14 : SQLUnescape(pszDstTableName)))
7651 : {
7652 6 : CSLDestroy(papszTokens);
7653 6 : return nullptr;
7654 : }
7655 : }
7656 : /* ALTER TABLE table RENAME COLUMN src_name TO dst_name */
7657 2 : else if (CSLCount(papszTokens) == 8 &&
7658 1 : EQUAL(papszTokens[3], "RENAME") &&
7659 3 : EQUAL(papszTokens[4], "COLUMN") && EQUAL(papszTokens[6], "TO"))
7660 : {
7661 1 : const char *pszTableName = papszTokens[2];
7662 1 : const char *pszSrcColumn = papszTokens[5];
7663 1 : const char *pszDstColumn = papszTokens[7];
7664 : OGRGeoPackageTableLayer *poLayer =
7665 0 : dynamic_cast<OGRGeoPackageTableLayer *>(
7666 1 : GetLayerByName(SQLUnescape(pszTableName)));
7667 1 : if (poLayer)
7668 : {
7669 2 : int nSrcFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
7670 2 : SQLUnescape(pszSrcColumn));
7671 1 : if (nSrcFieldIdx >= 0)
7672 : {
7673 : // OFTString or any type will do as we just alter the name
7674 : // so it will be ignored.
7675 1 : OGRFieldDefn oFieldDefn(SQLUnescape(pszDstColumn),
7676 1 : OFTString);
7677 1 : poLayer->AlterFieldDefn(nSrcFieldIdx, &oFieldDefn,
7678 : ALTER_NAME_FLAG);
7679 1 : CSLDestroy(papszTokens);
7680 1 : return nullptr;
7681 : }
7682 : }
7683 : }
7684 : /* ALTER TABLE table DROP COLUMN col_name */
7685 2 : else if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "DROP") &&
7686 1 : EQUAL(papszTokens[4], "COLUMN"))
7687 : {
7688 1 : const char *pszTableName = papszTokens[2];
7689 1 : const char *pszColumnName = papszTokens[5];
7690 : OGRGeoPackageTableLayer *poLayer =
7691 0 : dynamic_cast<OGRGeoPackageTableLayer *>(
7692 1 : GetLayerByName(SQLUnescape(pszTableName)));
7693 1 : if (poLayer)
7694 : {
7695 2 : int nFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
7696 2 : SQLUnescape(pszColumnName));
7697 1 : if (nFieldIdx >= 0)
7698 : {
7699 1 : poLayer->DeleteField(nFieldIdx);
7700 1 : CSLDestroy(papszTokens);
7701 1 : return nullptr;
7702 : }
7703 : }
7704 : }
7705 1 : CSLDestroy(papszTokens);
7706 : }
7707 :
7708 5333 : if (EQUAL(osSQLCommand, "VACUUM"))
7709 : {
7710 12 : ResetReadingAllLayers();
7711 : }
7712 :
7713 5333 : if (EQUAL(osSQLCommand, "BEGIN"))
7714 : {
7715 0 : SoftStartTransaction();
7716 0 : return nullptr;
7717 : }
7718 5333 : else if (EQUAL(osSQLCommand, "COMMIT"))
7719 : {
7720 0 : SoftCommitTransaction();
7721 0 : return nullptr;
7722 : }
7723 5333 : else if (EQUAL(osSQLCommand, "ROLLBACK"))
7724 : {
7725 0 : SoftRollbackTransaction();
7726 0 : return nullptr;
7727 : }
7728 :
7729 5333 : else if (STARTS_WITH_CI(osSQLCommand, "DELETE FROM "))
7730 : {
7731 : // Optimize truncation of a table, especially if it has a spatial
7732 : // index.
7733 20 : const CPLStringList aosTokens(SQLTokenize(osSQLCommand));
7734 20 : if (aosTokens.size() == 3)
7735 : {
7736 14 : const char *pszTableName = aosTokens[2];
7737 : OGRGeoPackageTableLayer *poLayer =
7738 8 : dynamic_cast<OGRGeoPackageTableLayer *>(
7739 22 : GetLayerByName(SQLUnescape(pszTableName)));
7740 14 : if (poLayer)
7741 : {
7742 6 : poLayer->Truncate();
7743 6 : return nullptr;
7744 : }
7745 : }
7746 : }
7747 :
7748 5313 : else if (pszDialect != nullptr && EQUAL(pszDialect, "INDIRECT_SQLITE"))
7749 1 : return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter, "SQLITE");
7750 5312 : else if (pszDialect != nullptr && !EQUAL(pszDialect, "") &&
7751 66 : !EQUAL(pszDialect, "NATIVE") && !EQUAL(pszDialect, "SQLITE") &&
7752 66 : !EQUAL(pszDialect, "DEBUG"))
7753 0 : return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter,
7754 0 : pszDialect);
7755 :
7756 : /* -------------------------------------------------------------------- */
7757 : /* Prepare statement. */
7758 : /* -------------------------------------------------------------------- */
7759 5326 : sqlite3_stmt *hSQLStmt = nullptr;
7760 :
7761 : /* This will speed-up layer creation */
7762 : /* ORDER BY are costly to evaluate and are not necessary to establish */
7763 : /* the layer definition. */
7764 5326 : bool bUseStatementForGetNextFeature = true;
7765 5326 : bool bEmptyLayer = false;
7766 10652 : CPLString osSQLCommandTruncated(osSQLCommand);
7767 :
7768 17554 : if (osSQLCommand.ifind("SELECT ") == 0 &&
7769 6114 : CPLString(osSQLCommand.substr(1)).ifind("SELECT ") ==
7770 754 : std::string::npos &&
7771 754 : osSQLCommand.ifind(" UNION ") == std::string::npos &&
7772 6868 : osSQLCommand.ifind(" INTERSECT ") == std::string::npos &&
7773 754 : osSQLCommand.ifind(" EXCEPT ") == std::string::npos)
7774 : {
7775 754 : size_t nOrderByPos = osSQLCommand.ifind(" ORDER BY ");
7776 754 : if (nOrderByPos != std::string::npos)
7777 : {
7778 8 : osSQLCommandTruncated.resize(nOrderByPos);
7779 8 : bUseStatementForGetNextFeature = false;
7780 : }
7781 : }
7782 :
7783 5326 : int rc = prepareSql(hDB, osSQLCommandTruncated.c_str(),
7784 5326 : static_cast<int>(osSQLCommandTruncated.size()),
7785 : &hSQLStmt, nullptr);
7786 :
7787 5326 : if (rc != SQLITE_OK)
7788 : {
7789 9 : CPLError(CE_Failure, CPLE_AppDefined,
7790 : "In ExecuteSQL(): sqlite3_prepare_v2(%s):\n %s",
7791 : osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
7792 :
7793 9 : if (hSQLStmt != nullptr)
7794 : {
7795 0 : sqlite3_finalize(hSQLStmt);
7796 : }
7797 :
7798 9 : return nullptr;
7799 : }
7800 :
7801 : /* -------------------------------------------------------------------- */
7802 : /* Do we get a resultset? */
7803 : /* -------------------------------------------------------------------- */
7804 5317 : rc = sqlite3_step(hSQLStmt);
7805 :
7806 6878 : for (int i = 0; i < m_nLayers; i++)
7807 : {
7808 1561 : m_papoLayers[i]->RunDeferredDropRTreeTableIfNecessary();
7809 : }
7810 :
7811 5317 : if (rc != SQLITE_ROW)
7812 : {
7813 4606 : if (rc != SQLITE_DONE)
7814 : {
7815 7 : CPLError(CE_Failure, CPLE_AppDefined,
7816 : "In ExecuteSQL(): sqlite3_step(%s):\n %s",
7817 : osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
7818 :
7819 7 : sqlite3_finalize(hSQLStmt);
7820 7 : return nullptr;
7821 : }
7822 :
7823 4599 : if (EQUAL(osSQLCommand, "VACUUM"))
7824 : {
7825 12 : sqlite3_finalize(hSQLStmt);
7826 : /* VACUUM rewrites the DB, so we need to reset the application id */
7827 12 : SetApplicationAndUserVersionId();
7828 12 : return nullptr;
7829 : }
7830 :
7831 4587 : if (!STARTS_WITH_CI(osSQLCommand, "SELECT "))
7832 : {
7833 4466 : sqlite3_finalize(hSQLStmt);
7834 4466 : return nullptr;
7835 : }
7836 :
7837 121 : bUseStatementForGetNextFeature = false;
7838 121 : bEmptyLayer = true;
7839 : }
7840 :
7841 : /* -------------------------------------------------------------------- */
7842 : /* Special case for some functions which must be run */
7843 : /* only once */
7844 : /* -------------------------------------------------------------------- */
7845 832 : if (STARTS_WITH_CI(osSQLCommand, "SELECT "))
7846 : {
7847 3784 : for (unsigned int i = 0; i < sizeof(apszFuncsWithSideEffects) /
7848 : sizeof(apszFuncsWithSideEffects[0]);
7849 : i++)
7850 : {
7851 3053 : if (EQUALN(apszFuncsWithSideEffects[i], osSQLCommand.c_str() + 7,
7852 : strlen(apszFuncsWithSideEffects[i])))
7853 : {
7854 112 : if (sqlite3_column_count(hSQLStmt) == 1 &&
7855 56 : sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
7856 : {
7857 56 : int ret = sqlite3_column_int(hSQLStmt, 0);
7858 :
7859 56 : sqlite3_finalize(hSQLStmt);
7860 :
7861 : return new OGRSQLiteSingleFeatureLayer(
7862 56 : apszFuncsWithSideEffects[i], ret);
7863 : }
7864 : }
7865 : }
7866 : }
7867 45 : else if (STARTS_WITH_CI(osSQLCommand, "PRAGMA "))
7868 : {
7869 63 : if (sqlite3_column_count(hSQLStmt) == 1 &&
7870 18 : sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
7871 : {
7872 15 : int ret = sqlite3_column_int(hSQLStmt, 0);
7873 :
7874 15 : sqlite3_finalize(hSQLStmt);
7875 :
7876 15 : return new OGRSQLiteSingleFeatureLayer(osSQLCommand.c_str() + 7,
7877 15 : ret);
7878 : }
7879 33 : else if (sqlite3_column_count(hSQLStmt) == 1 &&
7880 3 : sqlite3_column_type(hSQLStmt, 0) == SQLITE_TEXT)
7881 : {
7882 : const char *pszRet = reinterpret_cast<const char *>(
7883 3 : sqlite3_column_text(hSQLStmt, 0));
7884 :
7885 : OGRLayer *poRet = new OGRSQLiteSingleFeatureLayer(
7886 3 : osSQLCommand.c_str() + 7, pszRet);
7887 :
7888 3 : sqlite3_finalize(hSQLStmt);
7889 :
7890 3 : return poRet;
7891 : }
7892 : }
7893 :
7894 : /* -------------------------------------------------------------------- */
7895 : /* Create layer. */
7896 : /* -------------------------------------------------------------------- */
7897 :
7898 : OGRLayer *poLayer = new OGRGeoPackageSelectLayer(
7899 : this, osSQLCommand, hSQLStmt, bUseStatementForGetNextFeature,
7900 758 : bEmptyLayer);
7901 :
7902 761 : if (poSpatialFilter != nullptr &&
7903 3 : poLayer->GetLayerDefn()->GetGeomFieldCount() > 0)
7904 3 : poLayer->SetSpatialFilter(0, poSpatialFilter);
7905 :
7906 758 : return poLayer;
7907 : }
7908 :
7909 : /************************************************************************/
7910 : /* ReleaseResultSet() */
7911 : /************************************************************************/
7912 :
7913 788 : void GDALGeoPackageDataset::ReleaseResultSet(OGRLayer *poLayer)
7914 :
7915 : {
7916 788 : delete poLayer;
7917 788 : }
7918 :
7919 : /************************************************************************/
7920 : /* HasExtensionsTable() */
7921 : /************************************************************************/
7922 :
7923 5616 : bool GDALGeoPackageDataset::HasExtensionsTable()
7924 : {
7925 5616 : return SQLGetInteger(
7926 : hDB,
7927 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_extensions' "
7928 : "AND type IN ('table', 'view')",
7929 5616 : nullptr) == 1;
7930 : }
7931 :
7932 : /************************************************************************/
7933 : /* CheckUnknownExtensions() */
7934 : /************************************************************************/
7935 :
7936 1327 : void GDALGeoPackageDataset::CheckUnknownExtensions(bool bCheckRasterTable)
7937 : {
7938 1327 : if (!HasExtensionsTable())
7939 198 : return;
7940 :
7941 1129 : char *pszSQL = nullptr;
7942 1129 : if (!bCheckRasterTable)
7943 931 : pszSQL = sqlite3_mprintf(
7944 : "SELECT extension_name, definition, scope FROM gpkg_extensions "
7945 : "WHERE (table_name IS NULL "
7946 : "AND extension_name IS NOT NULL "
7947 : "AND definition IS NOT NULL "
7948 : "AND scope IS NOT NULL "
7949 : "AND extension_name NOT IN ("
7950 : "'gdal_aspatial', "
7951 : "'gpkg_elevation_tiles', " // Old name before GPKG 1.2 approval
7952 : "'2d_gridded_coverage', " // Old name after GPKG 1.2 and before OGC
7953 : // 17-066r1 finalization
7954 : "'gpkg_2d_gridded_coverage', " // Name in OGC 17-066r1 final
7955 : "'gpkg_metadata', "
7956 : "'gpkg_schema', "
7957 : "'gpkg_crs_wkt', "
7958 : "'gpkg_crs_wkt_1_1', "
7959 : "'related_tables', 'gpkg_related_tables')) "
7960 : #ifdef WORKAROUND_SQLITE3_BUGS
7961 : "OR 0 "
7962 : #endif
7963 : "LIMIT 1000");
7964 : else
7965 198 : pszSQL = sqlite3_mprintf(
7966 : "SELECT extension_name, definition, scope FROM gpkg_extensions "
7967 : "WHERE (lower(table_name) = lower('%q') "
7968 : "AND extension_name IS NOT NULL "
7969 : "AND definition IS NOT NULL "
7970 : "AND scope IS NOT NULL "
7971 : "AND extension_name NOT IN ("
7972 : "'gpkg_elevation_tiles', " // Old name before GPKG 1.2 approval
7973 : "'2d_gridded_coverage', " // Old name after GPKG 1.2 and before OGC
7974 : // 17-066r1 finalization
7975 : "'gpkg_2d_gridded_coverage', " // Name in OGC 17-066r1 final
7976 : "'gpkg_metadata', "
7977 : "'gpkg_schema', "
7978 : "'gpkg_crs_wkt', "
7979 : "'gpkg_crs_wkt_1_1', "
7980 : "'related_tables', 'gpkg_related_tables')) "
7981 : #ifdef WORKAROUND_SQLITE3_BUGS
7982 : "OR 0 "
7983 : #endif
7984 : "LIMIT 1000",
7985 : m_osRasterTable.c_str());
7986 :
7987 2258 : auto oResultTable = SQLQuery(GetDB(), pszSQL);
7988 1129 : sqlite3_free(pszSQL);
7989 1129 : if (oResultTable && oResultTable->RowCount() > 0)
7990 : {
7991 44 : for (int i = 0; i < oResultTable->RowCount(); i++)
7992 : {
7993 22 : const char *pszExtName = oResultTable->GetValue(0, i);
7994 22 : const char *pszDefinition = oResultTable->GetValue(1, i);
7995 22 : const char *pszScope = oResultTable->GetValue(2, i);
7996 22 : if (pszExtName == nullptr || pszDefinition == nullptr ||
7997 : pszScope == nullptr)
7998 : {
7999 0 : continue;
8000 : }
8001 :
8002 22 : if (EQUAL(pszExtName, "gpkg_webp"))
8003 : {
8004 16 : if (GDALGetDriverByName("WEBP") == nullptr)
8005 : {
8006 1 : CPLError(
8007 : CE_Warning, CPLE_AppDefined,
8008 : "Table %s contains WEBP tiles, but GDAL configured "
8009 : "without WEBP support. Data will be missing",
8010 : m_osRasterTable.c_str());
8011 : }
8012 16 : m_eTF = GPKG_TF_WEBP;
8013 16 : continue;
8014 : }
8015 6 : if (EQUAL(pszExtName, "gpkg_zoom_other"))
8016 : {
8017 2 : m_bZoomOther = true;
8018 2 : continue;
8019 : }
8020 :
8021 4 : if (GetUpdate() && EQUAL(pszScope, "write-only"))
8022 : {
8023 1 : CPLError(
8024 : CE_Warning, CPLE_AppDefined,
8025 : "Database relies on the '%s' (%s) extension that should "
8026 : "be implemented for safe write-support, but is not "
8027 : "currently. "
8028 : "Update of that database are strongly discouraged to avoid "
8029 : "corruption.",
8030 : pszExtName, pszDefinition);
8031 : }
8032 3 : else if (GetUpdate() && EQUAL(pszScope, "read-write"))
8033 : {
8034 1 : CPLError(
8035 : CE_Warning, CPLE_AppDefined,
8036 : "Database relies on the '%s' (%s) extension that should "
8037 : "be implemented in order to read/write it safely, but is "
8038 : "not currently. "
8039 : "Some data may be missing while reading that database, and "
8040 : "updates are strongly discouraged.",
8041 : pszExtName, pszDefinition);
8042 : }
8043 2 : else if (EQUAL(pszScope, "read-write") &&
8044 : // None of the NGA extensions at
8045 : // http://ngageoint.github.io/GeoPackage/docs/extensions/
8046 : // affect read-only scenarios
8047 1 : !STARTS_WITH(pszExtName, "nga_"))
8048 : {
8049 1 : CPLError(
8050 : CE_Warning, CPLE_AppDefined,
8051 : "Database relies on the '%s' (%s) extension that should "
8052 : "be implemented in order to read it safely, but is not "
8053 : "currently. "
8054 : "Some data may be missing while reading that database.",
8055 : pszExtName, pszDefinition);
8056 : }
8057 : }
8058 : }
8059 : }
8060 :
8061 : /************************************************************************/
8062 : /* HasGDALAspatialExtension() */
8063 : /************************************************************************/
8064 :
8065 885 : bool GDALGeoPackageDataset::HasGDALAspatialExtension()
8066 : {
8067 885 : if (!HasExtensionsTable())
8068 91 : return false;
8069 :
8070 : auto oResultTable = SQLQuery(hDB, "SELECT * FROM gpkg_extensions "
8071 : "WHERE (extension_name = 'gdal_aspatial' "
8072 : "AND table_name IS NULL "
8073 : "AND column_name IS NULL)"
8074 : #ifdef WORKAROUND_SQLITE3_BUGS
8075 : " OR 0"
8076 : #endif
8077 794 : );
8078 794 : bool bHasExtension = (oResultTable && oResultTable->RowCount() == 1);
8079 794 : return bHasExtension;
8080 : }
8081 :
8082 : std::string
8083 168 : GDALGeoPackageDataset::CreateRasterTriggersSQL(const std::string &osTableName)
8084 : {
8085 : char *pszSQL;
8086 168 : std::string osSQL;
8087 : /* From D.5. sample_tile_pyramid Table 43. tiles table Trigger
8088 : * Definition SQL */
8089 168 : pszSQL = sqlite3_mprintf(
8090 : "CREATE TRIGGER \"%w_zoom_insert\" "
8091 : "BEFORE INSERT ON \"%w\" "
8092 : "FOR EACH ROW BEGIN "
8093 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
8094 : "constraint: zoom_level not specified for table in "
8095 : "gpkg_tile_matrix') "
8096 : "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
8097 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
8098 : "END; "
8099 : "CREATE TRIGGER \"%w_zoom_update\" "
8100 : "BEFORE UPDATE OF zoom_level ON \"%w\" "
8101 : "FOR EACH ROW BEGIN "
8102 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8103 : "constraint: zoom_level not specified for table in "
8104 : "gpkg_tile_matrix') "
8105 : "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
8106 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
8107 : "END; "
8108 : "CREATE TRIGGER \"%w_tile_column_insert\" "
8109 : "BEFORE INSERT ON \"%w\" "
8110 : "FOR EACH ROW BEGIN "
8111 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
8112 : "constraint: tile_column cannot be < 0') "
8113 : "WHERE (NEW.tile_column < 0) ; "
8114 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
8115 : "constraint: tile_column must by < matrix_width specified for "
8116 : "table and zoom level in gpkg_tile_matrix') "
8117 : "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
8118 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8119 : "zoom_level = NEW.zoom_level)); "
8120 : "END; "
8121 : "CREATE TRIGGER \"%w_tile_column_update\" "
8122 : "BEFORE UPDATE OF tile_column ON \"%w\" "
8123 : "FOR EACH ROW BEGIN "
8124 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8125 : "constraint: tile_column cannot be < 0') "
8126 : "WHERE (NEW.tile_column < 0) ; "
8127 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8128 : "constraint: tile_column must by < matrix_width specified for "
8129 : "table and zoom level in gpkg_tile_matrix') "
8130 : "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
8131 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8132 : "zoom_level = NEW.zoom_level)); "
8133 : "END; "
8134 : "CREATE TRIGGER \"%w_tile_row_insert\" "
8135 : "BEFORE INSERT ON \"%w\" "
8136 : "FOR EACH ROW BEGIN "
8137 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
8138 : "constraint: tile_row cannot be < 0') "
8139 : "WHERE (NEW.tile_row < 0) ; "
8140 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
8141 : "constraint: tile_row must by < matrix_height specified for "
8142 : "table and zoom level in gpkg_tile_matrix') "
8143 : "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
8144 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8145 : "zoom_level = NEW.zoom_level)); "
8146 : "END; "
8147 : "CREATE TRIGGER \"%w_tile_row_update\" "
8148 : "BEFORE UPDATE OF tile_row ON \"%w\" "
8149 : "FOR EACH ROW BEGIN "
8150 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8151 : "constraint: tile_row cannot be < 0') "
8152 : "WHERE (NEW.tile_row < 0) ; "
8153 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8154 : "constraint: tile_row must by < matrix_height specified for "
8155 : "table and zoom level in gpkg_tile_matrix') "
8156 : "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
8157 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8158 : "zoom_level = NEW.zoom_level)); "
8159 : "END; ",
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(), osTableName.c_str(), osTableName.c_str(),
8168 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8169 : osTableName.c_str());
8170 168 : osSQL = pszSQL;
8171 168 : sqlite3_free(pszSQL);
8172 168 : return osSQL;
8173 : }
8174 :
8175 : /************************************************************************/
8176 : /* CreateExtensionsTableIfNecessary() */
8177 : /************************************************************************/
8178 :
8179 1017 : OGRErr GDALGeoPackageDataset::CreateExtensionsTableIfNecessary()
8180 : {
8181 : /* Check if the table gpkg_extensions exists */
8182 1017 : if (HasExtensionsTable())
8183 372 : return OGRERR_NONE;
8184 :
8185 : /* Requirement 79 : Every extension of a GeoPackage SHALL be registered */
8186 : /* in a corresponding row in the gpkg_extensions table. The absence of a */
8187 : /* gpkg_extensions table or the absence of rows in gpkg_extensions table */
8188 : /* SHALL both indicate the absence of extensions to a GeoPackage. */
8189 645 : const char *pszCreateGpkgExtensions =
8190 : "CREATE TABLE gpkg_extensions ("
8191 : "table_name TEXT,"
8192 : "column_name TEXT,"
8193 : "extension_name TEXT NOT NULL,"
8194 : "definition TEXT NOT NULL,"
8195 : "scope TEXT NOT NULL,"
8196 : "CONSTRAINT ge_tce UNIQUE (table_name, column_name, extension_name)"
8197 : ")";
8198 :
8199 645 : return SQLCommand(hDB, pszCreateGpkgExtensions);
8200 : }
8201 :
8202 : /************************************************************************/
8203 : /* OGR_GPKG_Intersects_Spatial_Filter() */
8204 : /************************************************************************/
8205 :
8206 23112 : void OGR_GPKG_Intersects_Spatial_Filter(sqlite3_context *pContext, int argc,
8207 : sqlite3_value **argv)
8208 : {
8209 23112 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8210 : {
8211 0 : sqlite3_result_int(pContext, 0);
8212 23102 : return;
8213 : }
8214 :
8215 : auto poLayer =
8216 23112 : static_cast<OGRGeoPackageTableLayer *>(sqlite3_user_data(pContext));
8217 :
8218 23112 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8219 : const GByte *pabyBLOB =
8220 23112 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8221 :
8222 : GPkgHeader sHeader;
8223 46224 : if (poLayer->m_bFilterIsEnvelope &&
8224 23112 : OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false, 0))
8225 : {
8226 23112 : if (sHeader.bExtentHasXY)
8227 : {
8228 72 : OGREnvelope sEnvelope;
8229 72 : sEnvelope.MinX = sHeader.MinX;
8230 72 : sEnvelope.MinY = sHeader.MinY;
8231 72 : sEnvelope.MaxX = sHeader.MaxX;
8232 72 : sEnvelope.MaxY = sHeader.MaxY;
8233 72 : if (poLayer->m_sFilterEnvelope.Contains(sEnvelope))
8234 : {
8235 17 : sqlite3_result_int(pContext, 1);
8236 17 : return;
8237 : }
8238 : }
8239 :
8240 : // Check if at least one point falls into the layer filter envelope
8241 : // nHeaderLen is > 0 for GeoPackage geometries
8242 46190 : if (sHeader.nHeaderLen > 0 &&
8243 23095 : OGRWKBIntersectsPessimistic(pabyBLOB + sHeader.nHeaderLen,
8244 23095 : nBLOBLen - sHeader.nHeaderLen,
8245 23095 : poLayer->m_sFilterEnvelope))
8246 : {
8247 23085 : sqlite3_result_int(pContext, 1);
8248 23085 : return;
8249 : }
8250 : }
8251 :
8252 : auto poGeom = std::unique_ptr<OGRGeometry>(
8253 10 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8254 10 : if (poGeom == nullptr)
8255 : {
8256 : // Try also spatialite geometry blobs
8257 0 : OGRGeometry *poGeomSpatialite = nullptr;
8258 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8259 0 : &poGeomSpatialite) != OGRERR_NONE)
8260 : {
8261 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8262 0 : sqlite3_result_int(pContext, 0);
8263 0 : return;
8264 : }
8265 0 : poGeom.reset(poGeomSpatialite);
8266 : }
8267 :
8268 10 : sqlite3_result_int(pContext, poLayer->FilterGeometry(poGeom.get()));
8269 : }
8270 :
8271 : /************************************************************************/
8272 : /* OGRGeoPackageSTMinX() */
8273 : /************************************************************************/
8274 :
8275 242445 : static void OGRGeoPackageSTMinX(sqlite3_context *pContext, int argc,
8276 : sqlite3_value **argv)
8277 : {
8278 : GPkgHeader sHeader;
8279 242445 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8280 : {
8281 3 : sqlite3_result_null(pContext);
8282 3 : return;
8283 : }
8284 242442 : sqlite3_result_double(pContext, sHeader.MinX);
8285 : }
8286 :
8287 : /************************************************************************/
8288 : /* OGRGeoPackageSTMinY() */
8289 : /************************************************************************/
8290 :
8291 242443 : static void OGRGeoPackageSTMinY(sqlite3_context *pContext, int argc,
8292 : sqlite3_value **argv)
8293 : {
8294 : GPkgHeader sHeader;
8295 242443 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8296 : {
8297 1 : sqlite3_result_null(pContext);
8298 1 : return;
8299 : }
8300 242442 : sqlite3_result_double(pContext, sHeader.MinY);
8301 : }
8302 :
8303 : /************************************************************************/
8304 : /* OGRGeoPackageSTMaxX() */
8305 : /************************************************************************/
8306 :
8307 242443 : static void OGRGeoPackageSTMaxX(sqlite3_context *pContext, int argc,
8308 : sqlite3_value **argv)
8309 : {
8310 : GPkgHeader sHeader;
8311 242443 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8312 : {
8313 1 : sqlite3_result_null(pContext);
8314 1 : return;
8315 : }
8316 242442 : sqlite3_result_double(pContext, sHeader.MaxX);
8317 : }
8318 :
8319 : /************************************************************************/
8320 : /* OGRGeoPackageSTMaxY() */
8321 : /************************************************************************/
8322 :
8323 242443 : static void OGRGeoPackageSTMaxY(sqlite3_context *pContext, int argc,
8324 : sqlite3_value **argv)
8325 : {
8326 : GPkgHeader sHeader;
8327 242443 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8328 : {
8329 1 : sqlite3_result_null(pContext);
8330 1 : return;
8331 : }
8332 242442 : sqlite3_result_double(pContext, sHeader.MaxY);
8333 : }
8334 :
8335 : /************************************************************************/
8336 : /* OGRGeoPackageSTIsEmpty() */
8337 : /************************************************************************/
8338 :
8339 243679 : static void OGRGeoPackageSTIsEmpty(sqlite3_context *pContext, int argc,
8340 : sqlite3_value **argv)
8341 : {
8342 : GPkgHeader sHeader;
8343 243679 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8344 : {
8345 2 : sqlite3_result_null(pContext);
8346 2 : return;
8347 : }
8348 243677 : sqlite3_result_int(pContext, sHeader.bEmpty);
8349 : }
8350 :
8351 : /************************************************************************/
8352 : /* OGRGeoPackageSTGeometryType() */
8353 : /************************************************************************/
8354 :
8355 7 : static void OGRGeoPackageSTGeometryType(sqlite3_context *pContext, int /*argc*/,
8356 : sqlite3_value **argv)
8357 : {
8358 : GPkgHeader sHeader;
8359 :
8360 7 : int nBLOBLen = sqlite3_value_bytes(argv[0]);
8361 : const GByte *pabyBLOB =
8362 7 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8363 : OGRwkbGeometryType eGeometryType;
8364 :
8365 13 : if (nBLOBLen < 8 ||
8366 6 : GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
8367 : {
8368 2 : if (OGRSQLiteGetSpatialiteGeometryHeader(
8369 : pabyBLOB, nBLOBLen, nullptr, &eGeometryType, nullptr, nullptr,
8370 2 : nullptr, nullptr, nullptr) == OGRERR_NONE)
8371 : {
8372 1 : sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
8373 : SQLITE_TRANSIENT);
8374 4 : return;
8375 : }
8376 : else
8377 : {
8378 1 : sqlite3_result_null(pContext);
8379 1 : return;
8380 : }
8381 : }
8382 :
8383 5 : if (static_cast<size_t>(nBLOBLen) < sHeader.nHeaderLen + 5)
8384 : {
8385 2 : sqlite3_result_null(pContext);
8386 2 : return;
8387 : }
8388 :
8389 3 : OGRErr err = OGRReadWKBGeometryType(pabyBLOB + sHeader.nHeaderLen,
8390 : wkbVariantIso, &eGeometryType);
8391 3 : if (err != OGRERR_NONE)
8392 1 : sqlite3_result_null(pContext);
8393 : else
8394 2 : sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
8395 : SQLITE_TRANSIENT);
8396 : }
8397 :
8398 : /************************************************************************/
8399 : /* OGRGeoPackageSTEnvelopesIntersects() */
8400 : /************************************************************************/
8401 :
8402 358 : static void OGRGeoPackageSTEnvelopesIntersects(sqlite3_context *pContext,
8403 : int argc, sqlite3_value **argv)
8404 : {
8405 : GPkgHeader sHeader;
8406 358 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8407 : {
8408 2 : sqlite3_result_int(pContext, FALSE);
8409 197 : return;
8410 : }
8411 356 : const double dfMinX = sqlite3_value_double(argv[1]);
8412 356 : if (sHeader.MaxX < dfMinX)
8413 : {
8414 105 : sqlite3_result_int(pContext, FALSE);
8415 105 : return;
8416 : }
8417 251 : const double dfMinY = sqlite3_value_double(argv[2]);
8418 251 : if (sHeader.MaxY < dfMinY)
8419 : {
8420 23 : sqlite3_result_int(pContext, FALSE);
8421 23 : return;
8422 : }
8423 228 : const double dfMaxX = sqlite3_value_double(argv[3]);
8424 228 : if (sHeader.MinX > dfMaxX)
8425 : {
8426 67 : sqlite3_result_int(pContext, FALSE);
8427 67 : return;
8428 : }
8429 161 : const double dfMaxY = sqlite3_value_double(argv[4]);
8430 161 : sqlite3_result_int(pContext, sHeader.MinY <= dfMaxY);
8431 : }
8432 :
8433 : /************************************************************************/
8434 : /* OGRGeoPackageSTEnvelopesIntersectsTwoParams() */
8435 : /************************************************************************/
8436 :
8437 : static void
8438 3 : OGRGeoPackageSTEnvelopesIntersectsTwoParams(sqlite3_context *pContext, int argc,
8439 : sqlite3_value **argv)
8440 : {
8441 : GPkgHeader sHeader;
8442 3 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false, 0))
8443 : {
8444 0 : sqlite3_result_int(pContext, FALSE);
8445 2 : return;
8446 : }
8447 : GPkgHeader sHeader2;
8448 3 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader2, true, false,
8449 : 1))
8450 : {
8451 0 : sqlite3_result_int(pContext, FALSE);
8452 0 : return;
8453 : }
8454 3 : if (sHeader.MaxX < sHeader2.MinX)
8455 : {
8456 1 : sqlite3_result_int(pContext, FALSE);
8457 1 : return;
8458 : }
8459 2 : if (sHeader.MaxY < sHeader2.MinY)
8460 : {
8461 0 : sqlite3_result_int(pContext, FALSE);
8462 0 : return;
8463 : }
8464 2 : if (sHeader.MinX > sHeader2.MaxX)
8465 : {
8466 1 : sqlite3_result_int(pContext, FALSE);
8467 1 : return;
8468 : }
8469 1 : sqlite3_result_int(pContext, sHeader.MinY <= sHeader2.MaxY);
8470 : }
8471 :
8472 : /************************************************************************/
8473 : /* OGRGeoPackageGPKGIsAssignable() */
8474 : /************************************************************************/
8475 :
8476 8 : static void OGRGeoPackageGPKGIsAssignable(sqlite3_context *pContext,
8477 : int /*argc*/, sqlite3_value **argv)
8478 : {
8479 15 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8480 7 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
8481 : {
8482 2 : sqlite3_result_int(pContext, 0);
8483 2 : return;
8484 : }
8485 :
8486 : const char *pszExpected =
8487 6 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8488 : const char *pszActual =
8489 6 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8490 6 : int bIsAssignable = OGR_GT_IsSubClassOf(OGRFromOGCGeomType(pszActual),
8491 : OGRFromOGCGeomType(pszExpected));
8492 6 : sqlite3_result_int(pContext, bIsAssignable);
8493 : }
8494 :
8495 : /************************************************************************/
8496 : /* OGRGeoPackageSTSRID() */
8497 : /************************************************************************/
8498 :
8499 12 : static void OGRGeoPackageSTSRID(sqlite3_context *pContext, int argc,
8500 : sqlite3_value **argv)
8501 : {
8502 : GPkgHeader sHeader;
8503 12 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8504 : {
8505 2 : sqlite3_result_null(pContext);
8506 2 : return;
8507 : }
8508 10 : sqlite3_result_int(pContext, sHeader.iSrsId);
8509 : }
8510 :
8511 : /************************************************************************/
8512 : /* OGRGeoPackageSetSRID() */
8513 : /************************************************************************/
8514 :
8515 28 : static void OGRGeoPackageSetSRID(sqlite3_context *pContext, int /* argc */,
8516 : sqlite3_value **argv)
8517 : {
8518 28 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8519 : {
8520 1 : sqlite3_result_null(pContext);
8521 1 : return;
8522 : }
8523 27 : const int nDestSRID = sqlite3_value_int(argv[1]);
8524 : GPkgHeader sHeader;
8525 27 : int nBLOBLen = sqlite3_value_bytes(argv[0]);
8526 : const GByte *pabyBLOB =
8527 27 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8528 :
8529 54 : if (nBLOBLen < 8 ||
8530 27 : GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
8531 : {
8532 : // Try also spatialite geometry blobs
8533 0 : OGRGeometry *poGeom = nullptr;
8534 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeom) !=
8535 : OGRERR_NONE)
8536 : {
8537 0 : sqlite3_result_null(pContext);
8538 0 : return;
8539 : }
8540 0 : size_t nBLOBDestLen = 0;
8541 : GByte *pabyDestBLOB =
8542 0 : GPkgGeometryFromOGR(poGeom, nDestSRID, nullptr, &nBLOBDestLen);
8543 0 : if (!pabyDestBLOB)
8544 : {
8545 0 : sqlite3_result_null(pContext);
8546 0 : return;
8547 : }
8548 0 : sqlite3_result_blob(pContext, pabyDestBLOB,
8549 : static_cast<int>(nBLOBDestLen), VSIFree);
8550 0 : return;
8551 : }
8552 :
8553 27 : GByte *pabyDestBLOB = static_cast<GByte *>(CPLMalloc(nBLOBLen));
8554 27 : memcpy(pabyDestBLOB, pabyBLOB, nBLOBLen);
8555 27 : int32_t nSRIDToSerialize = nDestSRID;
8556 27 : if (OGR_SWAP(sHeader.eByteOrder))
8557 0 : nSRIDToSerialize = CPL_SWAP32(nSRIDToSerialize);
8558 27 : memcpy(pabyDestBLOB + 4, &nSRIDToSerialize, 4);
8559 27 : sqlite3_result_blob(pContext, pabyDestBLOB, nBLOBLen, VSIFree);
8560 : }
8561 :
8562 : /************************************************************************/
8563 : /* OGRGeoPackageSTMakeValid() */
8564 : /************************************************************************/
8565 :
8566 3 : static void OGRGeoPackageSTMakeValid(sqlite3_context *pContext, int argc,
8567 : sqlite3_value **argv)
8568 : {
8569 3 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8570 : {
8571 2 : sqlite3_result_null(pContext);
8572 2 : return;
8573 : }
8574 1 : int nBLOBLen = sqlite3_value_bytes(argv[0]);
8575 : const GByte *pabyBLOB =
8576 1 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8577 :
8578 : GPkgHeader sHeader;
8579 1 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8580 : {
8581 0 : sqlite3_result_null(pContext);
8582 0 : return;
8583 : }
8584 :
8585 : auto poGeom = std::unique_ptr<OGRGeometry>(
8586 1 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8587 1 : if (poGeom == nullptr)
8588 : {
8589 : // Try also spatialite geometry blobs
8590 0 : OGRGeometry *poGeomPtr = nullptr;
8591 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
8592 : OGRERR_NONE)
8593 : {
8594 0 : sqlite3_result_null(pContext);
8595 0 : return;
8596 : }
8597 0 : poGeom.reset(poGeomPtr);
8598 : }
8599 1 : auto poValid = std::unique_ptr<OGRGeometry>(poGeom->MakeValid());
8600 1 : if (poValid == nullptr)
8601 : {
8602 0 : sqlite3_result_null(pContext);
8603 0 : return;
8604 : }
8605 :
8606 1 : size_t nBLOBDestLen = 0;
8607 1 : GByte *pabyDestBLOB = GPkgGeometryFromOGR(poValid.get(), sHeader.iSrsId,
8608 : nullptr, &nBLOBDestLen);
8609 1 : if (!pabyDestBLOB)
8610 : {
8611 0 : sqlite3_result_null(pContext);
8612 0 : return;
8613 : }
8614 1 : sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
8615 : VSIFree);
8616 : }
8617 :
8618 : /************************************************************************/
8619 : /* OGRGeoPackageSTArea() */
8620 : /************************************************************************/
8621 :
8622 19 : static void OGRGeoPackageSTArea(sqlite3_context *pContext, int /*argc*/,
8623 : sqlite3_value **argv)
8624 : {
8625 19 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8626 : {
8627 1 : sqlite3_result_null(pContext);
8628 15 : return;
8629 : }
8630 18 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8631 : const GByte *pabyBLOB =
8632 18 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8633 :
8634 : GPkgHeader sHeader;
8635 0 : std::unique_ptr<OGRGeometry> poGeom;
8636 18 : if (GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) == OGRERR_NONE)
8637 : {
8638 16 : if (sHeader.bEmpty)
8639 : {
8640 3 : sqlite3_result_double(pContext, 0);
8641 13 : return;
8642 : }
8643 13 : const GByte *pabyWkb = pabyBLOB + sHeader.nHeaderLen;
8644 13 : size_t nWKBSize = nBLOBLen - sHeader.nHeaderLen;
8645 : bool bNeedSwap;
8646 : uint32_t nType;
8647 13 : if (OGRWKBGetGeomType(pabyWkb, nWKBSize, bNeedSwap, nType))
8648 : {
8649 13 : if (nType == wkbPolygon || nType == wkbPolygon25D ||
8650 11 : nType == wkbPolygon + 1000 || // wkbPolygonZ
8651 10 : nType == wkbPolygonM || nType == wkbPolygonZM)
8652 : {
8653 : double dfArea;
8654 5 : if (OGRWKBPolygonGetArea(pabyWkb, nWKBSize, dfArea))
8655 : {
8656 5 : sqlite3_result_double(pContext, dfArea);
8657 5 : return;
8658 0 : }
8659 : }
8660 8 : else if (nType == wkbMultiPolygon || nType == wkbMultiPolygon25D ||
8661 6 : nType == wkbMultiPolygon + 1000 || // wkbMultiPolygonZ
8662 5 : nType == wkbMultiPolygonM || nType == wkbMultiPolygonZM)
8663 : {
8664 : double dfArea;
8665 5 : if (OGRWKBMultiPolygonGetArea(pabyWkb, nWKBSize, dfArea))
8666 : {
8667 5 : sqlite3_result_double(pContext, dfArea);
8668 5 : return;
8669 : }
8670 : }
8671 : }
8672 :
8673 : // For curve geometries, fallback to OGRGeometry methods
8674 3 : poGeom.reset(GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8675 : }
8676 : else
8677 : {
8678 : // Try also spatialite geometry blobs
8679 2 : OGRGeometry *poGeomPtr = nullptr;
8680 2 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
8681 : OGRERR_NONE)
8682 : {
8683 1 : sqlite3_result_null(pContext);
8684 1 : return;
8685 : }
8686 1 : poGeom.reset(poGeomPtr);
8687 : }
8688 4 : auto poSurface = dynamic_cast<OGRSurface *>(poGeom.get());
8689 4 : if (poSurface == nullptr)
8690 : {
8691 2 : auto poMultiSurface = dynamic_cast<OGRMultiSurface *>(poGeom.get());
8692 2 : if (poMultiSurface == nullptr)
8693 : {
8694 1 : sqlite3_result_double(pContext, 0);
8695 : }
8696 : else
8697 : {
8698 1 : sqlite3_result_double(pContext, poMultiSurface->get_Area());
8699 : }
8700 : }
8701 : else
8702 : {
8703 2 : sqlite3_result_double(pContext, poSurface->get_Area());
8704 : }
8705 : }
8706 :
8707 : /************************************************************************/
8708 : /* OGRGeoPackageGeodesicArea() */
8709 : /************************************************************************/
8710 :
8711 5 : static void OGRGeoPackageGeodesicArea(sqlite3_context *pContext, int argc,
8712 : sqlite3_value **argv)
8713 : {
8714 5 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8715 : {
8716 1 : sqlite3_result_null(pContext);
8717 3 : return;
8718 : }
8719 4 : if (sqlite3_value_int(argv[1]) != 1)
8720 : {
8721 2 : CPLError(CE_Warning, CPLE_NotSupported,
8722 : "ST_Area(geom, use_ellipsoid) is only supported for "
8723 : "use_ellipsoid = 1");
8724 : }
8725 :
8726 4 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8727 : const GByte *pabyBLOB =
8728 4 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8729 : GPkgHeader sHeader;
8730 4 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8731 : {
8732 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8733 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8734 1 : return;
8735 : }
8736 :
8737 : GDALGeoPackageDataset *poDS =
8738 3 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8739 :
8740 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSrcSRS(
8741 3 : poDS->GetSpatialRef(sHeader.iSrsId, true));
8742 3 : if (poSrcSRS == nullptr)
8743 : {
8744 1 : CPLError(CE_Failure, CPLE_AppDefined,
8745 : "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8746 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8747 1 : return;
8748 : }
8749 :
8750 : auto poGeom = std::unique_ptr<OGRGeometry>(
8751 2 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8752 2 : if (poGeom == nullptr)
8753 : {
8754 : // Try also spatialite geometry blobs
8755 0 : OGRGeometry *poGeomSpatialite = nullptr;
8756 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8757 0 : &poGeomSpatialite) != OGRERR_NONE)
8758 : {
8759 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8760 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8761 0 : return;
8762 : }
8763 0 : poGeom.reset(poGeomSpatialite);
8764 : }
8765 :
8766 2 : poGeom->assignSpatialReference(poSrcSRS.get());
8767 2 : sqlite3_result_double(
8768 : pContext, OGR_G_GeodesicArea(OGRGeometry::ToHandle(poGeom.get())));
8769 : }
8770 :
8771 : /************************************************************************/
8772 : /* OGRGeoPackageLengthOrGeodesicLength() */
8773 : /************************************************************************/
8774 :
8775 8 : static void OGRGeoPackageLengthOrGeodesicLength(sqlite3_context *pContext,
8776 : int argc, sqlite3_value **argv)
8777 : {
8778 8 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8779 : {
8780 2 : sqlite3_result_null(pContext);
8781 5 : return;
8782 : }
8783 6 : if (argc == 2 && sqlite3_value_int(argv[1]) != 1)
8784 : {
8785 2 : CPLError(CE_Warning, CPLE_NotSupported,
8786 : "ST_Length(geom, use_ellipsoid) is only supported for "
8787 : "use_ellipsoid = 1");
8788 : }
8789 :
8790 6 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8791 : const GByte *pabyBLOB =
8792 6 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8793 : GPkgHeader sHeader;
8794 6 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8795 : {
8796 2 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8797 2 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8798 2 : return;
8799 : }
8800 :
8801 : GDALGeoPackageDataset *poDS =
8802 4 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8803 :
8804 0 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSrcSRS;
8805 4 : if (argc == 2)
8806 : {
8807 3 : poSrcSRS = poDS->GetSpatialRef(sHeader.iSrsId, true);
8808 3 : if (!poSrcSRS)
8809 : {
8810 1 : CPLError(CE_Failure, CPLE_AppDefined,
8811 : "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8812 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8813 1 : return;
8814 : }
8815 : }
8816 :
8817 : auto poGeom = std::unique_ptr<OGRGeometry>(
8818 3 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8819 3 : if (poGeom == nullptr)
8820 : {
8821 : // Try also spatialite geometry blobs
8822 0 : OGRGeometry *poGeomSpatialite = nullptr;
8823 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8824 0 : &poGeomSpatialite) != OGRERR_NONE)
8825 : {
8826 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8827 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8828 0 : return;
8829 : }
8830 0 : poGeom.reset(poGeomSpatialite);
8831 : }
8832 :
8833 3 : if (argc == 2)
8834 2 : poGeom->assignSpatialReference(poSrcSRS.get());
8835 :
8836 6 : sqlite3_result_double(
8837 : pContext,
8838 1 : argc == 1 ? OGR_G_Length(OGRGeometry::ToHandle(poGeom.get()))
8839 2 : : OGR_G_GeodesicLength(OGRGeometry::ToHandle(poGeom.get())));
8840 : }
8841 :
8842 : /************************************************************************/
8843 : /* OGRGeoPackageTransform() */
8844 : /************************************************************************/
8845 :
8846 : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
8847 : sqlite3_value **argv);
8848 :
8849 32 : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
8850 : sqlite3_value **argv)
8851 : {
8852 63 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB ||
8853 31 : sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
8854 : {
8855 2 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8856 32 : return;
8857 : }
8858 :
8859 30 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8860 : const GByte *pabyBLOB =
8861 30 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8862 : GPkgHeader sHeader;
8863 30 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8864 : {
8865 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8866 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8867 1 : return;
8868 : }
8869 :
8870 29 : const int nDestSRID = sqlite3_value_int(argv[1]);
8871 29 : if (sHeader.iSrsId == nDestSRID)
8872 : {
8873 : // Return blob unmodified
8874 3 : sqlite3_result_blob(pContext, pabyBLOB, nBLOBLen, SQLITE_TRANSIENT);
8875 3 : return;
8876 : }
8877 :
8878 : GDALGeoPackageDataset *poDS =
8879 26 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8880 :
8881 : // Try to get the cached coordinate transformation
8882 : OGRCoordinateTransformation *poCT;
8883 26 : if (poDS->m_nLastCachedCTSrcSRId == sHeader.iSrsId &&
8884 20 : poDS->m_nLastCachedCTDstSRId == nDestSRID)
8885 : {
8886 20 : poCT = poDS->m_poLastCachedCT.get();
8887 : }
8888 : else
8889 : {
8890 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
8891 6 : poSrcSRS(poDS->GetSpatialRef(sHeader.iSrsId, true));
8892 6 : if (poSrcSRS == nullptr)
8893 : {
8894 0 : CPLError(CE_Failure, CPLE_AppDefined,
8895 : "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8896 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8897 0 : return;
8898 : }
8899 :
8900 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
8901 6 : poDstSRS(poDS->GetSpatialRef(nDestSRID, true));
8902 6 : if (poDstSRS == nullptr)
8903 : {
8904 0 : CPLError(CE_Failure, CPLE_AppDefined, "Target SRID (%d) is invalid",
8905 : nDestSRID);
8906 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8907 0 : return;
8908 : }
8909 : poCT =
8910 6 : OGRCreateCoordinateTransformation(poSrcSRS.get(), poDstSRS.get());
8911 6 : if (poCT == nullptr)
8912 : {
8913 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8914 0 : return;
8915 : }
8916 :
8917 : // Cache coordinate transformation for potential later reuse
8918 6 : poDS->m_nLastCachedCTSrcSRId = sHeader.iSrsId;
8919 6 : poDS->m_nLastCachedCTDstSRId = nDestSRID;
8920 6 : poDS->m_poLastCachedCT.reset(poCT);
8921 6 : poCT = poDS->m_poLastCachedCT.get();
8922 : }
8923 :
8924 26 : if (sHeader.nHeaderLen >= 8)
8925 : {
8926 26 : std::vector<GByte> &abyNewBLOB = poDS->m_abyWKBTransformCache;
8927 26 : abyNewBLOB.resize(nBLOBLen);
8928 26 : memcpy(abyNewBLOB.data(), pabyBLOB, nBLOBLen);
8929 :
8930 26 : OGREnvelope3D oEnv3d;
8931 26 : if (!OGRWKBTransform(abyNewBLOB.data() + sHeader.nHeaderLen,
8932 26 : nBLOBLen - sHeader.nHeaderLen, poCT,
8933 78 : poDS->m_oWKBTransformCache, oEnv3d) ||
8934 26 : !GPkgUpdateHeader(abyNewBLOB.data(), nBLOBLen, nDestSRID,
8935 : oEnv3d.MinX, oEnv3d.MaxX, oEnv3d.MinY,
8936 : oEnv3d.MaxY, oEnv3d.MinZ, oEnv3d.MaxZ))
8937 : {
8938 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8939 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8940 0 : return;
8941 : }
8942 :
8943 26 : sqlite3_result_blob(pContext, abyNewBLOB.data(), nBLOBLen,
8944 : SQLITE_TRANSIENT);
8945 26 : return;
8946 : }
8947 :
8948 : // Try also spatialite geometry blobs
8949 0 : OGRGeometry *poGeomSpatialite = nullptr;
8950 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8951 0 : &poGeomSpatialite) != OGRERR_NONE)
8952 : {
8953 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8954 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8955 0 : return;
8956 : }
8957 0 : auto poGeom = std::unique_ptr<OGRGeometry>(poGeomSpatialite);
8958 :
8959 0 : if (poGeom->transform(poCT) != OGRERR_NONE)
8960 : {
8961 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8962 0 : return;
8963 : }
8964 :
8965 0 : size_t nBLOBDestLen = 0;
8966 : GByte *pabyDestBLOB =
8967 0 : GPkgGeometryFromOGR(poGeom.get(), nDestSRID, nullptr, &nBLOBDestLen);
8968 0 : if (!pabyDestBLOB)
8969 : {
8970 0 : sqlite3_result_null(pContext);
8971 0 : return;
8972 : }
8973 0 : sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
8974 : VSIFree);
8975 : }
8976 :
8977 : /************************************************************************/
8978 : /* OGRGeoPackageSridFromAuthCRS() */
8979 : /************************************************************************/
8980 :
8981 4 : static void OGRGeoPackageSridFromAuthCRS(sqlite3_context *pContext,
8982 : int /*argc*/, sqlite3_value **argv)
8983 : {
8984 7 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8985 3 : sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
8986 : {
8987 2 : sqlite3_result_int(pContext, -1);
8988 2 : return;
8989 : }
8990 :
8991 : GDALGeoPackageDataset *poDS =
8992 2 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8993 :
8994 2 : char *pszSQL = sqlite3_mprintf(
8995 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
8996 : "lower(organization) = lower('%q') AND organization_coordsys_id = %d",
8997 2 : sqlite3_value_text(argv[0]), sqlite3_value_int(argv[1]));
8998 2 : OGRErr err = OGRERR_NONE;
8999 2 : int nSRSId = SQLGetInteger(poDS->GetDB(), pszSQL, &err);
9000 2 : sqlite3_free(pszSQL);
9001 2 : if (err != OGRERR_NONE)
9002 1 : nSRSId = -1;
9003 2 : sqlite3_result_int(pContext, nSRSId);
9004 : }
9005 :
9006 : /************************************************************************/
9007 : /* OGRGeoPackageImportFromEPSG() */
9008 : /************************************************************************/
9009 :
9010 4 : static void OGRGeoPackageImportFromEPSG(sqlite3_context *pContext, int /*argc*/,
9011 : sqlite3_value **argv)
9012 : {
9013 4 : if (sqlite3_value_type(argv[0]) != SQLITE_INTEGER)
9014 : {
9015 1 : sqlite3_result_int(pContext, -1);
9016 2 : return;
9017 : }
9018 :
9019 : GDALGeoPackageDataset *poDS =
9020 3 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9021 3 : OGRSpatialReference oSRS;
9022 3 : if (oSRS.importFromEPSG(sqlite3_value_int(argv[0])) != OGRERR_NONE)
9023 : {
9024 1 : sqlite3_result_int(pContext, -1);
9025 1 : return;
9026 : }
9027 :
9028 2 : sqlite3_result_int(pContext, poDS->GetSrsId(&oSRS));
9029 : }
9030 :
9031 : /************************************************************************/
9032 : /* OGRGeoPackageRegisterGeometryExtension() */
9033 : /************************************************************************/
9034 :
9035 1 : static void OGRGeoPackageRegisterGeometryExtension(sqlite3_context *pContext,
9036 : int /*argc*/,
9037 : sqlite3_value **argv)
9038 : {
9039 1 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9040 2 : sqlite3_value_type(argv[1]) != SQLITE_TEXT ||
9041 1 : sqlite3_value_type(argv[2]) != SQLITE_TEXT)
9042 : {
9043 0 : sqlite3_result_int(pContext, 0);
9044 0 : return;
9045 : }
9046 :
9047 : const char *pszTableName =
9048 1 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9049 : const char *pszGeomName =
9050 1 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9051 : const char *pszGeomType =
9052 1 : reinterpret_cast<const char *>(sqlite3_value_text(argv[2]));
9053 :
9054 : GDALGeoPackageDataset *poDS =
9055 1 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9056 :
9057 1 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
9058 1 : poDS->GetLayerByName(pszTableName));
9059 1 : if (poLyr == nullptr)
9060 : {
9061 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
9062 0 : sqlite3_result_int(pContext, 0);
9063 0 : return;
9064 : }
9065 1 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
9066 : {
9067 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
9068 0 : sqlite3_result_int(pContext, 0);
9069 0 : return;
9070 : }
9071 1 : const OGRwkbGeometryType eGeomType = OGRFromOGCGeomType(pszGeomType);
9072 1 : if (eGeomType == wkbUnknown)
9073 : {
9074 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry type name");
9075 0 : sqlite3_result_int(pContext, 0);
9076 0 : return;
9077 : }
9078 :
9079 1 : sqlite3_result_int(
9080 : pContext,
9081 1 : static_cast<int>(poLyr->CreateGeometryExtensionIfNecessary(eGeomType)));
9082 : }
9083 :
9084 : /************************************************************************/
9085 : /* OGRGeoPackageCreateSpatialIndex() */
9086 : /************************************************************************/
9087 :
9088 14 : static void OGRGeoPackageCreateSpatialIndex(sqlite3_context *pContext,
9089 : int /*argc*/, sqlite3_value **argv)
9090 : {
9091 27 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9092 13 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9093 : {
9094 2 : sqlite3_result_int(pContext, 0);
9095 2 : return;
9096 : }
9097 :
9098 : const char *pszTableName =
9099 12 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9100 : const char *pszGeomName =
9101 12 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9102 : GDALGeoPackageDataset *poDS =
9103 12 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9104 :
9105 12 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
9106 12 : poDS->GetLayerByName(pszTableName));
9107 12 : if (poLyr == nullptr)
9108 : {
9109 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
9110 1 : sqlite3_result_int(pContext, 0);
9111 1 : return;
9112 : }
9113 11 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
9114 : {
9115 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
9116 1 : sqlite3_result_int(pContext, 0);
9117 1 : return;
9118 : }
9119 :
9120 10 : sqlite3_result_int(pContext, poLyr->CreateSpatialIndex());
9121 : }
9122 :
9123 : /************************************************************************/
9124 : /* OGRGeoPackageDisableSpatialIndex() */
9125 : /************************************************************************/
9126 :
9127 12 : static void OGRGeoPackageDisableSpatialIndex(sqlite3_context *pContext,
9128 : int /*argc*/, sqlite3_value **argv)
9129 : {
9130 23 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9131 11 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9132 : {
9133 2 : sqlite3_result_int(pContext, 0);
9134 2 : return;
9135 : }
9136 :
9137 : const char *pszTableName =
9138 10 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9139 : const char *pszGeomName =
9140 10 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9141 : GDALGeoPackageDataset *poDS =
9142 10 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9143 :
9144 10 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
9145 10 : poDS->GetLayerByName(pszTableName));
9146 10 : if (poLyr == nullptr)
9147 : {
9148 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
9149 1 : sqlite3_result_int(pContext, 0);
9150 1 : return;
9151 : }
9152 9 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
9153 : {
9154 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
9155 1 : sqlite3_result_int(pContext, 0);
9156 1 : return;
9157 : }
9158 :
9159 8 : sqlite3_result_int(pContext, poLyr->DropSpatialIndex(true));
9160 : }
9161 :
9162 : /************************************************************************/
9163 : /* OGRGeoPackageHasSpatialIndex() */
9164 : /************************************************************************/
9165 :
9166 29 : static void OGRGeoPackageHasSpatialIndex(sqlite3_context *pContext,
9167 : int /*argc*/, sqlite3_value **argv)
9168 : {
9169 57 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9170 28 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9171 : {
9172 2 : sqlite3_result_int(pContext, 0);
9173 2 : return;
9174 : }
9175 :
9176 : const char *pszTableName =
9177 27 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9178 : const char *pszGeomName =
9179 27 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9180 : GDALGeoPackageDataset *poDS =
9181 27 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9182 :
9183 27 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
9184 27 : poDS->GetLayerByName(pszTableName));
9185 27 : if (poLyr == nullptr)
9186 : {
9187 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
9188 1 : sqlite3_result_int(pContext, 0);
9189 1 : return;
9190 : }
9191 26 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
9192 : {
9193 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
9194 1 : sqlite3_result_int(pContext, 0);
9195 1 : return;
9196 : }
9197 :
9198 25 : poLyr->RunDeferredCreationIfNecessary();
9199 25 : poLyr->CreateSpatialIndexIfNecessary();
9200 :
9201 25 : sqlite3_result_int(pContext, poLyr->HasSpatialIndex());
9202 : }
9203 :
9204 : /************************************************************************/
9205 : /* GPKG_hstore_get_value() */
9206 : /************************************************************************/
9207 :
9208 4 : static void GPKG_hstore_get_value(sqlite3_context *pContext, int /*argc*/,
9209 : sqlite3_value **argv)
9210 : {
9211 7 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9212 3 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9213 : {
9214 2 : sqlite3_result_null(pContext);
9215 2 : return;
9216 : }
9217 :
9218 : const char *pszHStore =
9219 2 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9220 : const char *pszSearchedKey =
9221 2 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9222 2 : char *pszValue = OGRHStoreGetValue(pszHStore, pszSearchedKey);
9223 2 : if (pszValue != nullptr)
9224 1 : sqlite3_result_text(pContext, pszValue, -1, CPLFree);
9225 : else
9226 1 : sqlite3_result_null(pContext);
9227 : }
9228 :
9229 : /************************************************************************/
9230 : /* GPKG_GDAL_GetMemFileFromBlob() */
9231 : /************************************************************************/
9232 :
9233 105 : static CPLString GPKG_GDAL_GetMemFileFromBlob(sqlite3_value **argv)
9234 : {
9235 105 : int nBytes = sqlite3_value_bytes(argv[0]);
9236 : const GByte *pabyBLOB =
9237 105 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
9238 : const CPLString osMemFileName(
9239 105 : VSIMemGenerateHiddenFilename("GPKG_GDAL_GetMemFileFromBlob"));
9240 105 : VSILFILE *fp = VSIFileFromMemBuffer(
9241 : osMemFileName.c_str(), const_cast<GByte *>(pabyBLOB), nBytes, FALSE);
9242 105 : VSIFCloseL(fp);
9243 105 : return osMemFileName;
9244 : }
9245 :
9246 : /************************************************************************/
9247 : /* GPKG_GDAL_GetMimeType() */
9248 : /************************************************************************/
9249 :
9250 35 : static void GPKG_GDAL_GetMimeType(sqlite3_context *pContext, int /*argc*/,
9251 : sqlite3_value **argv)
9252 : {
9253 35 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
9254 : {
9255 0 : sqlite3_result_null(pContext);
9256 0 : return;
9257 : }
9258 :
9259 70 : CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
9260 : GDALDriver *poDriver =
9261 35 : GDALDriver::FromHandle(GDALIdentifyDriver(osMemFileName, nullptr));
9262 35 : if (poDriver != nullptr)
9263 : {
9264 35 : const char *pszRes = nullptr;
9265 35 : if (EQUAL(poDriver->GetDescription(), "PNG"))
9266 23 : pszRes = "image/png";
9267 12 : else if (EQUAL(poDriver->GetDescription(), "JPEG"))
9268 6 : pszRes = "image/jpeg";
9269 6 : else if (EQUAL(poDriver->GetDescription(), "WEBP"))
9270 6 : pszRes = "image/x-webp";
9271 0 : else if (EQUAL(poDriver->GetDescription(), "GTIFF"))
9272 0 : pszRes = "image/tiff";
9273 : else
9274 0 : pszRes = CPLSPrintf("gdal/%s", poDriver->GetDescription());
9275 35 : sqlite3_result_text(pContext, pszRes, -1, SQLITE_TRANSIENT);
9276 : }
9277 : else
9278 0 : sqlite3_result_null(pContext);
9279 35 : VSIUnlink(osMemFileName);
9280 : }
9281 :
9282 : /************************************************************************/
9283 : /* GPKG_GDAL_GetBandCount() */
9284 : /************************************************************************/
9285 :
9286 35 : static void GPKG_GDAL_GetBandCount(sqlite3_context *pContext, int /*argc*/,
9287 : sqlite3_value **argv)
9288 : {
9289 35 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
9290 : {
9291 0 : sqlite3_result_null(pContext);
9292 0 : return;
9293 : }
9294 :
9295 70 : CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
9296 : auto poDS = std::unique_ptr<GDALDataset>(
9297 : GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
9298 70 : nullptr, nullptr, nullptr));
9299 35 : if (poDS != nullptr)
9300 : {
9301 35 : sqlite3_result_int(pContext, poDS->GetRasterCount());
9302 : }
9303 : else
9304 0 : sqlite3_result_null(pContext);
9305 35 : VSIUnlink(osMemFileName);
9306 : }
9307 :
9308 : /************************************************************************/
9309 : /* GPKG_GDAL_HasColorTable() */
9310 : /************************************************************************/
9311 :
9312 35 : static void GPKG_GDAL_HasColorTable(sqlite3_context *pContext, int /*argc*/,
9313 : sqlite3_value **argv)
9314 : {
9315 35 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
9316 : {
9317 0 : sqlite3_result_null(pContext);
9318 0 : return;
9319 : }
9320 :
9321 70 : CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
9322 : auto poDS = std::unique_ptr<GDALDataset>(
9323 : GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
9324 70 : nullptr, nullptr, nullptr));
9325 35 : if (poDS != nullptr)
9326 : {
9327 35 : sqlite3_result_int(
9328 46 : pContext, poDS->GetRasterCount() == 1 &&
9329 11 : poDS->GetRasterBand(1)->GetColorTable() != nullptr);
9330 : }
9331 : else
9332 0 : sqlite3_result_null(pContext);
9333 35 : VSIUnlink(osMemFileName);
9334 : }
9335 :
9336 : /************************************************************************/
9337 : /* GetRasterLayerDataset() */
9338 : /************************************************************************/
9339 :
9340 : GDALDataset *
9341 12 : GDALGeoPackageDataset::GetRasterLayerDataset(const char *pszLayerName)
9342 : {
9343 12 : const auto oIter = m_oCachedRasterDS.find(pszLayerName);
9344 12 : if (oIter != m_oCachedRasterDS.end())
9345 10 : return oIter->second.get();
9346 :
9347 : auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
9348 4 : (std::string("GPKG:\"") + m_pszFilename + "\":" + pszLayerName).c_str(),
9349 4 : GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
9350 2 : if (!poDS)
9351 : {
9352 0 : return nullptr;
9353 : }
9354 2 : m_oCachedRasterDS[pszLayerName] = std::move(poDS);
9355 2 : return m_oCachedRasterDS[pszLayerName].get();
9356 : }
9357 :
9358 : /************************************************************************/
9359 : /* GPKG_gdal_get_layer_pixel_value() */
9360 : /************************************************************************/
9361 :
9362 : // NOTE: keep in sync implementations in ogrsqlitesqlfunctionscommon.cpp
9363 : // and ogrgeopackagedatasource.cpp
9364 13 : static void GPKG_gdal_get_layer_pixel_value(sqlite3_context *pContext, int argc,
9365 : sqlite3_value **argv)
9366 : {
9367 13 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
9368 : {
9369 1 : CPLError(CE_Failure, CPLE_AppDefined,
9370 : "Invalid arguments to gdal_get_layer_pixel_value()");
9371 1 : sqlite3_result_null(pContext);
9372 1 : return;
9373 : }
9374 :
9375 : const char *pszLayerName =
9376 12 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9377 :
9378 : GDALGeoPackageDataset *poGlobalDS =
9379 12 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9380 12 : auto poDS = poGlobalDS->GetRasterLayerDataset(pszLayerName);
9381 12 : if (!poDS)
9382 : {
9383 0 : sqlite3_result_null(pContext);
9384 0 : return;
9385 : }
9386 :
9387 12 : OGRSQLite_gdal_get_pixel_value_common("gdal_get_layer_pixel_value",
9388 : pContext, argc, argv, poDS);
9389 : }
9390 :
9391 : /************************************************************************/
9392 : /* GPKG_ogr_layer_Extent() */
9393 : /************************************************************************/
9394 :
9395 3 : static void GPKG_ogr_layer_Extent(sqlite3_context *pContext, int /*argc*/,
9396 : sqlite3_value **argv)
9397 : {
9398 3 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
9399 : {
9400 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s: Invalid argument type",
9401 : "ogr_layer_Extent");
9402 1 : sqlite3_result_null(pContext);
9403 2 : return;
9404 : }
9405 :
9406 : const char *pszLayerName =
9407 2 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9408 : GDALGeoPackageDataset *poDS =
9409 2 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9410 2 : OGRLayer *poLayer = poDS->GetLayerByName(pszLayerName);
9411 2 : if (!poLayer)
9412 : {
9413 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s: unknown layer",
9414 : "ogr_layer_Extent");
9415 1 : sqlite3_result_null(pContext);
9416 1 : return;
9417 : }
9418 :
9419 1 : if (poLayer->GetGeomType() == wkbNone)
9420 : {
9421 0 : sqlite3_result_null(pContext);
9422 0 : return;
9423 : }
9424 :
9425 1 : OGREnvelope sExtent;
9426 1 : if (poLayer->GetExtent(&sExtent) != OGRERR_NONE)
9427 : {
9428 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s: Cannot fetch layer extent",
9429 : "ogr_layer_Extent");
9430 0 : sqlite3_result_null(pContext);
9431 0 : return;
9432 : }
9433 :
9434 1 : OGRPolygon oPoly;
9435 1 : OGRLinearRing *poRing = new OGRLinearRing();
9436 1 : oPoly.addRingDirectly(poRing);
9437 1 : poRing->addPoint(sExtent.MinX, sExtent.MinY);
9438 1 : poRing->addPoint(sExtent.MaxX, sExtent.MinY);
9439 1 : poRing->addPoint(sExtent.MaxX, sExtent.MaxY);
9440 1 : poRing->addPoint(sExtent.MinX, sExtent.MaxY);
9441 1 : poRing->addPoint(sExtent.MinX, sExtent.MinY);
9442 :
9443 1 : const auto poSRS = poLayer->GetSpatialRef();
9444 1 : const int nSRID = poDS->GetSrsId(poSRS);
9445 1 : size_t nBLOBDestLen = 0;
9446 : GByte *pabyDestBLOB =
9447 1 : GPkgGeometryFromOGR(&oPoly, nSRID, nullptr, &nBLOBDestLen);
9448 1 : if (!pabyDestBLOB)
9449 : {
9450 0 : sqlite3_result_null(pContext);
9451 0 : return;
9452 : }
9453 1 : sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
9454 : VSIFree);
9455 : }
9456 :
9457 : /************************************************************************/
9458 : /* InstallSQLFunctions() */
9459 : /************************************************************************/
9460 :
9461 : #ifndef SQLITE_DETERMINISTIC
9462 : #define SQLITE_DETERMINISTIC 0
9463 : #endif
9464 :
9465 : #ifndef SQLITE_INNOCUOUS
9466 : #define SQLITE_INNOCUOUS 0
9467 : #endif
9468 :
9469 : #ifndef UTF8_INNOCUOUS
9470 : #define UTF8_INNOCUOUS (SQLITE_UTF8 | SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS)
9471 : #endif
9472 :
9473 1820 : void GDALGeoPackageDataset::InstallSQLFunctions()
9474 : {
9475 1820 : InitSpatialite();
9476 :
9477 : // Enable SpatiaLite 4.3 "amphibious" mode, i.e. that SpatiaLite functions
9478 : // that take geometries will accept GPKG encoded geometries without
9479 : // explicit conversion.
9480 : // Use sqlite3_exec() instead of SQLCommand() since we don't want verbose
9481 : // error.
9482 1820 : sqlite3_exec(hDB, "SELECT EnableGpkgAmphibiousMode()", nullptr, nullptr,
9483 : nullptr);
9484 :
9485 : /* Used by RTree Spatial Index Extension */
9486 1820 : sqlite3_create_function(hDB, "ST_MinX", 1, UTF8_INNOCUOUS, nullptr,
9487 : OGRGeoPackageSTMinX, nullptr, nullptr);
9488 1820 : sqlite3_create_function(hDB, "ST_MinY", 1, UTF8_INNOCUOUS, nullptr,
9489 : OGRGeoPackageSTMinY, nullptr, nullptr);
9490 1820 : sqlite3_create_function(hDB, "ST_MaxX", 1, UTF8_INNOCUOUS, nullptr,
9491 : OGRGeoPackageSTMaxX, nullptr, nullptr);
9492 1820 : sqlite3_create_function(hDB, "ST_MaxY", 1, UTF8_INNOCUOUS, nullptr,
9493 : OGRGeoPackageSTMaxY, nullptr, nullptr);
9494 1820 : sqlite3_create_function(hDB, "ST_IsEmpty", 1, UTF8_INNOCUOUS, nullptr,
9495 : OGRGeoPackageSTIsEmpty, nullptr, nullptr);
9496 :
9497 : /* Used by Geometry Type Triggers Extension */
9498 1820 : sqlite3_create_function(hDB, "ST_GeometryType", 1, UTF8_INNOCUOUS, nullptr,
9499 : OGRGeoPackageSTGeometryType, nullptr, nullptr);
9500 1820 : sqlite3_create_function(hDB, "GPKG_IsAssignable", 2, UTF8_INNOCUOUS,
9501 : nullptr, OGRGeoPackageGPKGIsAssignable, nullptr,
9502 : nullptr);
9503 :
9504 : /* Used by Geometry SRS ID Triggers Extension */
9505 1820 : sqlite3_create_function(hDB, "ST_SRID", 1, UTF8_INNOCUOUS, nullptr,
9506 : OGRGeoPackageSTSRID, nullptr, nullptr);
9507 :
9508 : /* Spatialite-like functions */
9509 1820 : sqlite3_create_function(hDB, "CreateSpatialIndex", 2, SQLITE_UTF8, this,
9510 : OGRGeoPackageCreateSpatialIndex, nullptr, nullptr);
9511 1820 : sqlite3_create_function(hDB, "DisableSpatialIndex", 2, SQLITE_UTF8, this,
9512 : OGRGeoPackageDisableSpatialIndex, nullptr, nullptr);
9513 1820 : sqlite3_create_function(hDB, "HasSpatialIndex", 2, SQLITE_UTF8, this,
9514 : OGRGeoPackageHasSpatialIndex, nullptr, nullptr);
9515 :
9516 : // HSTORE functions
9517 1820 : sqlite3_create_function(hDB, "hstore_get_value", 2, UTF8_INNOCUOUS, nullptr,
9518 : GPKG_hstore_get_value, nullptr, nullptr);
9519 :
9520 : // Override a few Spatialite functions to work with gpkg_spatial_ref_sys
9521 1820 : sqlite3_create_function(hDB, "ST_Transform", 2, UTF8_INNOCUOUS, this,
9522 : OGRGeoPackageTransform, nullptr, nullptr);
9523 1820 : sqlite3_create_function(hDB, "Transform", 2, UTF8_INNOCUOUS, this,
9524 : OGRGeoPackageTransform, nullptr, nullptr);
9525 1820 : sqlite3_create_function(hDB, "SridFromAuthCRS", 2, SQLITE_UTF8, this,
9526 : OGRGeoPackageSridFromAuthCRS, nullptr, nullptr);
9527 :
9528 1820 : sqlite3_create_function(hDB, "ST_EnvIntersects", 2, UTF8_INNOCUOUS, nullptr,
9529 : OGRGeoPackageSTEnvelopesIntersectsTwoParams,
9530 : nullptr, nullptr);
9531 1820 : sqlite3_create_function(
9532 : hDB, "ST_EnvelopesIntersects", 2, UTF8_INNOCUOUS, nullptr,
9533 : OGRGeoPackageSTEnvelopesIntersectsTwoParams, nullptr, nullptr);
9534 :
9535 1820 : sqlite3_create_function(hDB, "ST_EnvIntersects", 5, UTF8_INNOCUOUS, nullptr,
9536 : OGRGeoPackageSTEnvelopesIntersects, nullptr,
9537 : nullptr);
9538 1820 : sqlite3_create_function(hDB, "ST_EnvelopesIntersects", 5, UTF8_INNOCUOUS,
9539 : nullptr, OGRGeoPackageSTEnvelopesIntersects,
9540 : nullptr, nullptr);
9541 :
9542 : // Implementation that directly hacks the GeoPackage geometry blob header
9543 1820 : sqlite3_create_function(hDB, "SetSRID", 2, UTF8_INNOCUOUS, nullptr,
9544 : OGRGeoPackageSetSRID, nullptr, nullptr);
9545 :
9546 : // GDAL specific function
9547 1820 : sqlite3_create_function(hDB, "ImportFromEPSG", 1, SQLITE_UTF8, this,
9548 : OGRGeoPackageImportFromEPSG, nullptr, nullptr);
9549 :
9550 : // May be used by ogrmerge.py
9551 1820 : sqlite3_create_function(hDB, "RegisterGeometryExtension", 3, SQLITE_UTF8,
9552 : this, OGRGeoPackageRegisterGeometryExtension,
9553 : nullptr, nullptr);
9554 :
9555 1820 : if (OGRGeometryFactory::haveGEOS())
9556 : {
9557 1820 : sqlite3_create_function(hDB, "ST_MakeValid", 1, UTF8_INNOCUOUS, nullptr,
9558 : OGRGeoPackageSTMakeValid, nullptr, nullptr);
9559 : }
9560 :
9561 1820 : sqlite3_create_function(hDB, "ST_Length", 1, UTF8_INNOCUOUS, nullptr,
9562 : OGRGeoPackageLengthOrGeodesicLength, nullptr,
9563 : nullptr);
9564 1820 : sqlite3_create_function(hDB, "ST_Length", 2, UTF8_INNOCUOUS, this,
9565 : OGRGeoPackageLengthOrGeodesicLength, nullptr,
9566 : nullptr);
9567 :
9568 1820 : sqlite3_create_function(hDB, "ST_Area", 1, UTF8_INNOCUOUS, nullptr,
9569 : OGRGeoPackageSTArea, nullptr, nullptr);
9570 1820 : sqlite3_create_function(hDB, "ST_Area", 2, UTF8_INNOCUOUS, this,
9571 : OGRGeoPackageGeodesicArea, nullptr, nullptr);
9572 :
9573 : // Debug functions
9574 1820 : if (CPLTestBool(CPLGetConfigOption("GPKG_DEBUG", "FALSE")))
9575 : {
9576 417 : sqlite3_create_function(hDB, "GDAL_GetMimeType", 1,
9577 : SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9578 : GPKG_GDAL_GetMimeType, nullptr, nullptr);
9579 417 : sqlite3_create_function(hDB, "GDAL_GetBandCount", 1,
9580 : SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9581 : GPKG_GDAL_GetBandCount, nullptr, nullptr);
9582 417 : sqlite3_create_function(hDB, "GDAL_HasColorTable", 1,
9583 : SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9584 : GPKG_GDAL_HasColorTable, nullptr, nullptr);
9585 : }
9586 :
9587 1820 : sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 5, SQLITE_UTF8,
9588 : this, GPKG_gdal_get_layer_pixel_value, nullptr,
9589 : nullptr);
9590 1820 : sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 6, SQLITE_UTF8,
9591 : this, GPKG_gdal_get_layer_pixel_value, nullptr,
9592 : nullptr);
9593 :
9594 : // Function from VirtualOGR
9595 1820 : sqlite3_create_function(hDB, "ogr_layer_Extent", 1, SQLITE_UTF8, this,
9596 : GPKG_ogr_layer_Extent, nullptr, nullptr);
9597 :
9598 1820 : m_pSQLFunctionData = OGRSQLiteRegisterSQLFunctionsCommon(hDB);
9599 1820 : }
9600 :
9601 : /************************************************************************/
9602 : /* OpenOrCreateDB() */
9603 : /************************************************************************/
9604 :
9605 1821 : bool GDALGeoPackageDataset::OpenOrCreateDB(int flags)
9606 : {
9607 1821 : const bool bSuccess = OGRSQLiteBaseDataSource::OpenOrCreateDB(
9608 : flags, /*bRegisterOGR2SQLiteExtensions=*/false,
9609 : /*bLoadExtensions=*/true);
9610 1821 : if (!bSuccess)
9611 6 : return false;
9612 :
9613 : // Turning on recursive_triggers is needed so that DELETE triggers fire
9614 : // in a INSERT OR REPLACE statement. In particular this is needed to
9615 : // make sure gpkg_ogr_contents.feature_count is properly updated.
9616 1815 : SQLCommand(hDB, "PRAGMA recursive_triggers = 1");
9617 :
9618 1815 : InstallSQLFunctions();
9619 :
9620 : const char *pszSqlitePragma =
9621 1815 : CPLGetConfigOption("OGR_SQLITE_PRAGMA", nullptr);
9622 1815 : OGRErr eErr = OGRERR_NONE;
9623 6 : if ((!pszSqlitePragma || !strstr(pszSqlitePragma, "trusted_schema")) &&
9624 : // Older sqlite versions don't have this pragma
9625 3636 : SQLGetInteger(hDB, "PRAGMA trusted_schema", &eErr) == 0 &&
9626 1815 : eErr == OGRERR_NONE)
9627 : {
9628 1815 : bool bNeedsTrustedSchema = false;
9629 :
9630 : // Current SQLite versions require PRAGMA trusted_schema = 1 to be
9631 : // able to use the RTree from triggers, which is only needed when
9632 : // modifying the RTree.
9633 4489 : if (((flags & SQLITE_OPEN_READWRITE) != 0 ||
9634 2771 : (flags & SQLITE_OPEN_CREATE) != 0) &&
9635 956 : OGRSQLiteRTreeRequiresTrustedSchemaOn())
9636 : {
9637 956 : bNeedsTrustedSchema = true;
9638 : }
9639 :
9640 : #ifdef HAVE_SPATIALITE
9641 : // Spatialite <= 5.1.0 doesn't declare its functions as SQLITE_INNOCUOUS
9642 859 : if (!bNeedsTrustedSchema && HasExtensionsTable() &&
9643 778 : SQLGetInteger(
9644 : hDB,
9645 : "SELECT 1 FROM gpkg_extensions WHERE "
9646 : "extension_name ='gdal_spatialite_computed_geom_column'",
9647 1 : nullptr) == 1 &&
9648 2674 : SpatialiteRequiresTrustedSchemaOn() && AreSpatialiteTriggersSafe())
9649 : {
9650 1 : bNeedsTrustedSchema = true;
9651 : }
9652 : #endif
9653 :
9654 1815 : if (bNeedsTrustedSchema)
9655 : {
9656 957 : CPLDebug("GPKG", "Setting PRAGMA trusted_schema = 1");
9657 957 : SQLCommand(hDB, "PRAGMA trusted_schema = 1");
9658 : }
9659 : }
9660 :
9661 1815 : return true;
9662 : }
9663 :
9664 : /************************************************************************/
9665 : /* GetLayerWithGetSpatialWhereByName() */
9666 : /************************************************************************/
9667 :
9668 : std::pair<OGRLayer *, IOGRSQLiteGetSpatialWhere *>
9669 90 : GDALGeoPackageDataset::GetLayerWithGetSpatialWhereByName(const char *pszName)
9670 : {
9671 : OGRGeoPackageLayer *poRet =
9672 90 : cpl::down_cast<OGRGeoPackageLayer *>(GetLayerByName(pszName));
9673 90 : return std::pair(poRet, poRet);
9674 : }
9675 :
9676 : /************************************************************************/
9677 : /* CommitTransaction() */
9678 : /************************************************************************/
9679 :
9680 155 : OGRErr GDALGeoPackageDataset::CommitTransaction()
9681 :
9682 : {
9683 155 : if (nSoftTransactionLevel == 1)
9684 : {
9685 154 : FlushMetadata();
9686 346 : for (int i = 0; i < m_nLayers; i++)
9687 : {
9688 192 : m_papoLayers[i]->DoJobAtTransactionCommit();
9689 : }
9690 : }
9691 :
9692 155 : return OGRSQLiteBaseDataSource::CommitTransaction();
9693 : }
9694 :
9695 : /************************************************************************/
9696 : /* RollbackTransaction() */
9697 : /************************************************************************/
9698 :
9699 29 : OGRErr GDALGeoPackageDataset::RollbackTransaction()
9700 :
9701 : {
9702 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9703 58 : std::vector<bool> abAddTriggers;
9704 29 : std::vector<bool> abTriggersDeletedInTransaction;
9705 : #endif
9706 29 : if (nSoftTransactionLevel == 1)
9707 : {
9708 28 : FlushMetadata();
9709 58 : for (int i = 0; i < m_nLayers; i++)
9710 : {
9711 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9712 30 : abAddTriggers.push_back(
9713 30 : m_papoLayers[i]->GetAddOGRFeatureCountTriggers());
9714 30 : abTriggersDeletedInTransaction.push_back(
9715 30 : m_papoLayers[i]
9716 30 : ->GetOGRFeatureCountTriggersDeletedInTransaction());
9717 30 : m_papoLayers[i]->SetAddOGRFeatureCountTriggers(false);
9718 : #endif
9719 30 : m_papoLayers[i]->DoJobAtTransactionRollback();
9720 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9721 30 : m_papoLayers[i]->DisableFeatureCount();
9722 : #endif
9723 : }
9724 : }
9725 :
9726 29 : OGRErr eErr = OGRSQLiteBaseDataSource::RollbackTransaction();
9727 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9728 29 : if (!abAddTriggers.empty())
9729 : {
9730 56 : for (int i = 0; i < m_nLayers; i++)
9731 : {
9732 30 : if (abTriggersDeletedInTransaction[i])
9733 : {
9734 7 : m_papoLayers[i]->SetOGRFeatureCountTriggersEnabled(true);
9735 : }
9736 : else
9737 : {
9738 23 : m_papoLayers[i]->SetAddOGRFeatureCountTriggers(
9739 46 : abAddTriggers[i]);
9740 : }
9741 : }
9742 : }
9743 : #endif
9744 58 : return eErr;
9745 : }
9746 :
9747 : /************************************************************************/
9748 : /* GetGeometryTypeString() */
9749 : /************************************************************************/
9750 :
9751 : const char *
9752 1243 : GDALGeoPackageDataset::GetGeometryTypeString(OGRwkbGeometryType eType)
9753 : {
9754 1243 : const char *pszGPKGGeomType = OGRToOGCGeomType(eType);
9755 1255 : if (EQUAL(pszGPKGGeomType, "GEOMETRYCOLLECTION") &&
9756 12 : CPLTestBool(CPLGetConfigOption("OGR_GPKG_GEOMCOLLECTION", "NO")))
9757 : {
9758 0 : pszGPKGGeomType = "GEOMCOLLECTION";
9759 : }
9760 1243 : return pszGPKGGeomType;
9761 : }
9762 :
9763 : /************************************************************************/
9764 : /* GetFieldDomainNames() */
9765 : /************************************************************************/
9766 :
9767 : std::vector<std::string>
9768 10 : GDALGeoPackageDataset::GetFieldDomainNames(CSLConstList) const
9769 : {
9770 10 : if (!HasDataColumnConstraintsTable())
9771 3 : return std::vector<std::string>();
9772 :
9773 14 : std::vector<std::string> oDomainNamesList;
9774 :
9775 7 : std::unique_ptr<SQLResult> oResultTable;
9776 : {
9777 : std::string osSQL =
9778 : "SELECT DISTINCT constraint_name "
9779 : "FROM gpkg_data_column_constraints "
9780 : "WHERE constraint_name NOT LIKE '_%_domain_description' "
9781 : "ORDER BY constraint_name "
9782 7 : "LIMIT 10000" // to avoid denial of service
9783 : ;
9784 7 : oResultTable = SQLQuery(hDB, osSQL.c_str());
9785 7 : if (!oResultTable)
9786 0 : return oDomainNamesList;
9787 : }
9788 :
9789 7 : if (oResultTable->RowCount() == 10000)
9790 : {
9791 0 : CPLError(CE_Warning, CPLE_AppDefined,
9792 : "Number of rows returned for field domain names has been "
9793 : "truncated.");
9794 : }
9795 7 : else if (oResultTable->RowCount() > 0)
9796 : {
9797 7 : oDomainNamesList.reserve(oResultTable->RowCount());
9798 89 : for (int i = 0; i < oResultTable->RowCount(); i++)
9799 : {
9800 82 : const char *pszConstraintName = oResultTable->GetValue(0, i);
9801 82 : if (!pszConstraintName)
9802 0 : continue;
9803 :
9804 82 : oDomainNamesList.emplace_back(pszConstraintName);
9805 : }
9806 : }
9807 :
9808 7 : return oDomainNamesList;
9809 : }
9810 :
9811 : /************************************************************************/
9812 : /* GetFieldDomain() */
9813 : /************************************************************************/
9814 :
9815 : const OGRFieldDomain *
9816 102 : GDALGeoPackageDataset::GetFieldDomain(const std::string &name) const
9817 : {
9818 102 : const auto baseRet = GDALDataset::GetFieldDomain(name);
9819 102 : if (baseRet)
9820 42 : return baseRet;
9821 :
9822 60 : if (!HasDataColumnConstraintsTable())
9823 4 : return nullptr;
9824 :
9825 56 : const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
9826 56 : const char *min_is_inclusive =
9827 56 : bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
9828 56 : const char *max_is_inclusive =
9829 56 : bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
9830 :
9831 56 : std::unique_ptr<SQLResult> oResultTable;
9832 : // Note: for coded domains, we use a little trick by using a dummy
9833 : // _{domainname}_domain_description enum that has a single entry whose
9834 : // description is the description of the main domain.
9835 : {
9836 56 : char *pszSQL = sqlite3_mprintf(
9837 : "SELECT constraint_type, value, min, %s, "
9838 : "max, %s, description, constraint_name "
9839 : "FROM gpkg_data_column_constraints "
9840 : "WHERE constraint_name IN ('%q', "
9841 : "'_%q_domain_description') "
9842 : "AND length(constraint_type) < 100 " // to
9843 : // avoid
9844 : // denial
9845 : // of
9846 : // service
9847 : "AND (value IS NULL OR length(value) < "
9848 : "10000) " // to avoid denial
9849 : // of service
9850 : "AND (description IS NULL OR "
9851 : "length(description) < 10000) " // to
9852 : // avoid
9853 : // denial
9854 : // of
9855 : // service
9856 : "ORDER BY value "
9857 : "LIMIT 10000", // to avoid denial of
9858 : // service
9859 : min_is_inclusive, max_is_inclusive, name.c_str(), name.c_str());
9860 56 : oResultTable = SQLQuery(hDB, pszSQL);
9861 56 : sqlite3_free(pszSQL);
9862 56 : if (!oResultTable)
9863 0 : return nullptr;
9864 : }
9865 56 : if (oResultTable->RowCount() == 0)
9866 : {
9867 15 : return nullptr;
9868 : }
9869 41 : if (oResultTable->RowCount() == 10000)
9870 : {
9871 0 : CPLError(CE_Warning, CPLE_AppDefined,
9872 : "Number of rows returned for field domain %s has been "
9873 : "truncated.",
9874 : name.c_str());
9875 : }
9876 :
9877 : // Try to find the field domain data type from fields that implement it
9878 41 : int nFieldType = -1;
9879 41 : OGRFieldSubType eSubType = OFSTNone;
9880 41 : if (HasDataColumnsTable())
9881 : {
9882 36 : char *pszSQL = sqlite3_mprintf(
9883 : "SELECT table_name, column_name FROM gpkg_data_columns WHERE "
9884 : "constraint_name = '%q' LIMIT 10",
9885 : name.c_str());
9886 72 : auto oResultTable2 = SQLQuery(hDB, pszSQL);
9887 36 : sqlite3_free(pszSQL);
9888 36 : if (oResultTable2 && oResultTable2->RowCount() >= 1)
9889 : {
9890 46 : for (int iRecord = 0; iRecord < oResultTable2->RowCount();
9891 : iRecord++)
9892 : {
9893 23 : const char *pszTableName = oResultTable2->GetValue(0, iRecord);
9894 23 : const char *pszColumnName = oResultTable2->GetValue(1, iRecord);
9895 23 : if (pszTableName == nullptr || pszColumnName == nullptr)
9896 0 : continue;
9897 : OGRLayer *poLayer =
9898 46 : const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
9899 23 : pszTableName);
9900 23 : if (poLayer)
9901 : {
9902 23 : const auto poFDefn = poLayer->GetLayerDefn();
9903 23 : int nIdx = poFDefn->GetFieldIndex(pszColumnName);
9904 23 : if (nIdx >= 0)
9905 : {
9906 23 : const auto poFieldDefn = poFDefn->GetFieldDefn(nIdx);
9907 23 : const auto eType = poFieldDefn->GetType();
9908 23 : if (nFieldType < 0)
9909 : {
9910 23 : nFieldType = eType;
9911 23 : eSubType = poFieldDefn->GetSubType();
9912 : }
9913 0 : else if ((eType == OFTInteger64 || eType == OFTReal) &&
9914 : nFieldType == OFTInteger)
9915 : {
9916 : // ok
9917 : }
9918 0 : else if (eType == OFTInteger &&
9919 0 : (nFieldType == OFTInteger64 ||
9920 : nFieldType == OFTReal))
9921 : {
9922 0 : nFieldType = OFTInteger;
9923 0 : eSubType = OFSTNone;
9924 : }
9925 0 : else if (nFieldType != eType)
9926 : {
9927 0 : nFieldType = -1;
9928 0 : eSubType = OFSTNone;
9929 0 : break;
9930 : }
9931 : }
9932 : }
9933 : }
9934 : }
9935 : }
9936 :
9937 41 : std::unique_ptr<OGRFieldDomain> poDomain;
9938 82 : std::vector<OGRCodedValue> asValues;
9939 41 : bool error = false;
9940 82 : CPLString osLastConstraintType;
9941 41 : int nFieldTypeFromEnumCode = -1;
9942 82 : std::string osConstraintDescription;
9943 82 : std::string osDescrConstraintName("_");
9944 41 : osDescrConstraintName += name;
9945 41 : osDescrConstraintName += "_domain_description";
9946 100 : for (int iRecord = 0; iRecord < oResultTable->RowCount(); iRecord++)
9947 : {
9948 63 : const char *pszConstraintType = oResultTable->GetValue(0, iRecord);
9949 63 : if (pszConstraintType == nullptr)
9950 0 : continue;
9951 63 : const char *pszValue = oResultTable->GetValue(1, iRecord);
9952 63 : const char *pszMin = oResultTable->GetValue(2, iRecord);
9953 : const bool bIsMinIncluded =
9954 63 : oResultTable->GetValueAsInteger(3, iRecord) == 1;
9955 63 : const char *pszMax = oResultTable->GetValue(4, iRecord);
9956 : const bool bIsMaxIncluded =
9957 63 : oResultTable->GetValueAsInteger(5, iRecord) == 1;
9958 63 : const char *pszDescription = oResultTable->GetValue(6, iRecord);
9959 63 : const char *pszConstraintName = oResultTable->GetValue(7, iRecord);
9960 :
9961 63 : if (!osLastConstraintType.empty() && osLastConstraintType != "enum")
9962 : {
9963 1 : CPLError(CE_Failure, CPLE_AppDefined,
9964 : "Only constraint of type 'enum' can have multiple rows");
9965 1 : error = true;
9966 1 : break;
9967 : }
9968 :
9969 62 : if (strcmp(pszConstraintType, "enum") == 0)
9970 : {
9971 42 : if (pszValue == nullptr)
9972 : {
9973 1 : CPLError(CE_Failure, CPLE_AppDefined,
9974 : "NULL in 'value' column of enumeration");
9975 1 : error = true;
9976 1 : break;
9977 : }
9978 41 : if (osDescrConstraintName == pszConstraintName)
9979 : {
9980 1 : if (pszDescription)
9981 : {
9982 1 : osConstraintDescription = pszDescription;
9983 : }
9984 1 : continue;
9985 : }
9986 40 : if (asValues.empty())
9987 : {
9988 20 : asValues.reserve(oResultTable->RowCount() + 1);
9989 : }
9990 : OGRCodedValue cv;
9991 : // intended: the 'value' column in GPKG is actually the code
9992 40 : cv.pszCode = VSI_STRDUP_VERBOSE(pszValue);
9993 40 : if (cv.pszCode == nullptr)
9994 : {
9995 0 : error = true;
9996 0 : break;
9997 : }
9998 40 : if (pszDescription)
9999 : {
10000 29 : cv.pszValue = VSI_STRDUP_VERBOSE(pszDescription);
10001 29 : if (cv.pszValue == nullptr)
10002 : {
10003 0 : VSIFree(cv.pszCode);
10004 0 : error = true;
10005 0 : break;
10006 : }
10007 : }
10008 : else
10009 : {
10010 11 : cv.pszValue = nullptr;
10011 : }
10012 :
10013 : // If we can't get the data type from field definition, guess it
10014 : // from code.
10015 40 : if (nFieldType < 0 && nFieldTypeFromEnumCode != OFTString)
10016 : {
10017 18 : switch (CPLGetValueType(cv.pszCode))
10018 : {
10019 13 : case CPL_VALUE_INTEGER:
10020 : {
10021 13 : if (nFieldTypeFromEnumCode != OFTReal &&
10022 : nFieldTypeFromEnumCode != OFTInteger64)
10023 : {
10024 9 : const auto nVal = CPLAtoGIntBig(cv.pszCode);
10025 17 : if (nVal < std::numeric_limits<int>::min() ||
10026 8 : nVal > std::numeric_limits<int>::max())
10027 : {
10028 3 : nFieldTypeFromEnumCode = OFTInteger64;
10029 : }
10030 : else
10031 : {
10032 6 : nFieldTypeFromEnumCode = OFTInteger;
10033 : }
10034 : }
10035 13 : break;
10036 : }
10037 :
10038 3 : case CPL_VALUE_REAL:
10039 3 : nFieldTypeFromEnumCode = OFTReal;
10040 3 : break;
10041 :
10042 2 : case CPL_VALUE_STRING:
10043 2 : nFieldTypeFromEnumCode = OFTString;
10044 2 : break;
10045 : }
10046 : }
10047 :
10048 40 : asValues.emplace_back(cv);
10049 : }
10050 20 : else if (strcmp(pszConstraintType, "range") == 0)
10051 : {
10052 : OGRField sMin;
10053 : OGRField sMax;
10054 14 : OGR_RawField_SetUnset(&sMin);
10055 14 : OGR_RawField_SetUnset(&sMax);
10056 14 : if (nFieldType != OFTInteger && nFieldType != OFTInteger64)
10057 8 : nFieldType = OFTReal;
10058 27 : if (pszMin != nullptr &&
10059 13 : CPLAtof(pszMin) != -std::numeric_limits<double>::infinity())
10060 : {
10061 10 : if (nFieldType == OFTInteger)
10062 3 : sMin.Integer = atoi(pszMin);
10063 7 : else if (nFieldType == OFTInteger64)
10064 3 : sMin.Integer64 = CPLAtoGIntBig(pszMin);
10065 : else /* if( nFieldType == OFTReal ) */
10066 4 : sMin.Real = CPLAtof(pszMin);
10067 : }
10068 27 : if (pszMax != nullptr &&
10069 13 : CPLAtof(pszMax) != std::numeric_limits<double>::infinity())
10070 : {
10071 10 : if (nFieldType == OFTInteger)
10072 3 : sMax.Integer = atoi(pszMax);
10073 7 : else if (nFieldType == OFTInteger64)
10074 3 : sMax.Integer64 = CPLAtoGIntBig(pszMax);
10075 : else /* if( nFieldType == OFTReal ) */
10076 4 : sMax.Real = CPLAtof(pszMax);
10077 : }
10078 28 : poDomain.reset(new OGRRangeFieldDomain(
10079 : name, pszDescription ? pszDescription : "",
10080 : static_cast<OGRFieldType>(nFieldType), eSubType, sMin,
10081 14 : bIsMinIncluded, sMax, bIsMaxIncluded));
10082 : }
10083 6 : else if (strcmp(pszConstraintType, "glob") == 0)
10084 : {
10085 5 : if (pszValue == nullptr)
10086 : {
10087 1 : CPLError(CE_Failure, CPLE_AppDefined,
10088 : "NULL in 'value' column of glob");
10089 1 : error = true;
10090 1 : break;
10091 : }
10092 4 : if (nFieldType < 0)
10093 1 : nFieldType = OFTString;
10094 8 : poDomain.reset(new OGRGlobFieldDomain(
10095 : name, pszDescription ? pszDescription : "",
10096 4 : static_cast<OGRFieldType>(nFieldType), eSubType, pszValue));
10097 : }
10098 : else
10099 : {
10100 1 : CPLError(CE_Failure, CPLE_AppDefined,
10101 : "Unhandled constraint_type: %s", pszConstraintType);
10102 1 : error = true;
10103 1 : break;
10104 : }
10105 :
10106 58 : osLastConstraintType = pszConstraintType;
10107 : }
10108 :
10109 41 : if (!asValues.empty())
10110 : {
10111 20 : if (nFieldType < 0)
10112 9 : nFieldType = nFieldTypeFromEnumCode;
10113 20 : poDomain.reset(
10114 : new OGRCodedFieldDomain(name, osConstraintDescription,
10115 : static_cast<OGRFieldType>(nFieldType),
10116 20 : eSubType, std::move(asValues)));
10117 : }
10118 :
10119 41 : if (error)
10120 : {
10121 4 : return nullptr;
10122 : }
10123 :
10124 37 : m_oMapFieldDomains[name] = std::move(poDomain);
10125 37 : return GDALDataset::GetFieldDomain(name);
10126 : }
10127 :
10128 : /************************************************************************/
10129 : /* AddFieldDomain() */
10130 : /************************************************************************/
10131 :
10132 18 : bool GDALGeoPackageDataset::AddFieldDomain(
10133 : std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
10134 : {
10135 36 : const std::string domainName(domain->GetName());
10136 18 : if (!GetUpdate())
10137 : {
10138 0 : CPLError(CE_Failure, CPLE_NotSupported,
10139 : "AddFieldDomain() not supported on read-only dataset");
10140 0 : return false;
10141 : }
10142 18 : if (GetFieldDomain(domainName) != nullptr)
10143 : {
10144 1 : failureReason = "A domain of identical name already exists";
10145 1 : return false;
10146 : }
10147 17 : if (!CreateColumnsTableAndColumnConstraintsTablesIfNecessary())
10148 0 : return false;
10149 :
10150 17 : const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
10151 17 : const char *min_is_inclusive =
10152 17 : bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
10153 17 : const char *max_is_inclusive =
10154 17 : bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
10155 :
10156 17 : const auto &osDescription = domain->GetDescription();
10157 17 : switch (domain->GetDomainType())
10158 : {
10159 11 : case OFDT_CODED:
10160 : {
10161 : const auto poCodedDomain =
10162 11 : cpl::down_cast<const OGRCodedFieldDomain *>(domain.get());
10163 11 : if (!osDescription.empty())
10164 : {
10165 : // We use a little trick by using a dummy
10166 : // _{domainname}_domain_description enum that has a single
10167 : // entry whose description is the description of the main
10168 : // domain.
10169 1 : char *pszSQL = sqlite3_mprintf(
10170 : "INSERT INTO gpkg_data_column_constraints ("
10171 : "constraint_name, constraint_type, value, "
10172 : "min, %s, max, %s, "
10173 : "description) VALUES ("
10174 : "'_%q_domain_description', 'enum', '', NULL, NULL, NULL, "
10175 : "NULL, %Q)",
10176 : min_is_inclusive, max_is_inclusive, domainName.c_str(),
10177 : osDescription.c_str());
10178 1 : CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
10179 1 : sqlite3_free(pszSQL);
10180 : }
10181 11 : const auto &enumeration = poCodedDomain->GetEnumeration();
10182 33 : for (int i = 0; enumeration[i].pszCode != nullptr; ++i)
10183 : {
10184 22 : char *pszSQL = sqlite3_mprintf(
10185 : "INSERT INTO gpkg_data_column_constraints ("
10186 : "constraint_name, constraint_type, value, "
10187 : "min, %s, max, %s, "
10188 : "description) VALUES ("
10189 : "'%q', 'enum', '%q', NULL, NULL, NULL, NULL, %Q)",
10190 : min_is_inclusive, max_is_inclusive, domainName.c_str(),
10191 22 : enumeration[i].pszCode, enumeration[i].pszValue);
10192 22 : bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
10193 22 : sqlite3_free(pszSQL);
10194 22 : if (!ok)
10195 0 : return false;
10196 : }
10197 11 : break;
10198 : }
10199 :
10200 5 : case OFDT_RANGE:
10201 : {
10202 : const auto poRangeDomain =
10203 5 : cpl::down_cast<const OGRRangeFieldDomain *>(domain.get());
10204 5 : const auto eFieldType = poRangeDomain->GetFieldType();
10205 5 : if (eFieldType != OFTInteger && eFieldType != OFTInteger64 &&
10206 : eFieldType != OFTReal)
10207 : {
10208 : failureReason = "Only range domains of numeric type are "
10209 0 : "supported in GeoPackage";
10210 0 : return false;
10211 : }
10212 :
10213 5 : double dfMin = -std::numeric_limits<double>::infinity();
10214 5 : double dfMax = std::numeric_limits<double>::infinity();
10215 5 : bool bMinIsInclusive = true;
10216 5 : const auto &sMin = poRangeDomain->GetMin(bMinIsInclusive);
10217 5 : bool bMaxIsInclusive = true;
10218 5 : const auto &sMax = poRangeDomain->GetMax(bMaxIsInclusive);
10219 5 : if (eFieldType == OFTInteger)
10220 : {
10221 1 : if (!OGR_RawField_IsUnset(&sMin))
10222 1 : dfMin = sMin.Integer;
10223 1 : if (!OGR_RawField_IsUnset(&sMax))
10224 1 : dfMax = sMax.Integer;
10225 : }
10226 4 : else if (eFieldType == OFTInteger64)
10227 : {
10228 1 : if (!OGR_RawField_IsUnset(&sMin))
10229 1 : dfMin = static_cast<double>(sMin.Integer64);
10230 1 : if (!OGR_RawField_IsUnset(&sMax))
10231 1 : dfMax = static_cast<double>(sMax.Integer64);
10232 : }
10233 : else /* if( eFieldType == OFTReal ) */
10234 : {
10235 3 : if (!OGR_RawField_IsUnset(&sMin))
10236 3 : dfMin = sMin.Real;
10237 3 : if (!OGR_RawField_IsUnset(&sMax))
10238 3 : dfMax = sMax.Real;
10239 : }
10240 :
10241 5 : sqlite3_stmt *hInsertStmt = nullptr;
10242 : const char *pszSQL =
10243 5 : CPLSPrintf("INSERT INTO gpkg_data_column_constraints ("
10244 : "constraint_name, constraint_type, value, "
10245 : "min, %s, max, %s, "
10246 : "description) VALUES ("
10247 : "?, 'range', NULL, ?, ?, ?, ?, ?)",
10248 : min_is_inclusive, max_is_inclusive);
10249 5 : if (sqlite3_prepare_v2(hDB, pszSQL, -1, &hInsertStmt, nullptr) !=
10250 : SQLITE_OK)
10251 : {
10252 0 : CPLError(CE_Failure, CPLE_AppDefined,
10253 : "failed to prepare SQL: %s", pszSQL);
10254 0 : return false;
10255 : }
10256 5 : sqlite3_bind_text(hInsertStmt, 1, domainName.c_str(),
10257 5 : static_cast<int>(domainName.size()),
10258 : SQLITE_TRANSIENT);
10259 5 : sqlite3_bind_double(hInsertStmt, 2, dfMin);
10260 5 : sqlite3_bind_int(hInsertStmt, 3, bMinIsInclusive ? 1 : 0);
10261 5 : sqlite3_bind_double(hInsertStmt, 4, dfMax);
10262 5 : sqlite3_bind_int(hInsertStmt, 5, bMaxIsInclusive ? 1 : 0);
10263 5 : if (osDescription.empty())
10264 : {
10265 3 : sqlite3_bind_null(hInsertStmt, 6);
10266 : }
10267 : else
10268 : {
10269 2 : sqlite3_bind_text(hInsertStmt, 6, osDescription.c_str(),
10270 2 : static_cast<int>(osDescription.size()),
10271 : SQLITE_TRANSIENT);
10272 : }
10273 5 : const int sqlite_err = sqlite3_step(hInsertStmt);
10274 5 : sqlite3_finalize(hInsertStmt);
10275 5 : if (sqlite_err != SQLITE_OK && sqlite_err != SQLITE_DONE)
10276 : {
10277 0 : CPLError(CE_Failure, CPLE_AppDefined,
10278 : "failed to execute insertion: %s",
10279 : sqlite3_errmsg(hDB));
10280 0 : return false;
10281 : }
10282 :
10283 5 : break;
10284 : }
10285 :
10286 1 : case OFDT_GLOB:
10287 : {
10288 : const auto poGlobDomain =
10289 1 : cpl::down_cast<const OGRGlobFieldDomain *>(domain.get());
10290 2 : char *pszSQL = sqlite3_mprintf(
10291 : "INSERT INTO gpkg_data_column_constraints ("
10292 : "constraint_name, constraint_type, value, "
10293 : "min, %s, max, %s, "
10294 : "description) VALUES ("
10295 : "'%q', 'glob', '%q', NULL, NULL, NULL, NULL, %Q)",
10296 : min_is_inclusive, max_is_inclusive, domainName.c_str(),
10297 1 : poGlobDomain->GetGlob().c_str(),
10298 2 : osDescription.empty() ? nullptr : osDescription.c_str());
10299 1 : bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
10300 1 : sqlite3_free(pszSQL);
10301 1 : if (!ok)
10302 0 : return false;
10303 :
10304 1 : break;
10305 : }
10306 : }
10307 :
10308 17 : m_oMapFieldDomains[domainName] = std::move(domain);
10309 17 : return true;
10310 : }
10311 :
10312 : /************************************************************************/
10313 : /* AddRelationship() */
10314 : /************************************************************************/
10315 :
10316 20 : bool GDALGeoPackageDataset::AddRelationship(
10317 : std::unique_ptr<GDALRelationship> &&relationship,
10318 : std::string &failureReason)
10319 : {
10320 20 : if (!GetUpdate())
10321 : {
10322 0 : CPLError(CE_Failure, CPLE_NotSupported,
10323 : "AddRelationship() not supported on read-only dataset");
10324 0 : return false;
10325 : }
10326 :
10327 : const std::string osRelationshipName = GenerateNameForRelationship(
10328 20 : relationship->GetLeftTableName().c_str(),
10329 20 : relationship->GetRightTableName().c_str(),
10330 80 : relationship->GetRelatedTableType().c_str());
10331 : // sanity checks
10332 20 : if (GetRelationship(osRelationshipName) != nullptr)
10333 : {
10334 1 : failureReason = "A relationship of identical name already exists";
10335 1 : return false;
10336 : }
10337 :
10338 19 : if (!ValidateRelationship(relationship.get(), failureReason))
10339 : {
10340 12 : return false;
10341 : }
10342 :
10343 7 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
10344 : {
10345 0 : return false;
10346 : }
10347 7 : if (!CreateRelationsTableIfNecessary())
10348 : {
10349 0 : failureReason = "Could not create gpkgext_relations table";
10350 0 : return false;
10351 : }
10352 7 : if (SQLGetInteger(GetDB(),
10353 : "SELECT 1 FROM gpkg_extensions WHERE "
10354 : "table_name = 'gpkgext_relations'",
10355 7 : nullptr) != 1)
10356 : {
10357 2 : if (OGRERR_NONE !=
10358 2 : SQLCommand(
10359 : GetDB(),
10360 : "INSERT INTO gpkg_extensions "
10361 : "(table_name,column_name,extension_name,definition,scope) "
10362 : "VALUES ('gpkgext_relations', NULL, 'gpkg_related_tables', "
10363 : "'http://www.geopackage.org/18-000.html', "
10364 : "'read-write')"))
10365 : {
10366 : failureReason =
10367 0 : "Could not create gpkg_extensions entry for gpkgext_relations";
10368 0 : return false;
10369 : }
10370 : }
10371 :
10372 7 : const std::string &osLeftTableName = relationship->GetLeftTableName();
10373 7 : const std::string &osRightTableName = relationship->GetRightTableName();
10374 7 : const auto &aosLeftTableFields = relationship->GetLeftTableFields();
10375 7 : const auto &aosRightTableFields = relationship->GetRightTableFields();
10376 :
10377 14 : std::string osRelatedTableType = relationship->GetRelatedTableType();
10378 7 : if (osRelatedTableType.empty())
10379 : {
10380 5 : osRelatedTableType = "features";
10381 : }
10382 :
10383 : // generate mapping table if not set
10384 14 : CPLString osMappingTableName = relationship->GetMappingTableName();
10385 7 : if (osMappingTableName.empty())
10386 : {
10387 3 : int nIndex = 1;
10388 3 : osMappingTableName = osLeftTableName + "_" + osRightTableName;
10389 3 : while (FindLayerIndex(osMappingTableName.c_str()) >= 0)
10390 : {
10391 0 : nIndex += 1;
10392 : osMappingTableName.Printf("%s_%s_%d", osLeftTableName.c_str(),
10393 0 : osRightTableName.c_str(), nIndex);
10394 : }
10395 :
10396 : // determine whether base/related keys are unique
10397 3 : bool bBaseKeyIsUnique = false;
10398 : {
10399 : const std::set<std::string> uniqueBaseFieldsUC =
10400 : SQLGetUniqueFieldUCConstraints(GetDB(),
10401 6 : osLeftTableName.c_str());
10402 6 : if (uniqueBaseFieldsUC.find(
10403 3 : CPLString(aosLeftTableFields[0]).toupper()) !=
10404 6 : uniqueBaseFieldsUC.end())
10405 : {
10406 2 : bBaseKeyIsUnique = true;
10407 : }
10408 : }
10409 3 : bool bRelatedKeyIsUnique = false;
10410 : {
10411 : const std::set<std::string> uniqueRelatedFieldsUC =
10412 : SQLGetUniqueFieldUCConstraints(GetDB(),
10413 6 : osRightTableName.c_str());
10414 6 : if (uniqueRelatedFieldsUC.find(
10415 3 : CPLString(aosRightTableFields[0]).toupper()) !=
10416 6 : uniqueRelatedFieldsUC.end())
10417 : {
10418 2 : bRelatedKeyIsUnique = true;
10419 : }
10420 : }
10421 :
10422 : // create mapping table
10423 :
10424 3 : std::string osBaseIdDefinition = "base_id INTEGER";
10425 3 : if (bBaseKeyIsUnique)
10426 : {
10427 2 : char *pszSQL = sqlite3_mprintf(
10428 : " CONSTRAINT 'fk_base_id_%q' REFERENCES \"%w\"(\"%w\") ON "
10429 : "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
10430 : "DEFERRED",
10431 : osMappingTableName.c_str(), osLeftTableName.c_str(),
10432 2 : aosLeftTableFields[0].c_str());
10433 2 : osBaseIdDefinition += pszSQL;
10434 2 : sqlite3_free(pszSQL);
10435 : }
10436 :
10437 3 : std::string osRelatedIdDefinition = "related_id INTEGER";
10438 3 : if (bRelatedKeyIsUnique)
10439 : {
10440 2 : char *pszSQL = sqlite3_mprintf(
10441 : " CONSTRAINT 'fk_related_id_%q' REFERENCES \"%w\"(\"%w\") ON "
10442 : "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
10443 : "DEFERRED",
10444 : osMappingTableName.c_str(), osRightTableName.c_str(),
10445 2 : aosRightTableFields[0].c_str());
10446 2 : osRelatedIdDefinition += pszSQL;
10447 2 : sqlite3_free(pszSQL);
10448 : }
10449 :
10450 3 : char *pszSQL = sqlite3_mprintf("CREATE TABLE \"%w\" ("
10451 : "id INTEGER PRIMARY KEY AUTOINCREMENT, "
10452 : "%s, %s);",
10453 : osMappingTableName.c_str(),
10454 : osBaseIdDefinition.c_str(),
10455 : osRelatedIdDefinition.c_str());
10456 3 : OGRErr eErr = SQLCommand(hDB, pszSQL);
10457 3 : sqlite3_free(pszSQL);
10458 3 : if (eErr != OGRERR_NONE)
10459 : {
10460 : failureReason =
10461 0 : ("Could not create mapping table " + osMappingTableName)
10462 0 : .c_str();
10463 0 : return false;
10464 : }
10465 :
10466 : /*
10467 : * Strictly speaking we should NOT be inserting the mapping table into gpkg_contents.
10468 : * The related tables extension explicitly states that the mapping table should only be
10469 : * in the gpkgext_relations table and not in gpkg_contents. (See also discussion at
10470 : * https://github.com/opengeospatial/geopackage/issues/679).
10471 : *
10472 : * However, if we don't insert the mapping table into gpkg_contents then it is no longer
10473 : * visible to some clients (eg ESRI software only allows opening tables that are present
10474 : * in gpkg_contents). So we'll do this anyway, for maximum compatibility and flexibility.
10475 : *
10476 : * More related discussion is at https://github.com/OSGeo/gdal/pull/9258
10477 : */
10478 3 : pszSQL = sqlite3_mprintf(
10479 : "INSERT INTO gpkg_contents "
10480 : "(table_name,data_type,identifier,description,last_change,srs_id) "
10481 : "VALUES "
10482 : "('%q','attributes','%q','Mapping table for relationship between "
10483 : "%q and %q',%s,0)",
10484 : osMappingTableName.c_str(), /*table_name*/
10485 : osMappingTableName.c_str(), /*identifier*/
10486 : osLeftTableName.c_str(), /*description left table name*/
10487 : osRightTableName.c_str(), /*description right table name*/
10488 6 : GDALGeoPackageDataset::GetCurrentDateEscapedSQL().c_str());
10489 :
10490 : // Note -- we explicitly ignore failures here, because hey, we aren't really
10491 : // supposed to be adding this table to gpkg_contents anyway!
10492 3 : (void)SQLCommand(hDB, pszSQL);
10493 3 : sqlite3_free(pszSQL);
10494 :
10495 3 : pszSQL = sqlite3_mprintf(
10496 : "CREATE INDEX \"idx_%w_base_id\" ON \"%w\" (base_id);",
10497 : osMappingTableName.c_str(), osMappingTableName.c_str());
10498 3 : eErr = SQLCommand(hDB, pszSQL);
10499 3 : sqlite3_free(pszSQL);
10500 3 : if (eErr != OGRERR_NONE)
10501 : {
10502 0 : failureReason = ("Could not create index for " +
10503 0 : osMappingTableName + " (base_id)")
10504 0 : .c_str();
10505 0 : return false;
10506 : }
10507 :
10508 3 : pszSQL = sqlite3_mprintf(
10509 : "CREATE INDEX \"idx_%qw_related_id\" ON \"%w\" (related_id);",
10510 : osMappingTableName.c_str(), osMappingTableName.c_str());
10511 3 : eErr = SQLCommand(hDB, pszSQL);
10512 3 : sqlite3_free(pszSQL);
10513 3 : if (eErr != OGRERR_NONE)
10514 : {
10515 0 : failureReason = ("Could not create index for " +
10516 0 : osMappingTableName + " (related_id)")
10517 0 : .c_str();
10518 0 : return false;
10519 : }
10520 : }
10521 : else
10522 : {
10523 : // validate mapping table structure
10524 4 : if (OGRGeoPackageTableLayer *poLayer =
10525 4 : cpl::down_cast<OGRGeoPackageTableLayer *>(
10526 4 : GetLayerByName(osMappingTableName)))
10527 : {
10528 3 : if (poLayer->GetLayerDefn()->GetFieldIndex("base_id") < 0)
10529 : {
10530 : failureReason =
10531 2 : ("Field base_id must exist in " + osMappingTableName)
10532 1 : .c_str();
10533 1 : return false;
10534 : }
10535 2 : if (poLayer->GetLayerDefn()->GetFieldIndex("related_id") < 0)
10536 : {
10537 : failureReason =
10538 2 : ("Field related_id must exist in " + osMappingTableName)
10539 1 : .c_str();
10540 1 : return false;
10541 : }
10542 : }
10543 : else
10544 : {
10545 : failureReason =
10546 1 : ("Could not retrieve table " + osMappingTableName).c_str();
10547 1 : return false;
10548 : }
10549 : }
10550 :
10551 4 : char *pszSQL = sqlite3_mprintf(
10552 : "INSERT INTO gpkg_extensions "
10553 : "(table_name,column_name,extension_name,definition,scope) "
10554 : "VALUES ('%q', NULL, 'gpkg_related_tables', "
10555 : "'http://www.geopackage.org/18-000.html', "
10556 : "'read-write')",
10557 : osMappingTableName.c_str());
10558 4 : OGRErr eErr = SQLCommand(hDB, pszSQL);
10559 4 : sqlite3_free(pszSQL);
10560 4 : if (eErr != OGRERR_NONE)
10561 : {
10562 0 : failureReason = ("Could not insert mapping table " +
10563 0 : osMappingTableName + " into gpkg_extensions")
10564 0 : .c_str();
10565 0 : return false;
10566 : }
10567 :
10568 12 : pszSQL = sqlite3_mprintf(
10569 : "INSERT INTO gpkgext_relations "
10570 : "(base_table_name,base_primary_column,related_table_name,related_"
10571 : "primary_column,relation_name,mapping_table_name) "
10572 : "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
10573 4 : osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
10574 4 : osRightTableName.c_str(), aosRightTableFields[0].c_str(),
10575 : osRelatedTableType.c_str(), osMappingTableName.c_str());
10576 4 : eErr = SQLCommand(hDB, pszSQL);
10577 4 : sqlite3_free(pszSQL);
10578 4 : if (eErr != OGRERR_NONE)
10579 : {
10580 0 : failureReason = "Could not insert relationship into gpkgext_relations";
10581 0 : return false;
10582 : }
10583 :
10584 4 : ClearCachedRelationships();
10585 4 : LoadRelationships();
10586 4 : return true;
10587 : }
10588 :
10589 : /************************************************************************/
10590 : /* DeleteRelationship() */
10591 : /************************************************************************/
10592 :
10593 4 : bool GDALGeoPackageDataset::DeleteRelationship(const std::string &name,
10594 : std::string &failureReason)
10595 : {
10596 4 : if (eAccess != GA_Update)
10597 : {
10598 0 : CPLError(CE_Failure, CPLE_NotSupported,
10599 : "DeleteRelationship() not supported on read-only dataset");
10600 0 : return false;
10601 : }
10602 :
10603 : // ensure relationships are up to date before we try to remove one
10604 4 : ClearCachedRelationships();
10605 4 : LoadRelationships();
10606 :
10607 8 : std::string osMappingTableName;
10608 : {
10609 4 : const GDALRelationship *poRelationship = GetRelationship(name);
10610 4 : if (poRelationship == nullptr)
10611 : {
10612 1 : failureReason = "Could not find relationship with name " + name;
10613 1 : return false;
10614 : }
10615 :
10616 3 : osMappingTableName = poRelationship->GetMappingTableName();
10617 : }
10618 :
10619 : // DeleteLayerCommon will delete existing relationship objects, so we can't
10620 : // refer to poRelationship or any of its members previously obtained here
10621 3 : if (DeleteLayerCommon(osMappingTableName.c_str()) != OGRERR_NONE)
10622 : {
10623 : failureReason =
10624 0 : "Could not remove mapping layer name " + osMappingTableName;
10625 :
10626 : // relationships may have been left in an inconsistent state -- reload
10627 : // them now
10628 0 : ClearCachedRelationships();
10629 0 : LoadRelationships();
10630 0 : return false;
10631 : }
10632 :
10633 3 : ClearCachedRelationships();
10634 3 : LoadRelationships();
10635 3 : return true;
10636 : }
10637 :
10638 : /************************************************************************/
10639 : /* UpdateRelationship() */
10640 : /************************************************************************/
10641 :
10642 6 : bool GDALGeoPackageDataset::UpdateRelationship(
10643 : std::unique_ptr<GDALRelationship> &&relationship,
10644 : std::string &failureReason)
10645 : {
10646 6 : if (eAccess != GA_Update)
10647 : {
10648 0 : CPLError(CE_Failure, CPLE_NotSupported,
10649 : "UpdateRelationship() not supported on read-only dataset");
10650 0 : return false;
10651 : }
10652 :
10653 : // ensure relationships are up to date before we try to update one
10654 6 : ClearCachedRelationships();
10655 6 : LoadRelationships();
10656 :
10657 6 : const std::string &osRelationshipName = relationship->GetName();
10658 6 : const std::string &osLeftTableName = relationship->GetLeftTableName();
10659 6 : const std::string &osRightTableName = relationship->GetRightTableName();
10660 6 : const std::string &osMappingTableName = relationship->GetMappingTableName();
10661 6 : const auto &aosLeftTableFields = relationship->GetLeftTableFields();
10662 6 : const auto &aosRightTableFields = relationship->GetRightTableFields();
10663 :
10664 : // sanity checks
10665 : {
10666 : const GDALRelationship *poExistingRelationship =
10667 6 : GetRelationship(osRelationshipName);
10668 6 : if (poExistingRelationship == nullptr)
10669 : {
10670 : failureReason =
10671 1 : "The relationship should already exist to be updated";
10672 1 : return false;
10673 : }
10674 :
10675 5 : if (!ValidateRelationship(relationship.get(), failureReason))
10676 : {
10677 2 : return false;
10678 : }
10679 :
10680 : // we don't permit changes to the participating tables
10681 3 : if (osLeftTableName != poExistingRelationship->GetLeftTableName())
10682 : {
10683 0 : failureReason = ("Cannot change base table from " +
10684 0 : poExistingRelationship->GetLeftTableName() +
10685 0 : " to " + osLeftTableName)
10686 0 : .c_str();
10687 0 : return false;
10688 : }
10689 3 : if (osRightTableName != poExistingRelationship->GetRightTableName())
10690 : {
10691 0 : failureReason = ("Cannot change related table from " +
10692 0 : poExistingRelationship->GetRightTableName() +
10693 0 : " to " + osRightTableName)
10694 0 : .c_str();
10695 0 : return false;
10696 : }
10697 3 : if (osMappingTableName != poExistingRelationship->GetMappingTableName())
10698 : {
10699 0 : failureReason = ("Cannot change mapping table from " +
10700 0 : poExistingRelationship->GetMappingTableName() +
10701 0 : " to " + osMappingTableName)
10702 0 : .c_str();
10703 0 : return false;
10704 : }
10705 : }
10706 :
10707 6 : std::string osRelatedTableType = relationship->GetRelatedTableType();
10708 3 : if (osRelatedTableType.empty())
10709 : {
10710 0 : osRelatedTableType = "features";
10711 : }
10712 :
10713 3 : char *pszSQL = sqlite3_mprintf(
10714 : "DELETE FROM gpkgext_relations WHERE mapping_table_name='%q'",
10715 : osMappingTableName.c_str());
10716 3 : OGRErr eErr = SQLCommand(hDB, pszSQL);
10717 3 : sqlite3_free(pszSQL);
10718 3 : if (eErr != OGRERR_NONE)
10719 : {
10720 : failureReason =
10721 0 : "Could not delete old relationship from gpkgext_relations";
10722 0 : return false;
10723 : }
10724 :
10725 9 : pszSQL = sqlite3_mprintf(
10726 : "INSERT INTO gpkgext_relations "
10727 : "(base_table_name,base_primary_column,related_table_name,related_"
10728 : "primary_column,relation_name,mapping_table_name) "
10729 : "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
10730 3 : osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
10731 3 : osRightTableName.c_str(), aosRightTableFields[0].c_str(),
10732 : osRelatedTableType.c_str(), osMappingTableName.c_str());
10733 3 : eErr = SQLCommand(hDB, pszSQL);
10734 3 : sqlite3_free(pszSQL);
10735 3 : if (eErr != OGRERR_NONE)
10736 : {
10737 : failureReason =
10738 0 : "Could not insert updated relationship into gpkgext_relations";
10739 0 : return false;
10740 : }
10741 :
10742 3 : ClearCachedRelationships();
10743 3 : LoadRelationships();
10744 3 : return true;
10745 : }
10746 :
10747 : /************************************************************************/
10748 : /* GetSqliteMasterContent() */
10749 : /************************************************************************/
10750 :
10751 : const std::vector<SQLSqliteMasterContent> &
10752 2 : GDALGeoPackageDataset::GetSqliteMasterContent()
10753 : {
10754 2 : if (m_aoSqliteMasterContent.empty())
10755 : {
10756 : auto oResultTable =
10757 2 : SQLQuery(hDB, "SELECT sql, type, tbl_name FROM sqlite_master");
10758 1 : if (oResultTable)
10759 : {
10760 58 : for (int rowCnt = 0; rowCnt < oResultTable->RowCount(); ++rowCnt)
10761 : {
10762 114 : SQLSqliteMasterContent row;
10763 57 : const char *pszSQL = oResultTable->GetValue(0, rowCnt);
10764 57 : row.osSQL = pszSQL ? pszSQL : "";
10765 57 : const char *pszType = oResultTable->GetValue(1, rowCnt);
10766 57 : row.osType = pszType ? pszType : "";
10767 57 : const char *pszTableName = oResultTable->GetValue(2, rowCnt);
10768 57 : row.osTableName = pszTableName ? pszTableName : "";
10769 57 : m_aoSqliteMasterContent.emplace_back(std::move(row));
10770 : }
10771 : }
10772 : }
10773 2 : return m_aoSqliteMasterContent;
10774 : }
|