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 "gdal_alg.h"
18 : #include "gdalwarper.h"
19 : #include "gdal_utils.h"
20 : #include "ogrgeopackageutility.h"
21 : #include "ogrsqliteutility.h"
22 : #include "ogr_wkb.h"
23 : #include "vrt/vrtdataset.h"
24 :
25 : #include "tilematrixset.hpp"
26 :
27 : #include <cstdlib>
28 :
29 : #include <algorithm>
30 : #include <limits>
31 : #include <sstream>
32 :
33 : #define COMPILATION_ALLOWED
34 : #define DEFINE_OGRSQLiteSQLFunctionsSetCaseSensitiveLike
35 : #include "ogrsqlitesqlfunctionscommon.cpp"
36 :
37 : // Keep in sync prototype of those 2 functions between gdalopeninfo.cpp,
38 : // ogrsqlitedatasource.cpp and ogrgeopackagedatasource.cpp
39 : void GDALOpenInfoDeclareFileNotToOpen(const char *pszFilename,
40 : const GByte *pabyHeader,
41 : int nHeaderBytes);
42 : void GDALOpenInfoUnDeclareFileNotToOpen(const char *pszFilename);
43 :
44 : /************************************************************************/
45 : /* Tiling schemes */
46 : /************************************************************************/
47 :
48 : typedef struct
49 : {
50 : const char *pszName;
51 : int nEPSGCode;
52 : double dfMinX;
53 : double dfMaxY;
54 : int nTileXCountZoomLevel0;
55 : int nTileYCountZoomLevel0;
56 : int nTileWidth;
57 : int nTileHeight;
58 : double dfPixelXSizeZoomLevel0;
59 : double dfPixelYSizeZoomLevel0;
60 : } TilingSchemeDefinition;
61 :
62 : static const TilingSchemeDefinition asTilingSchemes[] = {
63 : /* See http://portal.opengeospatial.org/files/?artifact_id=35326 (WMTS 1.0),
64 : Annex E.3 */
65 : {"GoogleCRS84Quad", 4326, -180.0, 180.0, 1, 1, 256, 256, 360.0 / 256,
66 : 360.0 / 256},
67 :
68 : /* See global-mercator at
69 : http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
70 : {"PseudoTMS_GlobalMercator", 3857, -20037508.34, 20037508.34, 2, 2, 256,
71 : 256, 78271.516, 78271.516},
72 : };
73 :
74 : // Setting it above 30 would lead to integer overflow ((1 << 31) > INT_MAX)
75 : constexpr int MAX_ZOOM_LEVEL = 30;
76 :
77 : /************************************************************************/
78 : /* GetTilingScheme() */
79 : /************************************************************************/
80 :
81 : static std::unique_ptr<TilingSchemeDefinition>
82 570 : GetTilingScheme(const char *pszName)
83 : {
84 570 : if (EQUAL(pszName, "CUSTOM"))
85 442 : return nullptr;
86 :
87 256 : for (const auto &tilingScheme : asTilingSchemes)
88 : {
89 195 : if (EQUAL(pszName, tilingScheme.pszName))
90 : {
91 67 : return std::make_unique<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 992 : OGRErr GDALGeoPackageDataset::SetApplicationAndUserVersionId()
192 : {
193 992 : CPLAssert(hDB != nullptr);
194 :
195 992 : const CPLString osPragma(CPLString().Printf("PRAGMA application_id = %u;"
196 : "PRAGMA user_version = %u",
197 : m_nApplicationId,
198 1984 : m_nUserVersion));
199 1984 : return SQLCommand(hDB, osPragma.c_str());
200 : }
201 :
202 2673 : bool GDALGeoPackageDataset::CloseDB()
203 : {
204 2673 : OGRSQLiteUnregisterSQLFunctions(m_pSQLFunctionData);
205 2673 : m_pSQLFunctionData = nullptr;
206 2673 : return OGRSQLiteBaseDataSource::CloseDB();
207 : }
208 :
209 11 : bool GDALGeoPackageDataset::ReOpenDB()
210 : {
211 11 : CPLAssert(hDB != nullptr);
212 11 : CPLAssert(m_pszFilename != nullptr);
213 :
214 11 : FinishSpatialite();
215 :
216 11 : CloseDB();
217 :
218 : /* And re-open the file */
219 11 : return OpenOrCreateDB(SQLITE_OPEN_READWRITE);
220 : }
221 :
222 862 : static OGRErr GDALGPKGImportFromEPSG(OGRSpatialReference *poSpatialRef,
223 : int nEPSGCode)
224 : {
225 862 : CPLPushErrorHandler(CPLQuietErrorHandler);
226 862 : const OGRErr eErr = poSpatialRef->importFromEPSG(nEPSGCode);
227 862 : CPLPopErrorHandler();
228 862 : CPLErrorReset();
229 862 : return eErr;
230 : }
231 :
232 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
233 1295 : GDALGeoPackageDataset::GetSpatialRef(int iSrsId, bool bFallbackToEPSG,
234 : bool bEmitErrorIfNotFound)
235 : {
236 1295 : const auto oIter = m_oMapSrsIdToSrs.find(iSrsId);
237 1295 : if (oIter != m_oMapSrsIdToSrs.end())
238 : {
239 91 : if (oIter->second == nullptr)
240 33 : return nullptr;
241 58 : oIter->second->Reference();
242 : return std::unique_ptr<OGRSpatialReference,
243 58 : OGRSpatialReferenceReleaser>(oIter->second);
244 : }
245 :
246 1204 : if (iSrsId == 0 || iSrsId == -1)
247 : {
248 119 : OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
249 119 : poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
250 :
251 : // See corresponding tests in GDALGeoPackageDataset::GetSrsId
252 119 : if (iSrsId == 0)
253 : {
254 29 : poSpatialRef->SetGeogCS("Undefined geographic SRS", "unknown",
255 : "unknown", SRS_WGS84_SEMIMAJOR,
256 : SRS_WGS84_INVFLATTENING);
257 : }
258 90 : else if (iSrsId == -1)
259 : {
260 90 : poSpatialRef->SetLocalCS("Undefined Cartesian SRS");
261 90 : poSpatialRef->SetLinearUnits(SRS_UL_METER, 1.0);
262 : }
263 :
264 119 : m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
265 119 : poSpatialRef->Reference();
266 : return std::unique_ptr<OGRSpatialReference,
267 119 : OGRSpatialReferenceReleaser>(poSpatialRef);
268 : }
269 :
270 2170 : CPLString oSQL;
271 1085 : oSQL.Printf("SELECT srs_name, definition, organization, "
272 : "organization_coordsys_id%s%s "
273 : "FROM gpkg_spatial_ref_sys WHERE "
274 : "srs_id = %d LIMIT 2",
275 1085 : m_bHasDefinition12_063 ? ", definition_12_063" : "",
276 1085 : m_bHasEpochColumn ? ", epoch" : "", iSrsId);
277 :
278 2170 : auto oResult = SQLQuery(hDB, oSQL.c_str());
279 :
280 1085 : if (!oResult || oResult->RowCount() != 1)
281 : {
282 12 : if (bFallbackToEPSG)
283 : {
284 7 : CPLDebug("GPKG",
285 : "unable to read srs_id '%d' from gpkg_spatial_ref_sys",
286 : iSrsId);
287 7 : OGRSpatialReference *poSRS = new OGRSpatialReference();
288 7 : if (poSRS->importFromEPSG(iSrsId) == OGRERR_NONE)
289 : {
290 5 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
291 : return std::unique_ptr<OGRSpatialReference,
292 5 : OGRSpatialReferenceReleaser>(poSRS);
293 : }
294 2 : poSRS->Release();
295 : }
296 5 : else if (bEmitErrorIfNotFound)
297 : {
298 2 : CPLError(CE_Warning, CPLE_AppDefined,
299 : "unable to read srs_id '%d' from gpkg_spatial_ref_sys",
300 : iSrsId);
301 2 : m_oMapSrsIdToSrs[iSrsId] = nullptr;
302 : }
303 7 : return nullptr;
304 : }
305 :
306 1073 : const char *pszName = oResult->GetValue(0, 0);
307 1073 : if (pszName && EQUAL(pszName, "Undefined SRS"))
308 : {
309 457 : m_oMapSrsIdToSrs[iSrsId] = nullptr;
310 457 : return nullptr;
311 : }
312 616 : const char *pszWkt = oResult->GetValue(1, 0);
313 616 : if (pszWkt == nullptr)
314 0 : return nullptr;
315 616 : const char *pszOrganization = oResult->GetValue(2, 0);
316 616 : const char *pszOrganizationCoordsysID = oResult->GetValue(3, 0);
317 : const char *pszWkt2 =
318 616 : m_bHasDefinition12_063 ? oResult->GetValue(4, 0) : nullptr;
319 616 : if (pszWkt2 && !EQUAL(pszWkt2, "undefined"))
320 76 : pszWkt = pszWkt2;
321 : const char *pszCoordinateEpoch =
322 616 : m_bHasEpochColumn ? oResult->GetValue(5, 0) : nullptr;
323 : const double dfCoordinateEpoch =
324 616 : pszCoordinateEpoch ? CPLAtof(pszCoordinateEpoch) : 0.0;
325 :
326 616 : OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
327 616 : poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
328 : // Try to import first from EPSG code, and then from WKT
329 616 : if (!(pszOrganization && pszOrganizationCoordsysID &&
330 616 : EQUAL(pszOrganization, "EPSG") &&
331 596 : (atoi(pszOrganizationCoordsysID) == iSrsId ||
332 4 : (dfCoordinateEpoch > 0 && strstr(pszWkt, "DYNAMIC[") == nullptr)) &&
333 596 : GDALGPKGImportFromEPSG(
334 1232 : poSpatialRef, atoi(pszOrganizationCoordsysID)) == OGRERR_NONE) &&
335 20 : poSpatialRef->importFromWkt(pszWkt) != OGRERR_NONE)
336 : {
337 0 : CPLError(CE_Warning, CPLE_AppDefined,
338 : "Unable to parse srs_id '%d' well-known text '%s'", iSrsId,
339 : pszWkt);
340 0 : delete poSpatialRef;
341 0 : m_oMapSrsIdToSrs[iSrsId] = nullptr;
342 0 : return nullptr;
343 : }
344 :
345 616 : poSpatialRef->StripTOWGS84IfKnownDatumAndAllowed();
346 616 : poSpatialRef->SetCoordinateEpoch(dfCoordinateEpoch);
347 616 : m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
348 616 : poSpatialRef->Reference();
349 : return std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>(
350 616 : poSpatialRef);
351 : }
352 :
353 295 : const char *GDALGeoPackageDataset::GetSrsName(const OGRSpatialReference &oSRS)
354 : {
355 295 : const char *pszName = oSRS.GetName();
356 295 : if (pszName)
357 295 : return pszName;
358 :
359 : // Something odd. Return empty.
360 0 : return "Unnamed SRS";
361 : }
362 :
363 : /* Add the definition_12_063 column to an existing gpkg_spatial_ref_sys table */
364 7 : bool GDALGeoPackageDataset::ConvertGpkgSpatialRefSysToExtensionWkt2(
365 : bool bForceEpoch)
366 : {
367 7 : const bool bAddEpoch = (m_nUserVersion >= GPKG_1_4_VERSION || bForceEpoch);
368 : auto oResultTable = SQLQuery(
369 : hDB, "SELECT srs_name, srs_id, organization, organization_coordsys_id, "
370 14 : "definition, description FROM gpkg_spatial_ref_sys LIMIT 100000");
371 7 : if (!oResultTable)
372 0 : return false;
373 :
374 : // Temporary remove foreign key checks
375 : const GPKGTemporaryForeignKeyCheckDisabler
376 7 : oGPKGTemporaryForeignKeyCheckDisabler(this);
377 :
378 7 : bool bRet = SoftStartTransaction() == OGRERR_NONE;
379 :
380 7 : if (bRet)
381 : {
382 : std::string osSQL("CREATE TABLE gpkg_spatial_ref_sys_temp ("
383 : "srs_name TEXT NOT NULL,"
384 : "srs_id INTEGER NOT NULL PRIMARY KEY,"
385 : "organization TEXT NOT NULL,"
386 : "organization_coordsys_id INTEGER NOT NULL,"
387 : "definition TEXT NOT NULL,"
388 : "description TEXT, "
389 7 : "definition_12_063 TEXT NOT NULL");
390 7 : if (bAddEpoch)
391 6 : osSQL += ", epoch DOUBLE";
392 7 : osSQL += ")";
393 7 : bRet = SQLCommand(hDB, osSQL.c_str()) == OGRERR_NONE;
394 : }
395 :
396 7 : if (bRet)
397 : {
398 32 : for (int i = 0; bRet && i < oResultTable->RowCount(); i++)
399 : {
400 25 : const char *pszSrsName = oResultTable->GetValue(0, i);
401 25 : const char *pszSrsId = oResultTable->GetValue(1, i);
402 25 : const char *pszOrganization = oResultTable->GetValue(2, i);
403 : const char *pszOrganizationCoordsysID =
404 25 : oResultTable->GetValue(3, i);
405 25 : const char *pszDefinition = oResultTable->GetValue(4, i);
406 : if (pszSrsName == nullptr || pszSrsId == nullptr ||
407 : pszOrganization == nullptr ||
408 : pszOrganizationCoordsysID == nullptr)
409 : {
410 : // should not happen as there are NOT NULL constraints
411 : // But a database could lack such NOT NULL constraints or have
412 : // large values that would cause a memory allocation failure.
413 : }
414 25 : const char *pszDescription = oResultTable->GetValue(5, i);
415 : char *pszSQL;
416 :
417 50 : OGRSpatialReference oSRS;
418 25 : if (pszOrganization && pszOrganizationCoordsysID &&
419 25 : EQUAL(pszOrganization, "EPSG"))
420 : {
421 9 : oSRS.importFromEPSG(atoi(pszOrganizationCoordsysID));
422 : }
423 34 : if (!oSRS.IsEmpty() && pszDefinition &&
424 9 : !EQUAL(pszDefinition, "undefined"))
425 : {
426 9 : oSRS.SetFromUserInput(pszDefinition);
427 : }
428 25 : char *pszWKT2 = nullptr;
429 25 : if (!oSRS.IsEmpty())
430 : {
431 9 : const char *const apszOptionsWkt2[] = {"FORMAT=WKT2_2015",
432 : nullptr};
433 9 : oSRS.exportToWkt(&pszWKT2, apszOptionsWkt2);
434 9 : if (pszWKT2 && pszWKT2[0] == '\0')
435 : {
436 0 : CPLFree(pszWKT2);
437 0 : pszWKT2 = nullptr;
438 : }
439 : }
440 25 : if (pszWKT2 == nullptr)
441 : {
442 16 : pszWKT2 = CPLStrdup("undefined");
443 : }
444 :
445 25 : if (pszDescription)
446 : {
447 22 : pszSQL = sqlite3_mprintf(
448 : "INSERT INTO gpkg_spatial_ref_sys_temp(srs_name, srs_id, "
449 : "organization, organization_coordsys_id, definition, "
450 : "description, definition_12_063) VALUES ('%q', '%q', '%q', "
451 : "'%q', '%q', '%q', '%q')",
452 : pszSrsName, pszSrsId, pszOrganization,
453 : pszOrganizationCoordsysID, pszDefinition, pszDescription,
454 : pszWKT2);
455 : }
456 : else
457 : {
458 3 : pszSQL = sqlite3_mprintf(
459 : "INSERT INTO gpkg_spatial_ref_sys_temp(srs_name, srs_id, "
460 : "organization, organization_coordsys_id, definition, "
461 : "description, definition_12_063) VALUES ('%q', '%q', '%q', "
462 : "'%q', '%q', NULL, '%q')",
463 : pszSrsName, pszSrsId, pszOrganization,
464 : pszOrganizationCoordsysID, pszDefinition, pszWKT2);
465 : }
466 :
467 25 : CPLFree(pszWKT2);
468 25 : bRet &= SQLCommand(hDB, pszSQL) == OGRERR_NONE;
469 25 : sqlite3_free(pszSQL);
470 : }
471 : }
472 :
473 7 : if (bRet)
474 : {
475 7 : bRet =
476 7 : SQLCommand(hDB, "DROP TABLE gpkg_spatial_ref_sys") == OGRERR_NONE;
477 : }
478 7 : if (bRet)
479 : {
480 7 : bRet = SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys_temp RENAME "
481 : "TO gpkg_spatial_ref_sys") == OGRERR_NONE;
482 : }
483 7 : if (bRet)
484 : {
485 14 : bRet = OGRERR_NONE == CreateExtensionsTableIfNecessary() &&
486 7 : OGRERR_NONE == SQLCommand(hDB,
487 : "INSERT INTO gpkg_extensions "
488 : "(table_name, column_name, "
489 : "extension_name, definition, scope) "
490 : "VALUES "
491 : "('gpkg_spatial_ref_sys', "
492 : "'definition_12_063', 'gpkg_crs_wkt', "
493 : "'http://www.geopackage.org/spec120/"
494 : "#extension_crs_wkt', 'read-write')");
495 : }
496 7 : if (bRet && bAddEpoch)
497 : {
498 6 : bRet =
499 : OGRERR_NONE ==
500 6 : SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
501 : "'gpkg_crs_wkt_1_1' "
502 12 : "WHERE extension_name = 'gpkg_crs_wkt'") &&
503 : OGRERR_NONE ==
504 6 : SQLCommand(
505 : hDB,
506 : "INSERT INTO gpkg_extensions "
507 : "(table_name, column_name, extension_name, definition, "
508 : "scope) "
509 : "VALUES "
510 : "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
511 : "'http://www.geopackage.org/spec/#extension_crs_wkt', "
512 : "'read-write')");
513 : }
514 7 : if (bRet)
515 : {
516 7 : SoftCommitTransaction();
517 7 : m_bHasDefinition12_063 = true;
518 7 : if (bAddEpoch)
519 6 : m_bHasEpochColumn = true;
520 : }
521 : else
522 : {
523 0 : SoftRollbackTransaction();
524 : }
525 :
526 7 : return bRet;
527 : }
528 :
529 955 : int GDALGeoPackageDataset::GetSrsId(const OGRSpatialReference *poSRSIn)
530 : {
531 955 : const char *pszName = poSRSIn ? poSRSIn->GetName() : nullptr;
532 1377 : if (!poSRSIn || poSRSIn->IsEmpty() ||
533 422 : (pszName && EQUAL(pszName, "Undefined SRS")))
534 : {
535 535 : OGRErr err = OGRERR_NONE;
536 535 : const int nSRSId = SQLGetInteger(
537 : hDB,
538 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE srs_name = "
539 : "'Undefined SRS' AND organization = 'GDAL'",
540 : &err);
541 535 : if (err == OGRERR_NONE)
542 57 : return nSRSId;
543 :
544 : // The below WKT definitions are somehow questionable (using a unknown
545 : // unit). For GDAL >= 3.9, they won't be used. They will only be used
546 : // for earlier versions.
547 : const char *pszSQL;
548 : #define UNDEFINED_CRS_SRS_ID 99999
549 : static_assert(UNDEFINED_CRS_SRS_ID == FIRST_CUSTOM_SRSID - 1);
550 : #define STRINGIFY(x) #x
551 : #define XSTRINGIFY(x) STRINGIFY(x)
552 478 : if (m_bHasDefinition12_063)
553 : {
554 : /* clang-format off */
555 1 : pszSQL =
556 : "INSERT INTO gpkg_spatial_ref_sys "
557 : "(srs_name,srs_id,organization,organization_coordsys_id,"
558 : "definition, definition_12_063, description) VALUES "
559 : "('Undefined SRS'," XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ",'GDAL',"
560 : XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ","
561 : "'LOCAL_CS[\"Undefined SRS\",LOCAL_DATUM[\"unknown\",32767],"
562 : "UNIT[\"unknown\",0],AXIS[\"Easting\",EAST],"
563 : "AXIS[\"Northing\",NORTH]]',"
564 : "'ENGCRS[\"Undefined SRS\",EDATUM[\"unknown\"],CS[Cartesian,2],"
565 : "AXIS[\"easting\",east,ORDER[1],LENGTHUNIT[\"unknown\",0]],"
566 : "AXIS[\"northing\",north,ORDER[2],LENGTHUNIT[\"unknown\",0]]]',"
567 : "'Custom undefined coordinate reference system')";
568 : /* clang-format on */
569 : }
570 : else
571 : {
572 : /* clang-format off */
573 477 : pszSQL =
574 : "INSERT INTO gpkg_spatial_ref_sys "
575 : "(srs_name,srs_id,organization,organization_coordsys_id,"
576 : "definition, description) VALUES "
577 : "('Undefined SRS'," XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ",'GDAL',"
578 : XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ","
579 : "'LOCAL_CS[\"Undefined SRS\",LOCAL_DATUM[\"unknown\",32767],"
580 : "UNIT[\"unknown\",0],AXIS[\"Easting\",EAST],"
581 : "AXIS[\"Northing\",NORTH]]',"
582 : "'Custom undefined coordinate reference system')";
583 : /* clang-format on */
584 : }
585 478 : if (SQLCommand(hDB, pszSQL) == OGRERR_NONE)
586 478 : return UNDEFINED_CRS_SRS_ID;
587 : #undef UNDEFINED_CRS_SRS_ID
588 : #undef XSTRINGIFY
589 : #undef STRINGIFY
590 0 : return -1;
591 : }
592 :
593 840 : std::unique_ptr<OGRSpatialReference> poSRS(poSRSIn->Clone());
594 :
595 420 : if (poSRS->IsGeographic() || poSRS->IsLocal())
596 : {
597 : // See corresponding tests in GDALGeoPackageDataset::GetSpatialRef
598 145 : if (pszName != nullptr && strlen(pszName) > 0)
599 : {
600 145 : if (EQUAL(pszName, "Undefined geographic SRS"))
601 2 : return 0;
602 :
603 143 : if (EQUAL(pszName, "Undefined Cartesian SRS"))
604 1 : return -1;
605 : }
606 : }
607 :
608 417 : const char *pszAuthorityName = poSRS->GetAuthorityName(nullptr);
609 :
610 417 : if (pszAuthorityName == nullptr || strlen(pszAuthorityName) == 0)
611 : {
612 : // Try to force identify an EPSG code.
613 26 : poSRS->AutoIdentifyEPSG();
614 :
615 26 : pszAuthorityName = poSRS->GetAuthorityName(nullptr);
616 26 : if (pszAuthorityName != nullptr && EQUAL(pszAuthorityName, "EPSG"))
617 : {
618 0 : const char *pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
619 0 : if (pszAuthorityCode != nullptr && strlen(pszAuthorityCode) > 0)
620 : {
621 : /* Import 'clean' SRS */
622 0 : poSRS->importFromEPSG(atoi(pszAuthorityCode));
623 :
624 0 : pszAuthorityName = poSRS->GetAuthorityName(nullptr);
625 : }
626 : }
627 :
628 26 : poSRS->SetCoordinateEpoch(poSRSIn->GetCoordinateEpoch());
629 : }
630 :
631 : // Check whether the EPSG authority code is already mapped to a
632 : // SRS ID.
633 417 : char *pszSQL = nullptr;
634 417 : int nSRSId = DEFAULT_SRID;
635 417 : int nAuthorityCode = 0;
636 417 : OGRErr err = OGRERR_NONE;
637 417 : bool bCanUseAuthorityCode = false;
638 417 : const char *const apszIsSameOptions[] = {
639 : "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES",
640 : "IGNORE_COORDINATE_EPOCH=YES", nullptr};
641 417 : if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0)
642 : {
643 391 : const char *pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
644 391 : if (pszAuthorityCode)
645 : {
646 391 : if (CPLGetValueType(pszAuthorityCode) == CPL_VALUE_INTEGER)
647 : {
648 391 : nAuthorityCode = atoi(pszAuthorityCode);
649 : }
650 : else
651 : {
652 0 : CPLDebug("GPKG",
653 : "SRS has %s:%s identification, but the code not "
654 : "being an integer value cannot be stored as such "
655 : "in the database.",
656 : pszAuthorityName, pszAuthorityCode);
657 0 : pszAuthorityName = nullptr;
658 : }
659 : }
660 : }
661 :
662 808 : if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
663 391 : poSRSIn->GetCoordinateEpoch() == 0)
664 : {
665 : pszSQL =
666 386 : sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
667 : "upper(organization) = upper('%q') AND "
668 : "organization_coordsys_id = %d",
669 : pszAuthorityName, nAuthorityCode);
670 :
671 386 : nSRSId = SQLGetInteger(hDB, pszSQL, &err);
672 386 : sqlite3_free(pszSQL);
673 :
674 : // Got a match? Return it!
675 386 : if (OGRERR_NONE == err)
676 : {
677 118 : auto poRefSRS = GetSpatialRef(nSRSId);
678 : bool bOK =
679 118 : (poRefSRS == nullptr ||
680 119 : poSRS->IsSame(poRefSRS.get(), apszIsSameOptions) ||
681 1 : !CPLTestBool(CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")));
682 118 : if (bOK)
683 : {
684 117 : return nSRSId;
685 : }
686 : else
687 : {
688 1 : CPLError(CE_Warning, CPLE_AppDefined,
689 : "Passed SRS uses %s:%d identification, but its "
690 : "definition is not compatible with the "
691 : "definition of that object already in the database. "
692 : "Registering it as a new entry into the database.",
693 : pszAuthorityName, nAuthorityCode);
694 1 : pszAuthorityName = nullptr;
695 1 : nAuthorityCode = 0;
696 : }
697 : }
698 : }
699 :
700 : // Translate SRS to WKT.
701 300 : CPLCharUniquePtr pszWKT1;
702 300 : CPLCharUniquePtr pszWKT2_2015;
703 300 : CPLCharUniquePtr pszWKT2_2019;
704 300 : const char *const apszOptionsWkt1[] = {"FORMAT=WKT1_GDAL", nullptr};
705 300 : const char *const apszOptionsWkt2_2015[] = {"FORMAT=WKT2_2015", nullptr};
706 300 : const char *const apszOptionsWkt2_2019[] = {"FORMAT=WKT2_2019", nullptr};
707 :
708 600 : std::string osEpochTest;
709 300 : if (poSRSIn->GetCoordinateEpoch() > 0 && m_bHasEpochColumn)
710 : {
711 : osEpochTest =
712 3 : CPLSPrintf(" AND epoch = %.17g", poSRSIn->GetCoordinateEpoch());
713 : }
714 :
715 591 : if (!(poSRS->IsGeographic() && poSRS->GetAxesCount() == 3) &&
716 291 : !poSRS->IsDerivedGeographic())
717 : {
718 582 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
719 291 : char *pszTmp = nullptr;
720 291 : poSRS->exportToWkt(&pszTmp, apszOptionsWkt1);
721 291 : pszWKT1.reset(pszTmp);
722 291 : if (pszWKT1 && pszWKT1.get()[0] == '\0')
723 : {
724 0 : pszWKT1.reset();
725 : }
726 : }
727 : {
728 600 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
729 300 : char *pszTmp = nullptr;
730 300 : poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2015);
731 300 : pszWKT2_2015.reset(pszTmp);
732 300 : if (pszWKT2_2015 && pszWKT2_2015.get()[0] == '\0')
733 : {
734 0 : pszWKT2_2015.reset();
735 : }
736 : }
737 : {
738 300 : char *pszTmp = nullptr;
739 300 : poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2019);
740 300 : pszWKT2_2019.reset(pszTmp);
741 300 : if (pszWKT2_2019 && pszWKT2_2019.get()[0] == '\0')
742 : {
743 0 : pszWKT2_2019.reset();
744 : }
745 : }
746 :
747 300 : if (!pszWKT1 && !pszWKT2_2015 && !pszWKT2_2019)
748 : {
749 0 : return DEFAULT_SRID;
750 : }
751 :
752 300 : if (poSRSIn->GetCoordinateEpoch() == 0 || m_bHasEpochColumn)
753 : {
754 : // Search if there is already an existing entry with this WKT
755 297 : if (m_bHasDefinition12_063 && (pszWKT2_2015 || pszWKT2_2019))
756 : {
757 42 : if (pszWKT1)
758 : {
759 144 : pszSQL = sqlite3_mprintf(
760 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
761 : "(definition = '%q' OR definition_12_063 IN ('%q','%q'))%s",
762 : pszWKT1.get(),
763 72 : pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
764 72 : pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
765 : osEpochTest.c_str());
766 : }
767 : else
768 : {
769 24 : pszSQL = sqlite3_mprintf(
770 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
771 : "definition_12_063 IN ('%q', '%q')%s",
772 12 : pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
773 12 : pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
774 : osEpochTest.c_str());
775 : }
776 : }
777 255 : else if (pszWKT1)
778 : {
779 : pszSQL =
780 252 : sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
781 : "definition = '%q'%s",
782 : pszWKT1.get(), osEpochTest.c_str());
783 : }
784 : else
785 : {
786 3 : pszSQL = nullptr;
787 : }
788 297 : if (pszSQL)
789 : {
790 294 : nSRSId = SQLGetInteger(hDB, pszSQL, &err);
791 294 : sqlite3_free(pszSQL);
792 294 : if (OGRERR_NONE == err)
793 : {
794 5 : return nSRSId;
795 : }
796 : }
797 : }
798 :
799 566 : if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
800 271 : poSRSIn->GetCoordinateEpoch() == 0)
801 : {
802 267 : bool bTryToReuseSRSId = true;
803 267 : if (EQUAL(pszAuthorityName, "EPSG"))
804 : {
805 532 : OGRSpatialReference oSRS_EPSG;
806 266 : if (GDALGPKGImportFromEPSG(&oSRS_EPSG, nAuthorityCode) ==
807 : OGRERR_NONE)
808 : {
809 267 : if (!poSRS->IsSame(&oSRS_EPSG, apszIsSameOptions) &&
810 1 : CPLTestBool(
811 : CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")))
812 : {
813 1 : bTryToReuseSRSId = false;
814 1 : CPLError(
815 : CE_Warning, CPLE_AppDefined,
816 : "Passed SRS uses %s:%d identification, but its "
817 : "definition is not compatible with the "
818 : "official definition of the object. "
819 : "Registering it as a non-%s entry into the database.",
820 : pszAuthorityName, nAuthorityCode, pszAuthorityName);
821 1 : pszAuthorityName = nullptr;
822 1 : nAuthorityCode = 0;
823 : }
824 : }
825 : }
826 267 : if (bTryToReuseSRSId)
827 : {
828 : // No match, but maybe we can use the nAuthorityCode as the nSRSId?
829 266 : pszSQL = sqlite3_mprintf(
830 : "SELECT Count(*) FROM gpkg_spatial_ref_sys WHERE "
831 : "srs_id = %d",
832 : nAuthorityCode);
833 :
834 : // Yep, we can!
835 266 : if (SQLGetInteger(hDB, pszSQL, nullptr) == 0)
836 265 : bCanUseAuthorityCode = true;
837 266 : sqlite3_free(pszSQL);
838 : }
839 : }
840 :
841 295 : bool bConvertGpkgSpatialRefSysToExtensionWkt2 = false;
842 295 : bool bForceEpoch = false;
843 298 : if (!m_bHasDefinition12_063 && pszWKT1 == nullptr &&
844 3 : (pszWKT2_2015 != nullptr || pszWKT2_2019 != nullptr))
845 : {
846 3 : bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
847 : }
848 :
849 : // Add epoch column if needed
850 295 : if (poSRSIn->GetCoordinateEpoch() > 0 && !m_bHasEpochColumn)
851 : {
852 3 : if (m_bHasDefinition12_063)
853 : {
854 0 : if (SoftStartTransaction() != OGRERR_NONE)
855 0 : return DEFAULT_SRID;
856 0 : if (SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys "
857 0 : "ADD COLUMN epoch DOUBLE") != OGRERR_NONE ||
858 0 : SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
859 : "'gpkg_crs_wkt_1_1' "
860 : "WHERE extension_name = 'gpkg_crs_wkt'") !=
861 0 : OGRERR_NONE ||
862 0 : SQLCommand(
863 : hDB,
864 : "INSERT INTO gpkg_extensions "
865 : "(table_name, column_name, extension_name, definition, "
866 : "scope) "
867 : "VALUES "
868 : "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
869 : "'http://www.geopackage.org/spec/#extension_crs_wkt', "
870 : "'read-write')") != OGRERR_NONE)
871 : {
872 0 : SoftRollbackTransaction();
873 0 : return DEFAULT_SRID;
874 : }
875 :
876 0 : if (SoftCommitTransaction() != OGRERR_NONE)
877 0 : return DEFAULT_SRID;
878 :
879 0 : m_bHasEpochColumn = true;
880 : }
881 : else
882 : {
883 3 : bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
884 3 : bForceEpoch = true;
885 : }
886 : }
887 :
888 301 : if (bConvertGpkgSpatialRefSysToExtensionWkt2 &&
889 6 : !ConvertGpkgSpatialRefSysToExtensionWkt2(bForceEpoch))
890 : {
891 0 : return DEFAULT_SRID;
892 : }
893 :
894 : // Reuse the authority code number as SRS_ID if we can
895 295 : if (bCanUseAuthorityCode)
896 : {
897 265 : nSRSId = nAuthorityCode;
898 : }
899 : // Otherwise, generate a new SRS_ID number (max + 1)
900 : else
901 : {
902 : // Get the current maximum srid in the srs table.
903 30 : const int nMaxSRSId = SQLGetInteger(
904 : hDB, "SELECT MAX(srs_id) FROM gpkg_spatial_ref_sys", nullptr);
905 30 : nSRSId = std::max(FIRST_CUSTOM_SRSID, nMaxSRSId + 1);
906 : }
907 :
908 590 : std::string osEpochColumn;
909 295 : std::string osEpochVal;
910 295 : if (poSRSIn->GetCoordinateEpoch() > 0)
911 : {
912 5 : osEpochColumn = ", epoch";
913 5 : osEpochVal = CPLSPrintf(", %.17g", poSRSIn->GetCoordinateEpoch());
914 : }
915 :
916 : // Add new SRS row to gpkg_spatial_ref_sys.
917 295 : if (m_bHasDefinition12_063)
918 : {
919 : // Force WKT2_2019 when we have a dynamic CRS and coordinate epoch
920 45 : const char *pszWKT2 = poSRSIn->IsDynamic() &&
921 10 : poSRSIn->GetCoordinateEpoch() > 0 &&
922 1 : pszWKT2_2019
923 1 : ? pszWKT2_2019.get()
924 44 : : pszWKT2_2015 ? pszWKT2_2015.get()
925 97 : : pszWKT2_2019.get();
926 :
927 45 : if (pszAuthorityName != nullptr && nAuthorityCode > 0)
928 : {
929 99 : pszSQL = sqlite3_mprintf(
930 : "INSERT INTO gpkg_spatial_ref_sys "
931 : "(srs_name,srs_id,organization,organization_coordsys_id,"
932 : "definition, definition_12_063%s) VALUES "
933 : "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
934 33 : osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId,
935 : pszAuthorityName, nAuthorityCode,
936 62 : pszWKT1 ? pszWKT1.get() : "undefined",
937 : pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
938 : }
939 : else
940 : {
941 36 : pszSQL = sqlite3_mprintf(
942 : "INSERT INTO gpkg_spatial_ref_sys "
943 : "(srs_name,srs_id,organization,organization_coordsys_id,"
944 : "definition, definition_12_063%s) VALUES "
945 : "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
946 12 : osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId, "NONE",
947 21 : nSRSId, pszWKT1 ? pszWKT1.get() : "undefined",
948 : pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
949 : }
950 : }
951 : else
952 : {
953 250 : if (pszAuthorityName != nullptr && nAuthorityCode > 0)
954 : {
955 474 : pszSQL = sqlite3_mprintf(
956 : "INSERT INTO gpkg_spatial_ref_sys "
957 : "(srs_name,srs_id,organization,organization_coordsys_id,"
958 : "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
959 237 : GetSrsName(*poSRS), nSRSId, pszAuthorityName, nAuthorityCode,
960 474 : pszWKT1 ? pszWKT1.get() : "undefined");
961 : }
962 : else
963 : {
964 26 : pszSQL = sqlite3_mprintf(
965 : "INSERT INTO gpkg_spatial_ref_sys "
966 : "(srs_name,srs_id,organization,organization_coordsys_id,"
967 : "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
968 13 : GetSrsName(*poSRS), nSRSId, "NONE", nSRSId,
969 26 : pszWKT1 ? pszWKT1.get() : "undefined");
970 : }
971 : }
972 :
973 : // Add new row to gpkg_spatial_ref_sys.
974 295 : CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
975 :
976 : // Free everything that was allocated.
977 295 : sqlite3_free(pszSQL);
978 :
979 295 : return nSRSId;
980 : }
981 :
982 : /************************************************************************/
983 : /* ~GDALGeoPackageDataset() */
984 : /************************************************************************/
985 :
986 5324 : GDALGeoPackageDataset::~GDALGeoPackageDataset()
987 : {
988 2662 : GDALGeoPackageDataset::Close();
989 5324 : }
990 :
991 : /************************************************************************/
992 : /* Close() */
993 : /************************************************************************/
994 :
995 4477 : CPLErr GDALGeoPackageDataset::Close(GDALProgressFunc, void *)
996 : {
997 4477 : CPLErr eErr = CE_None;
998 4477 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
999 : {
1000 1562 : if (eAccess == GA_Update && m_poParentDS == nullptr &&
1001 4224 : !m_osRasterTable.empty() && !m_bGeoTransformValid)
1002 : {
1003 3 : CPLError(CE_Failure, CPLE_AppDefined,
1004 : "Raster table %s not correctly initialized due to missing "
1005 : "call to SetGeoTransform()",
1006 : m_osRasterTable.c_str());
1007 : }
1008 :
1009 5313 : if (!IsMarkedSuppressOnClose() &&
1010 2651 : GDALGeoPackageDataset::FlushCache(true) != CE_None)
1011 : {
1012 7 : eErr = CE_Failure;
1013 : }
1014 :
1015 : // Destroy bands now since we don't want
1016 : // GDALGPKGMBTilesLikeRasterBand::FlushCache() to run after dataset
1017 : // destruction
1018 4484 : for (int i = 0; i < nBands; i++)
1019 1822 : delete papoBands[i];
1020 2662 : nBands = 0;
1021 2662 : CPLFree(papoBands);
1022 2662 : papoBands = nullptr;
1023 :
1024 : // Destroy overviews before cleaning m_hTempDB as they could still
1025 : // need it
1026 2662 : m_apoOverviewDS.clear();
1027 :
1028 2662 : if (m_poParentDS)
1029 : {
1030 325 : hDB = nullptr;
1031 : }
1032 :
1033 2662 : m_apoLayers.clear();
1034 :
1035 : std::map<int, OGRSpatialReference *>::iterator oIter =
1036 2662 : m_oMapSrsIdToSrs.begin();
1037 3856 : for (; oIter != m_oMapSrsIdToSrs.end(); ++oIter)
1038 : {
1039 1194 : OGRSpatialReference *poSRS = oIter->second;
1040 1194 : if (poSRS)
1041 735 : poSRS->Release();
1042 : }
1043 :
1044 2662 : if (!CloseDB())
1045 0 : eErr = CE_Failure;
1046 :
1047 2662 : if (OGRSQLiteBaseDataSource::Close() != CE_None)
1048 0 : eErr = CE_Failure;
1049 : }
1050 4477 : return eErr;
1051 : }
1052 :
1053 : /************************************************************************/
1054 : /* ICanIWriteBlock() */
1055 : /************************************************************************/
1056 :
1057 5696 : bool GDALGeoPackageDataset::ICanIWriteBlock()
1058 : {
1059 5696 : if (!GetUpdate())
1060 : {
1061 0 : CPLError(
1062 : CE_Failure, CPLE_NotSupported,
1063 : "IWriteBlock() not supported on dataset opened in read-only mode");
1064 0 : return false;
1065 : }
1066 :
1067 5696 : if (m_pabyCachedTiles == nullptr)
1068 : {
1069 0 : return false;
1070 : }
1071 :
1072 5696 : if (!m_bGeoTransformValid || m_nSRID == UNKNOWN_SRID)
1073 : {
1074 0 : CPLError(CE_Failure, CPLE_NotSupported,
1075 : "IWriteBlock() not supported if georeferencing not set");
1076 0 : return false;
1077 : }
1078 5696 : return true;
1079 : }
1080 :
1081 : /************************************************************************/
1082 : /* IRasterIO() */
1083 : /************************************************************************/
1084 :
1085 134 : CPLErr GDALGeoPackageDataset::IRasterIO(
1086 : GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
1087 : void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
1088 : int nBandCount, BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
1089 : GSpacing nLineSpace, GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
1090 :
1091 : {
1092 134 : CPLErr eErr = OGRSQLiteBaseDataSource::IRasterIO(
1093 : eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
1094 : eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace, nBandSpace,
1095 : psExtraArg);
1096 :
1097 : // If writing all bands, in non-shifted mode, flush all entirely written
1098 : // tiles This can avoid "stressing" the block cache with too many dirty
1099 : // blocks. Note: this logic would be useless with a per-dataset block cache.
1100 134 : if (eErr == CE_None && eRWFlag == GF_Write && nXSize == nBufXSize &&
1101 123 : nYSize == nBufYSize && nBandCount == nBands &&
1102 120 : m_nShiftXPixelsMod == 0 && m_nShiftYPixelsMod == 0)
1103 : {
1104 : auto poBand =
1105 116 : cpl::down_cast<GDALGPKGMBTilesLikeRasterBand *>(GetRasterBand(1));
1106 : int nBlockXSize, nBlockYSize;
1107 116 : poBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
1108 116 : const int nBlockXStart = DIV_ROUND_UP(nXOff, nBlockXSize);
1109 116 : const int nBlockYStart = DIV_ROUND_UP(nYOff, nBlockYSize);
1110 116 : const int nBlockXEnd = (nXOff + nXSize) / nBlockXSize;
1111 116 : const int nBlockYEnd = (nYOff + nYSize) / nBlockYSize;
1112 270 : for (int nBlockY = nBlockXStart; nBlockY < nBlockYEnd; nBlockY++)
1113 : {
1114 4371 : for (int nBlockX = nBlockYStart; nBlockX < nBlockXEnd; nBlockX++)
1115 : {
1116 : GDALRasterBlock *poBlock =
1117 4217 : poBand->AccessibleTryGetLockedBlockRef(nBlockX, nBlockY);
1118 4217 : if (poBlock)
1119 : {
1120 : // GetDirty() should be true in most situation (otherwise
1121 : // it means the block cache is under extreme pressure!)
1122 4215 : if (poBlock->GetDirty())
1123 : {
1124 : // IWriteBlock() on one band will check the dirty state
1125 : // of the corresponding blocks in other bands, to decide
1126 : // if it can call WriteTile(), so we have only to do
1127 : // that on one of the bands
1128 4215 : if (poBlock->Write() != CE_None)
1129 250 : eErr = CE_Failure;
1130 : }
1131 4215 : poBlock->DropLock();
1132 : }
1133 : }
1134 : }
1135 : }
1136 :
1137 134 : return eErr;
1138 : }
1139 :
1140 : /************************************************************************/
1141 : /* GetOGRTableLimit() */
1142 : /************************************************************************/
1143 :
1144 4334 : static int GetOGRTableLimit()
1145 : {
1146 4334 : return atoi(CPLGetConfigOption("OGR_TABLE_LIMIT", "10000"));
1147 : }
1148 :
1149 : /************************************************************************/
1150 : /* GetNameTypeMapFromSQliteMaster() */
1151 : /************************************************************************/
1152 :
1153 : const std::map<CPLString, CPLString> &
1154 1336 : GDALGeoPackageDataset::GetNameTypeMapFromSQliteMaster()
1155 : {
1156 1336 : if (!m_oMapNameToType.empty())
1157 349 : return m_oMapNameToType;
1158 :
1159 : CPLString osSQL(
1160 : "SELECT name, type FROM sqlite_master WHERE "
1161 : "type IN ('view', 'table') OR "
1162 1974 : "(name LIKE 'trigger_%_feature_count_%' AND type = 'trigger')");
1163 987 : const int nTableLimit = GetOGRTableLimit();
1164 987 : if (nTableLimit > 0)
1165 : {
1166 987 : osSQL += " LIMIT ";
1167 987 : osSQL += CPLSPrintf("%d", 1 + 3 * nTableLimit);
1168 : }
1169 :
1170 987 : auto oResult = SQLQuery(hDB, osSQL);
1171 987 : if (oResult)
1172 : {
1173 16377 : for (int i = 0; i < oResult->RowCount(); i++)
1174 : {
1175 15390 : const char *pszName = oResult->GetValue(0, i);
1176 15390 : const char *pszType = oResult->GetValue(1, i);
1177 15390 : m_oMapNameToType[CPLString(pszName).toupper()] = pszType;
1178 : }
1179 : }
1180 :
1181 987 : return m_oMapNameToType;
1182 : }
1183 :
1184 : /************************************************************************/
1185 : /* RemoveTableFromSQLiteMasterCache() */
1186 : /************************************************************************/
1187 :
1188 57 : void GDALGeoPackageDataset::RemoveTableFromSQLiteMasterCache(
1189 : const char *pszTableName)
1190 : {
1191 57 : m_oMapNameToType.erase(CPLString(pszTableName).toupper());
1192 57 : }
1193 :
1194 : /************************************************************************/
1195 : /* GetUnknownExtensionsTableSpecific() */
1196 : /************************************************************************/
1197 :
1198 : const std::map<CPLString, std::vector<GPKGExtensionDesc>> &
1199 938 : GDALGeoPackageDataset::GetUnknownExtensionsTableSpecific()
1200 : {
1201 938 : if (m_bMapTableToExtensionsBuilt)
1202 92 : return m_oMapTableToExtensions;
1203 846 : m_bMapTableToExtensionsBuilt = true;
1204 :
1205 846 : if (!HasExtensionsTable())
1206 52 : return m_oMapTableToExtensions;
1207 :
1208 : CPLString osSQL(
1209 : "SELECT table_name, extension_name, definition, scope "
1210 : "FROM gpkg_extensions WHERE "
1211 : "table_name IS NOT NULL "
1212 : "AND extension_name IS NOT NULL "
1213 : "AND definition IS NOT NULL "
1214 : "AND scope IS NOT NULL "
1215 : "AND extension_name NOT IN ('gpkg_geom_CIRCULARSTRING', "
1216 : "'gpkg_geom_COMPOUNDCURVE', 'gpkg_geom_CURVEPOLYGON', "
1217 : "'gpkg_geom_MULTICURVE', "
1218 : "'gpkg_geom_MULTISURFACE', 'gpkg_geom_CURVE', 'gpkg_geom_SURFACE', "
1219 : "'gpkg_geom_POLYHEDRALSURFACE', 'gpkg_geom_TIN', 'gpkg_geom_TRIANGLE', "
1220 : "'gpkg_rtree_index', 'gpkg_geometry_type_trigger', "
1221 : "'gpkg_srs_id_trigger', "
1222 : "'gpkg_crs_wkt', 'gpkg_crs_wkt_1_1', 'gpkg_schema', "
1223 : "'gpkg_related_tables', 'related_tables'"
1224 : #ifdef HAVE_SPATIALITE
1225 : ", 'gdal_spatialite_computed_geom_column'"
1226 : #endif
1227 1588 : ")");
1228 794 : const int nTableLimit = GetOGRTableLimit();
1229 794 : if (nTableLimit > 0)
1230 : {
1231 794 : osSQL += " LIMIT ";
1232 794 : osSQL += CPLSPrintf("%d", 1 + 10 * nTableLimit);
1233 : }
1234 :
1235 794 : auto oResult = SQLQuery(hDB, osSQL);
1236 794 : if (oResult)
1237 : {
1238 1467 : for (int i = 0; i < oResult->RowCount(); i++)
1239 : {
1240 673 : const char *pszTableName = oResult->GetValue(0, i);
1241 673 : const char *pszExtensionName = oResult->GetValue(1, i);
1242 673 : const char *pszDefinition = oResult->GetValue(2, i);
1243 673 : const char *pszScope = oResult->GetValue(3, i);
1244 673 : if (pszTableName && pszExtensionName && pszDefinition && pszScope)
1245 : {
1246 673 : GPKGExtensionDesc oDesc;
1247 673 : oDesc.osExtensionName = pszExtensionName;
1248 673 : oDesc.osDefinition = pszDefinition;
1249 673 : oDesc.osScope = pszScope;
1250 1346 : m_oMapTableToExtensions[CPLString(pszTableName).toupper()]
1251 673 : .push_back(std::move(oDesc));
1252 : }
1253 : }
1254 : }
1255 :
1256 794 : return m_oMapTableToExtensions;
1257 : }
1258 :
1259 : /************************************************************************/
1260 : /* GetContents() */
1261 : /************************************************************************/
1262 :
1263 : const std::map<CPLString, GPKGContentsDesc> &
1264 920 : GDALGeoPackageDataset::GetContents()
1265 : {
1266 920 : if (m_bMapTableToContentsBuilt)
1267 76 : return m_oMapTableToContents;
1268 844 : m_bMapTableToContentsBuilt = true;
1269 :
1270 : CPLString osSQL("SELECT table_name, data_type, identifier, "
1271 : "description, min_x, min_y, max_x, max_y "
1272 1688 : "FROM gpkg_contents");
1273 844 : const int nTableLimit = GetOGRTableLimit();
1274 844 : if (nTableLimit > 0)
1275 : {
1276 844 : osSQL += " LIMIT ";
1277 844 : osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1278 : }
1279 :
1280 844 : auto oResult = SQLQuery(hDB, osSQL);
1281 844 : if (oResult)
1282 : {
1283 1813 : for (int i = 0; i < oResult->RowCount(); i++)
1284 : {
1285 969 : const char *pszTableName = oResult->GetValue(0, i);
1286 969 : if (pszTableName == nullptr)
1287 0 : continue;
1288 969 : const char *pszDataType = oResult->GetValue(1, i);
1289 969 : const char *pszIdentifier = oResult->GetValue(2, i);
1290 969 : const char *pszDescription = oResult->GetValue(3, i);
1291 969 : const char *pszMinX = oResult->GetValue(4, i);
1292 969 : const char *pszMinY = oResult->GetValue(5, i);
1293 969 : const char *pszMaxX = oResult->GetValue(6, i);
1294 969 : const char *pszMaxY = oResult->GetValue(7, i);
1295 969 : GPKGContentsDesc oDesc;
1296 969 : if (pszDataType)
1297 969 : oDesc.osDataType = pszDataType;
1298 969 : if (pszIdentifier)
1299 969 : oDesc.osIdentifier = pszIdentifier;
1300 969 : if (pszDescription)
1301 968 : oDesc.osDescription = pszDescription;
1302 969 : if (pszMinX)
1303 656 : oDesc.osMinX = pszMinX;
1304 969 : if (pszMinY)
1305 656 : oDesc.osMinY = pszMinY;
1306 969 : if (pszMaxX)
1307 656 : oDesc.osMaxX = pszMaxX;
1308 969 : if (pszMaxY)
1309 656 : oDesc.osMaxY = pszMaxY;
1310 1938 : m_oMapTableToContents[CPLString(pszTableName).toupper()] =
1311 1938 : std::move(oDesc);
1312 : }
1313 : }
1314 :
1315 844 : return m_oMapTableToContents;
1316 : }
1317 :
1318 : /************************************************************************/
1319 : /* Open() */
1320 : /************************************************************************/
1321 :
1322 1317 : int GDALGeoPackageDataset::Open(GDALOpenInfo *poOpenInfo,
1323 : const std::string &osFilenameInZip)
1324 : {
1325 1317 : m_osFilenameInZip = osFilenameInZip;
1326 1317 : CPLAssert(m_apoLayers.empty());
1327 1317 : CPLAssert(hDB == nullptr);
1328 :
1329 1317 : SetDescription(poOpenInfo->pszFilename);
1330 2634 : CPLString osFilename(poOpenInfo->pszFilename);
1331 2634 : CPLString osSubdatasetTableName;
1332 : GByte abyHeaderLetMeHerePlease[100];
1333 1317 : const GByte *pabyHeader = poOpenInfo->pabyHeader;
1334 1317 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GPKG:"))
1335 : {
1336 248 : char **papszTokens = CSLTokenizeString2(poOpenInfo->pszFilename, ":",
1337 : CSLT_HONOURSTRINGS);
1338 248 : int nCount = CSLCount(papszTokens);
1339 248 : if (nCount < 2)
1340 : {
1341 0 : CSLDestroy(papszTokens);
1342 0 : return FALSE;
1343 : }
1344 :
1345 248 : if (nCount <= 3)
1346 : {
1347 246 : osFilename = papszTokens[1];
1348 : }
1349 : /* GPKG:C:\BLA.GPKG:foo */
1350 2 : else if (nCount == 4 && strlen(papszTokens[1]) == 1 &&
1351 2 : (papszTokens[2][0] == '/' || papszTokens[2][0] == '\\'))
1352 : {
1353 2 : osFilename = CPLString(papszTokens[1]) + ":" + papszTokens[2];
1354 : }
1355 : // GPKG:/vsicurl/http[s]://[user:passwd@]example.com[:8080]/foo.gpkg:bar
1356 0 : else if (/*nCount >= 4 && */
1357 0 : (EQUAL(papszTokens[1], "/vsicurl/http") ||
1358 0 : EQUAL(papszTokens[1], "/vsicurl/https")))
1359 : {
1360 0 : osFilename = CPLString(papszTokens[1]);
1361 0 : for (int i = 2; i < nCount - 1; i++)
1362 : {
1363 0 : osFilename += ':';
1364 0 : osFilename += papszTokens[i];
1365 : }
1366 : }
1367 248 : if (nCount >= 3)
1368 14 : osSubdatasetTableName = papszTokens[nCount - 1];
1369 :
1370 248 : CSLDestroy(papszTokens);
1371 248 : VSILFILE *fp = VSIFOpenL(osFilename, "rb");
1372 248 : if (fp != nullptr)
1373 : {
1374 248 : VSIFReadL(abyHeaderLetMeHerePlease, 1, 100, fp);
1375 248 : VSIFCloseL(fp);
1376 : }
1377 248 : pabyHeader = abyHeaderLetMeHerePlease;
1378 : }
1379 1069 : else if (poOpenInfo->pabyHeader &&
1380 1069 : STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
1381 : "SQLite format 3"))
1382 : {
1383 1062 : m_bCallUndeclareFileNotToOpen = true;
1384 1062 : GDALOpenInfoDeclareFileNotToOpen(osFilename, poOpenInfo->pabyHeader,
1385 : poOpenInfo->nHeaderBytes);
1386 : }
1387 :
1388 1317 : eAccess = poOpenInfo->eAccess;
1389 1317 : if (!m_osFilenameInZip.empty())
1390 : {
1391 2 : m_pszFilename = CPLStrdup(CPLSPrintf(
1392 : "/vsizip/{%s}/%s", osFilename.c_str(), m_osFilenameInZip.c_str()));
1393 : }
1394 : else
1395 : {
1396 1315 : m_pszFilename = CPLStrdup(osFilename);
1397 : }
1398 :
1399 1317 : if (poOpenInfo->papszOpenOptions)
1400 : {
1401 100 : CSLDestroy(papszOpenOptions);
1402 100 : papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
1403 : }
1404 :
1405 : #ifdef ENABLE_SQL_GPKG_FORMAT
1406 1317 : if (poOpenInfo->pabyHeader &&
1407 1069 : STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
1408 5 : "-- SQL GPKG") &&
1409 5 : poOpenInfo->fpL != nullptr)
1410 : {
1411 5 : if (sqlite3_open_v2(":memory:", &hDB, SQLITE_OPEN_READWRITE, nullptr) !=
1412 : SQLITE_OK)
1413 : {
1414 0 : return FALSE;
1415 : }
1416 :
1417 5 : InstallSQLFunctions();
1418 :
1419 : // Ingest the lines of the dump
1420 5 : VSIFSeekL(poOpenInfo->fpL, 0, SEEK_SET);
1421 : const char *pszLine;
1422 76 : while ((pszLine = CPLReadLineL(poOpenInfo->fpL)) != nullptr)
1423 : {
1424 71 : if (STARTS_WITH(pszLine, "--"))
1425 5 : continue;
1426 :
1427 66 : if (!SQLCheckLineIsSafe(pszLine))
1428 0 : return false;
1429 :
1430 66 : char *pszErrMsg = nullptr;
1431 66 : if (sqlite3_exec(hDB, pszLine, nullptr, nullptr, &pszErrMsg) !=
1432 : SQLITE_OK)
1433 : {
1434 0 : if (pszErrMsg)
1435 0 : CPLDebug("SQLITE", "Error %s", pszErrMsg);
1436 : }
1437 66 : sqlite3_free(pszErrMsg);
1438 5 : }
1439 : }
1440 :
1441 1312 : else if (pabyHeader != nullptr)
1442 : #endif
1443 : {
1444 1312 : if (poOpenInfo->fpL)
1445 : {
1446 : // See above comment about -wal locking for the importance of
1447 : // closing that file, prior to calling sqlite3_open()
1448 964 : VSIFCloseL(poOpenInfo->fpL);
1449 964 : poOpenInfo->fpL = nullptr;
1450 : }
1451 :
1452 : /* See if we can open the SQLite database */
1453 1312 : if (!OpenOrCreateDB(GetUpdate() ? SQLITE_OPEN_READWRITE
1454 : : SQLITE_OPEN_READONLY))
1455 2 : return FALSE;
1456 :
1457 1310 : memcpy(&m_nApplicationId, pabyHeader + knApplicationIdPos, 4);
1458 1310 : m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
1459 1310 : memcpy(&m_nUserVersion, pabyHeader + knUserVersionPos, 4);
1460 1310 : m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
1461 1310 : if (m_nApplicationId == GP10_APPLICATION_ID)
1462 : {
1463 7 : CPLDebug("GPKG", "GeoPackage v1.0");
1464 : }
1465 1303 : else if (m_nApplicationId == GP11_APPLICATION_ID)
1466 : {
1467 2 : CPLDebug("GPKG", "GeoPackage v1.1");
1468 : }
1469 1301 : else if (m_nApplicationId == GPKG_APPLICATION_ID &&
1470 1297 : m_nUserVersion >= GPKG_1_2_VERSION)
1471 : {
1472 1295 : CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
1473 1295 : (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
1474 : }
1475 : }
1476 :
1477 : /* Requirement 6: The SQLite PRAGMA integrity_check SQL command SHALL return
1478 : * “ok” */
1479 : /* http://opengis.github.io/geopackage/#_file_integrity */
1480 : /* Disable integrity check by default, since it is expensive on big files */
1481 1315 : if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_INTEGRITY_CHECK", "NO")) &&
1482 0 : OGRERR_NONE != PragmaCheck("integrity_check", "ok", 1))
1483 : {
1484 0 : CPLError(CE_Failure, CPLE_AppDefined,
1485 : "pragma integrity_check on '%s' failed", m_pszFilename);
1486 0 : return FALSE;
1487 : }
1488 :
1489 : /* Requirement 7: The SQLite PRAGMA foreign_key_check() SQL with no */
1490 : /* parameter value SHALL return an empty result set */
1491 : /* http://opengis.github.io/geopackage/#_file_integrity */
1492 : /* Disable the check by default, since it is to corrupt databases, and */
1493 : /* that causes issues to downstream software that can't open them. */
1494 1315 : if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_FOREIGN_KEY_CHECK", "NO")) &&
1495 0 : OGRERR_NONE != PragmaCheck("foreign_key_check", "", 0))
1496 : {
1497 0 : CPLError(CE_Failure, CPLE_AppDefined,
1498 : "pragma foreign_key_check on '%s' failed.", m_pszFilename);
1499 0 : return FALSE;
1500 : }
1501 :
1502 : /* Check for requirement metadata tables */
1503 : /* Requirement 10: gpkg_spatial_ref_sys must exist */
1504 : /* Requirement 13: gpkg_contents must exist */
1505 1315 : if (SQLGetInteger(hDB,
1506 : "SELECT COUNT(*) FROM sqlite_master WHERE "
1507 : "name IN ('gpkg_spatial_ref_sys', 'gpkg_contents') AND "
1508 : "type IN ('table', 'view')",
1509 1315 : nullptr) != 2)
1510 : {
1511 0 : CPLError(CE_Failure, CPLE_AppDefined,
1512 : "At least one of the required GeoPackage tables, "
1513 : "gpkg_spatial_ref_sys or gpkg_contents, is missing");
1514 0 : return FALSE;
1515 : }
1516 :
1517 1315 : DetectSpatialRefSysColumns();
1518 :
1519 : #ifdef ENABLE_GPKG_OGR_CONTENTS
1520 1315 : if (SQLGetInteger(hDB,
1521 : "SELECT 1 FROM sqlite_master WHERE "
1522 : "name = 'gpkg_ogr_contents' AND type = 'table'",
1523 1315 : nullptr) == 1)
1524 : {
1525 1307 : m_bHasGPKGOGRContents = true;
1526 : }
1527 : #endif
1528 :
1529 1315 : CheckUnknownExtensions();
1530 :
1531 1315 : int bRet = FALSE;
1532 1315 : bool bHasGPKGExtRelations = false;
1533 1315 : if (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR)
1534 : {
1535 1128 : m_bHasGPKGGeometryColumns =
1536 1128 : SQLGetInteger(hDB,
1537 : "SELECT 1 FROM sqlite_master WHERE "
1538 : "name = 'gpkg_geometry_columns' AND "
1539 : "type IN ('table', 'view')",
1540 1128 : nullptr) == 1;
1541 1128 : bHasGPKGExtRelations = HasGpkgextRelationsTable();
1542 : }
1543 1315 : if (m_bHasGPKGGeometryColumns)
1544 : {
1545 : /* Load layer definitions for all tables in gpkg_contents &
1546 : * gpkg_geometry_columns */
1547 : /* and non-spatial tables as well */
1548 : std::string osSQL =
1549 : "SELECT c.table_name, c.identifier, 1 as is_spatial, "
1550 : "g.column_name, g.geometry_type_name, g.z, g.m, c.min_x, c.min_y, "
1551 : "c.max_x, c.max_y, 1 AS is_in_gpkg_contents, "
1552 : "(SELECT type FROM sqlite_master WHERE lower(name) = "
1553 : "lower(c.table_name) AND type IN ('table', 'view')) AS object_type "
1554 : " FROM gpkg_geometry_columns g "
1555 : " JOIN gpkg_contents c ON (g.table_name = c.table_name)"
1556 : " WHERE "
1557 : " c.table_name <> 'ogr_empty_table' AND"
1558 : " c.data_type = 'features' "
1559 : // aspatial: Was the only method available in OGR 2.0 and 2.1
1560 : // attributes: GPKG 1.2 or later
1561 : "UNION ALL "
1562 : "SELECT table_name, identifier, 0 as is_spatial, NULL, NULL, 0, 0, "
1563 : "0 AS xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 1 AS "
1564 : "is_in_gpkg_contents, "
1565 : "(SELECT type FROM sqlite_master WHERE lower(name) = "
1566 : "lower(table_name) AND type IN ('table', 'view')) AS object_type "
1567 : " FROM gpkg_contents"
1568 1127 : " WHERE data_type IN ('aspatial', 'attributes') ";
1569 :
1570 2254 : const char *pszListAllTables = CSLFetchNameValueDef(
1571 1127 : poOpenInfo->papszOpenOptions, "LIST_ALL_TABLES", "AUTO");
1572 1127 : bool bHasASpatialOrAttributes = HasGDALAspatialExtension();
1573 1127 : if (!bHasASpatialOrAttributes)
1574 : {
1575 : auto oResultTable =
1576 : SQLQuery(hDB, "SELECT * FROM gpkg_contents WHERE "
1577 1126 : "data_type = 'attributes' LIMIT 1");
1578 1126 : bHasASpatialOrAttributes =
1579 1126 : (oResultTable && oResultTable->RowCount() == 1);
1580 : }
1581 1127 : if (bHasGPKGExtRelations)
1582 : {
1583 : osSQL += "UNION ALL "
1584 : "SELECT mapping_table_name, mapping_table_name, 0 as "
1585 : "is_spatial, NULL, NULL, 0, 0, 0 AS "
1586 : "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
1587 : "is_in_gpkg_contents, 'table' AS object_type "
1588 : "FROM gpkgext_relations WHERE "
1589 : "lower(mapping_table_name) NOT IN (SELECT "
1590 : "lower(table_name) FROM gpkg_contents) AND "
1591 : "EXISTS (SELECT 1 FROM sqlite_master WHERE "
1592 : "type IN ('table', 'view') AND "
1593 18 : "lower(name) = lower(mapping_table_name))";
1594 : }
1595 1127 : if (EQUAL(pszListAllTables, "YES") ||
1596 1126 : (!bHasASpatialOrAttributes && EQUAL(pszListAllTables, "AUTO")))
1597 : {
1598 : // vgpkg_ is Spatialite virtual table
1599 : osSQL +=
1600 : "UNION ALL "
1601 : "SELECT name, name, 0 as is_spatial, NULL, NULL, 0, 0, 0 AS "
1602 : "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
1603 : "is_in_gpkg_contents, type AS object_type "
1604 : "FROM sqlite_master WHERE type IN ('table', 'view') "
1605 : "AND name NOT LIKE 'gpkg_%' "
1606 : "AND name NOT LIKE 'vgpkg_%' "
1607 : "AND name NOT LIKE 'rtree_%' AND name NOT LIKE 'sqlite_%' "
1608 : // Avoid reading those views from simple_sewer_features.gpkg
1609 : "AND name NOT IN ('st_spatial_ref_sys', 'spatial_ref_sys', "
1610 : "'st_geometry_columns', 'geometry_columns') "
1611 : "AND lower(name) NOT IN (SELECT lower(table_name) FROM "
1612 1057 : "gpkg_contents)";
1613 1057 : if (bHasGPKGExtRelations)
1614 : {
1615 : osSQL += " AND lower(name) NOT IN (SELECT "
1616 : "lower(mapping_table_name) FROM "
1617 13 : "gpkgext_relations)";
1618 : }
1619 : }
1620 1127 : const int nTableLimit = GetOGRTableLimit();
1621 1127 : if (nTableLimit > 0)
1622 : {
1623 1127 : osSQL += " LIMIT ";
1624 1127 : osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1625 : }
1626 :
1627 1127 : auto oResult = SQLQuery(hDB, osSQL.c_str());
1628 1127 : if (!oResult)
1629 : {
1630 0 : return FALSE;
1631 : }
1632 :
1633 1127 : if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
1634 : {
1635 1 : CPLError(CE_Warning, CPLE_AppDefined,
1636 : "File has more than %d vector tables. "
1637 : "Limiting to first %d (can be overridden with "
1638 : "OGR_TABLE_LIMIT config option)",
1639 : nTableLimit, nTableLimit);
1640 1 : oResult->LimitRowCount(nTableLimit);
1641 : }
1642 :
1643 1127 : if (oResult->RowCount() > 0)
1644 : {
1645 1010 : bRet = TRUE;
1646 :
1647 1010 : m_apoLayers.reserve(oResult->RowCount());
1648 :
1649 2020 : std::map<std::string, int> oMapTableRefCount;
1650 4219 : for (int i = 0; i < oResult->RowCount(); i++)
1651 : {
1652 3209 : const char *pszTableName = oResult->GetValue(0, i);
1653 3209 : if (pszTableName == nullptr)
1654 0 : continue;
1655 3209 : if (++oMapTableRefCount[pszTableName] == 2)
1656 : {
1657 : // This should normally not happen if all constraints are
1658 : // properly set
1659 2 : CPLError(CE_Warning, CPLE_AppDefined,
1660 : "Table %s appearing several times in "
1661 : "gpkg_contents and/or gpkg_geometry_columns",
1662 : pszTableName);
1663 : }
1664 : }
1665 :
1666 2020 : std::set<std::string> oExistingLayers;
1667 4219 : for (int i = 0; i < oResult->RowCount(); i++)
1668 : {
1669 3209 : const char *pszTableName = oResult->GetValue(0, i);
1670 3209 : if (pszTableName == nullptr)
1671 2 : continue;
1672 : const bool bTableHasSeveralGeomColumns =
1673 3209 : oMapTableRefCount[pszTableName] > 1;
1674 3209 : bool bIsSpatial = CPL_TO_BOOL(oResult->GetValueAsInteger(2, i));
1675 3209 : const char *pszGeomColName = oResult->GetValue(3, i);
1676 3209 : const char *pszGeomType = oResult->GetValue(4, i);
1677 3209 : const char *pszZ = oResult->GetValue(5, i);
1678 3209 : const char *pszM = oResult->GetValue(6, i);
1679 : bool bIsInGpkgContents =
1680 3209 : CPL_TO_BOOL(oResult->GetValueAsInteger(11, i));
1681 3209 : if (!bIsInGpkgContents)
1682 44 : m_bNonSpatialTablesNonRegisteredInGpkgContentsFound = true;
1683 3209 : const char *pszObjectType = oResult->GetValue(12, i);
1684 3209 : if (pszObjectType == nullptr ||
1685 3208 : !(EQUAL(pszObjectType, "table") ||
1686 21 : EQUAL(pszObjectType, "view")))
1687 : {
1688 1 : CPLError(CE_Warning, CPLE_AppDefined,
1689 : "Table/view %s is referenced in gpkg_contents, "
1690 : "but does not exist",
1691 : pszTableName);
1692 1 : continue;
1693 : }
1694 : // Non-standard and undocumented behavior:
1695 : // if the same table appears to have several geometry columns,
1696 : // handle it for now as multiple layers named
1697 : // "table_name (geom_col_name)"
1698 : // The way we handle that might change in the future (e.g
1699 : // could be a single layer with multiple geometry columns)
1700 : std::string osLayerNameWithGeomColName =
1701 6478 : pszGeomColName ? std::string(pszTableName) + " (" +
1702 : pszGeomColName + ')'
1703 6416 : : std::string(pszTableName);
1704 3208 : if (cpl::contains(oExistingLayers, osLayerNameWithGeomColName))
1705 1 : continue;
1706 3207 : oExistingLayers.insert(osLayerNameWithGeomColName);
1707 : const std::string osLayerName =
1708 : bTableHasSeveralGeomColumns
1709 3 : ? std::move(osLayerNameWithGeomColName)
1710 6417 : : std::string(pszTableName);
1711 : auto poLayer = std::make_unique<OGRGeoPackageTableLayer>(
1712 6414 : this, osLayerName.c_str());
1713 3207 : bool bHasZ = pszZ && atoi(pszZ) > 0;
1714 3207 : bool bHasM = pszM && atoi(pszM) > 0;
1715 3207 : if (pszGeomType && EQUAL(pszGeomType, "GEOMETRY"))
1716 : {
1717 644 : if (pszZ && atoi(pszZ) == 2)
1718 14 : bHasZ = false;
1719 644 : if (pszM && atoi(pszM) == 2)
1720 6 : bHasM = false;
1721 : }
1722 3207 : poLayer->SetOpeningParameters(
1723 : pszTableName, pszObjectType, bIsInGpkgContents, bIsSpatial,
1724 : pszGeomColName, pszGeomType, bHasZ, bHasM);
1725 3207 : m_apoLayers.push_back(std::move(poLayer));
1726 : }
1727 : }
1728 : }
1729 :
1730 1315 : bool bHasTileMatrixSet = false;
1731 1315 : if (poOpenInfo->nOpenFlags & GDAL_OF_RASTER)
1732 : {
1733 584 : bHasTileMatrixSet = SQLGetInteger(hDB,
1734 : "SELECT 1 FROM sqlite_master WHERE "
1735 : "name = 'gpkg_tile_matrix_set' AND "
1736 : "type IN ('table', 'view')",
1737 : nullptr) == 1;
1738 : }
1739 1315 : if (bHasTileMatrixSet)
1740 : {
1741 : std::string osSQL =
1742 : "SELECT c.table_name, c.identifier, c.description, c.srs_id, "
1743 : "c.min_x, c.min_y, c.max_x, c.max_y, "
1744 : "tms.min_x, tms.min_y, tms.max_x, tms.max_y, c.data_type "
1745 : "FROM gpkg_contents c JOIN gpkg_tile_matrix_set tms ON "
1746 : "c.table_name = tms.table_name WHERE "
1747 582 : "data_type IN ('tiles', '2d-gridded-coverage')";
1748 582 : if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE"))
1749 : osSubdatasetTableName =
1750 2 : CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE");
1751 582 : if (!osSubdatasetTableName.empty())
1752 : {
1753 16 : char *pszTmp = sqlite3_mprintf(" AND c.table_name='%q'",
1754 : osSubdatasetTableName.c_str());
1755 16 : osSQL += pszTmp;
1756 16 : sqlite3_free(pszTmp);
1757 16 : SetPhysicalFilename(osFilename.c_str());
1758 : }
1759 582 : const int nTableLimit = GetOGRTableLimit();
1760 582 : if (nTableLimit > 0)
1761 : {
1762 582 : osSQL += " LIMIT ";
1763 582 : osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1764 : }
1765 :
1766 582 : auto oResult = SQLQuery(hDB, osSQL.c_str());
1767 582 : if (!oResult)
1768 : {
1769 0 : return FALSE;
1770 : }
1771 :
1772 582 : if (oResult->RowCount() == 0 && !osSubdatasetTableName.empty())
1773 : {
1774 1 : CPLError(CE_Failure, CPLE_AppDefined,
1775 : "Cannot find table '%s' in GeoPackage dataset",
1776 : osSubdatasetTableName.c_str());
1777 : }
1778 581 : else if (oResult->RowCount() == 1)
1779 : {
1780 279 : const char *pszTableName = oResult->GetValue(0, 0);
1781 279 : const char *pszIdentifier = oResult->GetValue(1, 0);
1782 279 : const char *pszDescription = oResult->GetValue(2, 0);
1783 279 : const char *pszSRSId = oResult->GetValue(3, 0);
1784 279 : const char *pszMinX = oResult->GetValue(4, 0);
1785 279 : const char *pszMinY = oResult->GetValue(5, 0);
1786 279 : const char *pszMaxX = oResult->GetValue(6, 0);
1787 279 : const char *pszMaxY = oResult->GetValue(7, 0);
1788 279 : const char *pszTMSMinX = oResult->GetValue(8, 0);
1789 279 : const char *pszTMSMinY = oResult->GetValue(9, 0);
1790 279 : const char *pszTMSMaxX = oResult->GetValue(10, 0);
1791 279 : const char *pszTMSMaxY = oResult->GetValue(11, 0);
1792 279 : const char *pszDataType = oResult->GetValue(12, 0);
1793 279 : if (pszTableName && pszTMSMinX && pszTMSMinY && pszTMSMaxX &&
1794 : pszTMSMaxY)
1795 : {
1796 558 : bRet = OpenRaster(
1797 : pszTableName, pszIdentifier, pszDescription,
1798 279 : pszSRSId ? atoi(pszSRSId) : 0, CPLAtof(pszTMSMinX),
1799 : CPLAtof(pszTMSMinY), CPLAtof(pszTMSMaxX),
1800 : CPLAtof(pszTMSMaxY), pszMinX, pszMinY, pszMaxX, pszMaxY,
1801 279 : EQUAL(pszDataType, "tiles"), poOpenInfo->papszOpenOptions);
1802 : }
1803 : }
1804 302 : else if (oResult->RowCount() >= 1)
1805 : {
1806 5 : bRet = TRUE;
1807 :
1808 5 : if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
1809 : {
1810 1 : CPLError(CE_Warning, CPLE_AppDefined,
1811 : "File has more than %d raster tables. "
1812 : "Limiting to first %d (can be overridden with "
1813 : "OGR_TABLE_LIMIT config option)",
1814 : nTableLimit, nTableLimit);
1815 1 : oResult->LimitRowCount(nTableLimit);
1816 : }
1817 :
1818 5 : int nSDSCount = 0;
1819 2013 : for (int i = 0; i < oResult->RowCount(); i++)
1820 : {
1821 2008 : const char *pszTableName = oResult->GetValue(0, i);
1822 2008 : const char *pszIdentifier = oResult->GetValue(1, i);
1823 2008 : if (pszTableName == nullptr)
1824 0 : continue;
1825 : m_aosSubDatasets.AddNameValue(
1826 : CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
1827 2008 : CPLSPrintf("GPKG:%s:%s", m_pszFilename, pszTableName));
1828 : m_aosSubDatasets.AddNameValue(
1829 : CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
1830 : pszIdentifier
1831 2008 : ? CPLSPrintf("%s - %s", pszTableName, pszIdentifier)
1832 4016 : : pszTableName);
1833 2008 : nSDSCount++;
1834 : }
1835 : }
1836 : }
1837 :
1838 1315 : if (!bRet && (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR))
1839 : {
1840 33 : if ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE))
1841 : {
1842 22 : bRet = TRUE;
1843 : }
1844 : else
1845 : {
1846 11 : CPLDebug("GPKG",
1847 : "This GeoPackage has no vector content and is opened "
1848 : "in read-only mode. If you open it in update mode, "
1849 : "opening will be successful.");
1850 : }
1851 : }
1852 :
1853 1315 : if (eAccess == GA_Update)
1854 : {
1855 259 : FixupWrongRTreeTrigger();
1856 259 : FixupWrongMedataReferenceColumnNameUpdate();
1857 : }
1858 :
1859 1315 : SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
1860 :
1861 1315 : return bRet;
1862 : }
1863 :
1864 : /************************************************************************/
1865 : /* DetectSpatialRefSysColumns() */
1866 : /************************************************************************/
1867 :
1868 1325 : void GDALGeoPackageDataset::DetectSpatialRefSysColumns()
1869 : {
1870 : // Detect definition_12_063 column
1871 : {
1872 1325 : sqlite3_stmt *hSQLStmt = nullptr;
1873 1325 : int rc = sqlite3_prepare_v2(
1874 : hDB, "SELECT definition_12_063 FROM gpkg_spatial_ref_sys ", -1,
1875 : &hSQLStmt, nullptr);
1876 1325 : if (rc == SQLITE_OK)
1877 : {
1878 85 : m_bHasDefinition12_063 = true;
1879 85 : sqlite3_finalize(hSQLStmt);
1880 : }
1881 : }
1882 :
1883 : // Detect epoch column
1884 1325 : if (m_bHasDefinition12_063)
1885 : {
1886 85 : sqlite3_stmt *hSQLStmt = nullptr;
1887 : int rc =
1888 85 : sqlite3_prepare_v2(hDB, "SELECT epoch FROM gpkg_spatial_ref_sys ",
1889 : -1, &hSQLStmt, nullptr);
1890 85 : if (rc == SQLITE_OK)
1891 : {
1892 76 : m_bHasEpochColumn = true;
1893 76 : sqlite3_finalize(hSQLStmt);
1894 : }
1895 : }
1896 1325 : }
1897 :
1898 : /************************************************************************/
1899 : /* FixupWrongRTreeTrigger() */
1900 : /************************************************************************/
1901 :
1902 259 : void GDALGeoPackageDataset::FixupWrongRTreeTrigger()
1903 : {
1904 : auto oResult = SQLQuery(
1905 : hDB,
1906 : "SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND "
1907 259 : "NAME LIKE 'rtree_%_update3' AND sql LIKE '% AFTER UPDATE OF % ON %'");
1908 259 : if (oResult == nullptr)
1909 0 : return;
1910 259 : if (oResult->RowCount() > 0)
1911 : {
1912 1 : CPLDebug("GPKG", "Fixing incorrect trigger(s) related to RTree");
1913 : }
1914 261 : for (int i = 0; i < oResult->RowCount(); i++)
1915 : {
1916 2 : const char *pszName = oResult->GetValue(0, i);
1917 2 : const char *pszSQL = oResult->GetValue(1, i);
1918 2 : const char *pszPtr1 = strstr(pszSQL, " AFTER UPDATE OF ");
1919 2 : if (pszPtr1)
1920 : {
1921 2 : const char *pszPtr = pszPtr1 + strlen(" AFTER UPDATE OF ");
1922 : // Skipping over geometry column name
1923 4 : while (*pszPtr == ' ')
1924 2 : pszPtr++;
1925 2 : if (pszPtr[0] == '"' || pszPtr[0] == '\'')
1926 : {
1927 1 : char chStringDelim = pszPtr[0];
1928 1 : pszPtr++;
1929 9 : while (*pszPtr != '\0' && *pszPtr != chStringDelim)
1930 : {
1931 8 : if (*pszPtr == '\\' && pszPtr[1] == chStringDelim)
1932 0 : pszPtr += 2;
1933 : else
1934 8 : pszPtr += 1;
1935 : }
1936 1 : if (*pszPtr == chStringDelim)
1937 1 : pszPtr++;
1938 : }
1939 : else
1940 : {
1941 1 : pszPtr++;
1942 8 : while (*pszPtr != ' ')
1943 7 : pszPtr++;
1944 : }
1945 2 : if (*pszPtr == ' ')
1946 : {
1947 2 : SQLCommand(hDB,
1948 4 : ("DROP TRIGGER \"" + SQLEscapeName(pszName) + "\"")
1949 : .c_str());
1950 4 : CPLString newSQL;
1951 2 : newSQL.assign(pszSQL, pszPtr1 - pszSQL);
1952 2 : newSQL += " AFTER UPDATE";
1953 2 : newSQL += pszPtr;
1954 2 : SQLCommand(hDB, newSQL);
1955 : }
1956 : }
1957 : }
1958 : }
1959 :
1960 : /************************************************************************/
1961 : /* FixupWrongMedataReferenceColumnNameUpdate() */
1962 : /************************************************************************/
1963 :
1964 259 : void GDALGeoPackageDataset::FixupWrongMedataReferenceColumnNameUpdate()
1965 : {
1966 : // Fix wrong trigger that was generated by GDAL < 2.4.0
1967 : // See https://github.com/qgis/QGIS/issues/42768
1968 : auto oResult = SQLQuery(
1969 : hDB, "SELECT sql FROM sqlite_master WHERE type = 'trigger' AND "
1970 : "NAME ='gpkg_metadata_reference_column_name_update' AND "
1971 259 : "sql LIKE '%column_nameIS%'");
1972 259 : if (oResult == nullptr)
1973 0 : return;
1974 259 : if (oResult->RowCount() == 1)
1975 : {
1976 1 : CPLDebug("GPKG", "Fixing incorrect trigger "
1977 : "gpkg_metadata_reference_column_name_update");
1978 1 : const char *pszSQL = oResult->GetValue(0, 0);
1979 : std::string osNewSQL(
1980 3 : CPLString(pszSQL).replaceAll("column_nameIS", "column_name IS"));
1981 :
1982 1 : SQLCommand(hDB,
1983 : "DROP TRIGGER gpkg_metadata_reference_column_name_update");
1984 1 : SQLCommand(hDB, osNewSQL.c_str());
1985 : }
1986 : }
1987 :
1988 : /************************************************************************/
1989 : /* ClearCachedRelationships() */
1990 : /************************************************************************/
1991 :
1992 36 : void GDALGeoPackageDataset::ClearCachedRelationships()
1993 : {
1994 36 : m_bHasPopulatedRelationships = false;
1995 36 : m_osMapRelationships.clear();
1996 36 : }
1997 :
1998 : /************************************************************************/
1999 : /* LoadRelationships() */
2000 : /************************************************************************/
2001 :
2002 85 : void GDALGeoPackageDataset::LoadRelationships() const
2003 : {
2004 85 : m_osMapRelationships.clear();
2005 :
2006 85 : std::vector<std::string> oExcludedTables;
2007 85 : if (HasGpkgextRelationsTable())
2008 : {
2009 37 : LoadRelationshipsUsingRelatedTablesExtension();
2010 :
2011 89 : for (const auto &oRelationship : m_osMapRelationships)
2012 : {
2013 : oExcludedTables.emplace_back(
2014 52 : oRelationship.second->GetMappingTableName());
2015 : }
2016 : }
2017 :
2018 : // Also load relationships defined using foreign keys (i.e. one-to-many
2019 : // relationships). Here we must exclude any relationships defined from the
2020 : // related tables extension, we don't want them included twice.
2021 85 : LoadRelationshipsFromForeignKeys(oExcludedTables);
2022 85 : m_bHasPopulatedRelationships = true;
2023 85 : }
2024 :
2025 : /************************************************************************/
2026 : /* LoadRelationshipsUsingRelatedTablesExtension() */
2027 : /************************************************************************/
2028 :
2029 37 : void GDALGeoPackageDataset::LoadRelationshipsUsingRelatedTablesExtension() const
2030 : {
2031 37 : m_osMapRelationships.clear();
2032 :
2033 : auto oResultTable = SQLQuery(
2034 37 : hDB, "SELECT base_table_name, base_primary_column, "
2035 : "related_table_name, related_primary_column, relation_name, "
2036 74 : "mapping_table_name FROM gpkgext_relations");
2037 37 : if (oResultTable && oResultTable->RowCount() > 0)
2038 : {
2039 86 : for (int i = 0; i < oResultTable->RowCount(); i++)
2040 : {
2041 53 : const char *pszBaseTableName = oResultTable->GetValue(0, i);
2042 53 : if (!pszBaseTableName)
2043 : {
2044 0 : CPLError(CE_Warning, CPLE_AppDefined,
2045 : "Could not retrieve base_table_name from "
2046 : "gpkgext_relations");
2047 1 : continue;
2048 : }
2049 53 : const char *pszBasePrimaryColumn = oResultTable->GetValue(1, i);
2050 53 : if (!pszBasePrimaryColumn)
2051 : {
2052 0 : CPLError(CE_Warning, CPLE_AppDefined,
2053 : "Could not retrieve base_primary_column from "
2054 : "gpkgext_relations");
2055 0 : continue;
2056 : }
2057 53 : const char *pszRelatedTableName = oResultTable->GetValue(2, i);
2058 53 : if (!pszRelatedTableName)
2059 : {
2060 0 : CPLError(CE_Warning, CPLE_AppDefined,
2061 : "Could not retrieve related_table_name from "
2062 : "gpkgext_relations");
2063 0 : continue;
2064 : }
2065 53 : const char *pszRelatedPrimaryColumn = oResultTable->GetValue(3, i);
2066 53 : if (!pszRelatedPrimaryColumn)
2067 : {
2068 0 : CPLError(CE_Warning, CPLE_AppDefined,
2069 : "Could not retrieve related_primary_column from "
2070 : "gpkgext_relations");
2071 0 : continue;
2072 : }
2073 53 : const char *pszRelationName = oResultTable->GetValue(4, i);
2074 53 : if (!pszRelationName)
2075 : {
2076 0 : CPLError(
2077 : CE_Warning, CPLE_AppDefined,
2078 : "Could not retrieve relation_name from gpkgext_relations");
2079 0 : continue;
2080 : }
2081 53 : const char *pszMappingTableName = oResultTable->GetValue(5, i);
2082 53 : if (!pszMappingTableName)
2083 : {
2084 0 : CPLError(CE_Warning, CPLE_AppDefined,
2085 : "Could not retrieve mapping_table_name from "
2086 : "gpkgext_relations");
2087 0 : continue;
2088 : }
2089 :
2090 : // confirm that mapping table exists
2091 : char *pszSQL =
2092 53 : sqlite3_mprintf("SELECT 1 FROM sqlite_master WHERE "
2093 : "name='%q' AND type IN ('table', 'view')",
2094 : pszMappingTableName);
2095 53 : const int nMappingTableCount = SQLGetInteger(hDB, pszSQL, nullptr);
2096 53 : sqlite3_free(pszSQL);
2097 :
2098 55 : if (nMappingTableCount < 1 &&
2099 2 : !const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
2100 2 : pszMappingTableName))
2101 : {
2102 1 : CPLError(CE_Warning, CPLE_AppDefined,
2103 : "Relationship mapping table %s does not exist",
2104 : pszMappingTableName);
2105 1 : continue;
2106 : }
2107 :
2108 : const std::string osRelationName = GenerateNameForRelationship(
2109 104 : pszBaseTableName, pszRelatedTableName, pszRelationName);
2110 :
2111 104 : std::string osType{};
2112 : // defined requirement classes -- for these types the relation name
2113 : // will be specific string value from the related tables extension.
2114 : // In this case we need to construct a unique relationship name
2115 : // based on the related tables
2116 52 : if (EQUAL(pszRelationName, "media") ||
2117 40 : EQUAL(pszRelationName, "simple_attributes") ||
2118 40 : EQUAL(pszRelationName, "features") ||
2119 18 : EQUAL(pszRelationName, "attributes") ||
2120 2 : EQUAL(pszRelationName, "tiles"))
2121 : {
2122 50 : osType = pszRelationName;
2123 : }
2124 : else
2125 : {
2126 : // user defined types default to features
2127 2 : osType = "features";
2128 : }
2129 :
2130 : auto poRelationship = std::make_unique<GDALRelationship>(
2131 : osRelationName, pszBaseTableName, pszRelatedTableName,
2132 104 : GRC_MANY_TO_MANY);
2133 :
2134 104 : poRelationship->SetLeftTableFields({pszBasePrimaryColumn});
2135 104 : poRelationship->SetRightTableFields({pszRelatedPrimaryColumn});
2136 104 : poRelationship->SetLeftMappingTableFields({"base_id"});
2137 104 : poRelationship->SetRightMappingTableFields({"related_id"});
2138 52 : poRelationship->SetMappingTableName(pszMappingTableName);
2139 52 : poRelationship->SetRelatedTableType(osType);
2140 :
2141 52 : m_osMapRelationships[osRelationName] = std::move(poRelationship);
2142 : }
2143 : }
2144 37 : }
2145 :
2146 : /************************************************************************/
2147 : /* GenerateNameForRelationship() */
2148 : /************************************************************************/
2149 :
2150 76 : std::string GDALGeoPackageDataset::GenerateNameForRelationship(
2151 : const char *pszBaseTableName, const char *pszRelatedTableName,
2152 : const char *pszType)
2153 : {
2154 : // defined requirement classes -- for these types the relation name will be
2155 : // specific string value from the related tables extension. In this case we
2156 : // need to construct a unique relationship name based on the related tables
2157 76 : if (EQUAL(pszType, "media") || EQUAL(pszType, "simple_attributes") ||
2158 53 : EQUAL(pszType, "features") || EQUAL(pszType, "attributes") ||
2159 8 : EQUAL(pszType, "tiles"))
2160 : {
2161 136 : std::ostringstream stream;
2162 : stream << pszBaseTableName << '_' << pszRelatedTableName << '_'
2163 68 : << pszType;
2164 68 : return stream.str();
2165 : }
2166 : else
2167 : {
2168 : // user defined types default to features
2169 8 : return pszType;
2170 : }
2171 : }
2172 :
2173 : /************************************************************************/
2174 : /* ValidateRelationship() */
2175 : /************************************************************************/
2176 :
2177 28 : bool GDALGeoPackageDataset::ValidateRelationship(
2178 : const GDALRelationship *poRelationship, std::string &failureReason)
2179 : {
2180 :
2181 28 : if (poRelationship->GetCardinality() !=
2182 : GDALRelationshipCardinality::GRC_MANY_TO_MANY)
2183 : {
2184 3 : failureReason = "Only many to many relationships are supported";
2185 3 : return false;
2186 : }
2187 :
2188 50 : std::string osRelatedTableType = poRelationship->GetRelatedTableType();
2189 65 : if (!osRelatedTableType.empty() && osRelatedTableType != "features" &&
2190 30 : osRelatedTableType != "media" &&
2191 20 : osRelatedTableType != "simple_attributes" &&
2192 55 : osRelatedTableType != "attributes" && osRelatedTableType != "tiles")
2193 : {
2194 : failureReason =
2195 4 : ("Related table type " + osRelatedTableType +
2196 : " is not a valid value for the GeoPackage specification. "
2197 : "Valid values are: features, media, simple_attributes, "
2198 : "attributes, tiles.")
2199 2 : .c_str();
2200 2 : return false;
2201 : }
2202 :
2203 23 : const std::string &osLeftTableName = poRelationship->GetLeftTableName();
2204 23 : OGRGeoPackageLayer *poLeftTable = cpl::down_cast<OGRGeoPackageLayer *>(
2205 23 : GetLayerByName(osLeftTableName.c_str()));
2206 23 : if (!poLeftTable)
2207 : {
2208 4 : failureReason = ("Left table " + osLeftTableName +
2209 : " is not an existing layer in the dataset")
2210 2 : .c_str();
2211 2 : return false;
2212 : }
2213 21 : const std::string &osRightTableName = poRelationship->GetRightTableName();
2214 21 : OGRGeoPackageLayer *poRightTable = cpl::down_cast<OGRGeoPackageLayer *>(
2215 21 : GetLayerByName(osRightTableName.c_str()));
2216 21 : if (!poRightTable)
2217 : {
2218 4 : failureReason = ("Right table " + osRightTableName +
2219 : " is not an existing layer in the dataset")
2220 2 : .c_str();
2221 2 : return false;
2222 : }
2223 :
2224 19 : const auto &aosLeftTableFields = poRelationship->GetLeftTableFields();
2225 19 : if (aosLeftTableFields.empty())
2226 : {
2227 1 : failureReason = "No left table fields were specified";
2228 1 : return false;
2229 : }
2230 18 : else if (aosLeftTableFields.size() > 1)
2231 : {
2232 : failureReason = "Only a single left table field is permitted for the "
2233 1 : "GeoPackage specification";
2234 1 : return false;
2235 : }
2236 : else
2237 : {
2238 : // validate left field exists
2239 34 : if (poLeftTable->GetLayerDefn()->GetFieldIndex(
2240 37 : aosLeftTableFields[0].c_str()) < 0 &&
2241 3 : !EQUAL(poLeftTable->GetFIDColumn(), aosLeftTableFields[0].c_str()))
2242 : {
2243 2 : failureReason = ("Left table field " + aosLeftTableFields[0] +
2244 2 : " does not exist in " + osLeftTableName)
2245 1 : .c_str();
2246 1 : return false;
2247 : }
2248 : }
2249 :
2250 16 : const auto &aosRightTableFields = poRelationship->GetRightTableFields();
2251 16 : if (aosRightTableFields.empty())
2252 : {
2253 1 : failureReason = "No right table fields were specified";
2254 1 : return false;
2255 : }
2256 15 : else if (aosRightTableFields.size() > 1)
2257 : {
2258 : failureReason = "Only a single right table field is permitted for the "
2259 1 : "GeoPackage specification";
2260 1 : return false;
2261 : }
2262 : else
2263 : {
2264 : // validate right field exists
2265 28 : if (poRightTable->GetLayerDefn()->GetFieldIndex(
2266 32 : aosRightTableFields[0].c_str()) < 0 &&
2267 4 : !EQUAL(poRightTable->GetFIDColumn(),
2268 : aosRightTableFields[0].c_str()))
2269 : {
2270 4 : failureReason = ("Right table field " + aosRightTableFields[0] +
2271 4 : " does not exist in " + osRightTableName)
2272 2 : .c_str();
2273 2 : return false;
2274 : }
2275 : }
2276 :
2277 12 : return true;
2278 : }
2279 :
2280 : /************************************************************************/
2281 : /* InitRaster() */
2282 : /************************************************************************/
2283 :
2284 363 : bool GDALGeoPackageDataset::InitRaster(
2285 : GDALGeoPackageDataset *poParentDS, const char *pszTableName, double dfMinX,
2286 : double dfMinY, double dfMaxX, double dfMaxY, const char *pszContentsMinX,
2287 : const char *pszContentsMinY, const char *pszContentsMaxX,
2288 : const char *pszContentsMaxY, CSLConstList papszOpenOptionsIn,
2289 : const SQLResult &oResult, int nIdxInResult)
2290 : {
2291 363 : m_osRasterTable = pszTableName;
2292 363 : m_dfTMSMinX = dfMinX;
2293 363 : m_dfTMSMaxY = dfMaxY;
2294 :
2295 : // Despite prior checking, the type might be Binary and
2296 : // SQLResultGetValue() not working properly on it
2297 363 : int nZoomLevel = atoi(oResult.GetValue(0, nIdxInResult));
2298 363 : if (nZoomLevel < 0 || nZoomLevel > 65536)
2299 : {
2300 0 : return false;
2301 : }
2302 363 : double dfPixelXSize = CPLAtof(oResult.GetValue(1, nIdxInResult));
2303 363 : double dfPixelYSize = CPLAtof(oResult.GetValue(2, nIdxInResult));
2304 363 : if (dfPixelXSize <= 0 || dfPixelYSize <= 0)
2305 : {
2306 0 : return false;
2307 : }
2308 363 : int nTileWidth = atoi(oResult.GetValue(3, nIdxInResult));
2309 363 : int nTileHeight = atoi(oResult.GetValue(4, nIdxInResult));
2310 363 : if (nTileWidth <= 0 || nTileWidth > 65536 || nTileHeight <= 0 ||
2311 : nTileHeight > 65536)
2312 : {
2313 0 : return false;
2314 : }
2315 : int nTileMatrixWidth = static_cast<int>(
2316 726 : std::min(static_cast<GIntBig>(INT_MAX),
2317 363 : CPLAtoGIntBig(oResult.GetValue(5, nIdxInResult))));
2318 : int nTileMatrixHeight = static_cast<int>(
2319 726 : std::min(static_cast<GIntBig>(INT_MAX),
2320 363 : CPLAtoGIntBig(oResult.GetValue(6, nIdxInResult))));
2321 363 : if (nTileMatrixWidth <= 0 || nTileMatrixHeight <= 0)
2322 : {
2323 0 : return false;
2324 : }
2325 :
2326 : /* Use content bounds in priority over tile_matrix_set bounds */
2327 363 : double dfGDALMinX = dfMinX;
2328 363 : double dfGDALMinY = dfMinY;
2329 363 : double dfGDALMaxX = dfMaxX;
2330 363 : double dfGDALMaxY = dfMaxY;
2331 : pszContentsMinX =
2332 363 : CSLFetchNameValueDef(papszOpenOptionsIn, "MINX", pszContentsMinX);
2333 : pszContentsMinY =
2334 363 : CSLFetchNameValueDef(papszOpenOptionsIn, "MINY", pszContentsMinY);
2335 : pszContentsMaxX =
2336 363 : CSLFetchNameValueDef(papszOpenOptionsIn, "MAXX", pszContentsMaxX);
2337 : pszContentsMaxY =
2338 363 : CSLFetchNameValueDef(papszOpenOptionsIn, "MAXY", pszContentsMaxY);
2339 363 : if (pszContentsMinX != nullptr && pszContentsMinY != nullptr &&
2340 363 : pszContentsMaxX != nullptr && pszContentsMaxY != nullptr)
2341 : {
2342 725 : if (CPLAtof(pszContentsMinX) < CPLAtof(pszContentsMaxX) &&
2343 362 : CPLAtof(pszContentsMinY) < CPLAtof(pszContentsMaxY))
2344 : {
2345 362 : dfGDALMinX = CPLAtof(pszContentsMinX);
2346 362 : dfGDALMinY = CPLAtof(pszContentsMinY);
2347 362 : dfGDALMaxX = CPLAtof(pszContentsMaxX);
2348 362 : dfGDALMaxY = CPLAtof(pszContentsMaxY);
2349 : }
2350 : else
2351 : {
2352 1 : CPLError(CE_Warning, CPLE_AppDefined,
2353 : "Illegal min_x/min_y/max_x/max_y values for %s in open "
2354 : "options and/or gpkg_contents. Using bounds of "
2355 : "gpkg_tile_matrix_set instead",
2356 : pszTableName);
2357 : }
2358 : }
2359 363 : if (dfGDALMinX >= dfGDALMaxX || dfGDALMinY >= dfGDALMaxY)
2360 : {
2361 0 : CPLError(CE_Failure, CPLE_AppDefined,
2362 : "Illegal min_x/min_y/max_x/max_y values for %s", pszTableName);
2363 0 : return false;
2364 : }
2365 :
2366 363 : int nBandCount = 0;
2367 : const char *pszBAND_COUNT =
2368 363 : CSLFetchNameValue(papszOpenOptionsIn, "BAND_COUNT");
2369 363 : if (poParentDS)
2370 : {
2371 86 : nBandCount = poParentDS->GetRasterCount();
2372 : }
2373 277 : else if (m_eDT != GDT_UInt8)
2374 : {
2375 65 : if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO") &&
2376 0 : !EQUAL(pszBAND_COUNT, "1"))
2377 : {
2378 0 : CPLError(CE_Warning, CPLE_AppDefined,
2379 : "BAND_COUNT ignored for non-Byte data");
2380 : }
2381 65 : nBandCount = 1;
2382 : }
2383 : else
2384 : {
2385 212 : if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO"))
2386 : {
2387 69 : nBandCount = atoi(pszBAND_COUNT);
2388 69 : if (nBandCount == 1)
2389 5 : GetMetadata("IMAGE_STRUCTURE");
2390 : }
2391 : else
2392 : {
2393 143 : GetMetadata("IMAGE_STRUCTURE");
2394 143 : nBandCount = m_nBandCountFromMetadata;
2395 143 : if (nBandCount == 1)
2396 44 : m_eTF = GPKG_TF_PNG;
2397 : }
2398 212 : if (nBandCount == 1 && !m_osTFFromMetadata.empty())
2399 : {
2400 2 : m_eTF = GDALGPKGMBTilesGetTileFormat(m_osTFFromMetadata.c_str());
2401 : }
2402 212 : if (nBandCount <= 0 || nBandCount > 4)
2403 85 : nBandCount = 4;
2404 : }
2405 :
2406 363 : return InitRaster(poParentDS, pszTableName, nZoomLevel, nBandCount, dfMinX,
2407 : dfMaxY, dfPixelXSize, dfPixelYSize, nTileWidth,
2408 : nTileHeight, nTileMatrixWidth, nTileMatrixHeight,
2409 363 : dfGDALMinX, dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
2410 : }
2411 :
2412 : /************************************************************************/
2413 : /* ComputeTileAndPixelShifts() */
2414 : /************************************************************************/
2415 :
2416 789 : bool GDALGeoPackageDataset::ComputeTileAndPixelShifts()
2417 : {
2418 : int nTileWidth, nTileHeight;
2419 789 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
2420 :
2421 : // Compute shift between GDAL origin and TileMatrixSet origin
2422 789 : const double dfShiftXPixels = (m_gt[0] - m_dfTMSMinX) / m_gt[1];
2423 789 : if (!(dfShiftXPixels / nTileWidth >= INT_MIN &&
2424 786 : dfShiftXPixels / nTileWidth < INT_MAX))
2425 : {
2426 3 : return false;
2427 : }
2428 786 : const int64_t nShiftXPixels =
2429 786 : static_cast<int64_t>(floor(0.5 + dfShiftXPixels));
2430 786 : m_nShiftXTiles = static_cast<int>(nShiftXPixels / nTileWidth);
2431 786 : if (nShiftXPixels < 0 && (nShiftXPixels % nTileWidth) != 0)
2432 11 : m_nShiftXTiles--;
2433 786 : m_nShiftXPixelsMod =
2434 786 : (static_cast<int>(nShiftXPixels % nTileWidth) + nTileWidth) %
2435 : nTileWidth;
2436 :
2437 786 : const double dfShiftYPixels = (m_gt[3] - m_dfTMSMaxY) / m_gt[5];
2438 786 : if (!(dfShiftYPixels / nTileHeight >= INT_MIN &&
2439 786 : dfShiftYPixels / nTileHeight < INT_MAX))
2440 : {
2441 1 : return false;
2442 : }
2443 785 : const int64_t nShiftYPixels =
2444 785 : static_cast<int64_t>(floor(0.5 + dfShiftYPixels));
2445 785 : m_nShiftYTiles = static_cast<int>(nShiftYPixels / nTileHeight);
2446 785 : if (nShiftYPixels < 0 && (nShiftYPixels % nTileHeight) != 0)
2447 11 : m_nShiftYTiles--;
2448 785 : m_nShiftYPixelsMod =
2449 785 : (static_cast<int>(nShiftYPixels % nTileHeight) + nTileHeight) %
2450 : nTileHeight;
2451 785 : return true;
2452 : }
2453 :
2454 : /************************************************************************/
2455 : /* AllocCachedTiles() */
2456 : /************************************************************************/
2457 :
2458 785 : bool GDALGeoPackageDataset::AllocCachedTiles()
2459 : {
2460 : int nTileWidth, nTileHeight;
2461 785 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
2462 :
2463 : // We currently need 4 caches because of
2464 : // GDALGPKGMBTilesLikePseudoDataset::ReadTile(int nRow, int nCol)
2465 785 : const int nCacheCount = 4;
2466 : /*
2467 : (m_nShiftXPixelsMod != 0 || m_nShiftYPixelsMod != 0) ? 4 :
2468 : (GetUpdate() && m_eDT == GDT_UInt8) ? 2 : 1;
2469 : */
2470 785 : m_pabyCachedTiles = static_cast<GByte *>(VSI_MALLOC3_VERBOSE(
2471 : cpl::fits_on<int>(nCacheCount * (m_eDT == GDT_UInt8 ? 4 : 1) *
2472 : m_nDTSize),
2473 : nTileWidth, nTileHeight));
2474 785 : if (m_pabyCachedTiles == nullptr)
2475 : {
2476 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too big tiles: %d x %d",
2477 : nTileWidth, nTileHeight);
2478 0 : return false;
2479 : }
2480 :
2481 785 : return true;
2482 : }
2483 :
2484 : /************************************************************************/
2485 : /* InitRaster() */
2486 : /************************************************************************/
2487 :
2488 602 : bool GDALGeoPackageDataset::InitRaster(
2489 : GDALGeoPackageDataset *poParentDS, const char *pszTableName, int nZoomLevel,
2490 : int nBandCount, double dfTMSMinX, double dfTMSMaxY, double dfPixelXSize,
2491 : double dfPixelYSize, int nTileWidth, int nTileHeight, int nTileMatrixWidth,
2492 : int nTileMatrixHeight, double dfGDALMinX, double dfGDALMinY,
2493 : double dfGDALMaxX, double dfGDALMaxY)
2494 : {
2495 602 : m_osRasterTable = pszTableName;
2496 602 : m_dfTMSMinX = dfTMSMinX;
2497 602 : m_dfTMSMaxY = dfTMSMaxY;
2498 602 : m_nZoomLevel = nZoomLevel;
2499 602 : m_nTileMatrixWidth = nTileMatrixWidth;
2500 602 : m_nTileMatrixHeight = nTileMatrixHeight;
2501 :
2502 602 : m_bGeoTransformValid = true;
2503 602 : m_gt[0] = dfGDALMinX;
2504 602 : m_gt[1] = dfPixelXSize;
2505 602 : m_gt[3] = dfGDALMaxY;
2506 602 : m_gt[5] = -dfPixelYSize;
2507 602 : double dfRasterXSize = 0.5 + (dfGDALMaxX - dfGDALMinX) / dfPixelXSize;
2508 602 : double dfRasterYSize = 0.5 + (dfGDALMaxY - dfGDALMinY) / dfPixelYSize;
2509 602 : if (dfRasterXSize > INT_MAX || dfRasterYSize > INT_MAX)
2510 : {
2511 0 : CPLError(CE_Failure, CPLE_NotSupported, "Too big raster: %f x %f",
2512 : dfRasterXSize, dfRasterYSize);
2513 0 : return false;
2514 : }
2515 602 : nRasterXSize = std::max(1, static_cast<int>(dfRasterXSize));
2516 602 : nRasterYSize = std::max(1, static_cast<int>(dfRasterYSize));
2517 :
2518 602 : if (poParentDS)
2519 : {
2520 325 : m_poParentDS = poParentDS;
2521 325 : eAccess = poParentDS->eAccess;
2522 325 : hDB = poParentDS->hDB;
2523 325 : m_eTF = poParentDS->m_eTF;
2524 325 : m_eDT = poParentDS->m_eDT;
2525 325 : m_nDTSize = poParentDS->m_nDTSize;
2526 325 : m_dfScale = poParentDS->m_dfScale;
2527 325 : m_dfOffset = poParentDS->m_dfOffset;
2528 325 : m_dfPrecision = poParentDS->m_dfPrecision;
2529 325 : m_usGPKGNull = poParentDS->m_usGPKGNull;
2530 325 : m_nQuality = poParentDS->m_nQuality;
2531 325 : m_nZLevel = poParentDS->m_nZLevel;
2532 325 : m_bDither = poParentDS->m_bDither;
2533 : /*m_nSRID = poParentDS->m_nSRID;*/
2534 325 : m_osWHERE = poParentDS->m_osWHERE;
2535 325 : SetDescription(CPLSPrintf("%s - zoom_level=%d",
2536 325 : poParentDS->GetDescription(), m_nZoomLevel));
2537 : }
2538 :
2539 2101 : for (int i = 1; i <= nBandCount; i++)
2540 : {
2541 : auto poNewBand = std::make_unique<GDALGeoPackageRasterBand>(
2542 1499 : this, nTileWidth, nTileHeight);
2543 1499 : if (poParentDS)
2544 : {
2545 761 : int bHasNoData = FALSE;
2546 : double dfNoDataValue =
2547 761 : poParentDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
2548 761 : if (bHasNoData)
2549 24 : poNewBand->SetNoDataValueInternal(dfNoDataValue);
2550 : }
2551 :
2552 1499 : if (nBandCount == 1 && m_poCTFromMetadata)
2553 : {
2554 3 : poNewBand->AssignColorTable(m_poCTFromMetadata.get());
2555 : }
2556 1499 : if (!m_osNodataValueFromMetadata.empty())
2557 : {
2558 8 : poNewBand->SetNoDataValueInternal(
2559 : CPLAtof(m_osNodataValueFromMetadata.c_str()));
2560 : }
2561 :
2562 1499 : SetBand(i, std::move(poNewBand));
2563 : }
2564 :
2565 602 : if (!ComputeTileAndPixelShifts())
2566 : {
2567 3 : CPLError(CE_Failure, CPLE_AppDefined,
2568 : "Overflow occurred in ComputeTileAndPixelShifts()");
2569 3 : return false;
2570 : }
2571 :
2572 599 : GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
2573 599 : GDALPamDataset::SetMetadataItem("ZOOM_LEVEL",
2574 : CPLSPrintf("%d", m_nZoomLevel));
2575 :
2576 599 : return AllocCachedTiles();
2577 : }
2578 :
2579 : /************************************************************************/
2580 : /* GDALGPKGMBTilesGetTileFormat() */
2581 : /************************************************************************/
2582 :
2583 80 : GPKGTileFormat GDALGPKGMBTilesGetTileFormat(const char *pszTF)
2584 : {
2585 80 : GPKGTileFormat eTF = GPKG_TF_PNG_JPEG;
2586 80 : if (pszTF)
2587 : {
2588 80 : if (EQUAL(pszTF, "PNG_JPEG") || EQUAL(pszTF, "AUTO"))
2589 1 : eTF = GPKG_TF_PNG_JPEG;
2590 79 : else if (EQUAL(pszTF, "PNG"))
2591 46 : eTF = GPKG_TF_PNG;
2592 33 : else if (EQUAL(pszTF, "PNG8"))
2593 6 : eTF = GPKG_TF_PNG8;
2594 27 : else if (EQUAL(pszTF, "JPEG"))
2595 14 : eTF = GPKG_TF_JPEG;
2596 13 : else if (EQUAL(pszTF, "WEBP"))
2597 13 : eTF = GPKG_TF_WEBP;
2598 : else
2599 : {
2600 0 : CPLError(CE_Failure, CPLE_NotSupported,
2601 : "Unsuppoted value for TILE_FORMAT: %s", pszTF);
2602 : }
2603 : }
2604 80 : return eTF;
2605 : }
2606 :
2607 28 : const char *GDALMBTilesGetTileFormatName(GPKGTileFormat eTF)
2608 : {
2609 28 : switch (eTF)
2610 : {
2611 26 : case GPKG_TF_PNG:
2612 : case GPKG_TF_PNG8:
2613 26 : return "png";
2614 1 : case GPKG_TF_JPEG:
2615 1 : return "jpg";
2616 1 : case GPKG_TF_WEBP:
2617 1 : return "webp";
2618 0 : default:
2619 0 : break;
2620 : }
2621 0 : CPLError(CE_Failure, CPLE_NotSupported,
2622 : "Unsuppoted value for TILE_FORMAT: %d", static_cast<int>(eTF));
2623 0 : return nullptr;
2624 : }
2625 :
2626 : /************************************************************************/
2627 : /* OpenRaster() */
2628 : /************************************************************************/
2629 :
2630 279 : bool GDALGeoPackageDataset::OpenRaster(
2631 : const char *pszTableName, const char *pszIdentifier,
2632 : const char *pszDescription, int nSRSId, double dfMinX, double dfMinY,
2633 : double dfMaxX, double dfMaxY, const char *pszContentsMinX,
2634 : const char *pszContentsMinY, const char *pszContentsMaxX,
2635 : const char *pszContentsMaxY, bool bIsTiles, CSLConstList papszOpenOptionsIn)
2636 : {
2637 279 : if (dfMinX >= dfMaxX || dfMinY >= dfMaxY)
2638 0 : return false;
2639 :
2640 : // Config option just for debug, and for example force set to NaN
2641 : // which is not supported
2642 558 : CPLString osDataNull = CPLGetConfigOption("GPKG_NODATA", "");
2643 558 : CPLString osUom;
2644 558 : CPLString osFieldName;
2645 558 : CPLString osGridCellEncoding;
2646 279 : if (!bIsTiles)
2647 : {
2648 65 : char *pszSQL = sqlite3_mprintf(
2649 : "SELECT datatype, scale, offset, data_null, precision FROM "
2650 : "gpkg_2d_gridded_coverage_ancillary "
2651 : "WHERE tile_matrix_set_name = '%q' "
2652 : "AND datatype IN ('integer', 'float')"
2653 : "AND (scale > 0 OR scale IS NULL)",
2654 : pszTableName);
2655 65 : auto oResult = SQLQuery(hDB, pszSQL);
2656 65 : sqlite3_free(pszSQL);
2657 65 : if (!oResult || oResult->RowCount() == 0)
2658 : {
2659 0 : return false;
2660 : }
2661 65 : const char *pszDataType = oResult->GetValue(0, 0);
2662 65 : const char *pszScale = oResult->GetValue(1, 0);
2663 65 : const char *pszOffset = oResult->GetValue(2, 0);
2664 65 : const char *pszDataNull = oResult->GetValue(3, 0);
2665 65 : const char *pszPrecision = oResult->GetValue(4, 0);
2666 65 : if (pszDataNull)
2667 23 : osDataNull = pszDataNull;
2668 65 : if (EQUAL(pszDataType, "float"))
2669 : {
2670 6 : SetDataType(GDT_Float32);
2671 6 : m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
2672 : }
2673 : else
2674 : {
2675 59 : SetDataType(GDT_Float32);
2676 59 : m_eTF = GPKG_TF_PNG_16BIT;
2677 59 : const double dfScale = pszScale ? CPLAtof(pszScale) : 1.0;
2678 59 : const double dfOffset = pszOffset ? CPLAtof(pszOffset) : 0.0;
2679 59 : if (dfScale == 1.0)
2680 : {
2681 59 : if (dfOffset == 0.0)
2682 : {
2683 24 : SetDataType(GDT_UInt16);
2684 : }
2685 35 : else if (dfOffset == -32768.0)
2686 : {
2687 35 : SetDataType(GDT_Int16);
2688 : }
2689 : // coverity[tainted_data]
2690 0 : else if (dfOffset == -32767.0 && !osDataNull.empty() &&
2691 0 : CPLAtof(osDataNull) == 65535.0)
2692 : // Given that we will map the nodata value to -32768
2693 : {
2694 0 : SetDataType(GDT_Int16);
2695 : }
2696 : }
2697 :
2698 : // Check that the tile offset and scales are compatible of a
2699 : // final integer result.
2700 59 : if (m_eDT != GDT_Float32)
2701 : {
2702 : // coverity[tainted_data]
2703 59 : if (dfScale == 1.0 && dfOffset == -32768.0 &&
2704 118 : !osDataNull.empty() && CPLAtof(osDataNull) == 65535.0)
2705 : {
2706 : // Given that we will map the nodata value to -32768
2707 9 : pszSQL = sqlite3_mprintf(
2708 : "SELECT 1 FROM "
2709 : "gpkg_2d_gridded_tile_ancillary WHERE "
2710 : "tpudt_name = '%q' "
2711 : "AND NOT ((offset = 0.0 or offset = 1.0) "
2712 : "AND scale = 1.0) "
2713 : "LIMIT 1",
2714 : pszTableName);
2715 : }
2716 : else
2717 : {
2718 50 : pszSQL = sqlite3_mprintf(
2719 : "SELECT 1 FROM "
2720 : "gpkg_2d_gridded_tile_ancillary WHERE "
2721 : "tpudt_name = '%q' "
2722 : "AND NOT (offset = 0.0 AND scale = 1.0) LIMIT 1",
2723 : pszTableName);
2724 : }
2725 59 : sqlite3_stmt *hSQLStmt = nullptr;
2726 : int rc =
2727 59 : SQLPrepareWithError(hDB, pszSQL, -1, &hSQLStmt, nullptr);
2728 :
2729 59 : if (rc == SQLITE_OK)
2730 : {
2731 59 : if (sqlite3_step(hSQLStmt) == SQLITE_ROW)
2732 : {
2733 8 : SetDataType(GDT_Float32);
2734 : }
2735 59 : sqlite3_finalize(hSQLStmt);
2736 : }
2737 59 : sqlite3_free(pszSQL);
2738 : }
2739 :
2740 59 : SetGlobalOffsetScale(dfOffset, dfScale);
2741 : }
2742 65 : if (pszPrecision)
2743 65 : m_dfPrecision = CPLAtof(pszPrecision);
2744 :
2745 : // Request those columns in a separate query, so as to keep
2746 : // compatibility with pre OGC 17-066r1 databases
2747 : pszSQL =
2748 65 : sqlite3_mprintf("SELECT uom, field_name, grid_cell_encoding FROM "
2749 : "gpkg_2d_gridded_coverage_ancillary "
2750 : "WHERE tile_matrix_set_name = '%q'",
2751 : pszTableName);
2752 65 : CPLPushErrorHandler(CPLQuietErrorHandler);
2753 65 : oResult = SQLQuery(hDB, pszSQL);
2754 65 : CPLPopErrorHandler();
2755 65 : sqlite3_free(pszSQL);
2756 65 : if (oResult && oResult->RowCount() == 1)
2757 : {
2758 64 : const char *pszUom = oResult->GetValue(0, 0);
2759 64 : if (pszUom)
2760 2 : osUom = pszUom;
2761 64 : const char *pszFieldName = oResult->GetValue(1, 0);
2762 64 : if (pszFieldName)
2763 64 : osFieldName = pszFieldName;
2764 64 : const char *pszGridCellEncoding = oResult->GetValue(2, 0);
2765 64 : if (pszGridCellEncoding)
2766 64 : osGridCellEncoding = pszGridCellEncoding;
2767 : }
2768 : }
2769 :
2770 279 : m_bRecordInsertedInGPKGContent = true;
2771 279 : m_nSRID = nSRSId;
2772 :
2773 557 : if (auto poSRS = GetSpatialRef(nSRSId))
2774 : {
2775 278 : m_oSRS = *(poSRS.get());
2776 : }
2777 :
2778 : /* Various sanity checks added in the SELECT */
2779 279 : char *pszQuotedTableName = sqlite3_mprintf("'%q'", pszTableName);
2780 558 : CPLString osQuotedTableName(pszQuotedTableName);
2781 279 : sqlite3_free(pszQuotedTableName);
2782 279 : char *pszSQL = sqlite3_mprintf(
2783 : "SELECT zoom_level, pixel_x_size, pixel_y_size, tile_width, "
2784 : "tile_height, matrix_width, matrix_height "
2785 : "FROM gpkg_tile_matrix tm "
2786 : "WHERE table_name = %s "
2787 : // INT_MAX would be the theoretical maximum value to avoid
2788 : // overflows, but that's already a insane value.
2789 : "AND zoom_level >= 0 AND zoom_level <= 65536 "
2790 : "AND pixel_x_size > 0 AND pixel_y_size > 0 "
2791 : "AND tile_width >= 1 AND tile_width <= 65536 "
2792 : "AND tile_height >= 1 AND tile_height <= 65536 "
2793 : "AND matrix_width >= 1 AND matrix_height >= 1",
2794 : osQuotedTableName.c_str());
2795 558 : CPLString osSQL(pszSQL);
2796 : const char *pszZoomLevel =
2797 279 : CSLFetchNameValue(papszOpenOptionsIn, "ZOOM_LEVEL");
2798 279 : if (pszZoomLevel)
2799 : {
2800 5 : if (GetUpdate())
2801 1 : osSQL += CPLSPrintf(" AND zoom_level <= %d", atoi(pszZoomLevel));
2802 : else
2803 : {
2804 : osSQL += CPLSPrintf(
2805 : " AND (zoom_level = %d OR (zoom_level < %d AND EXISTS(SELECT 1 "
2806 : "FROM %s WHERE zoom_level = tm.zoom_level LIMIT 1)))",
2807 : atoi(pszZoomLevel), atoi(pszZoomLevel),
2808 4 : osQuotedTableName.c_str());
2809 : }
2810 : }
2811 : // In read-only mode, only lists non empty zoom levels
2812 274 : else if (!GetUpdate())
2813 : {
2814 : osSQL += CPLSPrintf(" AND EXISTS(SELECT 1 FROM %s WHERE zoom_level = "
2815 : "tm.zoom_level LIMIT 1)",
2816 220 : osQuotedTableName.c_str());
2817 : }
2818 : else // if( pszZoomLevel == nullptr )
2819 : {
2820 : osSQL +=
2821 : CPLSPrintf(" AND zoom_level <= (SELECT MAX(zoom_level) FROM %s)",
2822 54 : osQuotedTableName.c_str());
2823 : }
2824 279 : osSQL += " ORDER BY zoom_level DESC";
2825 : // To avoid denial of service.
2826 279 : osSQL += " LIMIT 100";
2827 :
2828 558 : auto oResult = SQLQuery(hDB, osSQL.c_str());
2829 279 : if (!oResult || oResult->RowCount() == 0)
2830 : {
2831 114 : if (oResult && oResult->RowCount() == 0 && pszContentsMinX != nullptr &&
2832 114 : pszContentsMinY != nullptr && pszContentsMaxX != nullptr &&
2833 : pszContentsMaxY != nullptr)
2834 : {
2835 56 : osSQL = pszSQL;
2836 56 : osSQL += " ORDER BY zoom_level DESC";
2837 56 : if (!GetUpdate())
2838 30 : osSQL += " LIMIT 1";
2839 56 : oResult = SQLQuery(hDB, osSQL.c_str());
2840 : }
2841 57 : if (!oResult || oResult->RowCount() == 0)
2842 : {
2843 1 : if (oResult && pszZoomLevel != nullptr)
2844 : {
2845 1 : CPLError(CE_Failure, CPLE_AppDefined,
2846 : "ZOOM_LEVEL is probably not valid w.r.t tile "
2847 : "table content");
2848 : }
2849 1 : sqlite3_free(pszSQL);
2850 1 : return false;
2851 : }
2852 : }
2853 278 : sqlite3_free(pszSQL);
2854 :
2855 : // If USE_TILE_EXTENT=YES, then query the tile table to find which tiles
2856 : // actually exist.
2857 :
2858 : // CAUTION: Do not move those variables inside inner scope !
2859 556 : CPLString osContentsMinX, osContentsMinY, osContentsMaxX, osContentsMaxY;
2860 :
2861 278 : if (CPLTestBool(
2862 : CSLFetchNameValueDef(papszOpenOptionsIn, "USE_TILE_EXTENT", "NO")))
2863 : {
2864 13 : pszSQL = sqlite3_mprintf(
2865 : "SELECT MIN(tile_column), MIN(tile_row), MAX(tile_column), "
2866 : "MAX(tile_row) FROM \"%w\" WHERE zoom_level = %d",
2867 : pszTableName, atoi(oResult->GetValue(0, 0)));
2868 13 : auto oResult2 = SQLQuery(hDB, pszSQL);
2869 13 : sqlite3_free(pszSQL);
2870 26 : if (!oResult2 || oResult2->RowCount() == 0 ||
2871 : // Can happen if table is empty
2872 38 : oResult2->GetValue(0, 0) == nullptr ||
2873 : // Can happen if table has no NOT NULL constraint on tile_row
2874 : // and that all tile_row are NULL
2875 12 : oResult2->GetValue(1, 0) == nullptr)
2876 : {
2877 1 : return false;
2878 : }
2879 12 : const double dfPixelXSize = CPLAtof(oResult->GetValue(1, 0));
2880 12 : const double dfPixelYSize = CPLAtof(oResult->GetValue(2, 0));
2881 12 : const int nTileWidth = atoi(oResult->GetValue(3, 0));
2882 12 : const int nTileHeight = atoi(oResult->GetValue(4, 0));
2883 : osContentsMinX =
2884 24 : CPLSPrintf("%.17g", dfMinX + dfPixelXSize * nTileWidth *
2885 12 : atoi(oResult2->GetValue(0, 0)));
2886 : osContentsMaxY =
2887 24 : CPLSPrintf("%.17g", dfMaxY - dfPixelYSize * nTileHeight *
2888 12 : atoi(oResult2->GetValue(1, 0)));
2889 : osContentsMaxX = CPLSPrintf(
2890 24 : "%.17g", dfMinX + dfPixelXSize * nTileWidth *
2891 12 : (1 + atoi(oResult2->GetValue(2, 0))));
2892 : osContentsMinY = CPLSPrintf(
2893 24 : "%.17g", dfMaxY - dfPixelYSize * nTileHeight *
2894 12 : (1 + atoi(oResult2->GetValue(3, 0))));
2895 12 : pszContentsMinX = osContentsMinX.c_str();
2896 12 : pszContentsMinY = osContentsMinY.c_str();
2897 12 : pszContentsMaxX = osContentsMaxX.c_str();
2898 12 : pszContentsMaxY = osContentsMaxY.c_str();
2899 : }
2900 :
2901 277 : if (!InitRaster(nullptr, pszTableName, dfMinX, dfMinY, dfMaxX, dfMaxY,
2902 : pszContentsMinX, pszContentsMinY, pszContentsMaxX,
2903 277 : pszContentsMaxY, papszOpenOptionsIn, *oResult, 0))
2904 : {
2905 3 : return false;
2906 : }
2907 :
2908 274 : auto poBand = cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(1));
2909 274 : if (!osDataNull.empty())
2910 : {
2911 23 : double dfGPKGNoDataValue = CPLAtof(osDataNull);
2912 23 : if (m_eTF == GPKG_TF_PNG_16BIT)
2913 : {
2914 21 : if (dfGPKGNoDataValue < 0 || dfGPKGNoDataValue > 65535 ||
2915 21 : static_cast<int>(dfGPKGNoDataValue) != dfGPKGNoDataValue)
2916 : {
2917 0 : CPLError(CE_Warning, CPLE_AppDefined,
2918 : "data_null = %.17g is invalid for integer data_type",
2919 : dfGPKGNoDataValue);
2920 : }
2921 : else
2922 : {
2923 21 : m_usGPKGNull = static_cast<GUInt16>(dfGPKGNoDataValue);
2924 21 : if (m_eDT == GDT_Int16 && m_usGPKGNull > 32767)
2925 9 : dfGPKGNoDataValue = -32768.0;
2926 12 : else if (m_eDT == GDT_Float32)
2927 : {
2928 : // Pick a value that is unlikely to be hit with offset &
2929 : // scale
2930 4 : dfGPKGNoDataValue = -std::numeric_limits<float>::max();
2931 : }
2932 21 : poBand->SetNoDataValueInternal(dfGPKGNoDataValue);
2933 : }
2934 : }
2935 : else
2936 : {
2937 2 : poBand->SetNoDataValueInternal(
2938 2 : static_cast<float>(dfGPKGNoDataValue));
2939 : }
2940 : }
2941 274 : if (!osUom.empty())
2942 : {
2943 2 : poBand->SetUnitTypeInternal(osUom);
2944 : }
2945 274 : if (!osFieldName.empty())
2946 : {
2947 64 : GetRasterBand(1)->GDALRasterBand::SetDescription(osFieldName);
2948 : }
2949 274 : if (!osGridCellEncoding.empty())
2950 : {
2951 64 : if (osGridCellEncoding == "grid-value-is-center")
2952 : {
2953 15 : GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
2954 : GDALMD_AOP_POINT);
2955 : }
2956 49 : else if (osGridCellEncoding == "grid-value-is-area")
2957 : {
2958 45 : GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
2959 : GDALMD_AOP_AREA);
2960 : }
2961 : else
2962 : {
2963 4 : GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
2964 : GDALMD_AOP_POINT);
2965 4 : GetRasterBand(1)->GDALRasterBand::SetMetadataItem(
2966 : "GRID_CELL_ENCODING", osGridCellEncoding);
2967 : }
2968 : }
2969 :
2970 274 : CheckUnknownExtensions(true);
2971 :
2972 : // Do this after CheckUnknownExtensions() so that m_eTF is set to
2973 : // GPKG_TF_WEBP if the table already registers the gpkg_webp extension
2974 274 : const char *pszTF = CSLFetchNameValue(papszOpenOptionsIn, "TILE_FORMAT");
2975 274 : if (pszTF)
2976 : {
2977 4 : if (!GetUpdate())
2978 : {
2979 0 : CPLError(CE_Warning, CPLE_AppDefined,
2980 : "TILE_FORMAT open option ignored in read-only mode");
2981 : }
2982 4 : else if (m_eTF == GPKG_TF_PNG_16BIT ||
2983 4 : m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
2984 : {
2985 0 : CPLError(CE_Warning, CPLE_AppDefined,
2986 : "TILE_FORMAT open option ignored on gridded coverages");
2987 : }
2988 : else
2989 : {
2990 4 : GPKGTileFormat eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
2991 4 : if (eTF == GPKG_TF_WEBP && m_eTF != eTF)
2992 : {
2993 1 : if (!RegisterWebPExtension())
2994 0 : return false;
2995 : }
2996 4 : m_eTF = eTF;
2997 : }
2998 : }
2999 :
3000 274 : ParseCompressionOptions(papszOpenOptionsIn);
3001 :
3002 274 : m_osWHERE = CSLFetchNameValueDef(papszOpenOptionsIn, "WHERE", "");
3003 :
3004 : // Set metadata
3005 274 : if (pszIdentifier && pszIdentifier[0])
3006 274 : GDALPamDataset::SetMetadataItem("IDENTIFIER", pszIdentifier);
3007 274 : if (pszDescription && pszDescription[0])
3008 21 : GDALPamDataset::SetMetadataItem("DESCRIPTION", pszDescription);
3009 :
3010 : // Add overviews
3011 359 : for (int i = 1; i < oResult->RowCount(); i++)
3012 : {
3013 86 : auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
3014 86 : poOvrDS->ShareLockWithParentDataset(this);
3015 172 : if (!poOvrDS->InitRaster(this, pszTableName, dfMinX, dfMinY, dfMaxX,
3016 : dfMaxY, pszContentsMinX, pszContentsMinY,
3017 : pszContentsMaxX, pszContentsMaxY,
3018 86 : papszOpenOptionsIn, *oResult, i))
3019 : {
3020 0 : break;
3021 : }
3022 :
3023 : int nTileWidth, nTileHeight;
3024 86 : poOvrDS->GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3025 : const bool bStop =
3026 87 : (eAccess == GA_ReadOnly && poOvrDS->GetRasterXSize() < nTileWidth &&
3027 1 : poOvrDS->GetRasterYSize() < nTileHeight);
3028 :
3029 86 : m_apoOverviewDS.push_back(std::move(poOvrDS));
3030 :
3031 86 : if (bStop)
3032 : {
3033 1 : break;
3034 : }
3035 : }
3036 :
3037 274 : return true;
3038 : }
3039 :
3040 : /************************************************************************/
3041 : /* GetSpatialRef() */
3042 : /************************************************************************/
3043 :
3044 17 : const OGRSpatialReference *GDALGeoPackageDataset::GetSpatialRef() const
3045 : {
3046 17 : if (GetLayerCount())
3047 1 : return GDALDataset::GetSpatialRef();
3048 16 : return GetSpatialRefRasterOnly();
3049 : }
3050 :
3051 : /************************************************************************/
3052 : /* GetSpatialRefRasterOnly() */
3053 : /************************************************************************/
3054 :
3055 : const OGRSpatialReference *
3056 17 : GDALGeoPackageDataset::GetSpatialRefRasterOnly() const
3057 :
3058 : {
3059 17 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
3060 : }
3061 :
3062 : /************************************************************************/
3063 : /* SetSpatialRef() */
3064 : /************************************************************************/
3065 :
3066 152 : CPLErr GDALGeoPackageDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
3067 : {
3068 152 : if (nBands == 0)
3069 : {
3070 1 : CPLError(CE_Failure, CPLE_NotSupported,
3071 : "SetProjection() not supported on a dataset with 0 band");
3072 1 : return CE_Failure;
3073 : }
3074 151 : if (eAccess != GA_Update)
3075 : {
3076 1 : CPLError(CE_Failure, CPLE_NotSupported,
3077 : "SetProjection() not supported on read-only dataset");
3078 1 : return CE_Failure;
3079 : }
3080 :
3081 150 : const int nSRID = GetSrsId(poSRS);
3082 300 : const auto poTS = GetTilingScheme(m_osTilingScheme);
3083 150 : if (poTS && nSRID != poTS->nEPSGCode)
3084 : {
3085 2 : CPLError(CE_Failure, CPLE_NotSupported,
3086 : "Projection should be EPSG:%d for %s tiling scheme",
3087 1 : poTS->nEPSGCode, m_osTilingScheme.c_str());
3088 1 : return CE_Failure;
3089 : }
3090 :
3091 149 : m_nSRID = nSRID;
3092 149 : m_oSRS.Clear();
3093 149 : if (poSRS)
3094 148 : m_oSRS = *poSRS;
3095 :
3096 149 : if (m_bRecordInsertedInGPKGContent)
3097 : {
3098 121 : char *pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET srs_id = %d "
3099 : "WHERE lower(table_name) = lower('%q')",
3100 : m_nSRID, m_osRasterTable.c_str());
3101 121 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3102 121 : sqlite3_free(pszSQL);
3103 121 : if (eErr != OGRERR_NONE)
3104 0 : return CE_Failure;
3105 :
3106 121 : pszSQL = sqlite3_mprintf("UPDATE gpkg_tile_matrix_set SET srs_id = %d "
3107 : "WHERE lower(table_name) = lower('%q')",
3108 : m_nSRID, m_osRasterTable.c_str());
3109 121 : eErr = SQLCommand(hDB, pszSQL);
3110 121 : sqlite3_free(pszSQL);
3111 121 : if (eErr != OGRERR_NONE)
3112 0 : return CE_Failure;
3113 : }
3114 :
3115 149 : return CE_None;
3116 : }
3117 :
3118 : /************************************************************************/
3119 : /* GetGeoTransform() */
3120 : /************************************************************************/
3121 :
3122 33 : CPLErr GDALGeoPackageDataset::GetGeoTransform(GDALGeoTransform >) const
3123 : {
3124 33 : gt = m_gt;
3125 33 : if (!m_bGeoTransformValid)
3126 2 : return CE_Failure;
3127 : else
3128 31 : return CE_None;
3129 : }
3130 :
3131 : /************************************************************************/
3132 : /* SetGeoTransform() */
3133 : /************************************************************************/
3134 :
3135 192 : CPLErr GDALGeoPackageDataset::SetGeoTransform(const GDALGeoTransform >)
3136 : {
3137 192 : if (nBands == 0)
3138 : {
3139 2 : CPLError(CE_Failure, CPLE_NotSupported,
3140 : "SetGeoTransform() not supported on a dataset with 0 band");
3141 2 : return CE_Failure;
3142 : }
3143 190 : if (eAccess != GA_Update)
3144 : {
3145 1 : CPLError(CE_Failure, CPLE_NotSupported,
3146 : "SetGeoTransform() not supported on read-only dataset");
3147 1 : return CE_Failure;
3148 : }
3149 189 : if (m_bGeoTransformValid)
3150 : {
3151 1 : CPLError(CE_Failure, CPLE_NotSupported,
3152 : "Cannot modify geotransform once set");
3153 1 : return CE_Failure;
3154 : }
3155 188 : if (gt[2] != 0.0 || gt[4] != 0 || gt[5] > 0.0)
3156 : {
3157 0 : CPLError(CE_Failure, CPLE_NotSupported,
3158 : "Only north-up non rotated geotransform supported");
3159 0 : return CE_Failure;
3160 : }
3161 :
3162 188 : if (m_nZoomLevel < 0)
3163 : {
3164 187 : const auto poTS = GetTilingScheme(m_osTilingScheme);
3165 187 : if (poTS)
3166 : {
3167 20 : double dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
3168 20 : double dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
3169 199 : for (m_nZoomLevel = 0; m_nZoomLevel < MAX_ZOOM_LEVEL;
3170 179 : m_nZoomLevel++)
3171 : {
3172 198 : double dfExpectedPixelXSize =
3173 198 : dfPixelXSizeZoomLevel0 / (1 << m_nZoomLevel);
3174 198 : double dfExpectedPixelYSize =
3175 198 : dfPixelYSizeZoomLevel0 / (1 << m_nZoomLevel);
3176 198 : if (fabs(gt[1] - dfExpectedPixelXSize) <
3177 217 : 1e-8 * dfExpectedPixelXSize &&
3178 19 : fabs(fabs(gt[5]) - dfExpectedPixelYSize) <
3179 19 : 1e-8 * dfExpectedPixelYSize)
3180 : {
3181 19 : break;
3182 : }
3183 : }
3184 20 : if (m_nZoomLevel == MAX_ZOOM_LEVEL)
3185 : {
3186 1 : m_nZoomLevel = -1;
3187 1 : CPLError(
3188 : CE_Failure, CPLE_NotSupported,
3189 : "Could not find an appropriate zoom level of %s tiling "
3190 : "scheme that matches raster pixel size",
3191 : m_osTilingScheme.c_str());
3192 1 : return CE_Failure;
3193 : }
3194 : }
3195 : }
3196 :
3197 187 : m_gt = gt;
3198 187 : m_bGeoTransformValid = true;
3199 :
3200 187 : return FinalizeRasterRegistration();
3201 : }
3202 :
3203 : /************************************************************************/
3204 : /* FinalizeRasterRegistration() */
3205 : /************************************************************************/
3206 :
3207 187 : CPLErr GDALGeoPackageDataset::FinalizeRasterRegistration()
3208 : {
3209 : OGRErr eErr;
3210 :
3211 187 : m_dfTMSMinX = m_gt[0];
3212 187 : m_dfTMSMaxY = m_gt[3];
3213 :
3214 : int nTileWidth, nTileHeight;
3215 187 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3216 :
3217 187 : if (m_nZoomLevel < 0)
3218 : {
3219 167 : m_nZoomLevel = 0;
3220 241 : while ((nRasterXSize >> m_nZoomLevel) > nTileWidth ||
3221 167 : (nRasterYSize >> m_nZoomLevel) > nTileHeight)
3222 74 : m_nZoomLevel++;
3223 : }
3224 :
3225 187 : double dfPixelXSizeZoomLevel0 = m_gt[1] * (1 << m_nZoomLevel);
3226 187 : double dfPixelYSizeZoomLevel0 = fabs(m_gt[5]) * (1 << m_nZoomLevel);
3227 : int nTileXCountZoomLevel0 =
3228 187 : std::max(1, DIV_ROUND_UP((nRasterXSize >> m_nZoomLevel), nTileWidth));
3229 : int nTileYCountZoomLevel0 =
3230 187 : std::max(1, DIV_ROUND_UP((nRasterYSize >> m_nZoomLevel), nTileHeight));
3231 :
3232 374 : const auto poTS = GetTilingScheme(m_osTilingScheme);
3233 187 : if (poTS)
3234 : {
3235 20 : CPLAssert(m_nZoomLevel >= 0);
3236 20 : m_dfTMSMinX = poTS->dfMinX;
3237 20 : m_dfTMSMaxY = poTS->dfMaxY;
3238 20 : dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
3239 20 : dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
3240 20 : nTileXCountZoomLevel0 = poTS->nTileXCountZoomLevel0;
3241 20 : nTileYCountZoomLevel0 = poTS->nTileYCountZoomLevel0;
3242 : }
3243 187 : m_nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << m_nZoomLevel);
3244 187 : m_nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << m_nZoomLevel);
3245 :
3246 187 : if (!ComputeTileAndPixelShifts())
3247 : {
3248 1 : CPLError(CE_Failure, CPLE_AppDefined,
3249 : "Overflow occurred in ComputeTileAndPixelShifts()");
3250 1 : return CE_Failure;
3251 : }
3252 :
3253 186 : if (!AllocCachedTiles())
3254 : {
3255 0 : return CE_Failure;
3256 : }
3257 :
3258 186 : double dfGDALMinX = m_gt[0];
3259 186 : double dfGDALMinY = m_gt[3] + nRasterYSize * m_gt[5];
3260 186 : double dfGDALMaxX = m_gt[0] + nRasterXSize * m_gt[1];
3261 186 : double dfGDALMaxY = m_gt[3];
3262 :
3263 186 : if (SoftStartTransaction() != OGRERR_NONE)
3264 0 : return CE_Failure;
3265 :
3266 : const char *pszCurrentDate =
3267 186 : CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
3268 : CPLString osInsertGpkgContentsFormatting(
3269 : "INSERT INTO gpkg_contents "
3270 : "(table_name,data_type,identifier,description,min_x,min_y,max_x,max_y,"
3271 : "last_change,srs_id) VALUES "
3272 372 : "('%q','%q','%q','%q',%.17g,%.17g,%.17g,%.17g,");
3273 186 : osInsertGpkgContentsFormatting += (pszCurrentDate) ? "'%q'" : "%s";
3274 186 : osInsertGpkgContentsFormatting += ",%d)";
3275 372 : char *pszSQL = sqlite3_mprintf(
3276 : osInsertGpkgContentsFormatting.c_str(), m_osRasterTable.c_str(),
3277 186 : (m_eDT == GDT_UInt8) ? "tiles" : "2d-gridded-coverage",
3278 : m_osIdentifier.c_str(), m_osDescription.c_str(), dfGDALMinX, dfGDALMinY,
3279 : dfGDALMaxX, dfGDALMaxY,
3280 : pszCurrentDate ? pszCurrentDate
3281 : : "strftime('%Y-%m-%dT%H:%M:%fZ','now')",
3282 : m_nSRID);
3283 :
3284 186 : eErr = SQLCommand(hDB, pszSQL);
3285 186 : sqlite3_free(pszSQL);
3286 186 : if (eErr != OGRERR_NONE)
3287 : {
3288 8 : SoftRollbackTransaction();
3289 8 : return CE_Failure;
3290 : }
3291 :
3292 178 : double dfTMSMaxX = m_dfTMSMinX + nTileXCountZoomLevel0 * nTileWidth *
3293 : dfPixelXSizeZoomLevel0;
3294 178 : double dfTMSMinY = m_dfTMSMaxY - nTileYCountZoomLevel0 * nTileHeight *
3295 : dfPixelYSizeZoomLevel0;
3296 :
3297 : pszSQL =
3298 178 : sqlite3_mprintf("INSERT INTO gpkg_tile_matrix_set "
3299 : "(table_name,srs_id,min_x,min_y,max_x,max_y) VALUES "
3300 : "('%q',%d,%.17g,%.17g,%.17g,%.17g)",
3301 : m_osRasterTable.c_str(), m_nSRID, m_dfTMSMinX,
3302 : dfTMSMinY, dfTMSMaxX, m_dfTMSMaxY);
3303 178 : eErr = SQLCommand(hDB, pszSQL);
3304 178 : sqlite3_free(pszSQL);
3305 178 : if (eErr != OGRERR_NONE)
3306 : {
3307 0 : SoftRollbackTransaction();
3308 0 : return CE_Failure;
3309 : }
3310 :
3311 178 : m_apoOverviewDS.resize(m_nZoomLevel);
3312 :
3313 591 : for (int i = 0; i <= m_nZoomLevel; i++)
3314 : {
3315 413 : double dfPixelXSizeZoomLevel = 0.0;
3316 413 : double dfPixelYSizeZoomLevel = 0.0;
3317 413 : int nTileMatrixWidth = 0;
3318 413 : int nTileMatrixHeight = 0;
3319 413 : if (EQUAL(m_osTilingScheme, "CUSTOM"))
3320 : {
3321 232 : dfPixelXSizeZoomLevel = m_gt[1] * (1 << (m_nZoomLevel - i));
3322 232 : dfPixelYSizeZoomLevel = fabs(m_gt[5]) * (1 << (m_nZoomLevel - i));
3323 : }
3324 : else
3325 : {
3326 181 : dfPixelXSizeZoomLevel = dfPixelXSizeZoomLevel0 / (1 << i);
3327 181 : dfPixelYSizeZoomLevel = dfPixelYSizeZoomLevel0 / (1 << i);
3328 : }
3329 413 : nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << i);
3330 413 : nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << i);
3331 :
3332 413 : pszSQL = sqlite3_mprintf(
3333 : "INSERT INTO gpkg_tile_matrix "
3334 : "(table_name,zoom_level,matrix_width,matrix_height,tile_width,tile_"
3335 : "height,pixel_x_size,pixel_y_size) VALUES "
3336 : "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
3337 : m_osRasterTable.c_str(), i, nTileMatrixWidth, nTileMatrixHeight,
3338 : nTileWidth, nTileHeight, dfPixelXSizeZoomLevel,
3339 : dfPixelYSizeZoomLevel);
3340 413 : eErr = SQLCommand(hDB, pszSQL);
3341 413 : sqlite3_free(pszSQL);
3342 413 : if (eErr != OGRERR_NONE)
3343 : {
3344 0 : SoftRollbackTransaction();
3345 0 : return CE_Failure;
3346 : }
3347 :
3348 413 : if (i < m_nZoomLevel)
3349 : {
3350 470 : auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
3351 235 : poOvrDS->ShareLockWithParentDataset(this);
3352 235 : poOvrDS->InitRaster(this, m_osRasterTable, i, nBands, m_dfTMSMinX,
3353 : m_dfTMSMaxY, dfPixelXSizeZoomLevel,
3354 : dfPixelYSizeZoomLevel, nTileWidth, nTileHeight,
3355 : nTileMatrixWidth, nTileMatrixHeight, dfGDALMinX,
3356 : dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
3357 :
3358 235 : m_apoOverviewDS[m_nZoomLevel - 1 - i] = std::move(poOvrDS);
3359 : }
3360 : }
3361 :
3362 178 : if (!m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.empty())
3363 : {
3364 40 : eErr = SQLCommand(
3365 : hDB, m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.c_str());
3366 40 : m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.clear();
3367 40 : if (eErr != OGRERR_NONE)
3368 : {
3369 0 : SoftRollbackTransaction();
3370 0 : return CE_Failure;
3371 : }
3372 : }
3373 :
3374 178 : SoftCommitTransaction();
3375 :
3376 178 : m_apoOverviewDS.resize(m_nZoomLevel);
3377 178 : m_bRecordInsertedInGPKGContent = true;
3378 :
3379 178 : return CE_None;
3380 : }
3381 :
3382 : /************************************************************************/
3383 : /* FlushCache() */
3384 : /************************************************************************/
3385 :
3386 2862 : CPLErr GDALGeoPackageDataset::FlushCache(bool bAtClosing)
3387 : {
3388 2862 : if (m_bInFlushCache)
3389 0 : return CE_None;
3390 :
3391 2862 : if (eAccess == GA_Update || !m_bMetadataDirty)
3392 : {
3393 2859 : SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
3394 : }
3395 :
3396 2862 : if (m_bRemoveOGREmptyTable)
3397 : {
3398 781 : m_bRemoveOGREmptyTable = false;
3399 781 : RemoveOGREmptyTable();
3400 : }
3401 :
3402 2862 : CPLErr eErr = IFlushCacheWithErrCode(bAtClosing);
3403 :
3404 2862 : FlushMetadata();
3405 :
3406 2862 : if (eAccess == GA_Update || !m_bMetadataDirty)
3407 : {
3408 : // Needed again as above IFlushCacheWithErrCode()
3409 : // may have call GDALGeoPackageRasterBand::InvalidateStatistics()
3410 : // which modifies metadata
3411 2862 : SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
3412 : }
3413 :
3414 2862 : return eErr;
3415 : }
3416 :
3417 5102 : CPLErr GDALGeoPackageDataset::IFlushCacheWithErrCode(bool bAtClosing)
3418 :
3419 : {
3420 5102 : if (m_bInFlushCache)
3421 2173 : return CE_None;
3422 2929 : m_bInFlushCache = true;
3423 2929 : if (hDB && eAccess == GA_ReadOnly && bAtClosing)
3424 : {
3425 : // Clean-up metadata that will go to PAM by removing items that
3426 : // are reconstructed.
3427 2138 : CPLStringList aosMD;
3428 1707 : for (CSLConstList papszIter = GetMetadata(); papszIter && *papszIter;
3429 : ++papszIter)
3430 : {
3431 638 : char *pszKey = nullptr;
3432 638 : CPLParseNameValue(*papszIter, &pszKey);
3433 1276 : if (pszKey &&
3434 638 : (EQUAL(pszKey, "AREA_OR_POINT") ||
3435 487 : EQUAL(pszKey, "IDENTIFIER") || EQUAL(pszKey, "DESCRIPTION") ||
3436 261 : EQUAL(pszKey, "ZOOM_LEVEL") ||
3437 668 : STARTS_WITH(pszKey, "GPKG_METADATA_ITEM_")))
3438 : {
3439 : // remove it
3440 : }
3441 : else
3442 : {
3443 30 : aosMD.AddString(*papszIter);
3444 : }
3445 638 : CPLFree(pszKey);
3446 : }
3447 1069 : oMDMD.SetMetadata(aosMD.List());
3448 1069 : oMDMD.SetMetadata(nullptr, "IMAGE_STRUCTURE");
3449 :
3450 2138 : GDALPamDataset::FlushCache(bAtClosing);
3451 : }
3452 : else
3453 : {
3454 : // Short circuit GDALPamDataset to avoid serialization to .aux.xml
3455 1860 : GDALDataset::FlushCache(bAtClosing);
3456 : }
3457 :
3458 7123 : for (auto &poLayer : m_apoLayers)
3459 : {
3460 4194 : poLayer->RunDeferredCreationIfNecessary();
3461 4194 : poLayer->CreateSpatialIndexIfNecessary();
3462 : }
3463 :
3464 : // Update raster table last_change column in gpkg_contents if needed
3465 2929 : if (m_bHasModifiedTiles)
3466 : {
3467 540 : for (int i = 1; i <= nBands; ++i)
3468 : {
3469 : auto poBand =
3470 359 : cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
3471 359 : if (!poBand->HaveStatsMetadataBeenSetInThisSession())
3472 : {
3473 346 : poBand->InvalidateStatistics();
3474 346 : if (psPam && psPam->pszPamFilename)
3475 346 : VSIUnlink(psPam->pszPamFilename);
3476 : }
3477 : }
3478 :
3479 181 : UpdateGpkgContentsLastChange(m_osRasterTable);
3480 :
3481 181 : m_bHasModifiedTiles = false;
3482 : }
3483 :
3484 2929 : CPLErr eErr = FlushTiles();
3485 :
3486 2929 : m_bInFlushCache = false;
3487 2929 : return eErr;
3488 : }
3489 :
3490 : /************************************************************************/
3491 : /* GetCurrentDateEscapedSQL() */
3492 : /************************************************************************/
3493 :
3494 2173 : std::string GDALGeoPackageDataset::GetCurrentDateEscapedSQL()
3495 : {
3496 : const char *pszCurrentDate =
3497 2173 : CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
3498 2173 : if (pszCurrentDate)
3499 10 : return '\'' + SQLEscapeLiteral(pszCurrentDate) + '\'';
3500 2168 : return "strftime('%Y-%m-%dT%H:%M:%fZ','now')";
3501 : }
3502 :
3503 : /************************************************************************/
3504 : /* UpdateGpkgContentsLastChange() */
3505 : /************************************************************************/
3506 :
3507 : OGRErr
3508 952 : GDALGeoPackageDataset::UpdateGpkgContentsLastChange(const char *pszTableName)
3509 : {
3510 : char *pszSQL =
3511 952 : sqlite3_mprintf("UPDATE gpkg_contents SET "
3512 : "last_change = %s "
3513 : "WHERE lower(table_name) = lower('%q')",
3514 1904 : GetCurrentDateEscapedSQL().c_str(), pszTableName);
3515 952 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3516 952 : sqlite3_free(pszSQL);
3517 952 : return eErr;
3518 : }
3519 :
3520 : /************************************************************************/
3521 : /* IBuildOverviews() */
3522 : /************************************************************************/
3523 :
3524 20 : CPLErr GDALGeoPackageDataset::IBuildOverviews(
3525 : const char *pszResampling, int nOverviews, const int *panOverviewList,
3526 : int nBandsIn, const int * /*panBandList*/, GDALProgressFunc pfnProgress,
3527 : void *pProgressData, CSLConstList papszOptions)
3528 : {
3529 20 : if (GetAccess() != GA_Update)
3530 : {
3531 1 : CPLError(CE_Failure, CPLE_NotSupported,
3532 : "Overview building not supported on a database opened in "
3533 : "read-only mode");
3534 1 : return CE_Failure;
3535 : }
3536 19 : if (m_poParentDS != nullptr)
3537 : {
3538 1 : CPLError(CE_Failure, CPLE_NotSupported,
3539 : "Overview building not supported on overview dataset");
3540 1 : return CE_Failure;
3541 : }
3542 :
3543 18 : if (nOverviews == 0)
3544 : {
3545 5 : for (auto &poOvrDS : m_apoOverviewDS)
3546 3 : poOvrDS->FlushCache(false);
3547 :
3548 2 : SoftStartTransaction();
3549 :
3550 2 : if (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
3551 : {
3552 1 : char *pszSQL = sqlite3_mprintf(
3553 : "DELETE FROM gpkg_2d_gridded_tile_ancillary WHERE id IN "
3554 : "(SELECT y.id FROM \"%w\" x "
3555 : "JOIN gpkg_2d_gridded_tile_ancillary y "
3556 : "ON x.id = y.tpudt_id AND y.tpudt_name = '%q' AND "
3557 : "x.zoom_level < %d)",
3558 : m_osRasterTable.c_str(), m_osRasterTable.c_str(), m_nZoomLevel);
3559 1 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3560 1 : sqlite3_free(pszSQL);
3561 1 : if (eErr != OGRERR_NONE)
3562 : {
3563 0 : SoftRollbackTransaction();
3564 0 : return CE_Failure;
3565 : }
3566 : }
3567 :
3568 : char *pszSQL =
3569 2 : sqlite3_mprintf("DELETE FROM \"%w\" WHERE zoom_level < %d",
3570 : m_osRasterTable.c_str(), m_nZoomLevel);
3571 2 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3572 2 : sqlite3_free(pszSQL);
3573 2 : if (eErr != OGRERR_NONE)
3574 : {
3575 0 : SoftRollbackTransaction();
3576 0 : return CE_Failure;
3577 : }
3578 :
3579 2 : SoftCommitTransaction();
3580 :
3581 2 : return CE_None;
3582 : }
3583 :
3584 16 : if (nBandsIn != nBands)
3585 : {
3586 0 : CPLError(CE_Failure, CPLE_NotSupported,
3587 : "Generation of overviews in GPKG only"
3588 : "supported when operating on all bands.");
3589 0 : return CE_Failure;
3590 : }
3591 :
3592 16 : if (m_apoOverviewDS.empty())
3593 : {
3594 0 : CPLError(CE_Failure, CPLE_AppDefined,
3595 : "Image too small to support overviews");
3596 0 : return CE_Failure;
3597 : }
3598 :
3599 16 : FlushCache(false);
3600 60 : for (int i = 0; i < nOverviews; i++)
3601 : {
3602 47 : if (panOverviewList[i] < 2)
3603 : {
3604 1 : CPLError(CE_Failure, CPLE_IllegalArg,
3605 : "Overview factor must be >= 2");
3606 1 : return CE_Failure;
3607 : }
3608 :
3609 46 : bool bFound = false;
3610 46 : int jCandidate = -1;
3611 46 : int nMaxOvFactor = 0;
3612 196 : for (int j = 0; j < static_cast<int>(m_apoOverviewDS.size()); j++)
3613 : {
3614 190 : const auto poODS = m_apoOverviewDS[j].get();
3615 : const int nOvFactor =
3616 190 : static_cast<int>(0.5 + poODS->m_gt[1] / m_gt[1]);
3617 :
3618 190 : nMaxOvFactor = nOvFactor;
3619 :
3620 190 : if (nOvFactor == panOverviewList[i])
3621 : {
3622 40 : bFound = true;
3623 40 : break;
3624 : }
3625 :
3626 150 : if (jCandidate < 0 && nOvFactor > panOverviewList[i])
3627 1 : jCandidate = j;
3628 : }
3629 :
3630 46 : if (!bFound)
3631 : {
3632 : /* Mostly for debug */
3633 6 : if (!CPLTestBool(CPLGetConfigOption(
3634 : "ALLOW_GPKG_ZOOM_OTHER_EXTENSION", "YES")))
3635 : {
3636 2 : CPLString osOvrList;
3637 4 : for (const auto &poODS : m_apoOverviewDS)
3638 : {
3639 : const int nOvFactor =
3640 2 : static_cast<int>(0.5 + poODS->m_gt[1] / m_gt[1]);
3641 :
3642 2 : if (!osOvrList.empty())
3643 0 : osOvrList += ' ';
3644 2 : osOvrList += CPLSPrintf("%d", nOvFactor);
3645 : }
3646 2 : CPLError(CE_Failure, CPLE_NotSupported,
3647 : "Only overviews %s can be computed",
3648 : osOvrList.c_str());
3649 2 : return CE_Failure;
3650 : }
3651 : else
3652 : {
3653 4 : int nOvFactor = panOverviewList[i];
3654 4 : if (jCandidate < 0)
3655 3 : jCandidate = static_cast<int>(m_apoOverviewDS.size());
3656 :
3657 4 : int nOvXSize = std::max(1, GetRasterXSize() / nOvFactor);
3658 4 : int nOvYSize = std::max(1, GetRasterYSize() / nOvFactor);
3659 4 : if (!(jCandidate == static_cast<int>(m_apoOverviewDS.size()) &&
3660 5 : nOvFactor == 2 * nMaxOvFactor) &&
3661 1 : !m_bZoomOther)
3662 : {
3663 1 : CPLError(CE_Warning, CPLE_AppDefined,
3664 : "Use of overview factor %d causes gpkg_zoom_other "
3665 : "extension to be needed",
3666 : nOvFactor);
3667 1 : RegisterZoomOtherExtension();
3668 1 : m_bZoomOther = true;
3669 : }
3670 :
3671 4 : SoftStartTransaction();
3672 :
3673 4 : CPLAssert(jCandidate > 0);
3674 : const int nNewZoomLevel =
3675 4 : m_apoOverviewDS[jCandidate - 1]->m_nZoomLevel;
3676 :
3677 : char *pszSQL;
3678 : OGRErr eErr;
3679 24 : for (int k = 0; k <= jCandidate; k++)
3680 : {
3681 60 : pszSQL = sqlite3_mprintf(
3682 : "UPDATE gpkg_tile_matrix SET zoom_level = %d "
3683 : "WHERE lower(table_name) = lower('%q') AND zoom_level "
3684 : "= %d",
3685 20 : m_nZoomLevel - k + 1, m_osRasterTable.c_str(),
3686 20 : m_nZoomLevel - k);
3687 20 : eErr = SQLCommand(hDB, pszSQL);
3688 20 : sqlite3_free(pszSQL);
3689 20 : if (eErr != OGRERR_NONE)
3690 : {
3691 0 : SoftRollbackTransaction();
3692 0 : return CE_Failure;
3693 : }
3694 :
3695 : pszSQL =
3696 20 : sqlite3_mprintf("UPDATE \"%w\" SET zoom_level = %d "
3697 : "WHERE zoom_level = %d",
3698 : m_osRasterTable.c_str(),
3699 20 : m_nZoomLevel - k + 1, m_nZoomLevel - k);
3700 20 : eErr = SQLCommand(hDB, pszSQL);
3701 20 : sqlite3_free(pszSQL);
3702 20 : if (eErr != OGRERR_NONE)
3703 : {
3704 0 : SoftRollbackTransaction();
3705 0 : return CE_Failure;
3706 : }
3707 : }
3708 :
3709 4 : double dfGDALMinX = m_gt[0];
3710 4 : double dfGDALMinY = m_gt[3] + nRasterYSize * m_gt[5];
3711 4 : double dfGDALMaxX = m_gt[0] + nRasterXSize * m_gt[1];
3712 4 : double dfGDALMaxY = m_gt[3];
3713 4 : double dfPixelXSizeZoomLevel = m_gt[1] * nOvFactor;
3714 4 : double dfPixelYSizeZoomLevel = fabs(m_gt[5]) * nOvFactor;
3715 : int nTileWidth, nTileHeight;
3716 4 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3717 4 : int nTileMatrixWidth = DIV_ROUND_UP(nOvXSize, nTileWidth);
3718 4 : int nTileMatrixHeight = DIV_ROUND_UP(nOvYSize, nTileHeight);
3719 4 : pszSQL = sqlite3_mprintf(
3720 : "INSERT INTO gpkg_tile_matrix "
3721 : "(table_name,zoom_level,matrix_width,matrix_height,tile_"
3722 : "width,tile_height,pixel_x_size,pixel_y_size) VALUES "
3723 : "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
3724 : m_osRasterTable.c_str(), nNewZoomLevel, nTileMatrixWidth,
3725 : nTileMatrixHeight, nTileWidth, nTileHeight,
3726 : dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel);
3727 4 : eErr = SQLCommand(hDB, pszSQL);
3728 4 : sqlite3_free(pszSQL);
3729 4 : if (eErr != OGRERR_NONE)
3730 : {
3731 0 : SoftRollbackTransaction();
3732 0 : return CE_Failure;
3733 : }
3734 :
3735 4 : SoftCommitTransaction();
3736 :
3737 4 : m_nZoomLevel++; /* this change our zoom level as well as
3738 : previous overviews */
3739 20 : for (int k = 0; k < jCandidate; k++)
3740 16 : m_apoOverviewDS[k]->m_nZoomLevel++;
3741 :
3742 4 : auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
3743 4 : poOvrDS->ShareLockWithParentDataset(this);
3744 4 : poOvrDS->InitRaster(
3745 : this, m_osRasterTable, nNewZoomLevel, nBands, m_dfTMSMinX,
3746 : m_dfTMSMaxY, dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel,
3747 : nTileWidth, nTileHeight, nTileMatrixWidth,
3748 : nTileMatrixHeight, dfGDALMinX, dfGDALMinY, dfGDALMaxX,
3749 : dfGDALMaxY);
3750 4 : m_apoOverviewDS.insert(m_apoOverviewDS.begin() + jCandidate,
3751 8 : std::move(poOvrDS));
3752 : }
3753 : }
3754 : }
3755 :
3756 : GDALRasterBand ***papapoOverviewBands = static_cast<GDALRasterBand ***>(
3757 13 : CPLCalloc(sizeof(GDALRasterBand **), nBands));
3758 13 : CPLErr eErr = CE_None;
3759 49 : for (int iBand = 0; eErr == CE_None && iBand < nBands; iBand++)
3760 : {
3761 72 : papapoOverviewBands[iBand] = static_cast<GDALRasterBand **>(
3762 36 : CPLCalloc(sizeof(GDALRasterBand *), nOverviews));
3763 36 : int iCurOverview = 0;
3764 185 : for (int i = 0; i < nOverviews; i++)
3765 : {
3766 149 : bool bFound = false;
3767 724 : for (const auto &poODS : m_apoOverviewDS)
3768 : {
3769 : const int nOvFactor =
3770 724 : static_cast<int>(0.5 + poODS->m_gt[1] / m_gt[1]);
3771 :
3772 724 : if (nOvFactor == panOverviewList[i])
3773 : {
3774 298 : papapoOverviewBands[iBand][iCurOverview] =
3775 149 : poODS->GetRasterBand(iBand + 1);
3776 149 : iCurOverview++;
3777 149 : bFound = true;
3778 149 : break;
3779 : }
3780 : }
3781 149 : if (!bFound)
3782 : {
3783 0 : CPLError(CE_Failure, CPLE_AppDefined,
3784 : "Could not find dataset corresponding to ov factor %d",
3785 0 : panOverviewList[i]);
3786 0 : eErr = CE_Failure;
3787 : }
3788 : }
3789 36 : if (eErr == CE_None)
3790 : {
3791 36 : CPLAssert(iCurOverview == nOverviews);
3792 : }
3793 : }
3794 :
3795 13 : if (eErr == CE_None)
3796 13 : eErr = GDALRegenerateOverviewsMultiBand(
3797 13 : nBands, papoBands, nOverviews, papapoOverviewBands, pszResampling,
3798 : pfnProgress, pProgressData, papszOptions);
3799 :
3800 49 : for (int iBand = 0; iBand < nBands; iBand++)
3801 : {
3802 36 : CPLFree(papapoOverviewBands[iBand]);
3803 : }
3804 13 : CPLFree(papapoOverviewBands);
3805 :
3806 13 : return eErr;
3807 : }
3808 :
3809 : /************************************************************************/
3810 : /* GetFileList() */
3811 : /************************************************************************/
3812 :
3813 38 : char **GDALGeoPackageDataset::GetFileList()
3814 : {
3815 38 : TryLoadXML();
3816 38 : return GDALPamDataset::GetFileList();
3817 : }
3818 :
3819 : /************************************************************************/
3820 : /* GetMetadataDomainList() */
3821 : /************************************************************************/
3822 :
3823 47 : char **GDALGeoPackageDataset::GetMetadataDomainList()
3824 : {
3825 47 : GetMetadata();
3826 47 : if (!m_osRasterTable.empty())
3827 5 : GetMetadata("GEOPACKAGE");
3828 47 : return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
3829 47 : TRUE, "SUBDATASETS", nullptr);
3830 : }
3831 :
3832 : /************************************************************************/
3833 : /* CheckMetadataDomain() */
3834 : /************************************************************************/
3835 :
3836 6071 : const char *GDALGeoPackageDataset::CheckMetadataDomain(const char *pszDomain)
3837 : {
3838 6256 : if (pszDomain != nullptr && EQUAL(pszDomain, "GEOPACKAGE") &&
3839 185 : m_osRasterTable.empty())
3840 : {
3841 4 : CPLError(
3842 : CE_Warning, CPLE_IllegalArg,
3843 : "Using GEOPACKAGE for a non-raster geopackage is not supported. "
3844 : "Using default domain instead");
3845 4 : return nullptr;
3846 : }
3847 6067 : return pszDomain;
3848 : }
3849 :
3850 : /************************************************************************/
3851 : /* HasMetadataTables() */
3852 : /************************************************************************/
3853 :
3854 5768 : bool GDALGeoPackageDataset::HasMetadataTables() const
3855 : {
3856 5768 : if (m_nHasMetadataTables < 0)
3857 : {
3858 : const int nCount =
3859 2210 : SQLGetInteger(hDB,
3860 : "SELECT COUNT(*) FROM sqlite_master WHERE name IN "
3861 : "('gpkg_metadata', 'gpkg_metadata_reference') "
3862 : "AND type IN ('table', 'view')",
3863 : nullptr);
3864 2210 : m_nHasMetadataTables = nCount == 2;
3865 : }
3866 5768 : return CPL_TO_BOOL(m_nHasMetadataTables);
3867 : }
3868 :
3869 : /************************************************************************/
3870 : /* HasDataColumnsTable() */
3871 : /************************************************************************/
3872 :
3873 1291 : bool GDALGeoPackageDataset::HasDataColumnsTable() const
3874 : {
3875 2582 : const int nCount = SQLGetInteger(
3876 1291 : hDB,
3877 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_data_columns'"
3878 : "AND type IN ('table', 'view')",
3879 : nullptr);
3880 1291 : return nCount == 1;
3881 : }
3882 :
3883 : /************************************************************************/
3884 : /* HasDataColumnConstraintsTable() */
3885 : /************************************************************************/
3886 :
3887 157 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTable() const
3888 : {
3889 157 : const int nCount = SQLGetInteger(hDB,
3890 : "SELECT 1 FROM sqlite_master WHERE name = "
3891 : "'gpkg_data_column_constraints'"
3892 : "AND type IN ('table', 'view')",
3893 : nullptr);
3894 157 : return nCount == 1;
3895 : }
3896 :
3897 : /************************************************************************/
3898 : /* HasDataColumnConstraintsTableGPKG_1_0() */
3899 : /************************************************************************/
3900 :
3901 109 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTableGPKG_1_0() const
3902 : {
3903 109 : if (m_nApplicationId != GP10_APPLICATION_ID)
3904 107 : return false;
3905 : // In GPKG 1.0, the columns were named minIsInclusive, maxIsInclusive
3906 : // They were changed in 1.1 to min_is_inclusive, max_is_inclusive
3907 2 : bool bRet = false;
3908 2 : sqlite3_stmt *hSQLStmt = nullptr;
3909 2 : int rc = sqlite3_prepare_v2(hDB,
3910 : "SELECT minIsInclusive, maxIsInclusive FROM "
3911 : "gpkg_data_column_constraints",
3912 : -1, &hSQLStmt, nullptr);
3913 2 : if (rc == SQLITE_OK)
3914 : {
3915 2 : bRet = true;
3916 2 : sqlite3_finalize(hSQLStmt);
3917 : }
3918 2 : return bRet;
3919 : }
3920 :
3921 : /************************************************************************/
3922 : /* CreateColumnsTableAndColumnConstraintsTablesIfNecessary() */
3923 : /************************************************************************/
3924 :
3925 50 : bool GDALGeoPackageDataset::
3926 : CreateColumnsTableAndColumnConstraintsTablesIfNecessary()
3927 : {
3928 50 : if (!HasDataColumnsTable())
3929 : {
3930 : // Geopackage < 1.3 had
3931 : // CONSTRAINT fk_gdc_tn FOREIGN KEY (table_name) REFERENCES
3932 : // gpkg_contents(table_name) instead of the unique constraint.
3933 10 : if (OGRERR_NONE !=
3934 10 : SQLCommand(
3935 : GetDB(),
3936 : "CREATE TABLE gpkg_data_columns ("
3937 : "table_name TEXT NOT NULL,"
3938 : "column_name TEXT NOT NULL,"
3939 : "name TEXT,"
3940 : "title TEXT,"
3941 : "description TEXT,"
3942 : "mime_type TEXT,"
3943 : "constraint_name TEXT,"
3944 : "CONSTRAINT pk_gdc PRIMARY KEY (table_name, column_name),"
3945 : "CONSTRAINT gdc_tn UNIQUE (table_name, name));"))
3946 : {
3947 0 : return false;
3948 : }
3949 : }
3950 50 : if (!HasDataColumnConstraintsTable())
3951 : {
3952 22 : const char *min_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
3953 11 : ? "min_is_inclusive"
3954 : : "minIsInclusive";
3955 22 : const char *max_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
3956 11 : ? "max_is_inclusive"
3957 : : "maxIsInclusive";
3958 :
3959 : const std::string osSQL(
3960 : CPLSPrintf("CREATE TABLE gpkg_data_column_constraints ("
3961 : "constraint_name TEXT NOT NULL,"
3962 : "constraint_type TEXT NOT NULL,"
3963 : "value TEXT,"
3964 : "min NUMERIC,"
3965 : "%s BOOLEAN,"
3966 : "max NUMERIC,"
3967 : "%s BOOLEAN,"
3968 : "description TEXT,"
3969 : "CONSTRAINT gdcc_ntv UNIQUE (constraint_name, "
3970 : "constraint_type, value));",
3971 11 : min_is_inclusive, max_is_inclusive));
3972 11 : if (OGRERR_NONE != SQLCommand(GetDB(), osSQL.c_str()))
3973 : {
3974 0 : return false;
3975 : }
3976 : }
3977 50 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
3978 : {
3979 0 : return false;
3980 : }
3981 50 : if (SQLGetInteger(GetDB(),
3982 : "SELECT 1 FROM gpkg_extensions WHERE "
3983 : "table_name = 'gpkg_data_columns'",
3984 50 : nullptr) != 1)
3985 : {
3986 11 : if (OGRERR_NONE !=
3987 11 : SQLCommand(
3988 : GetDB(),
3989 : "INSERT INTO gpkg_extensions "
3990 : "(table_name,column_name,extension_name,definition,scope) "
3991 : "VALUES ('gpkg_data_columns', NULL, 'gpkg_schema', "
3992 : "'http://www.geopackage.org/spec121/#extension_schema', "
3993 : "'read-write')"))
3994 : {
3995 0 : return false;
3996 : }
3997 : }
3998 50 : if (SQLGetInteger(GetDB(),
3999 : "SELECT 1 FROM gpkg_extensions WHERE "
4000 : "table_name = 'gpkg_data_column_constraints'",
4001 50 : nullptr) != 1)
4002 : {
4003 11 : if (OGRERR_NONE !=
4004 11 : SQLCommand(
4005 : GetDB(),
4006 : "INSERT INTO gpkg_extensions "
4007 : "(table_name,column_name,extension_name,definition,scope) "
4008 : "VALUES ('gpkg_data_column_constraints', NULL, 'gpkg_schema', "
4009 : "'http://www.geopackage.org/spec121/#extension_schema', "
4010 : "'read-write')"))
4011 : {
4012 0 : return false;
4013 : }
4014 : }
4015 :
4016 50 : return true;
4017 : }
4018 :
4019 : /************************************************************************/
4020 : /* HasGpkgextRelationsTable() */
4021 : /************************************************************************/
4022 :
4023 1287 : bool GDALGeoPackageDataset::HasGpkgextRelationsTable() const
4024 : {
4025 2574 : const int nCount = SQLGetInteger(
4026 1287 : hDB,
4027 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkgext_relations'"
4028 : "AND type IN ('table', 'view')",
4029 : nullptr);
4030 1287 : return nCount == 1;
4031 : }
4032 :
4033 : /************************************************************************/
4034 : /* CreateRelationsTableIfNecessary() */
4035 : /************************************************************************/
4036 :
4037 9 : bool GDALGeoPackageDataset::CreateRelationsTableIfNecessary()
4038 : {
4039 9 : if (HasGpkgextRelationsTable())
4040 : {
4041 5 : return true;
4042 : }
4043 :
4044 4 : if (OGRERR_NONE !=
4045 4 : SQLCommand(GetDB(), "CREATE TABLE gpkgext_relations ("
4046 : "id INTEGER PRIMARY KEY AUTOINCREMENT,"
4047 : "base_table_name TEXT NOT NULL,"
4048 : "base_primary_column TEXT NOT NULL DEFAULT 'id',"
4049 : "related_table_name TEXT NOT NULL,"
4050 : "related_primary_column TEXT NOT NULL DEFAULT 'id',"
4051 : "relation_name TEXT NOT NULL,"
4052 : "mapping_table_name TEXT NOT NULL UNIQUE);"))
4053 : {
4054 0 : return false;
4055 : }
4056 :
4057 4 : return true;
4058 : }
4059 :
4060 : /************************************************************************/
4061 : /* HasQGISLayerStyles() */
4062 : /************************************************************************/
4063 :
4064 11 : bool GDALGeoPackageDataset::HasQGISLayerStyles() const
4065 : {
4066 : // QGIS layer_styles extension:
4067 : // https://github.com/pka/qgpkg/blob/master/qgis_geopackage_extension.md
4068 11 : bool bRet = false;
4069 : const int nCount =
4070 11 : SQLGetInteger(hDB,
4071 : "SELECT 1 FROM sqlite_master WHERE name = 'layer_styles'"
4072 : "AND type = 'table'",
4073 : nullptr);
4074 11 : if (nCount == 1)
4075 : {
4076 1 : sqlite3_stmt *hSQLStmt = nullptr;
4077 2 : int rc = sqlite3_prepare_v2(
4078 1 : hDB, "SELECT f_table_name, f_geometry_column FROM layer_styles", -1,
4079 : &hSQLStmt, nullptr);
4080 1 : if (rc == SQLITE_OK)
4081 : {
4082 1 : bRet = true;
4083 1 : sqlite3_finalize(hSQLStmt);
4084 : }
4085 : }
4086 11 : return bRet;
4087 : }
4088 :
4089 : /************************************************************************/
4090 : /* GetMetadata() */
4091 : /************************************************************************/
4092 :
4093 3990 : CSLConstList GDALGeoPackageDataset::GetMetadata(const char *pszDomain)
4094 :
4095 : {
4096 3990 : pszDomain = CheckMetadataDomain(pszDomain);
4097 3990 : if (pszDomain != nullptr && EQUAL(pszDomain, "SUBDATASETS"))
4098 74 : return m_aosSubDatasets.List();
4099 :
4100 3916 : if (m_bHasReadMetadataFromStorage)
4101 1773 : return GDALPamDataset::GetMetadata(pszDomain);
4102 :
4103 2143 : m_bHasReadMetadataFromStorage = true;
4104 :
4105 2143 : TryLoadXML();
4106 :
4107 2143 : if (!HasMetadataTables())
4108 1626 : return GDALPamDataset::GetMetadata(pszDomain);
4109 :
4110 517 : char *pszSQL = nullptr;
4111 517 : if (!m_osRasterTable.empty())
4112 : {
4113 175 : pszSQL = sqlite3_mprintf(
4114 : "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
4115 : "mdr.reference_scope FROM gpkg_metadata md "
4116 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4117 : "WHERE "
4118 : "(mdr.reference_scope = 'geopackage' OR "
4119 : "(mdr.reference_scope = 'table' AND lower(mdr.table_name) = "
4120 : "lower('%q'))) ORDER BY md.id "
4121 : "LIMIT 1000", // to avoid denial of service
4122 : m_osRasterTable.c_str());
4123 : }
4124 : else
4125 : {
4126 342 : pszSQL = sqlite3_mprintf(
4127 : "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
4128 : "mdr.reference_scope FROM gpkg_metadata md "
4129 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4130 : "WHERE "
4131 : "mdr.reference_scope = 'geopackage' ORDER BY md.id "
4132 : "LIMIT 1000" // to avoid denial of service
4133 : );
4134 : }
4135 :
4136 1034 : auto oResult = SQLQuery(hDB, pszSQL);
4137 517 : sqlite3_free(pszSQL);
4138 517 : if (!oResult)
4139 : {
4140 0 : return GDALPamDataset::GetMetadata(pszDomain);
4141 : }
4142 :
4143 517 : char **papszMetadata = CSLDuplicate(GDALPamDataset::GetMetadata());
4144 :
4145 : /* GDAL metadata */
4146 712 : for (int i = 0; i < oResult->RowCount(); i++)
4147 : {
4148 195 : const char *pszMetadata = oResult->GetValue(0, i);
4149 195 : const char *pszMDStandardURI = oResult->GetValue(1, i);
4150 195 : const char *pszMimeType = oResult->GetValue(2, i);
4151 195 : const char *pszReferenceScope = oResult->GetValue(3, i);
4152 195 : if (pszMetadata && pszMDStandardURI && pszMimeType &&
4153 195 : pszReferenceScope && EQUAL(pszMDStandardURI, "http://gdal.org") &&
4154 179 : EQUAL(pszMimeType, "text/xml"))
4155 : {
4156 179 : CPLXMLNode *psXMLNode = CPLParseXMLString(pszMetadata);
4157 179 : if (psXMLNode)
4158 : {
4159 358 : GDALMultiDomainMetadata oLocalMDMD;
4160 179 : oLocalMDMD.XMLInit(psXMLNode, FALSE);
4161 343 : if (!m_osRasterTable.empty() &&
4162 164 : EQUAL(pszReferenceScope, "geopackage"))
4163 : {
4164 6 : oMDMD.SetMetadata(oLocalMDMD.GetMetadata(), "GEOPACKAGE");
4165 : }
4166 : else
4167 : {
4168 : papszMetadata =
4169 173 : CSLMerge(papszMetadata, oLocalMDMD.GetMetadata());
4170 173 : CSLConstList papszDomainList = oLocalMDMD.GetDomainList();
4171 173 : CSLConstList papszIter = papszDomainList;
4172 462 : while (papszIter && *papszIter)
4173 : {
4174 289 : if (EQUAL(*papszIter, "IMAGE_STRUCTURE"))
4175 : {
4176 : CSLConstList papszMD =
4177 131 : oLocalMDMD.GetMetadata(*papszIter);
4178 : const char *pszBAND_COUNT =
4179 131 : CSLFetchNameValue(papszMD, "BAND_COUNT");
4180 131 : if (pszBAND_COUNT)
4181 129 : m_nBandCountFromMetadata = atoi(pszBAND_COUNT);
4182 :
4183 : const char *pszCOLOR_TABLE =
4184 131 : CSLFetchNameValue(papszMD, "COLOR_TABLE");
4185 131 : if (pszCOLOR_TABLE)
4186 : {
4187 : const CPLStringList aosTokens(
4188 : CSLTokenizeString2(pszCOLOR_TABLE, "{,",
4189 26 : 0));
4190 13 : if ((aosTokens.size() % 4) == 0)
4191 : {
4192 13 : const int nColors = aosTokens.size() / 4;
4193 : m_poCTFromMetadata =
4194 13 : std::make_unique<GDALColorTable>();
4195 3341 : for (int iColor = 0; iColor < nColors;
4196 : ++iColor)
4197 : {
4198 : GDALColorEntry sEntry;
4199 3328 : sEntry.c1 = static_cast<short>(
4200 3328 : atoi(aosTokens[4 * iColor + 0]));
4201 3328 : sEntry.c2 = static_cast<short>(
4202 3328 : atoi(aosTokens[4 * iColor + 1]));
4203 3328 : sEntry.c3 = static_cast<short>(
4204 3328 : atoi(aosTokens[4 * iColor + 2]));
4205 3328 : sEntry.c4 = static_cast<short>(
4206 3328 : atoi(aosTokens[4 * iColor + 3]));
4207 3328 : m_poCTFromMetadata->SetColorEntry(
4208 : iColor, &sEntry);
4209 : }
4210 : }
4211 : }
4212 :
4213 : const char *pszTILE_FORMAT =
4214 131 : CSLFetchNameValue(papszMD, "TILE_FORMAT");
4215 131 : if (pszTILE_FORMAT)
4216 : {
4217 8 : m_osTFFromMetadata = pszTILE_FORMAT;
4218 8 : oMDMD.SetMetadataItem("TILE_FORMAT",
4219 : pszTILE_FORMAT,
4220 : "IMAGE_STRUCTURE");
4221 : }
4222 :
4223 : const char *pszNodataValue =
4224 131 : CSLFetchNameValue(papszMD, "NODATA_VALUE");
4225 131 : if (pszNodataValue)
4226 : {
4227 2 : m_osNodataValueFromMetadata = pszNodataValue;
4228 : }
4229 : }
4230 :
4231 158 : else if (!EQUAL(*papszIter, "") &&
4232 16 : !STARTS_WITH(*papszIter, "BAND_"))
4233 : {
4234 12 : oMDMD.SetMetadata(
4235 6 : oLocalMDMD.GetMetadata(*papszIter), *papszIter);
4236 : }
4237 289 : papszIter++;
4238 : }
4239 : }
4240 179 : CPLDestroyXMLNode(psXMLNode);
4241 : }
4242 : }
4243 : }
4244 :
4245 517 : GDALPamDataset::SetMetadata(papszMetadata);
4246 517 : CSLDestroy(papszMetadata);
4247 517 : papszMetadata = nullptr;
4248 :
4249 : /* Add non-GDAL metadata now */
4250 517 : int nNonGDALMDILocal = 1;
4251 517 : int nNonGDALMDIGeopackage = 1;
4252 712 : for (int i = 0; i < oResult->RowCount(); i++)
4253 : {
4254 195 : const char *pszMetadata = oResult->GetValue(0, i);
4255 195 : const char *pszMDStandardURI = oResult->GetValue(1, i);
4256 195 : const char *pszMimeType = oResult->GetValue(2, i);
4257 195 : const char *pszReferenceScope = oResult->GetValue(3, i);
4258 195 : if (pszMetadata == nullptr || pszMDStandardURI == nullptr ||
4259 195 : pszMimeType == nullptr || pszReferenceScope == nullptr)
4260 : {
4261 : // should not happen as there are NOT NULL constraints
4262 : // But a database could lack such NOT NULL constraints or have
4263 : // large values that would cause a memory allocation failure.
4264 0 : continue;
4265 : }
4266 195 : int bIsGPKGScope = EQUAL(pszReferenceScope, "geopackage");
4267 195 : if (EQUAL(pszMDStandardURI, "http://gdal.org") &&
4268 179 : EQUAL(pszMimeType, "text/xml"))
4269 179 : continue;
4270 :
4271 16 : if (!m_osRasterTable.empty() && bIsGPKGScope)
4272 : {
4273 8 : oMDMD.SetMetadataItem(
4274 : CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDIGeopackage),
4275 : pszMetadata, "GEOPACKAGE");
4276 8 : nNonGDALMDIGeopackage++;
4277 : }
4278 : /*else if( strcmp( pszMDStandardURI, "http://www.isotc211.org/2005/gmd"
4279 : ) == 0 && strcmp( pszMimeType, "text/xml" ) == 0 )
4280 : {
4281 : char* apszMD[2];
4282 : apszMD[0] = (char*)pszMetadata;
4283 : apszMD[1] = NULL;
4284 : oMDMD.SetMetadata(apszMD, "xml:MD_Metadata");
4285 : }*/
4286 : else
4287 : {
4288 8 : oMDMD.SetMetadataItem(
4289 : CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDILocal),
4290 : pszMetadata);
4291 8 : nNonGDALMDILocal++;
4292 : }
4293 : }
4294 :
4295 517 : return GDALPamDataset::GetMetadata(pszDomain);
4296 : }
4297 :
4298 : /************************************************************************/
4299 : /* WriteMetadata() */
4300 : /************************************************************************/
4301 :
4302 735 : void GDALGeoPackageDataset::WriteMetadata(
4303 : CPLXMLNode *psXMLNode, /* will be destroyed by the method */
4304 : const char *pszTableName)
4305 : {
4306 735 : const bool bIsEmpty = (psXMLNode == nullptr);
4307 735 : if (!HasMetadataTables())
4308 : {
4309 534 : if (bIsEmpty || !CreateMetadataTables())
4310 : {
4311 244 : CPLDestroyXMLNode(psXMLNode);
4312 244 : return;
4313 : }
4314 : }
4315 :
4316 491 : char *pszXML = nullptr;
4317 491 : if (!bIsEmpty)
4318 : {
4319 : CPLXMLNode *psMasterXMLNode =
4320 338 : CPLCreateXMLNode(nullptr, CXT_Element, "GDALMultiDomainMetadata");
4321 338 : psMasterXMLNode->psChild = psXMLNode;
4322 338 : pszXML = CPLSerializeXMLTree(psMasterXMLNode);
4323 338 : CPLDestroyXMLNode(psMasterXMLNode);
4324 : }
4325 : // cppcheck-suppress uselessAssignmentPtrArg
4326 491 : psXMLNode = nullptr;
4327 :
4328 491 : char *pszSQL = nullptr;
4329 491 : if (pszTableName && pszTableName[0] != '\0')
4330 : {
4331 346 : pszSQL = sqlite3_mprintf(
4332 : "SELECT md.id FROM gpkg_metadata md "
4333 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4334 : "WHERE md.md_scope = 'dataset' AND "
4335 : "md.md_standard_uri='http://gdal.org' "
4336 : "AND md.mime_type='text/xml' AND mdr.reference_scope = 'table' AND "
4337 : "lower(mdr.table_name) = lower('%q')",
4338 : pszTableName);
4339 : }
4340 : else
4341 : {
4342 145 : pszSQL = sqlite3_mprintf(
4343 : "SELECT md.id FROM gpkg_metadata md "
4344 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4345 : "WHERE md.md_scope = 'dataset' AND "
4346 : "md.md_standard_uri='http://gdal.org' "
4347 : "AND md.mime_type='text/xml' AND mdr.reference_scope = "
4348 : "'geopackage'");
4349 : }
4350 : OGRErr err;
4351 491 : int mdId = SQLGetInteger(hDB, pszSQL, &err);
4352 491 : if (err != OGRERR_NONE)
4353 459 : mdId = -1;
4354 491 : sqlite3_free(pszSQL);
4355 :
4356 491 : if (bIsEmpty)
4357 : {
4358 153 : if (mdId >= 0)
4359 : {
4360 6 : SQLCommand(
4361 : hDB,
4362 : CPLSPrintf(
4363 : "DELETE FROM gpkg_metadata_reference WHERE md_file_id = %d",
4364 : mdId));
4365 6 : SQLCommand(
4366 : hDB,
4367 : CPLSPrintf("DELETE FROM gpkg_metadata WHERE id = %d", mdId));
4368 : }
4369 : }
4370 : else
4371 : {
4372 338 : if (mdId >= 0)
4373 : {
4374 26 : pszSQL = sqlite3_mprintf(
4375 : "UPDATE gpkg_metadata SET metadata = '%q' WHERE id = %d",
4376 : pszXML, mdId);
4377 : }
4378 : else
4379 : {
4380 : pszSQL =
4381 312 : sqlite3_mprintf("INSERT INTO gpkg_metadata (md_scope, "
4382 : "md_standard_uri, mime_type, metadata) VALUES "
4383 : "('dataset','http://gdal.org','text/xml','%q')",
4384 : pszXML);
4385 : }
4386 338 : SQLCommand(hDB, pszSQL);
4387 338 : sqlite3_free(pszSQL);
4388 :
4389 338 : CPLFree(pszXML);
4390 :
4391 338 : if (mdId < 0)
4392 : {
4393 312 : const sqlite_int64 nFID = sqlite3_last_insert_rowid(hDB);
4394 312 : if (pszTableName != nullptr && pszTableName[0] != '\0')
4395 : {
4396 300 : pszSQL = sqlite3_mprintf(
4397 : "INSERT INTO gpkg_metadata_reference (reference_scope, "
4398 : "table_name, timestamp, md_file_id) VALUES "
4399 : "('table', '%q', %s, %d)",
4400 600 : pszTableName, GetCurrentDateEscapedSQL().c_str(),
4401 : static_cast<int>(nFID));
4402 : }
4403 : else
4404 : {
4405 12 : pszSQL = sqlite3_mprintf(
4406 : "INSERT INTO gpkg_metadata_reference (reference_scope, "
4407 : "timestamp, md_file_id) VALUES "
4408 : "('geopackage', %s, %d)",
4409 24 : GetCurrentDateEscapedSQL().c_str(), static_cast<int>(nFID));
4410 : }
4411 : }
4412 : else
4413 : {
4414 26 : pszSQL = sqlite3_mprintf("UPDATE gpkg_metadata_reference SET "
4415 : "timestamp = %s WHERE md_file_id = %d",
4416 52 : GetCurrentDateEscapedSQL().c_str(), mdId);
4417 : }
4418 338 : SQLCommand(hDB, pszSQL);
4419 338 : sqlite3_free(pszSQL);
4420 : }
4421 : }
4422 :
4423 : /************************************************************************/
4424 : /* CreateMetadataTables() */
4425 : /************************************************************************/
4426 :
4427 309 : bool GDALGeoPackageDataset::CreateMetadataTables()
4428 : {
4429 : const bool bCreateTriggers =
4430 309 : CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "NO"));
4431 :
4432 : /* From C.10. gpkg_metadata Table 35. gpkg_metadata Table Definition SQL */
4433 : CPLString osSQL = "CREATE TABLE gpkg_metadata ("
4434 : "id INTEGER CONSTRAINT m_pk PRIMARY KEY ASC NOT NULL,"
4435 : "md_scope TEXT NOT NULL DEFAULT 'dataset',"
4436 : "md_standard_uri TEXT NOT NULL,"
4437 : "mime_type TEXT NOT NULL DEFAULT 'text/xml',"
4438 : "metadata TEXT NOT NULL DEFAULT ''"
4439 618 : ")";
4440 :
4441 : /* From D.2. metadata Table 40. metadata Trigger Definition SQL */
4442 309 : const char *pszMetadataTriggers =
4443 : "CREATE TRIGGER 'gpkg_metadata_md_scope_insert' "
4444 : "BEFORE INSERT ON 'gpkg_metadata' "
4445 : "FOR EACH ROW BEGIN "
4446 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata violates "
4447 : "constraint: md_scope must be one of undefined | fieldSession | "
4448 : "collectionSession | series | dataset | featureType | feature | "
4449 : "attributeType | attribute | tile | model | catalogue | schema | "
4450 : "taxonomy software | service | collectionHardware | "
4451 : "nonGeographicDataset | dimensionGroup') "
4452 : "WHERE NOT(NEW.md_scope IN "
4453 : "('undefined','fieldSession','collectionSession','series','dataset', "
4454 : "'featureType','feature','attributeType','attribute','tile','model', "
4455 : "'catalogue','schema','taxonomy','software','service', "
4456 : "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
4457 : "END; "
4458 : "CREATE TRIGGER 'gpkg_metadata_md_scope_update' "
4459 : "BEFORE UPDATE OF 'md_scope' ON 'gpkg_metadata' "
4460 : "FOR EACH ROW BEGIN "
4461 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata violates "
4462 : "constraint: md_scope must be one of undefined | fieldSession | "
4463 : "collectionSession | series | dataset | featureType | feature | "
4464 : "attributeType | attribute | tile | model | catalogue | schema | "
4465 : "taxonomy software | service | collectionHardware | "
4466 : "nonGeographicDataset | dimensionGroup') "
4467 : "WHERE NOT(NEW.md_scope IN "
4468 : "('undefined','fieldSession','collectionSession','series','dataset', "
4469 : "'featureType','feature','attributeType','attribute','tile','model', "
4470 : "'catalogue','schema','taxonomy','software','service', "
4471 : "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
4472 : "END";
4473 309 : if (bCreateTriggers)
4474 : {
4475 0 : osSQL += ";";
4476 0 : osSQL += pszMetadataTriggers;
4477 : }
4478 :
4479 : /* From C.11. gpkg_metadata_reference Table 36. gpkg_metadata_reference
4480 : * Table Definition SQL */
4481 : osSQL += ";"
4482 : "CREATE TABLE gpkg_metadata_reference ("
4483 : "reference_scope TEXT NOT NULL,"
4484 : "table_name TEXT,"
4485 : "column_name TEXT,"
4486 : "row_id_value INTEGER,"
4487 : "timestamp DATETIME NOT NULL DEFAULT "
4488 : "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
4489 : "md_file_id INTEGER NOT NULL,"
4490 : "md_parent_id INTEGER,"
4491 : "CONSTRAINT crmr_mfi_fk FOREIGN KEY (md_file_id) REFERENCES "
4492 : "gpkg_metadata(id),"
4493 : "CONSTRAINT crmr_mpi_fk FOREIGN KEY (md_parent_id) REFERENCES "
4494 : "gpkg_metadata(id)"
4495 309 : ")";
4496 :
4497 : /* From D.3. metadata_reference Table 41. gpkg_metadata_reference Trigger
4498 : * Definition SQL */
4499 309 : const char *pszMetadataReferenceTriggers =
4500 : "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_insert' "
4501 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4502 : "FOR EACH ROW BEGIN "
4503 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4504 : "violates constraint: reference_scope must be one of \"geopackage\", "
4505 : "table\", \"column\", \"row\", \"row/col\"') "
4506 : "WHERE NOT NEW.reference_scope IN "
4507 : "('geopackage','table','column','row','row/col'); "
4508 : "END; "
4509 : "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_update' "
4510 : "BEFORE UPDATE OF 'reference_scope' ON 'gpkg_metadata_reference' "
4511 : "FOR EACH ROW BEGIN "
4512 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4513 : "violates constraint: reference_scope must be one of \"geopackage\", "
4514 : "\"table\", \"column\", \"row\", \"row/col\"') "
4515 : "WHERE NOT NEW.reference_scope IN "
4516 : "('geopackage','table','column','row','row/col'); "
4517 : "END; "
4518 : "CREATE TRIGGER 'gpkg_metadata_reference_column_name_insert' "
4519 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4520 : "FOR EACH ROW BEGIN "
4521 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4522 : "violates constraint: column name must be NULL when reference_scope "
4523 : "is \"geopackage\", \"table\" or \"row\"') "
4524 : "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
4525 : "AND NEW.column_name IS NOT NULL); "
4526 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4527 : "violates constraint: column name must be defined for the specified "
4528 : "table when reference_scope is \"column\" or \"row/col\"') "
4529 : "WHERE (NEW.reference_scope IN ('column','row/col') "
4530 : "AND NOT NEW.table_name IN ( "
4531 : "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
4532 : "AND name = NEW.table_name "
4533 : "AND sql LIKE ('%' || NEW.column_name || '%'))); "
4534 : "END; "
4535 : "CREATE TRIGGER 'gpkg_metadata_reference_column_name_update' "
4536 : "BEFORE UPDATE OF column_name ON 'gpkg_metadata_reference' "
4537 : "FOR EACH ROW BEGIN "
4538 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4539 : "violates constraint: column name must be NULL when reference_scope "
4540 : "is \"geopackage\", \"table\" or \"row\"') "
4541 : "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
4542 : "AND NEW.column_name IS NOT NULL); "
4543 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4544 : "violates constraint: column name must be defined for the specified "
4545 : "table when reference_scope is \"column\" or \"row/col\"') "
4546 : "WHERE (NEW.reference_scope IN ('column','row/col') "
4547 : "AND NOT NEW.table_name IN ( "
4548 : "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
4549 : "AND name = NEW.table_name "
4550 : "AND sql LIKE ('%' || NEW.column_name || '%'))); "
4551 : "END; "
4552 : "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_insert' "
4553 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4554 : "FOR EACH ROW BEGIN "
4555 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4556 : "violates constraint: row_id_value must be NULL when reference_scope "
4557 : "is \"geopackage\", \"table\" or \"column\"') "
4558 : "WHERE NEW.reference_scope IN ('geopackage','table','column') "
4559 : "AND NEW.row_id_value IS NOT NULL; "
4560 : "END; "
4561 : "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_update' "
4562 : "BEFORE UPDATE OF 'row_id_value' ON 'gpkg_metadata_reference' "
4563 : "FOR EACH ROW BEGIN "
4564 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4565 : "violates constraint: row_id_value must be NULL when reference_scope "
4566 : "is \"geopackage\", \"table\" or \"column\"') "
4567 : "WHERE NEW.reference_scope IN ('geopackage','table','column') "
4568 : "AND NEW.row_id_value IS NOT NULL; "
4569 : "END; "
4570 : "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_insert' "
4571 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4572 : "FOR EACH ROW BEGIN "
4573 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4574 : "violates constraint: timestamp must be a valid time in ISO 8601 "
4575 : "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
4576 : "WHERE NOT (NEW.timestamp GLOB "
4577 : "'[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-"
4578 : "5][0-9].[0-9][0-9][0-9]Z' "
4579 : "AND strftime('%s',NEW.timestamp) NOT NULL); "
4580 : "END; "
4581 : "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_update' "
4582 : "BEFORE UPDATE OF 'timestamp' ON 'gpkg_metadata_reference' "
4583 : "FOR EACH ROW BEGIN "
4584 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4585 : "violates constraint: timestamp must be a valid time in ISO 8601 "
4586 : "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
4587 : "WHERE NOT (NEW.timestamp GLOB "
4588 : "'[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-"
4589 : "5][0-9].[0-9][0-9][0-9]Z' "
4590 : "AND strftime('%s',NEW.timestamp) NOT NULL); "
4591 : "END";
4592 309 : if (bCreateTriggers)
4593 : {
4594 0 : osSQL += ";";
4595 0 : osSQL += pszMetadataReferenceTriggers;
4596 : }
4597 :
4598 309 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
4599 2 : return false;
4600 :
4601 307 : osSQL += ";";
4602 : osSQL += "INSERT INTO gpkg_extensions "
4603 : "(table_name, column_name, extension_name, definition, scope) "
4604 : "VALUES "
4605 : "('gpkg_metadata', NULL, 'gpkg_metadata', "
4606 : "'http://www.geopackage.org/spec120/#extension_metadata', "
4607 307 : "'read-write')";
4608 :
4609 307 : osSQL += ";";
4610 : osSQL += "INSERT INTO gpkg_extensions "
4611 : "(table_name, column_name, extension_name, definition, scope) "
4612 : "VALUES "
4613 : "('gpkg_metadata_reference', NULL, 'gpkg_metadata', "
4614 : "'http://www.geopackage.org/spec120/#extension_metadata', "
4615 307 : "'read-write')";
4616 :
4617 307 : const bool bOK = SQLCommand(hDB, osSQL) == OGRERR_NONE;
4618 307 : m_nHasMetadataTables = bOK;
4619 307 : return bOK;
4620 : }
4621 :
4622 : /************************************************************************/
4623 : /* FlushMetadata() */
4624 : /************************************************************************/
4625 :
4626 8934 : void GDALGeoPackageDataset::FlushMetadata()
4627 : {
4628 8934 : if (!m_bMetadataDirty || m_poParentDS != nullptr ||
4629 368 : m_nCreateMetadataTables == FALSE)
4630 8572 : return;
4631 362 : m_bMetadataDirty = false;
4632 :
4633 362 : if (eAccess == GA_ReadOnly)
4634 : {
4635 3 : return;
4636 : }
4637 :
4638 359 : bool bCanWriteAreaOrPoint =
4639 716 : !m_bGridCellEncodingAsCO &&
4640 357 : (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT);
4641 359 : if (!m_osRasterTable.empty())
4642 : {
4643 : const char *pszIdentifier =
4644 144 : GDALGeoPackageDataset::GetMetadataItem("IDENTIFIER");
4645 : const char *pszDescription =
4646 144 : GDALGeoPackageDataset::GetMetadataItem("DESCRIPTION");
4647 173 : if (!m_bIdentifierAsCO && pszIdentifier != nullptr &&
4648 29 : pszIdentifier != m_osIdentifier)
4649 : {
4650 14 : m_osIdentifier = pszIdentifier;
4651 : char *pszSQL =
4652 14 : sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
4653 : "WHERE lower(table_name) = lower('%q')",
4654 : pszIdentifier, m_osRasterTable.c_str());
4655 14 : SQLCommand(hDB, pszSQL);
4656 14 : sqlite3_free(pszSQL);
4657 : }
4658 151 : if (!m_bDescriptionAsCO && pszDescription != nullptr &&
4659 7 : pszDescription != m_osDescription)
4660 : {
4661 7 : m_osDescription = pszDescription;
4662 : char *pszSQL =
4663 7 : sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
4664 : "WHERE lower(table_name) = lower('%q')",
4665 : pszDescription, m_osRasterTable.c_str());
4666 7 : SQLCommand(hDB, pszSQL);
4667 7 : sqlite3_free(pszSQL);
4668 : }
4669 144 : if (bCanWriteAreaOrPoint)
4670 : {
4671 : const char *pszAreaOrPoint =
4672 28 : GDALGeoPackageDataset::GetMetadataItem(GDALMD_AREA_OR_POINT);
4673 28 : if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_AREA))
4674 : {
4675 23 : bCanWriteAreaOrPoint = false;
4676 23 : char *pszSQL = sqlite3_mprintf(
4677 : "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
4678 : "grid_cell_encoding = 'grid-value-is-area' WHERE "
4679 : "lower(tile_matrix_set_name) = lower('%q')",
4680 : m_osRasterTable.c_str());
4681 23 : SQLCommand(hDB, pszSQL);
4682 23 : sqlite3_free(pszSQL);
4683 : }
4684 5 : else if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_POINT))
4685 : {
4686 1 : bCanWriteAreaOrPoint = false;
4687 1 : char *pszSQL = sqlite3_mprintf(
4688 : "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
4689 : "grid_cell_encoding = 'grid-value-is-center' WHERE "
4690 : "lower(tile_matrix_set_name) = lower('%q')",
4691 : m_osRasterTable.c_str());
4692 1 : SQLCommand(hDB, pszSQL);
4693 1 : sqlite3_free(pszSQL);
4694 : }
4695 : }
4696 : }
4697 :
4698 359 : char **papszMDDup = nullptr;
4699 205 : for (const char *pszKeyValue :
4700 769 : cpl::Iterate(GDALGeoPackageDataset::GetMetadata()))
4701 : {
4702 205 : if (STARTS_WITH_CI(pszKeyValue, "IDENTIFIER="))
4703 29 : continue;
4704 176 : if (STARTS_WITH_CI(pszKeyValue, "DESCRIPTION="))
4705 8 : continue;
4706 168 : if (STARTS_WITH_CI(pszKeyValue, "ZOOM_LEVEL="))
4707 14 : continue;
4708 154 : if (STARTS_WITH_CI(pszKeyValue, "GPKG_METADATA_ITEM_"))
4709 4 : continue;
4710 150 : if ((m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT) &&
4711 29 : !bCanWriteAreaOrPoint &&
4712 26 : STARTS_WITH_CI(pszKeyValue, GDALMD_AREA_OR_POINT))
4713 : {
4714 26 : continue;
4715 : }
4716 124 : papszMDDup = CSLInsertString(papszMDDup, -1, pszKeyValue);
4717 : }
4718 :
4719 359 : CPLXMLNode *psXMLNode = nullptr;
4720 : {
4721 359 : GDALMultiDomainMetadata oLocalMDMD;
4722 359 : CSLConstList papszDomainList = oMDMD.GetDomainList();
4723 359 : CSLConstList papszIter = papszDomainList;
4724 359 : oLocalMDMD.SetMetadata(papszMDDup);
4725 688 : while (papszIter && *papszIter)
4726 : {
4727 329 : if (!EQUAL(*papszIter, "") &&
4728 159 : !EQUAL(*papszIter, "IMAGE_STRUCTURE") &&
4729 15 : !EQUAL(*papszIter, "GEOPACKAGE"))
4730 : {
4731 8 : oLocalMDMD.SetMetadata(oMDMD.GetMetadata(*papszIter),
4732 : *papszIter);
4733 : }
4734 329 : papszIter++;
4735 : }
4736 359 : if (m_nBandCountFromMetadata > 0)
4737 : {
4738 74 : oLocalMDMD.SetMetadataItem(
4739 : "BAND_COUNT", CPLSPrintf("%d", m_nBandCountFromMetadata),
4740 : "IMAGE_STRUCTURE");
4741 74 : if (nBands == 1)
4742 : {
4743 50 : const auto poCT = GetRasterBand(1)->GetColorTable();
4744 50 : if (poCT)
4745 : {
4746 16 : std::string osVal("{");
4747 8 : const int nColorCount = poCT->GetColorEntryCount();
4748 2056 : for (int i = 0; i < nColorCount; ++i)
4749 : {
4750 2048 : if (i > 0)
4751 2040 : osVal += ',';
4752 2048 : const GDALColorEntry *psEntry = poCT->GetColorEntry(i);
4753 : osVal +=
4754 2048 : CPLSPrintf("{%d,%d,%d,%d}", psEntry->c1,
4755 2048 : psEntry->c2, psEntry->c3, psEntry->c4);
4756 : }
4757 8 : osVal += '}';
4758 8 : oLocalMDMD.SetMetadataItem("COLOR_TABLE", osVal.c_str(),
4759 : "IMAGE_STRUCTURE");
4760 : }
4761 : }
4762 74 : if (nBands == 1)
4763 : {
4764 50 : const char *pszTILE_FORMAT = nullptr;
4765 50 : switch (m_eTF)
4766 : {
4767 0 : case GPKG_TF_PNG_JPEG:
4768 0 : pszTILE_FORMAT = "JPEG_PNG";
4769 0 : break;
4770 44 : case GPKG_TF_PNG:
4771 44 : break;
4772 0 : case GPKG_TF_PNG8:
4773 0 : pszTILE_FORMAT = "PNG8";
4774 0 : break;
4775 3 : case GPKG_TF_JPEG:
4776 3 : pszTILE_FORMAT = "JPEG";
4777 3 : break;
4778 3 : case GPKG_TF_WEBP:
4779 3 : pszTILE_FORMAT = "WEBP";
4780 3 : break;
4781 0 : case GPKG_TF_PNG_16BIT:
4782 0 : break;
4783 0 : case GPKG_TF_TIFF_32BIT_FLOAT:
4784 0 : break;
4785 : }
4786 50 : if (pszTILE_FORMAT)
4787 6 : oLocalMDMD.SetMetadataItem("TILE_FORMAT", pszTILE_FORMAT,
4788 : "IMAGE_STRUCTURE");
4789 : }
4790 : }
4791 503 : if (GetRasterCount() > 0 &&
4792 144 : GetRasterBand(1)->GetRasterDataType() == GDT_UInt8)
4793 : {
4794 114 : int bHasNoData = FALSE;
4795 : const double dfNoDataValue =
4796 114 : GetRasterBand(1)->GetNoDataValue(&bHasNoData);
4797 114 : if (bHasNoData)
4798 : {
4799 3 : oLocalMDMD.SetMetadataItem("NODATA_VALUE",
4800 : CPLSPrintf("%.17g", dfNoDataValue),
4801 : "IMAGE_STRUCTURE");
4802 : }
4803 : }
4804 608 : for (int i = 1; i <= GetRasterCount(); ++i)
4805 : {
4806 : auto poBand =
4807 249 : cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
4808 249 : poBand->AddImplicitStatistics(false);
4809 249 : CSLConstList papszMD = GetRasterBand(i)->GetMetadata();
4810 249 : poBand->AddImplicitStatistics(true);
4811 249 : if (papszMD)
4812 : {
4813 14 : oLocalMDMD.SetMetadata(papszMD, CPLSPrintf("BAND_%d", i));
4814 : }
4815 : }
4816 359 : psXMLNode = oLocalMDMD.Serialize();
4817 : }
4818 :
4819 359 : CSLDestroy(papszMDDup);
4820 359 : papszMDDup = nullptr;
4821 :
4822 359 : WriteMetadata(psXMLNode, m_osRasterTable.c_str());
4823 :
4824 359 : if (!m_osRasterTable.empty())
4825 : {
4826 : CSLConstList papszGeopackageMD =
4827 144 : GDALGeoPackageDataset::GetMetadata("GEOPACKAGE");
4828 :
4829 144 : papszMDDup = nullptr;
4830 144 : for (CSLConstList papszIter = papszGeopackageMD;
4831 153 : papszIter && *papszIter; ++papszIter)
4832 : {
4833 9 : papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
4834 : }
4835 :
4836 288 : GDALMultiDomainMetadata oLocalMDMD;
4837 144 : oLocalMDMD.SetMetadata(papszMDDup);
4838 144 : CSLDestroy(papszMDDup);
4839 144 : papszMDDup = nullptr;
4840 144 : psXMLNode = oLocalMDMD.Serialize();
4841 :
4842 144 : WriteMetadata(psXMLNode, nullptr);
4843 : }
4844 :
4845 591 : for (auto &poLayer : m_apoLayers)
4846 : {
4847 232 : const char *pszIdentifier = poLayer->GetMetadataItem("IDENTIFIER");
4848 232 : const char *pszDescription = poLayer->GetMetadataItem("DESCRIPTION");
4849 232 : if (pszIdentifier != nullptr)
4850 : {
4851 : char *pszSQL =
4852 3 : sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
4853 : "WHERE lower(table_name) = lower('%q')",
4854 : pszIdentifier, poLayer->GetName());
4855 3 : SQLCommand(hDB, pszSQL);
4856 3 : sqlite3_free(pszSQL);
4857 : }
4858 232 : if (pszDescription != nullptr)
4859 : {
4860 : char *pszSQL =
4861 3 : sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
4862 : "WHERE lower(table_name) = lower('%q')",
4863 : pszDescription, poLayer->GetName());
4864 3 : SQLCommand(hDB, pszSQL);
4865 3 : sqlite3_free(pszSQL);
4866 : }
4867 :
4868 232 : papszMDDup = nullptr;
4869 628 : for (CSLConstList papszIter = poLayer->GetMetadata();
4870 628 : papszIter && *papszIter; ++papszIter)
4871 : {
4872 396 : if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
4873 3 : continue;
4874 393 : if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
4875 3 : continue;
4876 390 : if (STARTS_WITH_CI(*papszIter, "OLMD_FID64="))
4877 0 : continue;
4878 390 : papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
4879 : }
4880 :
4881 : {
4882 232 : GDALMultiDomainMetadata oLocalMDMD;
4883 232 : char **papszDomainList = poLayer->GetMetadataDomainList();
4884 232 : char **papszIter = papszDomainList;
4885 232 : oLocalMDMD.SetMetadata(papszMDDup);
4886 517 : while (papszIter && *papszIter)
4887 : {
4888 285 : if (!EQUAL(*papszIter, ""))
4889 66 : oLocalMDMD.SetMetadata(poLayer->GetMetadata(*papszIter),
4890 : *papszIter);
4891 285 : papszIter++;
4892 : }
4893 232 : CSLDestroy(papszDomainList);
4894 232 : psXMLNode = oLocalMDMD.Serialize();
4895 : }
4896 :
4897 232 : CSLDestroy(papszMDDup);
4898 232 : papszMDDup = nullptr;
4899 :
4900 232 : WriteMetadata(psXMLNode, poLayer->GetName());
4901 : }
4902 : }
4903 :
4904 : /************************************************************************/
4905 : /* GetMetadataItem() */
4906 : /************************************************************************/
4907 :
4908 1927 : const char *GDALGeoPackageDataset::GetMetadataItem(const char *pszName,
4909 : const char *pszDomain)
4910 : {
4911 1927 : pszDomain = CheckMetadataDomain(pszDomain);
4912 1927 : return CSLFetchNameValue(GetMetadata(pszDomain), pszName);
4913 : }
4914 :
4915 : /************************************************************************/
4916 : /* SetMetadata() */
4917 : /************************************************************************/
4918 :
4919 133 : CPLErr GDALGeoPackageDataset::SetMetadata(CSLConstList papszMetadata,
4920 : const char *pszDomain)
4921 : {
4922 133 : pszDomain = CheckMetadataDomain(pszDomain);
4923 133 : m_bMetadataDirty = true;
4924 133 : GetMetadata(); /* force loading from storage if needed */
4925 133 : return GDALPamDataset::SetMetadata(papszMetadata, pszDomain);
4926 : }
4927 :
4928 : /************************************************************************/
4929 : /* SetMetadataItem() */
4930 : /************************************************************************/
4931 :
4932 21 : CPLErr GDALGeoPackageDataset::SetMetadataItem(const char *pszName,
4933 : const char *pszValue,
4934 : const char *pszDomain)
4935 : {
4936 21 : pszDomain = CheckMetadataDomain(pszDomain);
4937 21 : m_bMetadataDirty = true;
4938 21 : GetMetadata(); /* force loading from storage if needed */
4939 21 : return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
4940 : }
4941 :
4942 : /************************************************************************/
4943 : /* Create() */
4944 : /************************************************************************/
4945 :
4946 1020 : int GDALGeoPackageDataset::Create(const char *pszFilename, int nXSize,
4947 : int nYSize, int nBandsIn, GDALDataType eDT,
4948 : CSLConstList papszOptions)
4949 : {
4950 2040 : CPLString osCommand;
4951 :
4952 : /* First, ensure there isn't any such file yet. */
4953 : VSIStatBufL sStatBuf;
4954 :
4955 1020 : if (nBandsIn != 0)
4956 : {
4957 226 : if (eDT == GDT_UInt8)
4958 : {
4959 156 : if (nBandsIn != 1 && nBandsIn != 2 && nBandsIn != 3 &&
4960 : nBandsIn != 4)
4961 : {
4962 1 : CPLError(CE_Failure, CPLE_NotSupported,
4963 : "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), "
4964 : "3 (RGB) or 4 (RGBA) band dataset supported for "
4965 : "Byte datatype");
4966 1 : return FALSE;
4967 : }
4968 : }
4969 70 : else if (eDT == GDT_Int16 || eDT == GDT_UInt16 || eDT == GDT_Float32)
4970 : {
4971 43 : if (nBandsIn != 1)
4972 : {
4973 3 : CPLError(CE_Failure, CPLE_NotSupported,
4974 : "Only single band dataset supported for non Byte "
4975 : "datatype");
4976 3 : return FALSE;
4977 : }
4978 : }
4979 : else
4980 : {
4981 27 : CPLError(CE_Failure, CPLE_NotSupported,
4982 : "Only Byte, Int16, UInt16 or Float32 supported");
4983 27 : return FALSE;
4984 : }
4985 : }
4986 :
4987 989 : const size_t nFilenameLen = strlen(pszFilename);
4988 989 : const bool bGpkgZip =
4989 984 : (nFilenameLen > strlen(".gpkg.zip") &&
4990 1973 : !STARTS_WITH(pszFilename, "/vsizip/") &&
4991 984 : EQUAL(pszFilename + nFilenameLen - strlen(".gpkg.zip"), ".gpkg.zip"));
4992 :
4993 : const bool bUseTempFile =
4994 990 : bGpkgZip || (CPLTestBool(CPLGetConfigOption(
4995 1 : "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", "NO")) &&
4996 1 : (VSIHasOptimizedReadMultiRange(pszFilename) != FALSE ||
4997 1 : EQUAL(CPLGetConfigOption(
4998 : "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", ""),
4999 989 : "FORCED")));
5000 :
5001 989 : bool bFileExists = false;
5002 989 : if (VSIStatL(pszFilename, &sStatBuf) == 0)
5003 : {
5004 10 : bFileExists = true;
5005 20 : if (nBandsIn == 0 || bUseTempFile ||
5006 10 : !CPLTestBool(
5007 : CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")))
5008 : {
5009 0 : CPLError(CE_Failure, CPLE_AppDefined,
5010 : "A file system object called '%s' already exists.",
5011 : pszFilename);
5012 :
5013 0 : return FALSE;
5014 : }
5015 : }
5016 :
5017 989 : if (bUseTempFile)
5018 : {
5019 3 : if (bGpkgZip)
5020 : {
5021 2 : std::string osFilenameInZip(CPLGetFilename(pszFilename));
5022 2 : osFilenameInZip.resize(osFilenameInZip.size() - strlen(".zip"));
5023 : m_osFinalFilename =
5024 2 : std::string("/vsizip/{") + pszFilename + "}/" + osFilenameInZip;
5025 : }
5026 : else
5027 : {
5028 1 : m_osFinalFilename = pszFilename;
5029 : }
5030 3 : m_pszFilename = CPLStrdup(
5031 6 : CPLGenerateTempFilenameSafe(CPLGetFilename(pszFilename)).c_str());
5032 3 : CPLDebug("GPKG", "Creating temporary file %s", m_pszFilename);
5033 : }
5034 : else
5035 : {
5036 986 : m_pszFilename = CPLStrdup(pszFilename);
5037 : }
5038 989 : m_bNew = true;
5039 989 : eAccess = GA_Update;
5040 989 : m_bDateTimeWithTZ =
5041 989 : EQUAL(CSLFetchNameValueDef(papszOptions, "DATETIME_FORMAT", "WITH_TZ"),
5042 : "WITH_TZ");
5043 :
5044 : // for test/debug purposes only. true is the nominal value
5045 989 : m_bPNGSupports2Bands =
5046 989 : CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_2BANDS", "TRUE"));
5047 989 : m_bPNGSupportsCT =
5048 989 : CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_CT", "TRUE"));
5049 :
5050 989 : if (!OpenOrCreateDB(bFileExists
5051 : ? SQLITE_OPEN_READWRITE
5052 : : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE))
5053 8 : return FALSE;
5054 :
5055 : /* Default to synchronous=off for performance for new file */
5056 1952 : if (!bFileExists &&
5057 971 : CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
5058 : {
5059 457 : SQLCommand(hDB, "PRAGMA synchronous = OFF");
5060 : }
5061 :
5062 : /* OGR UTF-8 support. If we set the UTF-8 Pragma early on, it */
5063 : /* will be written into the main file and supported henceforth */
5064 981 : SQLCommand(hDB, "PRAGMA encoding = \"UTF-8\"");
5065 :
5066 981 : if (bFileExists)
5067 : {
5068 10 : VSILFILE *fp = VSIFOpenL(pszFilename, "rb");
5069 10 : if (fp)
5070 : {
5071 : GByte abyHeader[100];
5072 10 : VSIFReadL(abyHeader, 1, sizeof(abyHeader), fp);
5073 10 : VSIFCloseL(fp);
5074 :
5075 10 : memcpy(&m_nApplicationId, abyHeader + knApplicationIdPos, 4);
5076 10 : m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
5077 10 : memcpy(&m_nUserVersion, abyHeader + knUserVersionPos, 4);
5078 10 : m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
5079 :
5080 10 : if (m_nApplicationId == GP10_APPLICATION_ID)
5081 : {
5082 0 : CPLDebug("GPKG", "GeoPackage v1.0");
5083 : }
5084 10 : else if (m_nApplicationId == GP11_APPLICATION_ID)
5085 : {
5086 0 : CPLDebug("GPKG", "GeoPackage v1.1");
5087 : }
5088 10 : else if (m_nApplicationId == GPKG_APPLICATION_ID &&
5089 10 : m_nUserVersion >= GPKG_1_2_VERSION)
5090 : {
5091 10 : CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
5092 10 : (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
5093 : }
5094 : }
5095 :
5096 10 : DetectSpatialRefSysColumns();
5097 : }
5098 :
5099 981 : const char *pszVersion = CSLFetchNameValue(papszOptions, "VERSION");
5100 981 : if (pszVersion && !EQUAL(pszVersion, "AUTO"))
5101 : {
5102 40 : if (EQUAL(pszVersion, "1.0"))
5103 : {
5104 2 : m_nApplicationId = GP10_APPLICATION_ID;
5105 2 : m_nUserVersion = 0;
5106 : }
5107 38 : else if (EQUAL(pszVersion, "1.1"))
5108 : {
5109 1 : m_nApplicationId = GP11_APPLICATION_ID;
5110 1 : m_nUserVersion = 0;
5111 : }
5112 37 : else if (EQUAL(pszVersion, "1.2"))
5113 : {
5114 15 : m_nApplicationId = GPKG_APPLICATION_ID;
5115 15 : m_nUserVersion = GPKG_1_2_VERSION;
5116 : }
5117 22 : else if (EQUAL(pszVersion, "1.3"))
5118 : {
5119 3 : m_nApplicationId = GPKG_APPLICATION_ID;
5120 3 : m_nUserVersion = GPKG_1_3_VERSION;
5121 : }
5122 19 : else if (EQUAL(pszVersion, "1.4"))
5123 : {
5124 19 : m_nApplicationId = GPKG_APPLICATION_ID;
5125 19 : m_nUserVersion = GPKG_1_4_VERSION;
5126 : }
5127 : }
5128 :
5129 981 : SoftStartTransaction();
5130 :
5131 1962 : CPLString osSQL;
5132 981 : if (!bFileExists)
5133 : {
5134 : /* Requirement 10: A GeoPackage SHALL include a gpkg_spatial_ref_sys
5135 : * table */
5136 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5137 : osSQL = "CREATE TABLE gpkg_spatial_ref_sys ("
5138 : "srs_name TEXT NOT NULL,"
5139 : "srs_id INTEGER NOT NULL PRIMARY KEY,"
5140 : "organization TEXT NOT NULL,"
5141 : "organization_coordsys_id INTEGER NOT NULL,"
5142 : "definition TEXT NOT NULL,"
5143 971 : "description TEXT";
5144 971 : if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "CRS_WKT_EXTENSION",
5145 1152 : "NO")) ||
5146 181 : (nBandsIn != 0 && eDT != GDT_UInt8))
5147 : {
5148 42 : m_bHasDefinition12_063 = true;
5149 42 : osSQL += ", definition_12_063 TEXT NOT NULL";
5150 42 : if (m_nUserVersion >= GPKG_1_4_VERSION)
5151 : {
5152 40 : osSQL += ", epoch DOUBLE";
5153 40 : m_bHasEpochColumn = true;
5154 : }
5155 : }
5156 : osSQL += ")"
5157 : ";"
5158 : /* Requirement 11: The gpkg_spatial_ref_sys table in a
5159 : GeoPackage SHALL */
5160 : /* contain a record for EPSG:4326, the geodetic WGS84 SRS */
5161 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5162 :
5163 : "INSERT INTO gpkg_spatial_ref_sys ("
5164 : "srs_name, srs_id, organization, organization_coordsys_id, "
5165 971 : "definition, description";
5166 971 : if (m_bHasDefinition12_063)
5167 42 : osSQL += ", definition_12_063";
5168 : osSQL +=
5169 : ") VALUES ("
5170 : "'WGS 84 geodetic', 4326, 'EPSG', 4326, '"
5171 : "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS "
5172 : "84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],"
5173 : "AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY["
5174 : "\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY["
5175 : "\"EPSG\",\"9122\"]],AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\","
5176 : "EAST],AUTHORITY[\"EPSG\",\"4326\"]]"
5177 : "', 'longitude/latitude coordinates in decimal degrees on the WGS "
5178 971 : "84 spheroid'";
5179 971 : if (m_bHasDefinition12_063)
5180 : osSQL +=
5181 : ", 'GEODCRS[\"WGS 84\", DATUM[\"World Geodetic System 1984\", "
5182 : "ELLIPSOID[\"WGS 84\",6378137, 298.257223563, "
5183 : "LENGTHUNIT[\"metre\", 1.0]]], PRIMEM[\"Greenwich\", 0.0, "
5184 : "ANGLEUNIT[\"degree\",0.0174532925199433]], CS[ellipsoidal, "
5185 : "2], AXIS[\"latitude\", north, ORDER[1]], AXIS[\"longitude\", "
5186 : "east, ORDER[2]], ANGLEUNIT[\"degree\", 0.0174532925199433], "
5187 42 : "ID[\"EPSG\", 4326]]'";
5188 : osSQL +=
5189 : ")"
5190 : ";"
5191 : /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
5192 : SHALL */
5193 : /* contain a record with an srs_id of -1, an organization of “NONE”,
5194 : */
5195 : /* an organization_coordsys_id of -1, and definition “undefined” */
5196 : /* for undefined Cartesian coordinate reference systems */
5197 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5198 : "INSERT INTO gpkg_spatial_ref_sys ("
5199 : "srs_name, srs_id, organization, organization_coordsys_id, "
5200 971 : "definition, description";
5201 971 : if (m_bHasDefinition12_063)
5202 42 : osSQL += ", definition_12_063";
5203 : osSQL += ") VALUES ("
5204 : "'Undefined Cartesian SRS', -1, 'NONE', -1, 'undefined', "
5205 971 : "'undefined Cartesian coordinate reference system'";
5206 971 : if (m_bHasDefinition12_063)
5207 42 : osSQL += ", 'undefined'";
5208 : osSQL +=
5209 : ")"
5210 : ";"
5211 : /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
5212 : SHALL */
5213 : /* contain a record with an srs_id of 0, an organization of “NONE”,
5214 : */
5215 : /* an organization_coordsys_id of 0, and definition “undefined” */
5216 : /* for undefined geographic coordinate reference systems */
5217 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5218 : "INSERT INTO gpkg_spatial_ref_sys ("
5219 : "srs_name, srs_id, organization, organization_coordsys_id, "
5220 971 : "definition, description";
5221 971 : if (m_bHasDefinition12_063)
5222 42 : osSQL += ", definition_12_063";
5223 : osSQL += ") VALUES ("
5224 : "'Undefined geographic SRS', 0, 'NONE', 0, 'undefined', "
5225 971 : "'undefined geographic coordinate reference system'";
5226 971 : if (m_bHasDefinition12_063)
5227 42 : osSQL += ", 'undefined'";
5228 : osSQL += ")"
5229 : ";"
5230 : /* Requirement 13: A GeoPackage file SHALL include a
5231 : gpkg_contents table */
5232 : /* http://opengis.github.io/geopackage/#_contents */
5233 : "CREATE TABLE gpkg_contents ("
5234 : "table_name TEXT NOT NULL PRIMARY KEY,"
5235 : "data_type TEXT NOT NULL,"
5236 : "identifier TEXT UNIQUE,"
5237 : "description TEXT DEFAULT '',"
5238 : "last_change DATETIME NOT NULL DEFAULT "
5239 : "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
5240 : "min_x DOUBLE, min_y DOUBLE,"
5241 : "max_x DOUBLE, max_y DOUBLE,"
5242 : "srs_id INTEGER,"
5243 : "CONSTRAINT fk_gc_r_srs_id FOREIGN KEY (srs_id) REFERENCES "
5244 : "gpkg_spatial_ref_sys(srs_id)"
5245 971 : ")";
5246 :
5247 : #ifdef ENABLE_GPKG_OGR_CONTENTS
5248 971 : if (CPLFetchBool(papszOptions, "ADD_GPKG_OGR_CONTENTS", true))
5249 : {
5250 966 : m_bHasGPKGOGRContents = true;
5251 : osSQL += ";"
5252 : "CREATE TABLE gpkg_ogr_contents("
5253 : "table_name TEXT NOT NULL PRIMARY KEY,"
5254 : "feature_count INTEGER DEFAULT NULL"
5255 966 : ")";
5256 : }
5257 : #endif
5258 :
5259 : /* Requirement 21: A GeoPackage with a gpkg_contents table row with a
5260 : * “features” */
5261 : /* data_type SHALL contain a gpkg_geometry_columns table or updateable
5262 : * view */
5263 : /* http://opengis.github.io/geopackage/#_geometry_columns */
5264 : const bool bCreateGeometryColumns =
5265 971 : CPLTestBool(CPLGetConfigOption("CREATE_GEOMETRY_COLUMNS", "YES"));
5266 971 : if (bCreateGeometryColumns)
5267 : {
5268 970 : m_bHasGPKGGeometryColumns = true;
5269 970 : osSQL += ";";
5270 970 : osSQL += pszCREATE_GPKG_GEOMETRY_COLUMNS;
5271 : }
5272 : }
5273 :
5274 : const bool bCreateTriggers =
5275 981 : CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "YES"));
5276 10 : if ((bFileExists && nBandsIn != 0 &&
5277 10 : SQLGetInteger(
5278 : hDB,
5279 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_tile_matrix_set' "
5280 : "AND type in ('table', 'view')",
5281 1962 : nullptr) == 0) ||
5282 980 : (!bFileExists &&
5283 971 : CPLTestBool(CPLGetConfigOption("CREATE_RASTER_TABLES", "YES"))))
5284 : {
5285 971 : if (!osSQL.empty())
5286 970 : osSQL += ";";
5287 :
5288 : /* From C.5. gpkg_tile_matrix_set Table 28. gpkg_tile_matrix_set Table
5289 : * Creation SQL */
5290 : osSQL += "CREATE TABLE gpkg_tile_matrix_set ("
5291 : "table_name TEXT NOT NULL PRIMARY KEY,"
5292 : "srs_id INTEGER NOT NULL,"
5293 : "min_x DOUBLE NOT NULL,"
5294 : "min_y DOUBLE NOT NULL,"
5295 : "max_x DOUBLE NOT NULL,"
5296 : "max_y DOUBLE NOT NULL,"
5297 : "CONSTRAINT fk_gtms_table_name FOREIGN KEY (table_name) "
5298 : "REFERENCES gpkg_contents(table_name),"
5299 : "CONSTRAINT fk_gtms_srs FOREIGN KEY (srs_id) REFERENCES "
5300 : "gpkg_spatial_ref_sys (srs_id)"
5301 : ")"
5302 : ";"
5303 :
5304 : /* From C.6. gpkg_tile_matrix Table 29. gpkg_tile_matrix Table
5305 : Creation SQL */
5306 : "CREATE TABLE gpkg_tile_matrix ("
5307 : "table_name TEXT NOT NULL,"
5308 : "zoom_level INTEGER NOT NULL,"
5309 : "matrix_width INTEGER NOT NULL,"
5310 : "matrix_height INTEGER NOT NULL,"
5311 : "tile_width INTEGER NOT NULL,"
5312 : "tile_height INTEGER NOT NULL,"
5313 : "pixel_x_size DOUBLE NOT NULL,"
5314 : "pixel_y_size DOUBLE NOT NULL,"
5315 : "CONSTRAINT pk_ttm PRIMARY KEY (table_name, zoom_level),"
5316 : "CONSTRAINT fk_tmm_table_name FOREIGN KEY (table_name) "
5317 : "REFERENCES gpkg_contents(table_name)"
5318 971 : ")";
5319 :
5320 971 : if (bCreateTriggers)
5321 : {
5322 : /* From D.1. gpkg_tile_matrix Table 39. gpkg_tile_matrix Trigger
5323 : * Definition SQL */
5324 971 : const char *pszTileMatrixTrigger =
5325 : "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_insert' "
5326 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5327 : "FOR EACH ROW BEGIN "
5328 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5329 : "violates constraint: zoom_level cannot be less than 0') "
5330 : "WHERE (NEW.zoom_level < 0); "
5331 : "END; "
5332 : "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_update' "
5333 : "BEFORE UPDATE of zoom_level ON 'gpkg_tile_matrix' "
5334 : "FOR EACH ROW BEGIN "
5335 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5336 : "violates constraint: zoom_level cannot be less than 0') "
5337 : "WHERE (NEW.zoom_level < 0); "
5338 : "END; "
5339 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_insert' "
5340 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5341 : "FOR EACH ROW BEGIN "
5342 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5343 : "violates constraint: matrix_width cannot be less than 1') "
5344 : "WHERE (NEW.matrix_width < 1); "
5345 : "END; "
5346 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_update' "
5347 : "BEFORE UPDATE OF matrix_width ON 'gpkg_tile_matrix' "
5348 : "FOR EACH ROW BEGIN "
5349 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5350 : "violates constraint: matrix_width cannot be less than 1') "
5351 : "WHERE (NEW.matrix_width < 1); "
5352 : "END; "
5353 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_insert' "
5354 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5355 : "FOR EACH ROW BEGIN "
5356 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5357 : "violates constraint: matrix_height cannot be less than 1') "
5358 : "WHERE (NEW.matrix_height < 1); "
5359 : "END; "
5360 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_update' "
5361 : "BEFORE UPDATE OF matrix_height ON 'gpkg_tile_matrix' "
5362 : "FOR EACH ROW BEGIN "
5363 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5364 : "violates constraint: matrix_height cannot be less than 1') "
5365 : "WHERE (NEW.matrix_height < 1); "
5366 : "END; "
5367 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_insert' "
5368 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5369 : "FOR EACH ROW BEGIN "
5370 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5371 : "violates constraint: pixel_x_size must be greater than 0') "
5372 : "WHERE NOT (NEW.pixel_x_size > 0); "
5373 : "END; "
5374 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_update' "
5375 : "BEFORE UPDATE OF pixel_x_size ON 'gpkg_tile_matrix' "
5376 : "FOR EACH ROW BEGIN "
5377 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5378 : "violates constraint: pixel_x_size must be greater than 0') "
5379 : "WHERE NOT (NEW.pixel_x_size > 0); "
5380 : "END; "
5381 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_insert' "
5382 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5383 : "FOR EACH ROW BEGIN "
5384 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5385 : "violates constraint: pixel_y_size must be greater than 0') "
5386 : "WHERE NOT (NEW.pixel_y_size > 0); "
5387 : "END; "
5388 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_update' "
5389 : "BEFORE UPDATE OF pixel_y_size ON 'gpkg_tile_matrix' "
5390 : "FOR EACH ROW BEGIN "
5391 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5392 : "violates constraint: pixel_y_size must be greater than 0') "
5393 : "WHERE NOT (NEW.pixel_y_size > 0); "
5394 : "END;";
5395 971 : osSQL += ";";
5396 971 : osSQL += pszTileMatrixTrigger;
5397 : }
5398 : }
5399 :
5400 981 : if (!osSQL.empty() && OGRERR_NONE != SQLCommand(hDB, osSQL))
5401 1 : return FALSE;
5402 :
5403 980 : if (!bFileExists)
5404 : {
5405 : const char *pszMetadataTables =
5406 970 : CSLFetchNameValue(papszOptions, "METADATA_TABLES");
5407 970 : if (pszMetadataTables)
5408 10 : m_nCreateMetadataTables = int(CPLTestBool(pszMetadataTables));
5409 :
5410 970 : if (m_nCreateMetadataTables == TRUE && !CreateMetadataTables())
5411 0 : return FALSE;
5412 :
5413 970 : if (m_bHasDefinition12_063)
5414 : {
5415 84 : if (OGRERR_NONE != CreateExtensionsTableIfNecessary() ||
5416 : OGRERR_NONE !=
5417 42 : SQLCommand(hDB, "INSERT INTO gpkg_extensions "
5418 : "(table_name, column_name, extension_name, "
5419 : "definition, scope) "
5420 : "VALUES "
5421 : "('gpkg_spatial_ref_sys', "
5422 : "'definition_12_063', 'gpkg_crs_wkt', "
5423 : "'http://www.geopackage.org/spec120/"
5424 : "#extension_crs_wkt', 'read-write')"))
5425 : {
5426 0 : return FALSE;
5427 : }
5428 42 : if (m_bHasEpochColumn)
5429 : {
5430 40 : if (OGRERR_NONE !=
5431 40 : SQLCommand(
5432 : hDB, "UPDATE gpkg_extensions SET extension_name = "
5433 : "'gpkg_crs_wkt_1_1' "
5434 80 : "WHERE extension_name = 'gpkg_crs_wkt'") ||
5435 : OGRERR_NONE !=
5436 40 : SQLCommand(hDB, "INSERT INTO gpkg_extensions "
5437 : "(table_name, column_name, "
5438 : "extension_name, definition, scope) "
5439 : "VALUES "
5440 : "('gpkg_spatial_ref_sys', 'epoch', "
5441 : "'gpkg_crs_wkt_1_1', "
5442 : "'http://www.geopackage.org/spec/"
5443 : "#extension_crs_wkt', "
5444 : "'read-write')"))
5445 : {
5446 0 : return FALSE;
5447 : }
5448 : }
5449 : }
5450 : }
5451 :
5452 980 : if (nBandsIn != 0)
5453 : {
5454 190 : const std::string osTableName = CPLGetBasenameSafe(m_pszFilename);
5455 : m_osRasterTable = CSLFetchNameValueDef(papszOptions, "RASTER_TABLE",
5456 190 : osTableName.c_str());
5457 190 : if (m_osRasterTable.empty())
5458 : {
5459 0 : CPLError(CE_Failure, CPLE_AppDefined,
5460 : "RASTER_TABLE must be set to a non empty value");
5461 0 : return FALSE;
5462 : }
5463 190 : m_bIdentifierAsCO =
5464 190 : CSLFetchNameValue(papszOptions, "RASTER_IDENTIFIER") != nullptr;
5465 : m_osIdentifier = CSLFetchNameValueDef(papszOptions, "RASTER_IDENTIFIER",
5466 190 : m_osRasterTable);
5467 190 : m_bDescriptionAsCO =
5468 190 : CSLFetchNameValue(papszOptions, "RASTER_DESCRIPTION") != nullptr;
5469 : m_osDescription =
5470 190 : CSLFetchNameValueDef(papszOptions, "RASTER_DESCRIPTION", "");
5471 190 : SetDataType(eDT);
5472 190 : if (eDT == GDT_Int16)
5473 16 : SetGlobalOffsetScale(-32768.0, 1.0);
5474 :
5475 : /* From C.7. sample_tile_pyramid (Informative) Table 31. EXAMPLE: tiles
5476 : * table Create Table SQL (Informative) */
5477 : char *pszSQL =
5478 190 : sqlite3_mprintf("CREATE TABLE \"%w\" ("
5479 : "id INTEGER PRIMARY KEY AUTOINCREMENT,"
5480 : "zoom_level INTEGER NOT NULL,"
5481 : "tile_column INTEGER NOT NULL,"
5482 : "tile_row INTEGER NOT NULL,"
5483 : "tile_data BLOB NOT NULL,"
5484 : "UNIQUE (zoom_level, tile_column, tile_row)"
5485 : ")",
5486 : m_osRasterTable.c_str());
5487 190 : osSQL = pszSQL;
5488 190 : sqlite3_free(pszSQL);
5489 :
5490 190 : if (bCreateTriggers)
5491 : {
5492 190 : osSQL += ";";
5493 190 : osSQL += CreateRasterTriggersSQL(m_osRasterTable);
5494 : }
5495 :
5496 190 : OGRErr eErr = SQLCommand(hDB, osSQL);
5497 190 : if (OGRERR_NONE != eErr)
5498 0 : return FALSE;
5499 :
5500 190 : const char *pszTF = CSLFetchNameValue(papszOptions, "TILE_FORMAT");
5501 190 : if (eDT == GDT_Int16 || eDT == GDT_UInt16)
5502 : {
5503 27 : m_eTF = GPKG_TF_PNG_16BIT;
5504 27 : if (pszTF)
5505 : {
5506 1 : if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "PNG"))
5507 : {
5508 0 : CPLError(CE_Warning, CPLE_NotSupported,
5509 : "Only AUTO or PNG supported "
5510 : "as tile format for Int16 / UInt16");
5511 : }
5512 : }
5513 : }
5514 163 : else if (eDT == GDT_Float32)
5515 : {
5516 13 : m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
5517 13 : if (pszTF)
5518 : {
5519 5 : if (EQUAL(pszTF, "PNG"))
5520 5 : m_eTF = GPKG_TF_PNG_16BIT;
5521 0 : else if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "TIFF"))
5522 : {
5523 0 : CPLError(CE_Warning, CPLE_NotSupported,
5524 : "Only AUTO, PNG or TIFF supported "
5525 : "as tile format for Float32");
5526 : }
5527 : }
5528 : }
5529 : else
5530 : {
5531 150 : if (pszTF)
5532 : {
5533 71 : m_eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
5534 71 : if (nBandsIn == 1 && m_eTF != GPKG_TF_PNG)
5535 7 : m_bMetadataDirty = true;
5536 : }
5537 79 : else if (nBandsIn == 1)
5538 68 : m_eTF = GPKG_TF_PNG;
5539 : }
5540 :
5541 190 : if (eDT != GDT_UInt8)
5542 : {
5543 40 : if (!CreateTileGriddedTable(papszOptions))
5544 0 : return FALSE;
5545 : }
5546 :
5547 190 : nRasterXSize = nXSize;
5548 190 : nRasterYSize = nYSize;
5549 :
5550 : const char *pszTileSize =
5551 190 : CSLFetchNameValueDef(papszOptions, "BLOCKSIZE", "256");
5552 : const char *pszTileWidth =
5553 190 : CSLFetchNameValueDef(papszOptions, "BLOCKXSIZE", pszTileSize);
5554 : const char *pszTileHeight =
5555 190 : CSLFetchNameValueDef(papszOptions, "BLOCKYSIZE", pszTileSize);
5556 190 : int nTileWidth = atoi(pszTileWidth);
5557 190 : int nTileHeight = atoi(pszTileHeight);
5558 190 : if ((nTileWidth < 8 || nTileWidth > 4096 || nTileHeight < 8 ||
5559 380 : nTileHeight > 4096) &&
5560 1 : !CPLTestBool(CPLGetConfigOption("GPKG_ALLOW_CRAZY_SETTINGS", "NO")))
5561 : {
5562 0 : CPLError(CE_Failure, CPLE_AppDefined,
5563 : "Invalid block dimensions: %dx%d", nTileWidth,
5564 : nTileHeight);
5565 0 : return FALSE;
5566 : }
5567 :
5568 513 : for (int i = 1; i <= nBandsIn; i++)
5569 : {
5570 323 : SetBand(i, std::make_unique<GDALGeoPackageRasterBand>(
5571 : this, nTileWidth, nTileHeight));
5572 : }
5573 :
5574 190 : GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL",
5575 : "IMAGE_STRUCTURE");
5576 190 : GDALPamDataset::SetMetadataItem("IDENTIFIER", m_osIdentifier);
5577 190 : if (!m_osDescription.empty())
5578 1 : GDALPamDataset::SetMetadataItem("DESCRIPTION", m_osDescription);
5579 :
5580 190 : ParseCompressionOptions(papszOptions);
5581 :
5582 190 : if (m_eTF == GPKG_TF_WEBP)
5583 : {
5584 10 : if (!RegisterWebPExtension())
5585 0 : return FALSE;
5586 : }
5587 :
5588 : m_osTilingScheme =
5589 190 : CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
5590 190 : if (!EQUAL(m_osTilingScheme, "CUSTOM"))
5591 : {
5592 22 : const auto poTS = GetTilingScheme(m_osTilingScheme);
5593 22 : if (!poTS)
5594 0 : return FALSE;
5595 :
5596 43 : if (nTileWidth != poTS->nTileWidth ||
5597 21 : nTileHeight != poTS->nTileHeight)
5598 : {
5599 2 : CPLError(CE_Failure, CPLE_NotSupported,
5600 : "Tile dimension should be %dx%d for %s tiling scheme",
5601 1 : poTS->nTileWidth, poTS->nTileHeight,
5602 : m_osTilingScheme.c_str());
5603 1 : return FALSE;
5604 : }
5605 :
5606 : const char *pszZoomLevel =
5607 21 : CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
5608 21 : if (pszZoomLevel)
5609 : {
5610 1 : m_nZoomLevel = atoi(pszZoomLevel);
5611 1 : int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
5612 1 : while ((1 << nMaxZoomLevelForThisTM) >
5613 2 : INT_MAX / poTS->nTileXCountZoomLevel0 ||
5614 1 : (1 << nMaxZoomLevelForThisTM) >
5615 1 : INT_MAX / poTS->nTileYCountZoomLevel0)
5616 : {
5617 0 : --nMaxZoomLevelForThisTM;
5618 : }
5619 :
5620 1 : if (m_nZoomLevel < 0 || m_nZoomLevel > nMaxZoomLevelForThisTM)
5621 : {
5622 0 : CPLError(CE_Failure, CPLE_AppDefined,
5623 : "ZOOM_LEVEL = %s is invalid. It should be in "
5624 : "[0,%d] range",
5625 : pszZoomLevel, nMaxZoomLevelForThisTM);
5626 0 : return FALSE;
5627 : }
5628 : }
5629 :
5630 : // Implicitly sets SRS.
5631 21 : OGRSpatialReference oSRS;
5632 21 : if (oSRS.importFromEPSG(poTS->nEPSGCode) != OGRERR_NONE)
5633 0 : return FALSE;
5634 21 : char *pszWKT = nullptr;
5635 21 : oSRS.exportToWkt(&pszWKT);
5636 21 : SetProjection(pszWKT);
5637 21 : CPLFree(pszWKT);
5638 : }
5639 : else
5640 : {
5641 168 : if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
5642 : {
5643 0 : CPLError(
5644 : CE_Failure, CPLE_NotSupported,
5645 : "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
5646 0 : return false;
5647 : }
5648 : }
5649 : }
5650 :
5651 979 : if (bFileExists && nBandsIn > 0 && eDT == GDT_UInt8)
5652 : {
5653 : // If there was an ogr_empty_table table, we can remove it
5654 9 : RemoveOGREmptyTable();
5655 : }
5656 :
5657 979 : SoftCommitTransaction();
5658 :
5659 : /* Requirement 2 */
5660 : /* We have to do this after there's some content so the database file */
5661 : /* is not zero length */
5662 979 : SetApplicationAndUserVersionId();
5663 :
5664 : /* Default to synchronous=off for performance for new file */
5665 1948 : if (!bFileExists &&
5666 969 : CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
5667 : {
5668 457 : SQLCommand(hDB, "PRAGMA synchronous = OFF");
5669 : }
5670 :
5671 979 : return TRUE;
5672 : }
5673 :
5674 : /************************************************************************/
5675 : /* RemoveOGREmptyTable() */
5676 : /************************************************************************/
5677 :
5678 790 : void GDALGeoPackageDataset::RemoveOGREmptyTable()
5679 : {
5680 : // Run with sqlite3_exec since we don't want errors to be emitted
5681 790 : sqlite3_exec(hDB, "DROP TABLE IF EXISTS ogr_empty_table", nullptr, nullptr,
5682 : nullptr);
5683 790 : sqlite3_exec(
5684 : hDB, "DELETE FROM gpkg_contents WHERE table_name = 'ogr_empty_table'",
5685 : nullptr, nullptr, nullptr);
5686 : #ifdef ENABLE_GPKG_OGR_CONTENTS
5687 790 : if (m_bHasGPKGOGRContents)
5688 : {
5689 776 : sqlite3_exec(hDB,
5690 : "DELETE FROM gpkg_ogr_contents WHERE "
5691 : "table_name = 'ogr_empty_table'",
5692 : nullptr, nullptr, nullptr);
5693 : }
5694 : #endif
5695 790 : sqlite3_exec(hDB,
5696 : "DELETE FROM gpkg_geometry_columns WHERE "
5697 : "table_name = 'ogr_empty_table'",
5698 : nullptr, nullptr, nullptr);
5699 790 : }
5700 :
5701 : /************************************************************************/
5702 : /* CreateTileGriddedTable() */
5703 : /************************************************************************/
5704 :
5705 40 : bool GDALGeoPackageDataset::CreateTileGriddedTable(CSLConstList papszOptions)
5706 : {
5707 80 : CPLString osSQL;
5708 40 : if (!HasGriddedCoverageAncillaryTable())
5709 : {
5710 : // It doesn't exist. So create gpkg_extensions table if necessary, and
5711 : // gpkg_2d_gridded_coverage_ancillary & gpkg_2d_gridded_tile_ancillary,
5712 : // and register them as extensions.
5713 40 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
5714 0 : return false;
5715 :
5716 : // Req 1 /table-defs/coverage-ancillary
5717 : osSQL = "CREATE TABLE gpkg_2d_gridded_coverage_ancillary ("
5718 : "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
5719 : "tile_matrix_set_name TEXT NOT NULL UNIQUE,"
5720 : "datatype TEXT NOT NULL DEFAULT 'integer',"
5721 : "scale REAL NOT NULL DEFAULT 1.0,"
5722 : "offset REAL NOT NULL DEFAULT 0.0,"
5723 : "precision REAL DEFAULT 1.0,"
5724 : "data_null REAL,"
5725 : "grid_cell_encoding TEXT DEFAULT 'grid-value-is-center',"
5726 : "uom TEXT,"
5727 : "field_name TEXT DEFAULT 'Height',"
5728 : "quantity_definition TEXT DEFAULT 'Height',"
5729 : "CONSTRAINT fk_g2dgtct_name FOREIGN KEY(tile_matrix_set_name) "
5730 : "REFERENCES gpkg_tile_matrix_set ( table_name ) "
5731 : "CHECK (datatype in ('integer','float')))"
5732 : ";"
5733 : // Requirement 2 /table-defs/tile-ancillary
5734 : "CREATE TABLE gpkg_2d_gridded_tile_ancillary ("
5735 : "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
5736 : "tpudt_name TEXT NOT NULL,"
5737 : "tpudt_id INTEGER NOT NULL,"
5738 : "scale REAL NOT NULL DEFAULT 1.0,"
5739 : "offset REAL NOT NULL DEFAULT 0.0,"
5740 : "min REAL DEFAULT NULL,"
5741 : "max REAL DEFAULT NULL,"
5742 : "mean REAL DEFAULT NULL,"
5743 : "std_dev REAL DEFAULT NULL,"
5744 : "CONSTRAINT fk_g2dgtat_name FOREIGN KEY (tpudt_name) "
5745 : "REFERENCES gpkg_contents(table_name),"
5746 : "UNIQUE (tpudt_name, tpudt_id))"
5747 : ";"
5748 : // Requirement 6 /gpkg-extensions
5749 : "INSERT INTO gpkg_extensions "
5750 : "(table_name, column_name, extension_name, definition, scope) "
5751 : "VALUES ('gpkg_2d_gridded_coverage_ancillary', NULL, "
5752 : "'gpkg_2d_gridded_coverage', "
5753 : "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5754 : "'read-write')"
5755 : ";"
5756 : // Requirement 6 /gpkg-extensions
5757 : "INSERT INTO gpkg_extensions "
5758 : "(table_name, column_name, extension_name, definition, scope) "
5759 : "VALUES ('gpkg_2d_gridded_tile_ancillary', NULL, "
5760 : "'gpkg_2d_gridded_coverage', "
5761 : "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5762 : "'read-write')"
5763 40 : ";";
5764 : }
5765 :
5766 : // Requirement 6 /gpkg-extensions
5767 40 : char *pszSQL = sqlite3_mprintf(
5768 : "INSERT INTO gpkg_extensions "
5769 : "(table_name, column_name, extension_name, definition, scope) "
5770 : "VALUES ('%q', 'tile_data', "
5771 : "'gpkg_2d_gridded_coverage', "
5772 : "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5773 : "'read-write')",
5774 : m_osRasterTable.c_str());
5775 40 : osSQL += pszSQL;
5776 40 : osSQL += ";";
5777 40 : sqlite3_free(pszSQL);
5778 :
5779 : // Requirement 7 /gpkg-2d-gridded-coverage-ancillary
5780 : // Requirement 8 /gpkg-2d-gridded-coverage-ancillary-set-name
5781 : // Requirement 9 /gpkg-2d-gridded-coverage-ancillary-datatype
5782 40 : m_dfPrecision =
5783 40 : CPLAtof(CSLFetchNameValueDef(papszOptions, "PRECISION", "1"));
5784 : CPLString osGridCellEncoding(CSLFetchNameValueDef(
5785 80 : papszOptions, "GRID_CELL_ENCODING", "grid-value-is-center"));
5786 40 : m_bGridCellEncodingAsCO =
5787 40 : CSLFetchNameValue(papszOptions, "GRID_CELL_ENCODING") != nullptr;
5788 80 : CPLString osUom(CSLFetchNameValueDef(papszOptions, "UOM", ""));
5789 : CPLString osFieldName(
5790 80 : CSLFetchNameValueDef(papszOptions, "FIELD_NAME", "Height"));
5791 : CPLString osQuantityDefinition(
5792 80 : CSLFetchNameValueDef(papszOptions, "QUANTITY_DEFINITION", "Height"));
5793 :
5794 121 : pszSQL = sqlite3_mprintf(
5795 : "INSERT INTO gpkg_2d_gridded_coverage_ancillary "
5796 : "(tile_matrix_set_name, datatype, scale, offset, precision, "
5797 : "grid_cell_encoding, uom, field_name, quantity_definition) "
5798 : "VALUES (%Q, '%s', %.17g, %.17g, %.17g, %Q, %Q, %Q, %Q)",
5799 : m_osRasterTable.c_str(),
5800 40 : (m_eTF == GPKG_TF_PNG_16BIT) ? "integer" : "float", m_dfScale,
5801 : m_dfOffset, m_dfPrecision, osGridCellEncoding.c_str(),
5802 41 : osUom.empty() ? nullptr : osUom.c_str(), osFieldName.c_str(),
5803 : osQuantityDefinition.c_str());
5804 40 : m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary = pszSQL;
5805 40 : sqlite3_free(pszSQL);
5806 :
5807 : // Requirement 3 /gpkg-spatial-ref-sys-row
5808 : auto oResultTable = SQLQuery(
5809 80 : hDB, "SELECT * FROM gpkg_spatial_ref_sys WHERE srs_id = 4979 LIMIT 2");
5810 40 : bool bHasEPSG4979 = (oResultTable && oResultTable->RowCount() == 1);
5811 40 : if (!bHasEPSG4979)
5812 : {
5813 41 : if (!m_bHasDefinition12_063 &&
5814 1 : !ConvertGpkgSpatialRefSysToExtensionWkt2(/*bForceEpoch=*/false))
5815 : {
5816 0 : return false;
5817 : }
5818 :
5819 : // This is WKT 2...
5820 40 : const char *pszWKT =
5821 : "GEODCRS[\"WGS 84\","
5822 : "DATUM[\"World Geodetic System 1984\","
5823 : " ELLIPSOID[\"WGS 84\",6378137,298.257223563,"
5824 : "LENGTHUNIT[\"metre\",1.0]]],"
5825 : "CS[ellipsoidal,3],"
5826 : " AXIS[\"latitude\",north,ORDER[1],ANGLEUNIT[\"degree\","
5827 : "0.0174532925199433]],"
5828 : " AXIS[\"longitude\",east,ORDER[2],ANGLEUNIT[\"degree\","
5829 : "0.0174532925199433]],"
5830 : " AXIS[\"ellipsoidal height\",up,ORDER[3],"
5831 : "LENGTHUNIT[\"metre\",1.0]],"
5832 : "ID[\"EPSG\",4979]]";
5833 :
5834 40 : pszSQL = sqlite3_mprintf(
5835 : "INSERT INTO gpkg_spatial_ref_sys "
5836 : "(srs_name,srs_id,organization,organization_coordsys_id,"
5837 : "definition,definition_12_063) VALUES "
5838 : "('WGS 84 3D', 4979, 'EPSG', 4979, 'undefined', '%q')",
5839 : pszWKT);
5840 40 : osSQL += ";";
5841 40 : osSQL += pszSQL;
5842 40 : sqlite3_free(pszSQL);
5843 : }
5844 :
5845 40 : return SQLCommand(hDB, osSQL) == OGRERR_NONE;
5846 : }
5847 :
5848 : /************************************************************************/
5849 : /* HasGriddedCoverageAncillaryTable() */
5850 : /************************************************************************/
5851 :
5852 44 : bool GDALGeoPackageDataset::HasGriddedCoverageAncillaryTable()
5853 : {
5854 : auto oResultTable = SQLQuery(
5855 : hDB, "SELECT * FROM sqlite_master WHERE type IN ('table', 'view') AND "
5856 44 : "name = 'gpkg_2d_gridded_coverage_ancillary'");
5857 44 : bool bHasTable = (oResultTable && oResultTable->RowCount() == 1);
5858 88 : return bHasTable;
5859 : }
5860 :
5861 : /************************************************************************/
5862 : /* GetUnderlyingDataset() */
5863 : /************************************************************************/
5864 :
5865 3 : static GDALDataset *GetUnderlyingDataset(GDALDataset *poSrcDS)
5866 : {
5867 3 : if (auto poVRTDS = dynamic_cast<VRTDataset *>(poSrcDS))
5868 : {
5869 0 : auto poTmpDS = poVRTDS->GetSingleSimpleSource();
5870 0 : if (poTmpDS)
5871 0 : return poTmpDS;
5872 : }
5873 :
5874 3 : return poSrcDS;
5875 : }
5876 :
5877 : /************************************************************************/
5878 : /* CreateCopy() */
5879 : /************************************************************************/
5880 :
5881 : typedef struct
5882 : {
5883 : const char *pszName;
5884 : GDALResampleAlg eResampleAlg;
5885 : } WarpResamplingAlg;
5886 :
5887 : static const WarpResamplingAlg asResamplingAlg[] = {
5888 : {"NEAREST", GRA_NearestNeighbour},
5889 : {"BILINEAR", GRA_Bilinear},
5890 : {"CUBIC", GRA_Cubic},
5891 : {"CUBICSPLINE", GRA_CubicSpline},
5892 : {"LANCZOS", GRA_Lanczos},
5893 : {"MODE", GRA_Mode},
5894 : {"AVERAGE", GRA_Average},
5895 : {"RMS", GRA_RMS},
5896 : };
5897 :
5898 162 : GDALDataset *GDALGeoPackageDataset::CreateCopy(const char *pszFilename,
5899 : GDALDataset *poSrcDS,
5900 : int bStrict,
5901 : CSLConstList papszOptions,
5902 : GDALProgressFunc pfnProgress,
5903 : void *pProgressData)
5904 : {
5905 162 : const int nBands = poSrcDS->GetRasterCount();
5906 162 : if (nBands == 0)
5907 : {
5908 2 : GDALDataset *poDS = nullptr;
5909 : GDALDriver *poThisDriver =
5910 2 : GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
5911 2 : if (poThisDriver != nullptr)
5912 : {
5913 2 : poDS = poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS,
5914 : bStrict, papszOptions,
5915 : pfnProgress, pProgressData);
5916 : }
5917 2 : return poDS;
5918 : }
5919 :
5920 : const char *pszTilingScheme =
5921 160 : CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
5922 :
5923 320 : CPLStringList apszUpdatedOptions(CSLDuplicate(papszOptions));
5924 160 : if (CPLTestBool(
5925 166 : CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")) &&
5926 6 : CSLFetchNameValue(papszOptions, "RASTER_TABLE") == nullptr)
5927 : {
5928 : const std::string osBasename(CPLGetBasenameSafe(
5929 6 : GetUnderlyingDataset(poSrcDS)->GetDescription()));
5930 3 : apszUpdatedOptions.SetNameValue("RASTER_TABLE", osBasename.c_str());
5931 : }
5932 :
5933 160 : if (nBands != 1 && nBands != 2 && nBands != 3 && nBands != 4)
5934 : {
5935 1 : CPLError(CE_Failure, CPLE_NotSupported,
5936 : "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), 3 (RGB) or "
5937 : "4 (RGBA) band dataset supported");
5938 1 : return nullptr;
5939 : }
5940 :
5941 159 : const char *pszUnitType = poSrcDS->GetRasterBand(1)->GetUnitType();
5942 318 : if (CSLFetchNameValue(papszOptions, "UOM") == nullptr && pszUnitType &&
5943 159 : !EQUAL(pszUnitType, ""))
5944 : {
5945 1 : apszUpdatedOptions.SetNameValue("UOM", pszUnitType);
5946 : }
5947 :
5948 159 : if (EQUAL(pszTilingScheme, "CUSTOM"))
5949 : {
5950 135 : if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
5951 : {
5952 0 : CPLError(CE_Failure, CPLE_NotSupported,
5953 : "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
5954 0 : return nullptr;
5955 : }
5956 :
5957 135 : GDALGeoPackageDataset *poDS = nullptr;
5958 : GDALDriver *poThisDriver =
5959 135 : GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
5960 135 : if (poThisDriver != nullptr)
5961 : {
5962 135 : apszUpdatedOptions.SetNameValue("SKIP_HOLES", "YES");
5963 135 : poDS = cpl::down_cast<GDALGeoPackageDataset *>(
5964 : poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
5965 : apszUpdatedOptions, pfnProgress,
5966 135 : pProgressData));
5967 :
5968 250 : if (poDS != nullptr &&
5969 135 : poSrcDS->GetRasterBand(1)->GetRasterDataType() == GDT_UInt8 &&
5970 : nBands <= 3)
5971 : {
5972 75 : poDS->m_nBandCountFromMetadata = nBands;
5973 75 : poDS->m_bMetadataDirty = true;
5974 : }
5975 : }
5976 135 : if (poDS)
5977 115 : poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
5978 135 : return poDS;
5979 : }
5980 :
5981 48 : const auto poTS = GetTilingScheme(pszTilingScheme);
5982 24 : if (!poTS)
5983 : {
5984 2 : return nullptr;
5985 : }
5986 22 : const int nEPSGCode = poTS->nEPSGCode;
5987 :
5988 44 : OGRSpatialReference oSRS;
5989 22 : if (oSRS.importFromEPSG(nEPSGCode) != OGRERR_NONE)
5990 : {
5991 0 : return nullptr;
5992 : }
5993 22 : char *pszWKT = nullptr;
5994 22 : oSRS.exportToWkt(&pszWKT);
5995 22 : char **papszTO = CSLSetNameValue(nullptr, "DST_SRS", pszWKT);
5996 :
5997 22 : void *hTransformArg = nullptr;
5998 :
5999 : // Hack to compensate for GDALSuggestedWarpOutput2() failure (or not
6000 : // ideal suggestion with PROJ 8) when reprojecting latitude = +/- 90 to
6001 : // EPSG:3857.
6002 22 : GDALGeoTransform srcGT;
6003 22 : std::unique_ptr<GDALDataset> poTmpDS;
6004 22 : bool bEPSG3857Adjust = false;
6005 8 : if (nEPSGCode == 3857 && poSrcDS->GetGeoTransform(srcGT) == CE_None &&
6006 30 : srcGT[2] == 0 && srcGT[4] == 0 && srcGT[5] < 0)
6007 : {
6008 8 : const auto poSrcSRS = poSrcDS->GetSpatialRef();
6009 8 : if (poSrcSRS && poSrcSRS->IsGeographic())
6010 : {
6011 2 : double maxLat = srcGT[3];
6012 2 : double minLat = srcGT[3] + poSrcDS->GetRasterYSize() * srcGT[5];
6013 : // Corresponds to the latitude of below MAX_GM
6014 2 : constexpr double MAX_LAT = 85.0511287798066;
6015 2 : bool bModified = false;
6016 2 : if (maxLat > MAX_LAT)
6017 : {
6018 2 : maxLat = MAX_LAT;
6019 2 : bModified = true;
6020 : }
6021 2 : if (minLat < -MAX_LAT)
6022 : {
6023 2 : minLat = -MAX_LAT;
6024 2 : bModified = true;
6025 : }
6026 2 : if (bModified)
6027 : {
6028 4 : CPLStringList aosOptions;
6029 2 : aosOptions.AddString("-of");
6030 2 : aosOptions.AddString("VRT");
6031 2 : aosOptions.AddString("-projwin");
6032 2 : aosOptions.AddString(CPLSPrintf("%.17g", srcGT[0]));
6033 2 : aosOptions.AddString(CPLSPrintf("%.17g", maxLat));
6034 : aosOptions.AddString(CPLSPrintf(
6035 2 : "%.17g", srcGT[0] + poSrcDS->GetRasterXSize() * srcGT[1]));
6036 2 : aosOptions.AddString(CPLSPrintf("%.17g", minLat));
6037 : auto psOptions =
6038 2 : GDALTranslateOptionsNew(aosOptions.List(), nullptr);
6039 2 : poTmpDS.reset(GDALDataset::FromHandle(GDALTranslate(
6040 : "", GDALDataset::ToHandle(poSrcDS), psOptions, nullptr)));
6041 2 : GDALTranslateOptionsFree(psOptions);
6042 2 : if (poTmpDS)
6043 : {
6044 2 : bEPSG3857Adjust = true;
6045 2 : hTransformArg = GDALCreateGenImgProjTransformer2(
6046 2 : GDALDataset::FromHandle(poTmpDS.get()), nullptr,
6047 : papszTO);
6048 : }
6049 : }
6050 : }
6051 : }
6052 22 : if (hTransformArg == nullptr)
6053 : {
6054 : hTransformArg =
6055 20 : GDALCreateGenImgProjTransformer2(poSrcDS, nullptr, papszTO);
6056 : }
6057 :
6058 22 : if (hTransformArg == nullptr)
6059 : {
6060 1 : CPLFree(pszWKT);
6061 1 : CSLDestroy(papszTO);
6062 1 : return nullptr;
6063 : }
6064 :
6065 21 : GDALTransformerInfo *psInfo =
6066 : static_cast<GDALTransformerInfo *>(hTransformArg);
6067 21 : GDALGeoTransform gt;
6068 : double adfExtent[4];
6069 : int nXSize, nYSize;
6070 :
6071 21 : if (GDALSuggestedWarpOutput2(poSrcDS, psInfo->pfnTransform, hTransformArg,
6072 : gt.data(), &nXSize, &nYSize, adfExtent,
6073 21 : 0) != CE_None)
6074 : {
6075 0 : CPLFree(pszWKT);
6076 0 : CSLDestroy(papszTO);
6077 0 : GDALDestroyGenImgProjTransformer(hTransformArg);
6078 0 : return nullptr;
6079 : }
6080 :
6081 21 : GDALDestroyGenImgProjTransformer(hTransformArg);
6082 21 : hTransformArg = nullptr;
6083 21 : poTmpDS.reset();
6084 :
6085 21 : if (bEPSG3857Adjust)
6086 : {
6087 2 : constexpr double SPHERICAL_RADIUS = 6378137.0;
6088 2 : constexpr double MAX_GM =
6089 : SPHERICAL_RADIUS * M_PI; // 20037508.342789244
6090 2 : double maxNorthing = gt[3];
6091 2 : double minNorthing = gt[3] + gt[5] * nYSize;
6092 2 : bool bChanged = false;
6093 2 : if (maxNorthing > MAX_GM)
6094 : {
6095 2 : bChanged = true;
6096 2 : maxNorthing = MAX_GM;
6097 : }
6098 2 : if (minNorthing < -MAX_GM)
6099 : {
6100 2 : bChanged = true;
6101 2 : minNorthing = -MAX_GM;
6102 : }
6103 2 : if (bChanged)
6104 : {
6105 2 : gt[3] = maxNorthing;
6106 2 : nYSize = int((maxNorthing - minNorthing) / (-gt[5]) + 0.5);
6107 2 : adfExtent[1] = maxNorthing + nYSize * gt[5];
6108 2 : adfExtent[3] = maxNorthing;
6109 : }
6110 : }
6111 :
6112 21 : double dfComputedRes = gt[1];
6113 21 : double dfPrevRes = 0.0;
6114 21 : double dfRes = 0.0;
6115 21 : int nZoomLevel = 0; // Used after for.
6116 21 : const char *pszZoomLevel = CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
6117 21 : if (pszZoomLevel)
6118 : {
6119 2 : nZoomLevel = atoi(pszZoomLevel);
6120 :
6121 2 : int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
6122 2 : while ((1 << nMaxZoomLevelForThisTM) >
6123 4 : INT_MAX / poTS->nTileXCountZoomLevel0 ||
6124 2 : (1 << nMaxZoomLevelForThisTM) >
6125 2 : INT_MAX / poTS->nTileYCountZoomLevel0)
6126 : {
6127 0 : --nMaxZoomLevelForThisTM;
6128 : }
6129 :
6130 2 : if (nZoomLevel < 0 || nZoomLevel > nMaxZoomLevelForThisTM)
6131 : {
6132 1 : CPLError(CE_Failure, CPLE_AppDefined,
6133 : "ZOOM_LEVEL = %s is invalid. It should be in [0,%d] range",
6134 : pszZoomLevel, nMaxZoomLevelForThisTM);
6135 1 : CPLFree(pszWKT);
6136 1 : CSLDestroy(papszTO);
6137 1 : return nullptr;
6138 : }
6139 : }
6140 : else
6141 : {
6142 171 : for (; nZoomLevel < MAX_ZOOM_LEVEL; nZoomLevel++)
6143 : {
6144 171 : dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
6145 171 : if (dfComputedRes > dfRes ||
6146 152 : fabs(dfComputedRes - dfRes) / dfRes <= 1e-8)
6147 : break;
6148 152 : dfPrevRes = dfRes;
6149 : }
6150 38 : if (nZoomLevel == MAX_ZOOM_LEVEL ||
6151 38 : (1 << nZoomLevel) > INT_MAX / poTS->nTileXCountZoomLevel0 ||
6152 19 : (1 << nZoomLevel) > INT_MAX / poTS->nTileYCountZoomLevel0)
6153 : {
6154 0 : CPLError(CE_Failure, CPLE_AppDefined,
6155 : "Could not find an appropriate zoom level");
6156 0 : CPLFree(pszWKT);
6157 0 : CSLDestroy(papszTO);
6158 0 : return nullptr;
6159 : }
6160 :
6161 19 : if (nZoomLevel > 0 && fabs(dfComputedRes - dfRes) / dfRes > 1e-8)
6162 : {
6163 17 : const char *pszZoomLevelStrategy = CSLFetchNameValueDef(
6164 : papszOptions, "ZOOM_LEVEL_STRATEGY", "AUTO");
6165 17 : if (EQUAL(pszZoomLevelStrategy, "LOWER"))
6166 : {
6167 1 : nZoomLevel--;
6168 : }
6169 16 : else if (EQUAL(pszZoomLevelStrategy, "UPPER"))
6170 : {
6171 : /* do nothing */
6172 : }
6173 : else
6174 : {
6175 15 : if (dfPrevRes / dfComputedRes < dfComputedRes / dfRes)
6176 13 : nZoomLevel--;
6177 : }
6178 : }
6179 : }
6180 :
6181 20 : dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
6182 :
6183 20 : double dfMinX = adfExtent[0];
6184 20 : double dfMinY = adfExtent[1];
6185 20 : double dfMaxX = adfExtent[2];
6186 20 : double dfMaxY = adfExtent[3];
6187 :
6188 20 : nXSize = static_cast<int>(0.5 + (dfMaxX - dfMinX) / dfRes);
6189 20 : nYSize = static_cast<int>(0.5 + (dfMaxY - dfMinY) / dfRes);
6190 20 : gt[1] = dfRes;
6191 20 : gt[5] = -dfRes;
6192 :
6193 20 : const GDALDataType eDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
6194 20 : int nTargetBands = nBands;
6195 : /* For grey level or RGB, if there's reprojection involved, add an alpha */
6196 : /* channel */
6197 37 : if (eDT == GDT_UInt8 &&
6198 13 : ((nBands == 1 &&
6199 17 : poSrcDS->GetRasterBand(1)->GetColorTable() == nullptr) ||
6200 : nBands == 3))
6201 : {
6202 30 : OGRSpatialReference oSrcSRS;
6203 15 : oSrcSRS.SetFromUserInput(poSrcDS->GetProjectionRef());
6204 15 : oSrcSRS.AutoIdentifyEPSG();
6205 30 : if (oSrcSRS.GetAuthorityCode(nullptr) == nullptr ||
6206 15 : atoi(oSrcSRS.GetAuthorityCode(nullptr)) != nEPSGCode)
6207 : {
6208 13 : nTargetBands++;
6209 : }
6210 : }
6211 :
6212 20 : GDALResampleAlg eResampleAlg = GRA_Bilinear;
6213 20 : const char *pszResampling = CSLFetchNameValue(papszOptions, "RESAMPLING");
6214 20 : if (pszResampling)
6215 : {
6216 6 : for (size_t iAlg = 0;
6217 6 : iAlg < sizeof(asResamplingAlg) / sizeof(asResamplingAlg[0]);
6218 : iAlg++)
6219 : {
6220 6 : if (EQUAL(pszResampling, asResamplingAlg[iAlg].pszName))
6221 : {
6222 3 : eResampleAlg = asResamplingAlg[iAlg].eResampleAlg;
6223 3 : break;
6224 : }
6225 : }
6226 : }
6227 :
6228 16 : if (nBands == 1 && poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr &&
6229 36 : eResampleAlg != GRA_NearestNeighbour && eResampleAlg != GRA_Mode)
6230 : {
6231 0 : CPLError(
6232 : CE_Warning, CPLE_AppDefined,
6233 : "Input dataset has a color table, which will likely lead to "
6234 : "bad results when using a resampling method other than "
6235 : "nearest neighbour or mode. Converting the dataset to 24/32 bit "
6236 : "(e.g. with gdal_translate -expand rgb/rgba) is advised.");
6237 : }
6238 :
6239 40 : auto poDS = std::make_unique<GDALGeoPackageDataset>();
6240 40 : if (!(poDS->Create(pszFilename, nXSize, nYSize, nTargetBands, eDT,
6241 20 : apszUpdatedOptions)))
6242 : {
6243 1 : CPLFree(pszWKT);
6244 1 : CSLDestroy(papszTO);
6245 1 : return nullptr;
6246 : }
6247 :
6248 : // Assign nodata values before the SetGeoTransform call.
6249 : // SetGeoTransform will trigger creation of the overview datasets for each
6250 : // zoom level and at that point the nodata value needs to be known.
6251 19 : int bHasNoData = FALSE;
6252 : double dfNoDataValue =
6253 19 : poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
6254 19 : if (eDT != GDT_UInt8 && bHasNoData)
6255 : {
6256 3 : poDS->GetRasterBand(1)->SetNoDataValue(dfNoDataValue);
6257 : }
6258 :
6259 19 : poDS->SetGeoTransform(gt);
6260 19 : poDS->SetProjection(pszWKT);
6261 19 : CPLFree(pszWKT);
6262 19 : pszWKT = nullptr;
6263 24 : if (nTargetBands == 1 && nBands == 1 &&
6264 5 : poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr)
6265 : {
6266 2 : poDS->GetRasterBand(1)->SetColorTable(
6267 1 : poSrcDS->GetRasterBand(1)->GetColorTable());
6268 : }
6269 :
6270 : hTransformArg =
6271 19 : GDALCreateGenImgProjTransformer2(poSrcDS, poDS.get(), papszTO);
6272 19 : CSLDestroy(papszTO);
6273 19 : if (hTransformArg == nullptr)
6274 : {
6275 0 : return nullptr;
6276 : }
6277 :
6278 19 : poDS->SetMetadata(poSrcDS->GetMetadata());
6279 :
6280 : /* -------------------------------------------------------------------- */
6281 : /* Warp the transformer with a linear approximator */
6282 : /* -------------------------------------------------------------------- */
6283 19 : hTransformArg = GDALCreateApproxTransformer(GDALGenImgProjTransform,
6284 : hTransformArg, 0.125);
6285 19 : GDALApproxTransformerOwnsSubtransformer(hTransformArg, TRUE);
6286 :
6287 : /* -------------------------------------------------------------------- */
6288 : /* Setup warp options. */
6289 : /* -------------------------------------------------------------------- */
6290 19 : GDALWarpOptions *psWO = GDALCreateWarpOptions();
6291 :
6292 19 : psWO->papszWarpOptions = CSLSetNameValue(nullptr, "OPTIMIZE_SIZE", "YES");
6293 19 : psWO->papszWarpOptions =
6294 19 : CSLSetNameValue(psWO->papszWarpOptions, "SAMPLE_GRID", "YES");
6295 19 : if (bHasNoData)
6296 : {
6297 3 : if (dfNoDataValue == 0.0)
6298 : {
6299 : // Do not initialize in the case where nodata != 0, since we
6300 : // want the GeoPackage driver to return empty tiles at the nodata
6301 : // value instead of 0 as GDAL core would
6302 0 : psWO->papszWarpOptions =
6303 0 : CSLSetNameValue(psWO->papszWarpOptions, "INIT_DEST", "0");
6304 : }
6305 :
6306 3 : psWO->padfSrcNoDataReal =
6307 3 : static_cast<double *>(CPLMalloc(sizeof(double)));
6308 3 : psWO->padfSrcNoDataReal[0] = dfNoDataValue;
6309 :
6310 3 : psWO->padfDstNoDataReal =
6311 3 : static_cast<double *>(CPLMalloc(sizeof(double)));
6312 3 : psWO->padfDstNoDataReal[0] = dfNoDataValue;
6313 : }
6314 19 : psWO->eWorkingDataType = eDT;
6315 19 : psWO->eResampleAlg = eResampleAlg;
6316 :
6317 19 : psWO->hSrcDS = poSrcDS;
6318 19 : psWO->hDstDS = poDS.get();
6319 :
6320 19 : psWO->pfnTransformer = GDALApproxTransform;
6321 19 : psWO->pTransformerArg = hTransformArg;
6322 :
6323 19 : psWO->pfnProgress = pfnProgress;
6324 19 : psWO->pProgressArg = pProgressData;
6325 :
6326 : /* -------------------------------------------------------------------- */
6327 : /* Setup band mapping. */
6328 : /* -------------------------------------------------------------------- */
6329 :
6330 19 : if (nBands == 2 || nBands == 4)
6331 1 : psWO->nBandCount = nBands - 1;
6332 : else
6333 18 : psWO->nBandCount = nBands;
6334 :
6335 19 : psWO->panSrcBands =
6336 19 : static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
6337 19 : psWO->panDstBands =
6338 19 : static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
6339 :
6340 46 : for (int i = 0; i < psWO->nBandCount; i++)
6341 : {
6342 27 : psWO->panSrcBands[i] = i + 1;
6343 27 : psWO->panDstBands[i] = i + 1;
6344 : }
6345 :
6346 19 : if (nBands == 2 || nBands == 4)
6347 : {
6348 1 : psWO->nSrcAlphaBand = nBands;
6349 : }
6350 19 : if (nTargetBands == 2 || nTargetBands == 4)
6351 : {
6352 13 : psWO->nDstAlphaBand = nTargetBands;
6353 : }
6354 :
6355 : /* -------------------------------------------------------------------- */
6356 : /* Initialize and execute the warp. */
6357 : /* -------------------------------------------------------------------- */
6358 38 : GDALWarpOperation oWO;
6359 :
6360 19 : CPLErr eErr = oWO.Initialize(psWO);
6361 19 : if (eErr == CE_None)
6362 : {
6363 : /*if( bMulti )
6364 : eErr = oWO.ChunkAndWarpMulti( 0, 0, nXSize, nYSize );
6365 : else*/
6366 19 : eErr = oWO.ChunkAndWarpImage(0, 0, nXSize, nYSize);
6367 : }
6368 19 : if (eErr != CE_None)
6369 : {
6370 0 : poDS.reset();
6371 : }
6372 :
6373 19 : GDALDestroyTransformer(hTransformArg);
6374 19 : GDALDestroyWarpOptions(psWO);
6375 :
6376 19 : if (poDS)
6377 19 : poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
6378 :
6379 19 : return poDS.release();
6380 : }
6381 :
6382 : /************************************************************************/
6383 : /* ParseCompressionOptions() */
6384 : /************************************************************************/
6385 :
6386 464 : void GDALGeoPackageDataset::ParseCompressionOptions(CSLConstList papszOptions)
6387 : {
6388 464 : const char *pszZLevel = CSLFetchNameValue(papszOptions, "ZLEVEL");
6389 464 : if (pszZLevel)
6390 0 : m_nZLevel = atoi(pszZLevel);
6391 :
6392 464 : const char *pszQuality = CSLFetchNameValue(papszOptions, "QUALITY");
6393 464 : if (pszQuality)
6394 0 : m_nQuality = atoi(pszQuality);
6395 :
6396 464 : const char *pszDither = CSLFetchNameValue(papszOptions, "DITHER");
6397 464 : if (pszDither)
6398 0 : m_bDither = CPLTestBool(pszDither);
6399 464 : }
6400 :
6401 : /************************************************************************/
6402 : /* RegisterWebPExtension() */
6403 : /************************************************************************/
6404 :
6405 11 : bool GDALGeoPackageDataset::RegisterWebPExtension()
6406 : {
6407 11 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
6408 0 : return false;
6409 :
6410 11 : char *pszSQL = sqlite3_mprintf(
6411 : "INSERT INTO gpkg_extensions "
6412 : "(table_name, column_name, extension_name, definition, scope) "
6413 : "VALUES "
6414 : "('%q', 'tile_data', 'gpkg_webp', "
6415 : "'http://www.geopackage.org/spec120/#extension_tiles_webp', "
6416 : "'read-write')",
6417 : m_osRasterTable.c_str());
6418 11 : const OGRErr eErr = SQLCommand(hDB, pszSQL);
6419 11 : sqlite3_free(pszSQL);
6420 :
6421 11 : return OGRERR_NONE == eErr;
6422 : }
6423 :
6424 : /************************************************************************/
6425 : /* RegisterZoomOtherExtension() */
6426 : /************************************************************************/
6427 :
6428 1 : bool GDALGeoPackageDataset::RegisterZoomOtherExtension()
6429 : {
6430 1 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
6431 0 : return false;
6432 :
6433 1 : char *pszSQL = sqlite3_mprintf(
6434 : "INSERT INTO gpkg_extensions "
6435 : "(table_name, column_name, extension_name, definition, scope) "
6436 : "VALUES "
6437 : "('%q', 'tile_data', 'gpkg_zoom_other', "
6438 : "'http://www.geopackage.org/spec120/#extension_zoom_other_intervals', "
6439 : "'read-write')",
6440 : m_osRasterTable.c_str());
6441 1 : const OGRErr eErr = SQLCommand(hDB, pszSQL);
6442 1 : sqlite3_free(pszSQL);
6443 1 : return OGRERR_NONE == eErr;
6444 : }
6445 :
6446 : /************************************************************************/
6447 : /* GetLayer() */
6448 : /************************************************************************/
6449 :
6450 16101 : const OGRLayer *GDALGeoPackageDataset::GetLayer(int iLayer) const
6451 :
6452 : {
6453 16101 : if (iLayer < 0 || iLayer >= static_cast<int>(m_apoLayers.size()))
6454 7 : return nullptr;
6455 : else
6456 16094 : return m_apoLayers[iLayer].get();
6457 : }
6458 :
6459 : /************************************************************************/
6460 : /* LaunderName() */
6461 : /************************************************************************/
6462 :
6463 : /** Launder identifiers (table, column names) according to guidance at
6464 : * https://www.geopackage.org/guidance/getting-started.html:
6465 : * "For maximum interoperability, start your database identifiers (table names,
6466 : * column names, etc.) with a lowercase character and only use lowercase
6467 : * characters, numbers 0-9, and underscores (_)."
6468 : */
6469 :
6470 : /* static */
6471 5 : std::string GDALGeoPackageDataset::LaunderName(const std::string &osStr)
6472 : {
6473 5 : char *pszASCII = CPLUTF8ForceToASCII(osStr.c_str(), '_');
6474 10 : const std::string osStrASCII(pszASCII);
6475 5 : CPLFree(pszASCII);
6476 :
6477 10 : std::string osRet;
6478 5 : osRet.reserve(osStrASCII.size());
6479 :
6480 29 : for (size_t i = 0; i < osStrASCII.size(); ++i)
6481 : {
6482 24 : if (osRet.empty())
6483 : {
6484 5 : if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
6485 : {
6486 2 : osRet += (osStrASCII[i] - 'A' + 'a');
6487 : }
6488 3 : else if (osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z')
6489 : {
6490 2 : osRet += osStrASCII[i];
6491 : }
6492 : else
6493 : {
6494 1 : continue;
6495 : }
6496 : }
6497 19 : else if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
6498 : {
6499 11 : osRet += (osStrASCII[i] - 'A' + 'a');
6500 : }
6501 9 : else if ((osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z') ||
6502 14 : (osStrASCII[i] >= '0' && osStrASCII[i] <= '9') ||
6503 5 : osStrASCII[i] == '_')
6504 : {
6505 7 : osRet += osStrASCII[i];
6506 : }
6507 : else
6508 : {
6509 1 : osRet += '_';
6510 : }
6511 : }
6512 :
6513 5 : if (osRet.empty() && !osStrASCII.empty())
6514 2 : return LaunderName(std::string("x").append(osStrASCII));
6515 :
6516 4 : if (osRet != osStr)
6517 : {
6518 3 : CPLDebug("PG", "LaunderName('%s') -> '%s'", osStr.c_str(),
6519 : osRet.c_str());
6520 : }
6521 :
6522 4 : return osRet;
6523 : }
6524 :
6525 : /************************************************************************/
6526 : /* ICreateLayer() */
6527 : /************************************************************************/
6528 :
6529 : OGRLayer *
6530 888 : GDALGeoPackageDataset::ICreateLayer(const char *pszLayerName,
6531 : const OGRGeomFieldDefn *poSrcGeomFieldDefn,
6532 : CSLConstList papszOptions)
6533 : {
6534 : /* -------------------------------------------------------------------- */
6535 : /* Verify we are in update mode. */
6536 : /* -------------------------------------------------------------------- */
6537 888 : if (!GetUpdate())
6538 : {
6539 0 : CPLError(CE_Failure, CPLE_NoWriteAccess,
6540 : "Data source %s opened read-only.\n"
6541 : "New layer %s cannot be created.\n",
6542 : m_pszFilename, pszLayerName);
6543 :
6544 0 : return nullptr;
6545 : }
6546 :
6547 : const bool bLaunder =
6548 888 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "LAUNDER", "NO"));
6549 : const std::string osTableName(bLaunder ? LaunderName(pszLayerName)
6550 2664 : : std::string(pszLayerName));
6551 :
6552 : const auto eGType =
6553 888 : poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone;
6554 : const auto poSpatialRef =
6555 888 : poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr;
6556 :
6557 888 : if (!m_bHasGPKGGeometryColumns)
6558 : {
6559 1 : if (SQLCommand(hDB, pszCREATE_GPKG_GEOMETRY_COLUMNS) != OGRERR_NONE)
6560 : {
6561 0 : return nullptr;
6562 : }
6563 1 : m_bHasGPKGGeometryColumns = true;
6564 : }
6565 :
6566 : // Check identifier unicity
6567 888 : const char *pszIdentifier = CSLFetchNameValue(papszOptions, "IDENTIFIER");
6568 888 : if (pszIdentifier != nullptr && pszIdentifier[0] == '\0')
6569 0 : pszIdentifier = nullptr;
6570 888 : if (pszIdentifier != nullptr)
6571 : {
6572 13 : for (auto &poLayer : m_apoLayers)
6573 : {
6574 : const char *pszOtherIdentifier =
6575 9 : poLayer->GetMetadataItem("IDENTIFIER");
6576 9 : if (pszOtherIdentifier == nullptr)
6577 6 : pszOtherIdentifier = poLayer->GetName();
6578 18 : if (pszOtherIdentifier != nullptr &&
6579 12 : EQUAL(pszOtherIdentifier, pszIdentifier) &&
6580 3 : !EQUAL(poLayer->GetName(), osTableName.c_str()))
6581 : {
6582 2 : CPLError(CE_Failure, CPLE_AppDefined,
6583 : "Identifier %s is already used by table %s",
6584 : pszIdentifier, poLayer->GetName());
6585 2 : return nullptr;
6586 : }
6587 : }
6588 :
6589 : // In case there would be table in gpkg_contents not listed as a
6590 : // vector layer
6591 4 : char *pszSQL = sqlite3_mprintf(
6592 : "SELECT table_name FROM gpkg_contents WHERE identifier = '%q' "
6593 : "LIMIT 2",
6594 : pszIdentifier);
6595 4 : auto oResult = SQLQuery(hDB, pszSQL);
6596 4 : sqlite3_free(pszSQL);
6597 8 : if (oResult && oResult->RowCount() > 0 &&
6598 9 : oResult->GetValue(0, 0) != nullptr &&
6599 1 : !EQUAL(oResult->GetValue(0, 0), osTableName.c_str()))
6600 : {
6601 1 : CPLError(CE_Failure, CPLE_AppDefined,
6602 : "Identifier %s is already used by table %s", pszIdentifier,
6603 : oResult->GetValue(0, 0));
6604 1 : return nullptr;
6605 : }
6606 : }
6607 :
6608 : /* Read GEOMETRY_NAME option */
6609 : const char *pszGeomColumnName =
6610 885 : CSLFetchNameValue(papszOptions, "GEOMETRY_NAME");
6611 885 : if (pszGeomColumnName == nullptr) /* deprecated name */
6612 799 : pszGeomColumnName = CSLFetchNameValue(papszOptions, "GEOMETRY_COLUMN");
6613 885 : if (pszGeomColumnName == nullptr && poSrcGeomFieldDefn)
6614 : {
6615 720 : pszGeomColumnName = poSrcGeomFieldDefn->GetNameRef();
6616 720 : if (pszGeomColumnName && pszGeomColumnName[0] == 0)
6617 716 : pszGeomColumnName = nullptr;
6618 : }
6619 885 : if (pszGeomColumnName == nullptr)
6620 795 : pszGeomColumnName = "geom";
6621 : const bool bGeomNullable =
6622 885 : CPLFetchBool(papszOptions, "GEOMETRY_NULLABLE", true);
6623 :
6624 : /* Read FID option */
6625 885 : const char *pszFIDColumnName = CSLFetchNameValue(papszOptions, "FID");
6626 885 : if (pszFIDColumnName == nullptr)
6627 802 : pszFIDColumnName = "fid";
6628 :
6629 885 : if (CPLTestBool(CPLGetConfigOption("GPKG_NAME_CHECK", "YES")))
6630 : {
6631 885 : if (strspn(pszFIDColumnName, "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") > 0)
6632 : {
6633 2 : CPLError(CE_Failure, CPLE_AppDefined,
6634 : "The primary key (%s) name may not contain special "
6635 : "characters or spaces",
6636 : pszFIDColumnName);
6637 2 : return nullptr;
6638 : }
6639 :
6640 : /* Avoiding gpkg prefixes is not an official requirement, but seems wise
6641 : */
6642 883 : if (STARTS_WITH(osTableName.c_str(), "gpkg"))
6643 : {
6644 0 : CPLError(CE_Failure, CPLE_AppDefined,
6645 : "The layer name may not begin with 'gpkg' as it is a "
6646 : "reserved geopackage prefix");
6647 0 : return nullptr;
6648 : }
6649 :
6650 : /* Preemptively try and avoid sqlite3 syntax errors due to */
6651 : /* illegal characters. */
6652 883 : if (strspn(osTableName.c_str(), "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") >
6653 : 0)
6654 : {
6655 0 : CPLError(
6656 : CE_Failure, CPLE_AppDefined,
6657 : "The layer name may not contain special characters or spaces");
6658 0 : return nullptr;
6659 : }
6660 : }
6661 :
6662 : /* Check for any existing layers that already use this name */
6663 1092 : for (int iLayer = 0; iLayer < static_cast<int>(m_apoLayers.size());
6664 : iLayer++)
6665 : {
6666 210 : if (EQUAL(osTableName.c_str(), m_apoLayers[iLayer]->GetName()))
6667 : {
6668 : const char *pszOverwrite =
6669 2 : CSLFetchNameValue(papszOptions, "OVERWRITE");
6670 2 : if (pszOverwrite != nullptr && CPLTestBool(pszOverwrite))
6671 : {
6672 1 : DeleteLayer(iLayer);
6673 : }
6674 : else
6675 : {
6676 1 : CPLError(CE_Failure, CPLE_AppDefined,
6677 : "Layer %s already exists, CreateLayer failed.\n"
6678 : "Use the layer creation option OVERWRITE=YES to "
6679 : "replace it.",
6680 : osTableName.c_str());
6681 1 : return nullptr;
6682 : }
6683 : }
6684 : }
6685 :
6686 882 : if (m_apoLayers.size() == 1)
6687 : {
6688 : // Async RTree building doesn't play well with multiple layer:
6689 : // SQLite3 locks being hold for a long time, random failed commits,
6690 : // etc.
6691 83 : m_apoLayers[0]->FinishOrDisableThreadedRTree();
6692 : }
6693 :
6694 : /* Create a blank layer. */
6695 : auto poLayer =
6696 1764 : std::make_unique<OGRGeoPackageTableLayer>(this, osTableName.c_str());
6697 :
6698 882 : OGRSpatialReference *poSRS = nullptr;
6699 882 : if (poSpatialRef)
6700 : {
6701 267 : poSRS = poSpatialRef->Clone();
6702 267 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
6703 : }
6704 1765 : poLayer->SetCreationParameters(
6705 : eGType,
6706 883 : bLaunder ? LaunderName(pszGeomColumnName).c_str() : pszGeomColumnName,
6707 : bGeomNullable, poSRS, CSLFetchNameValue(papszOptions, "SRID"),
6708 1764 : poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetCoordinatePrecision()
6709 : : OGRGeomCoordinatePrecision(),
6710 882 : CPLTestBool(
6711 : CSLFetchNameValueDef(papszOptions, "DISCARD_COORD_LSB", "NO")),
6712 882 : CPLTestBool(CSLFetchNameValueDef(
6713 : papszOptions, "UNDO_DISCARD_COORD_LSB_ON_READING", "NO")),
6714 883 : bLaunder ? LaunderName(pszFIDColumnName).c_str() : pszFIDColumnName,
6715 : pszIdentifier, CSLFetchNameValue(papszOptions, "DESCRIPTION"));
6716 882 : if (poSRS)
6717 : {
6718 267 : poSRS->Release();
6719 : }
6720 :
6721 882 : poLayer->SetLaunder(bLaunder);
6722 :
6723 : /* Should we create a spatial index ? */
6724 882 : const char *pszSI = CSLFetchNameValue(papszOptions, "SPATIAL_INDEX");
6725 882 : int bCreateSpatialIndex = (pszSI == nullptr || CPLTestBool(pszSI));
6726 882 : if (eGType != wkbNone && bCreateSpatialIndex)
6727 : {
6728 781 : poLayer->SetDeferredSpatialIndexCreation(true);
6729 : }
6730 :
6731 882 : poLayer->SetPrecisionFlag(CPLFetchBool(papszOptions, "PRECISION", true));
6732 882 : poLayer->SetTruncateFieldsFlag(
6733 882 : CPLFetchBool(papszOptions, "TRUNCATE_FIELDS", false));
6734 882 : if (eGType == wkbNone)
6735 : {
6736 79 : const char *pszASpatialVariant = CSLFetchNameValueDef(
6737 : papszOptions, "ASPATIAL_VARIANT",
6738 79 : m_bNonSpatialTablesNonRegisteredInGpkgContentsFound
6739 : ? "NOT_REGISTERED"
6740 : : "GPKG_ATTRIBUTES");
6741 79 : GPKGASpatialVariant eASpatialVariant = GPKG_ATTRIBUTES;
6742 79 : if (EQUAL(pszASpatialVariant, "GPKG_ATTRIBUTES"))
6743 67 : eASpatialVariant = GPKG_ATTRIBUTES;
6744 12 : else if (EQUAL(pszASpatialVariant, "OGR_ASPATIAL"))
6745 : {
6746 0 : CPLError(CE_Failure, CPLE_NotSupported,
6747 : "ASPATIAL_VARIANT=OGR_ASPATIAL is no longer supported");
6748 0 : return nullptr;
6749 : }
6750 12 : else if (EQUAL(pszASpatialVariant, "NOT_REGISTERED"))
6751 12 : eASpatialVariant = NOT_REGISTERED;
6752 : else
6753 : {
6754 0 : CPLError(CE_Failure, CPLE_NotSupported,
6755 : "Unsupported value for ASPATIAL_VARIANT: %s",
6756 : pszASpatialVariant);
6757 0 : return nullptr;
6758 : }
6759 79 : poLayer->SetASpatialVariant(eASpatialVariant);
6760 : }
6761 :
6762 : const char *pszDateTimePrecision =
6763 882 : CSLFetchNameValueDef(papszOptions, "DATETIME_PRECISION", "AUTO");
6764 882 : if (EQUAL(pszDateTimePrecision, "MILLISECOND"))
6765 : {
6766 2 : poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
6767 : }
6768 880 : else if (EQUAL(pszDateTimePrecision, "SECOND"))
6769 : {
6770 1 : if (m_nUserVersion < GPKG_1_4_VERSION)
6771 0 : CPLError(
6772 : CE_Warning, CPLE_AppDefined,
6773 : "DATETIME_PRECISION=SECOND is only valid since GeoPackage 1.4");
6774 1 : poLayer->SetDateTimePrecision(OGRISO8601Precision::SECOND);
6775 : }
6776 879 : else if (EQUAL(pszDateTimePrecision, "MINUTE"))
6777 : {
6778 1 : if (m_nUserVersion < GPKG_1_4_VERSION)
6779 0 : CPLError(
6780 : CE_Warning, CPLE_AppDefined,
6781 : "DATETIME_PRECISION=MINUTE is only valid since GeoPackage 1.4");
6782 1 : poLayer->SetDateTimePrecision(OGRISO8601Precision::MINUTE);
6783 : }
6784 878 : else if (EQUAL(pszDateTimePrecision, "AUTO"))
6785 : {
6786 877 : if (m_nUserVersion < GPKG_1_4_VERSION)
6787 13 : poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
6788 : }
6789 : else
6790 : {
6791 1 : CPLError(CE_Failure, CPLE_NotSupported,
6792 : "Unsupported value for DATETIME_PRECISION: %s",
6793 : pszDateTimePrecision);
6794 1 : return nullptr;
6795 : }
6796 :
6797 : // If there was an ogr_empty_table table, we can remove it
6798 : // But do it at dataset closing, otherwise locking performance issues
6799 : // can arise (probably when transactions are used).
6800 881 : m_bRemoveOGREmptyTable = true;
6801 :
6802 881 : m_apoLayers.emplace_back(std::move(poLayer));
6803 881 : return m_apoLayers.back().get();
6804 : }
6805 :
6806 : /************************************************************************/
6807 : /* FindLayerIndex() */
6808 : /************************************************************************/
6809 :
6810 27 : int GDALGeoPackageDataset::FindLayerIndex(const char *pszLayerName)
6811 :
6812 : {
6813 42 : for (int iLayer = 0; iLayer < static_cast<int>(m_apoLayers.size());
6814 : iLayer++)
6815 : {
6816 28 : if (EQUAL(pszLayerName, m_apoLayers[iLayer]->GetName()))
6817 13 : return iLayer;
6818 : }
6819 14 : return -1;
6820 : }
6821 :
6822 : /************************************************************************/
6823 : /* DeleteLayerCommon() */
6824 : /************************************************************************/
6825 :
6826 42 : OGRErr GDALGeoPackageDataset::DeleteLayerCommon(const char *pszLayerName)
6827 : {
6828 : // Temporary remove foreign key checks
6829 : const GPKGTemporaryForeignKeyCheckDisabler
6830 42 : oGPKGTemporaryForeignKeyCheckDisabler(this);
6831 :
6832 42 : char *pszSQL = sqlite3_mprintf(
6833 : "DELETE FROM gpkg_contents WHERE lower(table_name) = lower('%q')",
6834 : pszLayerName);
6835 42 : OGRErr eErr = SQLCommand(hDB, pszSQL);
6836 42 : sqlite3_free(pszSQL);
6837 :
6838 42 : if (eErr == OGRERR_NONE && HasExtensionsTable())
6839 : {
6840 40 : pszSQL = sqlite3_mprintf(
6841 : "DELETE FROM gpkg_extensions WHERE lower(table_name) = lower('%q')",
6842 : pszLayerName);
6843 40 : eErr = SQLCommand(hDB, pszSQL);
6844 40 : sqlite3_free(pszSQL);
6845 : }
6846 :
6847 42 : if (eErr == OGRERR_NONE && HasMetadataTables())
6848 : {
6849 : // Delete from gpkg_metadata metadata records that are only referenced
6850 : // by the table we are about to drop
6851 12 : pszSQL = sqlite3_mprintf(
6852 : "DELETE FROM gpkg_metadata WHERE id IN ("
6853 : "SELECT DISTINCT md_file_id FROM "
6854 : "gpkg_metadata_reference WHERE "
6855 : "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
6856 : "AND id NOT IN ("
6857 : "SELECT DISTINCT md_file_id FROM gpkg_metadata_reference WHERE "
6858 : "md_file_id IN (SELECT DISTINCT md_file_id FROM "
6859 : "gpkg_metadata_reference WHERE "
6860 : "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
6861 : "AND lower(table_name) <> lower('%q'))",
6862 : pszLayerName, pszLayerName, pszLayerName);
6863 12 : eErr = SQLCommand(hDB, pszSQL);
6864 12 : sqlite3_free(pszSQL);
6865 :
6866 12 : if (eErr == OGRERR_NONE)
6867 : {
6868 : pszSQL =
6869 12 : sqlite3_mprintf("DELETE FROM gpkg_metadata_reference WHERE "
6870 : "lower(table_name) = lower('%q')",
6871 : pszLayerName);
6872 12 : eErr = SQLCommand(hDB, pszSQL);
6873 12 : sqlite3_free(pszSQL);
6874 : }
6875 : }
6876 :
6877 42 : if (eErr == OGRERR_NONE && HasGpkgextRelationsTable())
6878 : {
6879 : // Remove reference to potential corresponding mapping table in
6880 : // gpkg_extensions
6881 4 : pszSQL = sqlite3_mprintf(
6882 : "DELETE FROM gpkg_extensions WHERE "
6883 : "extension_name IN ('related_tables', "
6884 : "'gpkg_related_tables') AND lower(table_name) = "
6885 : "(SELECT lower(mapping_table_name) FROM gpkgext_relations WHERE "
6886 : "lower(base_table_name) = lower('%q') OR "
6887 : "lower(related_table_name) = lower('%q') OR "
6888 : "lower(mapping_table_name) = lower('%q'))",
6889 : pszLayerName, pszLayerName, pszLayerName);
6890 4 : eErr = SQLCommand(hDB, pszSQL);
6891 4 : sqlite3_free(pszSQL);
6892 :
6893 4 : if (eErr == OGRERR_NONE)
6894 : {
6895 : // Remove reference to potential corresponding mapping table in
6896 : // gpkgext_relations
6897 : pszSQL =
6898 4 : sqlite3_mprintf("DELETE FROM gpkgext_relations WHERE "
6899 : "lower(base_table_name) = lower('%q') OR "
6900 : "lower(related_table_name) = lower('%q') OR "
6901 : "lower(mapping_table_name) = lower('%q')",
6902 : pszLayerName, pszLayerName, pszLayerName);
6903 4 : eErr = SQLCommand(hDB, pszSQL);
6904 4 : sqlite3_free(pszSQL);
6905 : }
6906 :
6907 4 : if (eErr == OGRERR_NONE && HasExtensionsTable())
6908 : {
6909 : // If there is no longer any mapping table, then completely
6910 : // remove any reference to the extension in gpkg_extensions
6911 : // as mandated per the related table specification.
6912 : OGRErr err;
6913 4 : if (SQLGetInteger(hDB,
6914 : "SELECT COUNT(*) FROM gpkg_extensions WHERE "
6915 : "extension_name IN ('related_tables', "
6916 : "'gpkg_related_tables') AND "
6917 : "lower(table_name) != 'gpkgext_relations'",
6918 4 : &err) == 0)
6919 : {
6920 2 : eErr = SQLCommand(hDB, "DELETE FROM gpkg_extensions WHERE "
6921 : "extension_name IN ('related_tables', "
6922 : "'gpkg_related_tables')");
6923 : }
6924 :
6925 4 : ClearCachedRelationships();
6926 : }
6927 : }
6928 :
6929 42 : if (eErr == OGRERR_NONE)
6930 : {
6931 42 : pszSQL = sqlite3_mprintf("DROP TABLE \"%w\"", pszLayerName);
6932 42 : eErr = SQLCommand(hDB, pszSQL);
6933 42 : sqlite3_free(pszSQL);
6934 : }
6935 :
6936 : // Check foreign key integrity
6937 42 : if (eErr == OGRERR_NONE)
6938 : {
6939 42 : eErr = PragmaCheck("foreign_key_check", "", 0);
6940 : }
6941 :
6942 84 : return eErr;
6943 : }
6944 :
6945 : /************************************************************************/
6946 : /* DeleteLayer() */
6947 : /************************************************************************/
6948 :
6949 39 : OGRErr GDALGeoPackageDataset::DeleteLayer(int iLayer)
6950 : {
6951 77 : if (!GetUpdate() || iLayer < 0 ||
6952 38 : iLayer >= static_cast<int>(m_apoLayers.size()))
6953 2 : return OGRERR_FAILURE;
6954 :
6955 37 : m_apoLayers[iLayer]->ResetReading();
6956 37 : m_apoLayers[iLayer]->SyncToDisk();
6957 :
6958 74 : CPLString osLayerName = m_apoLayers[iLayer]->GetName();
6959 :
6960 37 : CPLDebug("GPKG", "DeleteLayer(%s)", osLayerName.c_str());
6961 :
6962 : // Temporary remove foreign key checks
6963 : const GPKGTemporaryForeignKeyCheckDisabler
6964 37 : oGPKGTemporaryForeignKeyCheckDisabler(this);
6965 :
6966 37 : OGRErr eErr = SoftStartTransaction();
6967 :
6968 37 : if (eErr == OGRERR_NONE)
6969 : {
6970 37 : if (m_apoLayers[iLayer]->HasSpatialIndex())
6971 34 : m_apoLayers[iLayer]->DropSpatialIndex();
6972 :
6973 : char *pszSQL =
6974 37 : sqlite3_mprintf("DELETE FROM gpkg_geometry_columns WHERE "
6975 : "lower(table_name) = lower('%q')",
6976 : osLayerName.c_str());
6977 37 : eErr = SQLCommand(hDB, pszSQL);
6978 37 : sqlite3_free(pszSQL);
6979 : }
6980 :
6981 37 : if (eErr == OGRERR_NONE && HasDataColumnsTable())
6982 : {
6983 1 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_data_columns WHERE "
6984 : "lower(table_name) = lower('%q')",
6985 : osLayerName.c_str());
6986 1 : eErr = SQLCommand(hDB, pszSQL);
6987 1 : sqlite3_free(pszSQL);
6988 : }
6989 :
6990 : #ifdef ENABLE_GPKG_OGR_CONTENTS
6991 37 : if (eErr == OGRERR_NONE && m_bHasGPKGOGRContents)
6992 : {
6993 37 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_ogr_contents WHERE "
6994 : "lower(table_name) = lower('%q')",
6995 : osLayerName.c_str());
6996 37 : eErr = SQLCommand(hDB, pszSQL);
6997 37 : sqlite3_free(pszSQL);
6998 : }
6999 : #endif
7000 :
7001 37 : if (eErr == OGRERR_NONE)
7002 : {
7003 37 : eErr = DeleteLayerCommon(osLayerName.c_str());
7004 : }
7005 :
7006 37 : if (eErr == OGRERR_NONE)
7007 : {
7008 37 : eErr = SoftCommitTransaction();
7009 37 : if (eErr == OGRERR_NONE)
7010 : {
7011 : /* Delete the layer object */
7012 37 : m_apoLayers.erase(m_apoLayers.begin() + iLayer);
7013 : }
7014 : }
7015 : else
7016 : {
7017 0 : SoftRollbackTransaction();
7018 : }
7019 :
7020 37 : return eErr;
7021 : }
7022 :
7023 : /************************************************************************/
7024 : /* DeleteRasterLayer() */
7025 : /************************************************************************/
7026 :
7027 2 : OGRErr GDALGeoPackageDataset::DeleteRasterLayer(const char *pszLayerName)
7028 : {
7029 : // Temporary remove foreign key checks
7030 : const GPKGTemporaryForeignKeyCheckDisabler
7031 2 : oGPKGTemporaryForeignKeyCheckDisabler(this);
7032 :
7033 2 : OGRErr eErr = SoftStartTransaction();
7034 :
7035 2 : if (eErr == OGRERR_NONE)
7036 : {
7037 2 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix WHERE "
7038 : "lower(table_name) = lower('%q')",
7039 : pszLayerName);
7040 2 : eErr = SQLCommand(hDB, pszSQL);
7041 2 : sqlite3_free(pszSQL);
7042 : }
7043 :
7044 2 : if (eErr == OGRERR_NONE)
7045 : {
7046 2 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix_set WHERE "
7047 : "lower(table_name) = lower('%q')",
7048 : pszLayerName);
7049 2 : eErr = SQLCommand(hDB, pszSQL);
7050 2 : sqlite3_free(pszSQL);
7051 : }
7052 :
7053 2 : if (eErr == OGRERR_NONE && HasGriddedCoverageAncillaryTable())
7054 : {
7055 : char *pszSQL =
7056 1 : sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_coverage_ancillary "
7057 : "WHERE lower(tile_matrix_set_name) = lower('%q')",
7058 : pszLayerName);
7059 1 : eErr = SQLCommand(hDB, pszSQL);
7060 1 : sqlite3_free(pszSQL);
7061 :
7062 1 : if (eErr == OGRERR_NONE)
7063 : {
7064 : pszSQL =
7065 1 : sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_tile_ancillary "
7066 : "WHERE lower(tpudt_name) = lower('%q')",
7067 : pszLayerName);
7068 1 : eErr = SQLCommand(hDB, pszSQL);
7069 1 : sqlite3_free(pszSQL);
7070 : }
7071 : }
7072 :
7073 2 : if (eErr == OGRERR_NONE)
7074 : {
7075 2 : eErr = DeleteLayerCommon(pszLayerName);
7076 : }
7077 :
7078 2 : if (eErr == OGRERR_NONE)
7079 : {
7080 2 : eErr = SoftCommitTransaction();
7081 : }
7082 : else
7083 : {
7084 0 : SoftRollbackTransaction();
7085 : }
7086 :
7087 4 : return eErr;
7088 : }
7089 :
7090 : /************************************************************************/
7091 : /* DeleteVectorOrRasterLayer() */
7092 : /************************************************************************/
7093 :
7094 13 : bool GDALGeoPackageDataset::DeleteVectorOrRasterLayer(const char *pszLayerName)
7095 : {
7096 :
7097 13 : int idx = FindLayerIndex(pszLayerName);
7098 13 : if (idx >= 0)
7099 : {
7100 5 : DeleteLayer(idx);
7101 5 : return true;
7102 : }
7103 :
7104 : char *pszSQL =
7105 8 : sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
7106 : "lower(table_name) = lower('%q') "
7107 : "AND data_type IN ('tiles', '2d-gridded-coverage')",
7108 : pszLayerName);
7109 8 : bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
7110 8 : sqlite3_free(pszSQL);
7111 8 : if (bIsRasterTable)
7112 : {
7113 2 : DeleteRasterLayer(pszLayerName);
7114 2 : return true;
7115 : }
7116 6 : return false;
7117 : }
7118 :
7119 7 : bool GDALGeoPackageDataset::RenameVectorOrRasterLayer(
7120 : const char *pszLayerName, const char *pszNewLayerName)
7121 : {
7122 7 : int idx = FindLayerIndex(pszLayerName);
7123 7 : if (idx >= 0)
7124 : {
7125 4 : m_apoLayers[idx]->Rename(pszNewLayerName);
7126 4 : return true;
7127 : }
7128 :
7129 : char *pszSQL =
7130 3 : sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
7131 : "lower(table_name) = lower('%q') "
7132 : "AND data_type IN ('tiles', '2d-gridded-coverage')",
7133 : pszLayerName);
7134 3 : const bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
7135 3 : sqlite3_free(pszSQL);
7136 :
7137 3 : if (bIsRasterTable)
7138 : {
7139 2 : return RenameRasterLayer(pszLayerName, pszNewLayerName);
7140 : }
7141 :
7142 1 : return false;
7143 : }
7144 :
7145 2 : bool GDALGeoPackageDataset::RenameRasterLayer(const char *pszLayerName,
7146 : const char *pszNewLayerName)
7147 : {
7148 4 : std::string osSQL;
7149 :
7150 2 : char *pszSQL = sqlite3_mprintf(
7151 : "SELECT 1 FROM sqlite_master WHERE lower(name) = lower('%q') "
7152 : "AND type IN ('table', 'view')",
7153 : pszNewLayerName);
7154 2 : const bool bAlreadyExists = SQLGetInteger(GetDB(), pszSQL, nullptr) == 1;
7155 2 : sqlite3_free(pszSQL);
7156 2 : if (bAlreadyExists)
7157 : {
7158 0 : CPLError(CE_Failure, CPLE_AppDefined, "Table %s already exists",
7159 : pszNewLayerName);
7160 0 : return false;
7161 : }
7162 :
7163 : // Temporary remove foreign key checks
7164 : const GPKGTemporaryForeignKeyCheckDisabler
7165 4 : oGPKGTemporaryForeignKeyCheckDisabler(this);
7166 :
7167 2 : if (SoftStartTransaction() != OGRERR_NONE)
7168 : {
7169 0 : return false;
7170 : }
7171 :
7172 2 : pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET table_name = '%q' WHERE "
7173 : "lower(table_name) = lower('%q');",
7174 : pszNewLayerName, pszLayerName);
7175 2 : osSQL = pszSQL;
7176 2 : sqlite3_free(pszSQL);
7177 :
7178 2 : pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' WHERE "
7179 : "lower(identifier) = lower('%q');",
7180 : pszNewLayerName, pszLayerName);
7181 2 : osSQL += pszSQL;
7182 2 : sqlite3_free(pszSQL);
7183 :
7184 : pszSQL =
7185 2 : sqlite3_mprintf("UPDATE gpkg_tile_matrix SET table_name = '%q' WHERE "
7186 : "lower(table_name) = lower('%q');",
7187 : pszNewLayerName, pszLayerName);
7188 2 : osSQL += pszSQL;
7189 2 : sqlite3_free(pszSQL);
7190 :
7191 2 : pszSQL = sqlite3_mprintf(
7192 : "UPDATE gpkg_tile_matrix_set SET table_name = '%q' WHERE "
7193 : "lower(table_name) = lower('%q');",
7194 : pszNewLayerName, pszLayerName);
7195 2 : osSQL += pszSQL;
7196 2 : sqlite3_free(pszSQL);
7197 :
7198 2 : if (HasGriddedCoverageAncillaryTable())
7199 : {
7200 1 : pszSQL = sqlite3_mprintf("UPDATE gpkg_2d_gridded_coverage_ancillary "
7201 : "SET tile_matrix_set_name = '%q' WHERE "
7202 : "lower(tile_matrix_set_name) = lower('%q');",
7203 : pszNewLayerName, pszLayerName);
7204 1 : osSQL += pszSQL;
7205 1 : sqlite3_free(pszSQL);
7206 :
7207 1 : pszSQL = sqlite3_mprintf(
7208 : "UPDATE gpkg_2d_gridded_tile_ancillary SET tpudt_name = '%q' WHERE "
7209 : "lower(tpudt_name) = lower('%q');",
7210 : pszNewLayerName, pszLayerName);
7211 1 : osSQL += pszSQL;
7212 1 : sqlite3_free(pszSQL);
7213 : }
7214 :
7215 2 : if (HasExtensionsTable())
7216 : {
7217 2 : pszSQL = sqlite3_mprintf(
7218 : "UPDATE gpkg_extensions SET table_name = '%q' WHERE "
7219 : "lower(table_name) = lower('%q');",
7220 : pszNewLayerName, pszLayerName);
7221 2 : osSQL += pszSQL;
7222 2 : sqlite3_free(pszSQL);
7223 : }
7224 :
7225 2 : if (HasMetadataTables())
7226 : {
7227 1 : pszSQL = sqlite3_mprintf(
7228 : "UPDATE gpkg_metadata_reference SET table_name = '%q' WHERE "
7229 : "lower(table_name) = lower('%q');",
7230 : pszNewLayerName, pszLayerName);
7231 1 : osSQL += pszSQL;
7232 1 : sqlite3_free(pszSQL);
7233 : }
7234 :
7235 2 : if (HasDataColumnsTable())
7236 : {
7237 0 : pszSQL = sqlite3_mprintf(
7238 : "UPDATE gpkg_data_columns SET table_name = '%q' WHERE "
7239 : "lower(table_name) = lower('%q');",
7240 : pszNewLayerName, pszLayerName);
7241 0 : osSQL += pszSQL;
7242 0 : sqlite3_free(pszSQL);
7243 : }
7244 :
7245 2 : if (HasQGISLayerStyles())
7246 : {
7247 : // Update QGIS styles
7248 : pszSQL =
7249 0 : sqlite3_mprintf("UPDATE layer_styles SET f_table_name = '%q' WHERE "
7250 : "lower(f_table_name) = lower('%q');",
7251 : pszNewLayerName, pszLayerName);
7252 0 : osSQL += pszSQL;
7253 0 : sqlite3_free(pszSQL);
7254 : }
7255 :
7256 : #ifdef ENABLE_GPKG_OGR_CONTENTS
7257 2 : if (m_bHasGPKGOGRContents)
7258 : {
7259 2 : pszSQL = sqlite3_mprintf(
7260 : "UPDATE gpkg_ogr_contents SET table_name = '%q' WHERE "
7261 : "lower(table_name) = lower('%q');",
7262 : pszNewLayerName, pszLayerName);
7263 2 : osSQL += pszSQL;
7264 2 : sqlite3_free(pszSQL);
7265 : }
7266 : #endif
7267 :
7268 2 : if (HasGpkgextRelationsTable())
7269 : {
7270 0 : pszSQL = sqlite3_mprintf(
7271 : "UPDATE gpkgext_relations SET base_table_name = '%q' WHERE "
7272 : "lower(base_table_name) = lower('%q');",
7273 : pszNewLayerName, pszLayerName);
7274 0 : osSQL += pszSQL;
7275 0 : sqlite3_free(pszSQL);
7276 :
7277 0 : pszSQL = sqlite3_mprintf(
7278 : "UPDATE gpkgext_relations SET related_table_name = '%q' WHERE "
7279 : "lower(related_table_name) = lower('%q');",
7280 : pszNewLayerName, pszLayerName);
7281 0 : osSQL += pszSQL;
7282 0 : sqlite3_free(pszSQL);
7283 :
7284 0 : pszSQL = sqlite3_mprintf(
7285 : "UPDATE gpkgext_relations SET mapping_table_name = '%q' WHERE "
7286 : "lower(mapping_table_name) = lower('%q');",
7287 : pszNewLayerName, pszLayerName);
7288 0 : osSQL += pszSQL;
7289 0 : sqlite3_free(pszSQL);
7290 : }
7291 :
7292 : // Drop all triggers for the layer
7293 2 : pszSQL = sqlite3_mprintf("SELECT name FROM sqlite_master WHERE type = "
7294 : "'trigger' AND tbl_name = '%q'",
7295 : pszLayerName);
7296 2 : auto oTriggerResult = SQLQuery(GetDB(), pszSQL);
7297 2 : sqlite3_free(pszSQL);
7298 2 : if (oTriggerResult)
7299 : {
7300 14 : for (int i = 0; i < oTriggerResult->RowCount(); i++)
7301 : {
7302 12 : const char *pszTriggerName = oTriggerResult->GetValue(0, i);
7303 12 : pszSQL = sqlite3_mprintf("DROP TRIGGER IF EXISTS \"%w\";",
7304 : pszTriggerName);
7305 12 : osSQL += pszSQL;
7306 12 : sqlite3_free(pszSQL);
7307 : }
7308 : }
7309 :
7310 2 : pszSQL = sqlite3_mprintf("ALTER TABLE \"%w\" RENAME TO \"%w\";",
7311 : pszLayerName, pszNewLayerName);
7312 2 : osSQL += pszSQL;
7313 2 : sqlite3_free(pszSQL);
7314 :
7315 : // Recreate all zoom/tile triggers
7316 2 : if (oTriggerResult)
7317 : {
7318 2 : osSQL += CreateRasterTriggersSQL(pszNewLayerName);
7319 : }
7320 :
7321 2 : OGRErr eErr = SQLCommand(GetDB(), osSQL.c_str());
7322 :
7323 : // Check foreign key integrity
7324 2 : if (eErr == OGRERR_NONE)
7325 : {
7326 2 : eErr = PragmaCheck("foreign_key_check", "", 0);
7327 : }
7328 :
7329 2 : if (eErr == OGRERR_NONE)
7330 : {
7331 2 : eErr = SoftCommitTransaction();
7332 : }
7333 : else
7334 : {
7335 0 : SoftRollbackTransaction();
7336 : }
7337 :
7338 2 : return eErr == OGRERR_NONE;
7339 : }
7340 :
7341 : /************************************************************************/
7342 : /* TestCapability() */
7343 : /************************************************************************/
7344 :
7345 531 : int GDALGeoPackageDataset::TestCapability(const char *pszCap) const
7346 : {
7347 531 : if (EQUAL(pszCap, ODsCCreateLayer) || EQUAL(pszCap, ODsCDeleteLayer) ||
7348 343 : EQUAL(pszCap, "RenameLayer"))
7349 : {
7350 188 : return GetUpdate();
7351 : }
7352 343 : else if (EQUAL(pszCap, ODsCCurveGeometries))
7353 12 : return TRUE;
7354 331 : else if (EQUAL(pszCap, ODsCMeasuredGeometries))
7355 8 : return TRUE;
7356 323 : else if (EQUAL(pszCap, ODsCZGeometries))
7357 8 : return TRUE;
7358 315 : else if (EQUAL(pszCap, ODsCRandomLayerWrite) ||
7359 315 : EQUAL(pszCap, GDsCAddRelationship) ||
7360 315 : EQUAL(pszCap, GDsCDeleteRelationship) ||
7361 315 : EQUAL(pszCap, GDsCUpdateRelationship) ||
7362 315 : EQUAL(pszCap, ODsCAddFieldDomain) ||
7363 313 : EQUAL(pszCap, ODsCUpdateFieldDomain) ||
7364 311 : EQUAL(pszCap, ODsCDeleteFieldDomain))
7365 : {
7366 6 : return GetUpdate();
7367 : }
7368 :
7369 309 : return OGRSQLiteBaseDataSource::TestCapability(pszCap);
7370 : }
7371 :
7372 : /************************************************************************/
7373 : /* ResetReadingAllLayers() */
7374 : /************************************************************************/
7375 :
7376 205 : void GDALGeoPackageDataset::ResetReadingAllLayers()
7377 : {
7378 415 : for (auto &poLayer : m_apoLayers)
7379 : {
7380 210 : poLayer->ResetReading();
7381 : }
7382 205 : }
7383 :
7384 : /************************************************************************/
7385 : /* ExecuteSQL() */
7386 : /************************************************************************/
7387 :
7388 : static const char *const apszFuncsWithSideEffects[] = {
7389 : "CreateSpatialIndex",
7390 : "DisableSpatialIndex",
7391 : "HasSpatialIndex",
7392 : "RegisterGeometryExtension",
7393 : };
7394 :
7395 5704 : OGRLayer *GDALGeoPackageDataset::ExecuteSQL(const char *pszSQLCommand,
7396 : OGRGeometry *poSpatialFilter,
7397 : const char *pszDialect)
7398 :
7399 : {
7400 5704 : m_bHasReadMetadataFromStorage = false;
7401 :
7402 5704 : FlushMetadata();
7403 :
7404 5722 : while (*pszSQLCommand != '\0' &&
7405 5722 : isspace(static_cast<unsigned char>(*pszSQLCommand)))
7406 18 : pszSQLCommand++;
7407 :
7408 11408 : CPLString osSQLCommand(pszSQLCommand);
7409 5704 : if (!osSQLCommand.empty() && osSQLCommand.back() == ';')
7410 48 : osSQLCommand.pop_back();
7411 :
7412 11407 : if (osSQLCommand.ifind("AsGPB(ST_") != std::string::npos ||
7413 5703 : osSQLCommand.ifind("AsGPB( ST_") != std::string::npos)
7414 : {
7415 1 : CPLError(CE_Warning, CPLE_AppDefined,
7416 : "Use of AsGPB(ST_xxx(...)) found in \"%s\". Since GDAL 3.13, "
7417 : "ST_xxx() functions return a GeoPackage geometry when used "
7418 : "with a GeoPackage connection, and the use of AsGPB() is no "
7419 : "longer needed. It is here automatically removed",
7420 : osSQLCommand.c_str());
7421 1 : osSQLCommand.replaceAll("AsGPB(ST_", "(ST_");
7422 1 : osSQLCommand.replaceAll("AsGPB( ST_", "(ST_");
7423 : }
7424 :
7425 5704 : if (pszDialect == nullptr || !EQUAL(pszDialect, "DEBUG"))
7426 : {
7427 : // Some SQL commands will influence the feature count behind our
7428 : // back, so disable it in that case.
7429 : #ifdef ENABLE_GPKG_OGR_CONTENTS
7430 : const bool bInsertOrDelete =
7431 5635 : osSQLCommand.ifind("insert into ") != std::string::npos ||
7432 2514 : osSQLCommand.ifind("insert or replace into ") !=
7433 8149 : std::string::npos ||
7434 2477 : osSQLCommand.ifind("delete from ") != std::string::npos;
7435 : const bool bRollback =
7436 5635 : osSQLCommand.ifind("rollback ") != std::string::npos;
7437 : #endif
7438 :
7439 7518 : for (auto &poLayer : m_apoLayers)
7440 : {
7441 1883 : if (poLayer->SyncToDisk() != OGRERR_NONE)
7442 0 : return nullptr;
7443 : #ifdef ENABLE_GPKG_OGR_CONTENTS
7444 2087 : if (bRollback ||
7445 204 : (bInsertOrDelete &&
7446 204 : osSQLCommand.ifind(poLayer->GetName()) != std::string::npos))
7447 : {
7448 202 : poLayer->DisableFeatureCount();
7449 : }
7450 : #endif
7451 : }
7452 : }
7453 :
7454 5704 : if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 0") ||
7455 5703 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=0") ||
7456 5703 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =0") ||
7457 5703 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 0"))
7458 : {
7459 1 : OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, false);
7460 : }
7461 5703 : else if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 1") ||
7462 5702 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=1") ||
7463 5702 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =1") ||
7464 5702 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 1"))
7465 : {
7466 1 : OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, true);
7467 : }
7468 :
7469 : /* -------------------------------------------------------------------- */
7470 : /* DEBUG "SELECT nolock" command. */
7471 : /* -------------------------------------------------------------------- */
7472 5773 : if (pszDialect != nullptr && EQUAL(pszDialect, "DEBUG") &&
7473 69 : EQUAL(osSQLCommand, "SELECT nolock"))
7474 : {
7475 3 : return new OGRSQLiteSingleFeatureLayer(osSQLCommand, m_bNoLock ? 1 : 0);
7476 : }
7477 :
7478 : /* -------------------------------------------------------------------- */
7479 : /* Special case DELLAYER: command. */
7480 : /* -------------------------------------------------------------------- */
7481 5701 : if (STARTS_WITH_CI(osSQLCommand, "DELLAYER:"))
7482 : {
7483 4 : const char *pszLayerName = osSQLCommand.c_str() + strlen("DELLAYER:");
7484 :
7485 4 : while (*pszLayerName == ' ')
7486 0 : pszLayerName++;
7487 :
7488 4 : if (!DeleteVectorOrRasterLayer(pszLayerName))
7489 : {
7490 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
7491 : pszLayerName);
7492 : }
7493 4 : return nullptr;
7494 : }
7495 :
7496 : /* -------------------------------------------------------------------- */
7497 : /* Special case RECOMPUTE EXTENT ON command. */
7498 : /* -------------------------------------------------------------------- */
7499 5697 : if (STARTS_WITH_CI(osSQLCommand, "RECOMPUTE EXTENT ON "))
7500 : {
7501 : const char *pszLayerName =
7502 4 : osSQLCommand.c_str() + strlen("RECOMPUTE EXTENT ON ");
7503 :
7504 4 : while (*pszLayerName == ' ')
7505 0 : pszLayerName++;
7506 :
7507 4 : int idx = FindLayerIndex(pszLayerName);
7508 4 : if (idx >= 0)
7509 : {
7510 4 : m_apoLayers[idx]->RecomputeExtent();
7511 : }
7512 : else
7513 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
7514 : pszLayerName);
7515 4 : return nullptr;
7516 : }
7517 :
7518 : /* -------------------------------------------------------------------- */
7519 : /* Intercept DROP TABLE */
7520 : /* -------------------------------------------------------------------- */
7521 5693 : if (STARTS_WITH_CI(osSQLCommand, "DROP TABLE "))
7522 : {
7523 9 : const char *pszLayerName = osSQLCommand.c_str() + strlen("DROP TABLE ");
7524 :
7525 9 : while (*pszLayerName == ' ')
7526 0 : pszLayerName++;
7527 :
7528 9 : if (DeleteVectorOrRasterLayer(SQLUnescape(pszLayerName)))
7529 4 : return nullptr;
7530 : }
7531 :
7532 : /* -------------------------------------------------------------------- */
7533 : /* Intercept ALTER TABLE src_table RENAME TO dst_table */
7534 : /* and ALTER TABLE table RENAME COLUMN src_name TO dst_name */
7535 : /* and ALTER TABLE table DROP COLUMN col_name */
7536 : /* */
7537 : /* We do this because SQLite mechanisms can't deal with updating */
7538 : /* literal values in gpkg_ tables that refer to table and column */
7539 : /* names. */
7540 : /* -------------------------------------------------------------------- */
7541 5689 : if (STARTS_WITH_CI(osSQLCommand, "ALTER TABLE "))
7542 : {
7543 9 : char **papszTokens = SQLTokenize(osSQLCommand);
7544 : /* ALTER TABLE src_table RENAME TO dst_table */
7545 16 : if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "RENAME") &&
7546 7 : EQUAL(papszTokens[4], "TO"))
7547 : {
7548 7 : const char *pszSrcTableName = papszTokens[2];
7549 7 : const char *pszDstTableName = papszTokens[5];
7550 7 : if (RenameVectorOrRasterLayer(SQLUnescape(pszSrcTableName),
7551 14 : SQLUnescape(pszDstTableName)))
7552 : {
7553 6 : CSLDestroy(papszTokens);
7554 6 : return nullptr;
7555 : }
7556 : }
7557 : /* ALTER TABLE table RENAME COLUMN src_name TO dst_name */
7558 2 : else if (CSLCount(papszTokens) == 8 &&
7559 1 : EQUAL(papszTokens[3], "RENAME") &&
7560 3 : EQUAL(papszTokens[4], "COLUMN") && EQUAL(papszTokens[6], "TO"))
7561 : {
7562 1 : const char *pszTableName = papszTokens[2];
7563 1 : const char *pszSrcColumn = papszTokens[5];
7564 1 : const char *pszDstColumn = papszTokens[7];
7565 : OGRGeoPackageTableLayer *poLayer =
7566 0 : dynamic_cast<OGRGeoPackageTableLayer *>(
7567 1 : GetLayerByName(SQLUnescape(pszTableName)));
7568 1 : if (poLayer)
7569 : {
7570 2 : int nSrcFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
7571 2 : SQLUnescape(pszSrcColumn));
7572 1 : if (nSrcFieldIdx >= 0)
7573 : {
7574 : // OFTString or any type will do as we just alter the name
7575 : // so it will be ignored.
7576 1 : OGRFieldDefn oFieldDefn(SQLUnescape(pszDstColumn),
7577 1 : OFTString);
7578 1 : poLayer->AlterFieldDefn(nSrcFieldIdx, &oFieldDefn,
7579 : ALTER_NAME_FLAG);
7580 1 : CSLDestroy(papszTokens);
7581 1 : return nullptr;
7582 : }
7583 : }
7584 : }
7585 : /* ALTER TABLE table DROP COLUMN col_name */
7586 2 : else if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "DROP") &&
7587 1 : EQUAL(papszTokens[4], "COLUMN"))
7588 : {
7589 1 : const char *pszTableName = papszTokens[2];
7590 1 : const char *pszColumnName = papszTokens[5];
7591 : OGRGeoPackageTableLayer *poLayer =
7592 0 : dynamic_cast<OGRGeoPackageTableLayer *>(
7593 1 : GetLayerByName(SQLUnescape(pszTableName)));
7594 1 : if (poLayer)
7595 : {
7596 2 : int nFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
7597 2 : SQLUnescape(pszColumnName));
7598 1 : if (nFieldIdx >= 0)
7599 : {
7600 1 : poLayer->DeleteField(nFieldIdx);
7601 1 : CSLDestroy(papszTokens);
7602 1 : return nullptr;
7603 : }
7604 : }
7605 : }
7606 1 : CSLDestroy(papszTokens);
7607 : }
7608 :
7609 5681 : if (ProcessTransactionSQL(osSQLCommand))
7610 : {
7611 253 : return nullptr;
7612 : }
7613 :
7614 5428 : if (EQUAL(osSQLCommand, "VACUUM"))
7615 : {
7616 13 : ResetReadingAllLayers();
7617 : }
7618 5415 : else if (STARTS_WITH_CI(osSQLCommand, "DELETE FROM "))
7619 : {
7620 : // Optimize truncation of a table, especially if it has a spatial
7621 : // index.
7622 23 : const CPLStringList aosTokens(SQLTokenize(osSQLCommand));
7623 23 : if (aosTokens.size() == 3)
7624 : {
7625 16 : const char *pszTableName = aosTokens[2];
7626 : OGRGeoPackageTableLayer *poLayer =
7627 8 : dynamic_cast<OGRGeoPackageTableLayer *>(
7628 24 : GetLayerByName(SQLUnescape(pszTableName)));
7629 16 : if (poLayer)
7630 : {
7631 8 : poLayer->Truncate();
7632 8 : return nullptr;
7633 : }
7634 : }
7635 : }
7636 5392 : else if (pszDialect != nullptr && EQUAL(pszDialect, "INDIRECT_SQLITE"))
7637 1 : return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter, "SQLITE");
7638 5391 : else if (pszDialect != nullptr && !EQUAL(pszDialect, "") &&
7639 67 : !EQUAL(pszDialect, "NATIVE") && !EQUAL(pszDialect, "SQLITE") &&
7640 67 : !EQUAL(pszDialect, "DEBUG"))
7641 1 : return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter,
7642 1 : pszDialect);
7643 :
7644 : /* -------------------------------------------------------------------- */
7645 : /* Prepare statement. */
7646 : /* -------------------------------------------------------------------- */
7647 5418 : sqlite3_stmt *hSQLStmt = nullptr;
7648 :
7649 : /* This will speed-up layer creation */
7650 : /* ORDER BY are costly to evaluate and are not necessary to establish */
7651 : /* the layer definition. */
7652 5418 : bool bUseStatementForGetNextFeature = true;
7653 5418 : bool bEmptyLayer = false;
7654 10836 : CPLString osSQLCommandTruncated(osSQLCommand);
7655 :
7656 17970 : if (osSQLCommand.ifind("SELECT ") == 0 &&
7657 6276 : CPLString(osSQLCommand.substr(1)).ifind("SELECT ") ==
7658 823 : std::string::npos &&
7659 823 : osSQLCommand.ifind(" UNION ") == std::string::npos &&
7660 7099 : osSQLCommand.ifind(" INTERSECT ") == std::string::npos &&
7661 823 : osSQLCommand.ifind(" EXCEPT ") == std::string::npos)
7662 : {
7663 823 : size_t nOrderByPos = osSQLCommand.ifind(" ORDER BY ");
7664 823 : if (nOrderByPos != std::string::npos)
7665 : {
7666 9 : osSQLCommandTruncated.resize(nOrderByPos);
7667 9 : bUseStatementForGetNextFeature = false;
7668 : }
7669 : }
7670 :
7671 5418 : int rc = prepareSql(hDB, osSQLCommandTruncated.c_str(),
7672 5418 : static_cast<int>(osSQLCommandTruncated.size()),
7673 : &hSQLStmt, nullptr);
7674 :
7675 5418 : if (rc != SQLITE_OK)
7676 : {
7677 9 : CPLError(CE_Failure, CPLE_AppDefined,
7678 : "In ExecuteSQL(): sqlite3_prepare_v2(%s): %s",
7679 : osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
7680 :
7681 9 : if (hSQLStmt != nullptr)
7682 : {
7683 0 : sqlite3_finalize(hSQLStmt);
7684 : }
7685 :
7686 9 : return nullptr;
7687 : }
7688 :
7689 : /* -------------------------------------------------------------------- */
7690 : /* Do we get a resultset? */
7691 : /* -------------------------------------------------------------------- */
7692 5409 : rc = sqlite3_step(hSQLStmt);
7693 :
7694 7057 : for (auto &poLayer : m_apoLayers)
7695 : {
7696 1648 : poLayer->RunDeferredDropRTreeTableIfNecessary();
7697 : }
7698 :
7699 5409 : if (rc != SQLITE_ROW)
7700 : {
7701 4634 : if (rc != SQLITE_DONE)
7702 : {
7703 7 : CPLError(CE_Failure, CPLE_AppDefined,
7704 : "In ExecuteSQL(): sqlite3_step(%s):\n %s",
7705 : osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
7706 :
7707 7 : sqlite3_finalize(hSQLStmt);
7708 7 : return nullptr;
7709 : }
7710 :
7711 4627 : if (EQUAL(osSQLCommand, "VACUUM"))
7712 : {
7713 13 : sqlite3_finalize(hSQLStmt);
7714 : /* VACUUM rewrites the DB, so we need to reset the application id */
7715 13 : SetApplicationAndUserVersionId();
7716 13 : return nullptr;
7717 : }
7718 :
7719 4614 : if (!STARTS_WITH_CI(osSQLCommand, "SELECT "))
7720 : {
7721 4487 : sqlite3_finalize(hSQLStmt);
7722 4487 : return nullptr;
7723 : }
7724 :
7725 127 : bUseStatementForGetNextFeature = false;
7726 127 : bEmptyLayer = true;
7727 : }
7728 :
7729 : /* -------------------------------------------------------------------- */
7730 : /* Special case for some functions which must be run */
7731 : /* only once */
7732 : /* -------------------------------------------------------------------- */
7733 902 : if (STARTS_WITH_CI(osSQLCommand, "SELECT "))
7734 : {
7735 4134 : for (unsigned int i = 0; i < sizeof(apszFuncsWithSideEffects) /
7736 : sizeof(apszFuncsWithSideEffects[0]);
7737 : i++)
7738 : {
7739 3333 : if (EQUALN(apszFuncsWithSideEffects[i], osSQLCommand.c_str() + 7,
7740 : strlen(apszFuncsWithSideEffects[i])))
7741 : {
7742 112 : if (sqlite3_column_count(hSQLStmt) == 1 &&
7743 56 : sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
7744 : {
7745 56 : int ret = sqlite3_column_int(hSQLStmt, 0);
7746 :
7747 56 : sqlite3_finalize(hSQLStmt);
7748 :
7749 : return new OGRSQLiteSingleFeatureLayer(
7750 56 : apszFuncsWithSideEffects[i], ret);
7751 : }
7752 : }
7753 : }
7754 : }
7755 45 : else if (STARTS_WITH_CI(osSQLCommand, "PRAGMA "))
7756 : {
7757 63 : if (sqlite3_column_count(hSQLStmt) == 1 &&
7758 18 : sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
7759 : {
7760 15 : int ret = sqlite3_column_int(hSQLStmt, 0);
7761 :
7762 15 : sqlite3_finalize(hSQLStmt);
7763 :
7764 15 : return new OGRSQLiteSingleFeatureLayer(osSQLCommand.c_str() + 7,
7765 15 : ret);
7766 : }
7767 33 : else if (sqlite3_column_count(hSQLStmt) == 1 &&
7768 3 : sqlite3_column_type(hSQLStmt, 0) == SQLITE_TEXT)
7769 : {
7770 : const char *pszRet = reinterpret_cast<const char *>(
7771 3 : sqlite3_column_text(hSQLStmt, 0));
7772 :
7773 : OGRLayer *poRet = new OGRSQLiteSingleFeatureLayer(
7774 3 : osSQLCommand.c_str() + 7, pszRet);
7775 :
7776 3 : sqlite3_finalize(hSQLStmt);
7777 :
7778 3 : return poRet;
7779 : }
7780 : }
7781 :
7782 : /* -------------------------------------------------------------------- */
7783 : /* Create layer. */
7784 : /* -------------------------------------------------------------------- */
7785 :
7786 : auto poLayer = std::make_unique<OGRGeoPackageSelectLayer>(
7787 : this, osSQLCommand, hSQLStmt, bUseStatementForGetNextFeature,
7788 1656 : bEmptyLayer);
7789 :
7790 831 : if (poSpatialFilter != nullptr &&
7791 3 : poLayer->GetLayerDefn()->GetGeomFieldCount() > 0)
7792 3 : poLayer->SetSpatialFilter(0, poSpatialFilter);
7793 :
7794 828 : return poLayer.release();
7795 : }
7796 :
7797 : /************************************************************************/
7798 : /* ReleaseResultSet() */
7799 : /************************************************************************/
7800 :
7801 861 : void GDALGeoPackageDataset::ReleaseResultSet(OGRLayer *poLayer)
7802 :
7803 : {
7804 861 : delete poLayer;
7805 861 : }
7806 :
7807 : /************************************************************************/
7808 : /* HasExtensionsTable() */
7809 : /************************************************************************/
7810 :
7811 7138 : bool GDALGeoPackageDataset::HasExtensionsTable()
7812 : {
7813 7138 : return SQLGetInteger(
7814 : hDB,
7815 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_extensions' "
7816 : "AND type IN ('table', 'view')",
7817 7138 : nullptr) == 1;
7818 : }
7819 :
7820 : /************************************************************************/
7821 : /* CheckUnknownExtensions() */
7822 : /************************************************************************/
7823 :
7824 1589 : void GDALGeoPackageDataset::CheckUnknownExtensions(bool bCheckRasterTable)
7825 : {
7826 1589 : if (!HasExtensionsTable())
7827 209 : return;
7828 :
7829 1380 : char *pszSQL = nullptr;
7830 1380 : if (!bCheckRasterTable)
7831 1166 : pszSQL = sqlite3_mprintf(
7832 : "SELECT extension_name, definition, scope FROM gpkg_extensions "
7833 : "WHERE (table_name IS NULL "
7834 : "AND extension_name IS NOT NULL "
7835 : "AND definition IS NOT NULL "
7836 : "AND scope IS NOT NULL "
7837 : "AND extension_name NOT IN ("
7838 : "'gdal_aspatial', "
7839 : "'gpkg_elevation_tiles', " // Old name before GPKG 1.2 approval
7840 : "'2d_gridded_coverage', " // Old name after GPKG 1.2 and before OGC
7841 : // 17-066r1 finalization
7842 : "'gpkg_2d_gridded_coverage', " // Name in OGC 17-066r1 final
7843 : "'gpkg_metadata', "
7844 : "'gpkg_schema', "
7845 : "'gpkg_crs_wkt', "
7846 : "'gpkg_crs_wkt_1_1', "
7847 : "'related_tables', 'gpkg_related_tables')) "
7848 : #ifdef WORKAROUND_SQLITE3_BUGS
7849 : "OR 0 "
7850 : #endif
7851 : "LIMIT 1000");
7852 : else
7853 214 : pszSQL = sqlite3_mprintf(
7854 : "SELECT extension_name, definition, scope FROM gpkg_extensions "
7855 : "WHERE (lower(table_name) = lower('%q') "
7856 : "AND extension_name IS NOT NULL "
7857 : "AND definition IS NOT NULL "
7858 : "AND scope IS NOT NULL "
7859 : "AND extension_name NOT IN ("
7860 : "'gpkg_elevation_tiles', " // Old name before GPKG 1.2 approval
7861 : "'2d_gridded_coverage', " // Old name after GPKG 1.2 and before OGC
7862 : // 17-066r1 finalization
7863 : "'gpkg_2d_gridded_coverage', " // Name in OGC 17-066r1 final
7864 : "'gpkg_metadata', "
7865 : "'gpkg_schema', "
7866 : "'gpkg_crs_wkt', "
7867 : "'gpkg_crs_wkt_1_1', "
7868 : "'related_tables', 'gpkg_related_tables')) "
7869 : #ifdef WORKAROUND_SQLITE3_BUGS
7870 : "OR 0 "
7871 : #endif
7872 : "LIMIT 1000",
7873 : m_osRasterTable.c_str());
7874 :
7875 2760 : auto oResultTable = SQLQuery(GetDB(), pszSQL);
7876 1380 : sqlite3_free(pszSQL);
7877 1380 : if (oResultTable && oResultTable->RowCount() > 0)
7878 : {
7879 42 : for (int i = 0; i < oResultTable->RowCount(); i++)
7880 : {
7881 21 : const char *pszExtName = oResultTable->GetValue(0, i);
7882 21 : const char *pszDefinition = oResultTable->GetValue(1, i);
7883 21 : const char *pszScope = oResultTable->GetValue(2, i);
7884 21 : if (pszExtName == nullptr || pszDefinition == nullptr ||
7885 : pszScope == nullptr)
7886 : {
7887 0 : continue;
7888 : }
7889 :
7890 21 : if (EQUAL(pszExtName, "gpkg_webp"))
7891 : {
7892 15 : if (GDALGetDriverByName("WEBP") == nullptr)
7893 : {
7894 1 : CPLError(
7895 : CE_Warning, CPLE_AppDefined,
7896 : "Table %s contains WEBP tiles, but GDAL configured "
7897 : "without WEBP support. Data will be missing",
7898 : m_osRasterTable.c_str());
7899 : }
7900 15 : m_eTF = GPKG_TF_WEBP;
7901 15 : continue;
7902 : }
7903 6 : if (EQUAL(pszExtName, "gpkg_zoom_other"))
7904 : {
7905 2 : m_bZoomOther = true;
7906 2 : continue;
7907 : }
7908 :
7909 4 : if (GetUpdate() && EQUAL(pszScope, "write-only"))
7910 : {
7911 1 : CPLError(
7912 : CE_Warning, CPLE_AppDefined,
7913 : "Database relies on the '%s' (%s) extension that should "
7914 : "be implemented for safe write-support, but is not "
7915 : "currently. "
7916 : "Update of that database are strongly discouraged to avoid "
7917 : "corruption.",
7918 : pszExtName, pszDefinition);
7919 : }
7920 3 : else if (GetUpdate() && EQUAL(pszScope, "read-write"))
7921 : {
7922 1 : CPLError(
7923 : CE_Warning, CPLE_AppDefined,
7924 : "Database relies on the '%s' (%s) extension that should "
7925 : "be implemented in order to read/write it safely, but is "
7926 : "not currently. "
7927 : "Some data may be missing while reading that database, and "
7928 : "updates are strongly discouraged.",
7929 : pszExtName, pszDefinition);
7930 : }
7931 2 : else if (EQUAL(pszScope, "read-write") &&
7932 : // None of the NGA extensions at
7933 : // http://ngageoint.github.io/GeoPackage/docs/extensions/
7934 : // affect read-only scenarios
7935 1 : !STARTS_WITH(pszExtName, "nga_"))
7936 : {
7937 1 : CPLError(
7938 : CE_Warning, CPLE_AppDefined,
7939 : "Database relies on the '%s' (%s) extension that should "
7940 : "be implemented in order to read it safely, but is not "
7941 : "currently. "
7942 : "Some data may be missing while reading that database.",
7943 : pszExtName, pszDefinition);
7944 : }
7945 : }
7946 : }
7947 : }
7948 :
7949 : /************************************************************************/
7950 : /* HasGDALAspatialExtension() */
7951 : /************************************************************************/
7952 :
7953 1127 : bool GDALGeoPackageDataset::HasGDALAspatialExtension()
7954 : {
7955 1127 : if (!HasExtensionsTable())
7956 102 : return false;
7957 :
7958 : auto oResultTable = SQLQuery(hDB, "SELECT * FROM gpkg_extensions "
7959 : "WHERE (extension_name = 'gdal_aspatial' "
7960 : "AND table_name IS NULL "
7961 : "AND column_name IS NULL)"
7962 : #ifdef WORKAROUND_SQLITE3_BUGS
7963 : " OR 0"
7964 : #endif
7965 1025 : );
7966 1025 : bool bHasExtension = (oResultTable && oResultTable->RowCount() == 1);
7967 1025 : return bHasExtension;
7968 : }
7969 :
7970 : std::string
7971 192 : GDALGeoPackageDataset::CreateRasterTriggersSQL(const std::string &osTableName)
7972 : {
7973 : char *pszSQL;
7974 192 : std::string osSQL;
7975 : /* From D.5. sample_tile_pyramid Table 43. tiles table Trigger
7976 : * Definition SQL */
7977 192 : pszSQL = sqlite3_mprintf(
7978 : "CREATE TRIGGER \"%w_zoom_insert\" "
7979 : "BEFORE INSERT ON \"%w\" "
7980 : "FOR EACH ROW BEGIN "
7981 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
7982 : "constraint: zoom_level not specified for table in "
7983 : "gpkg_tile_matrix') "
7984 : "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
7985 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
7986 : "END; "
7987 : "CREATE TRIGGER \"%w_zoom_update\" "
7988 : "BEFORE UPDATE OF zoom_level ON \"%w\" "
7989 : "FOR EACH ROW BEGIN "
7990 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
7991 : "constraint: zoom_level not specified for table in "
7992 : "gpkg_tile_matrix') "
7993 : "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
7994 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
7995 : "END; "
7996 : "CREATE TRIGGER \"%w_tile_column_insert\" "
7997 : "BEFORE INSERT ON \"%w\" "
7998 : "FOR EACH ROW BEGIN "
7999 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
8000 : "constraint: tile_column cannot be < 0') "
8001 : "WHERE (NEW.tile_column < 0) ; "
8002 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
8003 : "constraint: tile_column must by < matrix_width specified for "
8004 : "table and zoom level in gpkg_tile_matrix') "
8005 : "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
8006 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8007 : "zoom_level = NEW.zoom_level)); "
8008 : "END; "
8009 : "CREATE TRIGGER \"%w_tile_column_update\" "
8010 : "BEFORE UPDATE OF tile_column ON \"%w\" "
8011 : "FOR EACH ROW BEGIN "
8012 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8013 : "constraint: tile_column cannot be < 0') "
8014 : "WHERE (NEW.tile_column < 0) ; "
8015 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8016 : "constraint: tile_column must by < matrix_width specified for "
8017 : "table and zoom level in gpkg_tile_matrix') "
8018 : "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
8019 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8020 : "zoom_level = NEW.zoom_level)); "
8021 : "END; "
8022 : "CREATE TRIGGER \"%w_tile_row_insert\" "
8023 : "BEFORE INSERT ON \"%w\" "
8024 : "FOR EACH ROW BEGIN "
8025 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
8026 : "constraint: tile_row cannot be < 0') "
8027 : "WHERE (NEW.tile_row < 0) ; "
8028 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
8029 : "constraint: tile_row must by < matrix_height specified for "
8030 : "table and zoom level in gpkg_tile_matrix') "
8031 : "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
8032 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8033 : "zoom_level = NEW.zoom_level)); "
8034 : "END; "
8035 : "CREATE TRIGGER \"%w_tile_row_update\" "
8036 : "BEFORE UPDATE OF tile_row ON \"%w\" "
8037 : "FOR EACH ROW BEGIN "
8038 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8039 : "constraint: tile_row cannot be < 0') "
8040 : "WHERE (NEW.tile_row < 0) ; "
8041 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8042 : "constraint: tile_row must by < matrix_height specified for "
8043 : "table and zoom level in gpkg_tile_matrix') "
8044 : "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
8045 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8046 : "zoom_level = NEW.zoom_level)); "
8047 : "END; ",
8048 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8049 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8050 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8051 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8052 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8053 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8054 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8055 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8056 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8057 : osTableName.c_str());
8058 192 : osSQL = pszSQL;
8059 192 : sqlite3_free(pszSQL);
8060 192 : return osSQL;
8061 : }
8062 :
8063 : /************************************************************************/
8064 : /* CreateExtensionsTableIfNecessary() */
8065 : /************************************************************************/
8066 :
8067 1273 : OGRErr GDALGeoPackageDataset::CreateExtensionsTableIfNecessary()
8068 : {
8069 : /* Check if the table gpkg_extensions exists */
8070 1273 : if (HasExtensionsTable())
8071 423 : return OGRERR_NONE;
8072 :
8073 : /* Requirement 79 : Every extension of a GeoPackage SHALL be registered */
8074 : /* in a corresponding row in the gpkg_extensions table. The absence of a */
8075 : /* gpkg_extensions table or the absence of rows in gpkg_extensions table */
8076 : /* SHALL both indicate the absence of extensions to a GeoPackage. */
8077 850 : const char *pszCreateGpkgExtensions =
8078 : "CREATE TABLE gpkg_extensions ("
8079 : "table_name TEXT,"
8080 : "column_name TEXT,"
8081 : "extension_name TEXT NOT NULL,"
8082 : "definition TEXT NOT NULL,"
8083 : "scope TEXT NOT NULL,"
8084 : "CONSTRAINT ge_tce UNIQUE (table_name, column_name, extension_name)"
8085 : ")";
8086 :
8087 850 : return SQLCommand(hDB, pszCreateGpkgExtensions);
8088 : }
8089 :
8090 : /************************************************************************/
8091 : /* OGR_GPKG_Intersects_Spatial_Filter() */
8092 : /************************************************************************/
8093 :
8094 23135 : void OGR_GPKG_Intersects_Spatial_Filter(sqlite3_context *pContext, int argc,
8095 : sqlite3_value **argv)
8096 : {
8097 23135 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8098 : {
8099 0 : sqlite3_result_int(pContext, 0);
8100 23125 : return;
8101 : }
8102 :
8103 : auto poLayer =
8104 23135 : static_cast<OGRGeoPackageTableLayer *>(sqlite3_user_data(pContext));
8105 :
8106 23135 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8107 : const GByte *pabyBLOB =
8108 23135 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8109 :
8110 : GPkgHeader sHeader;
8111 46270 : if (poLayer->m_bFilterIsEnvelope &&
8112 23135 : OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false, 0))
8113 : {
8114 23135 : if (sHeader.bExtentHasXY)
8115 : {
8116 95 : OGREnvelope sEnvelope;
8117 95 : sEnvelope.MinX = sHeader.MinX;
8118 95 : sEnvelope.MinY = sHeader.MinY;
8119 95 : sEnvelope.MaxX = sHeader.MaxX;
8120 95 : sEnvelope.MaxY = sHeader.MaxY;
8121 95 : if (poLayer->m_sFilterEnvelope.Contains(sEnvelope))
8122 : {
8123 31 : sqlite3_result_int(pContext, 1);
8124 31 : return;
8125 : }
8126 : }
8127 :
8128 : // Check if at least one point falls into the layer filter envelope
8129 : // nHeaderLen is > 0 for GeoPackage geometries
8130 46208 : if (sHeader.nHeaderLen > 0 &&
8131 23104 : OGRWKBIntersectsPessimistic(pabyBLOB + sHeader.nHeaderLen,
8132 23104 : nBLOBLen - sHeader.nHeaderLen,
8133 23104 : poLayer->m_sFilterEnvelope))
8134 : {
8135 23094 : sqlite3_result_int(pContext, 1);
8136 23094 : return;
8137 : }
8138 : }
8139 :
8140 : auto poGeom = std::unique_ptr<OGRGeometry>(
8141 10 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8142 10 : if (poGeom == nullptr)
8143 : {
8144 : // Try also spatialite geometry blobs
8145 0 : OGRGeometry *poGeomSpatialite = nullptr;
8146 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8147 0 : &poGeomSpatialite) != OGRERR_NONE)
8148 : {
8149 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8150 0 : sqlite3_result_int(pContext, 0);
8151 0 : return;
8152 : }
8153 0 : poGeom.reset(poGeomSpatialite);
8154 : }
8155 :
8156 10 : sqlite3_result_int(pContext, poLayer->FilterGeometry(poGeom.get()));
8157 : }
8158 :
8159 : /************************************************************************/
8160 : /* OGRGeoPackageSTMinX() */
8161 : /************************************************************************/
8162 :
8163 252845 : static void OGRGeoPackageSTMinX(sqlite3_context *pContext, int argc,
8164 : sqlite3_value **argv)
8165 : {
8166 : GPkgHeader sHeader;
8167 252845 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8168 : {
8169 17 : sqlite3_result_null(pContext);
8170 17 : return;
8171 : }
8172 252828 : sqlite3_result_double(pContext, sHeader.MinX);
8173 : }
8174 :
8175 : /************************************************************************/
8176 : /* OGRGeoPackageSTMinY() */
8177 : /************************************************************************/
8178 :
8179 252829 : static void OGRGeoPackageSTMinY(sqlite3_context *pContext, int argc,
8180 : sqlite3_value **argv)
8181 : {
8182 : GPkgHeader sHeader;
8183 252829 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8184 : {
8185 1 : sqlite3_result_null(pContext);
8186 1 : return;
8187 : }
8188 252828 : sqlite3_result_double(pContext, sHeader.MinY);
8189 : }
8190 :
8191 : /************************************************************************/
8192 : /* OGRGeoPackageSTMaxX() */
8193 : /************************************************************************/
8194 :
8195 252829 : static void OGRGeoPackageSTMaxX(sqlite3_context *pContext, int argc,
8196 : sqlite3_value **argv)
8197 : {
8198 : GPkgHeader sHeader;
8199 252829 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8200 : {
8201 1 : sqlite3_result_null(pContext);
8202 1 : return;
8203 : }
8204 252828 : sqlite3_result_double(pContext, sHeader.MaxX);
8205 : }
8206 :
8207 : /************************************************************************/
8208 : /* OGRGeoPackageSTMaxY() */
8209 : /************************************************************************/
8210 :
8211 252829 : static void OGRGeoPackageSTMaxY(sqlite3_context *pContext, int argc,
8212 : sqlite3_value **argv)
8213 : {
8214 : GPkgHeader sHeader;
8215 252829 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8216 : {
8217 1 : sqlite3_result_null(pContext);
8218 1 : return;
8219 : }
8220 252828 : sqlite3_result_double(pContext, sHeader.MaxY);
8221 : }
8222 :
8223 : /************************************************************************/
8224 : /* OGRGeoPackageSTIsEmpty() */
8225 : /************************************************************************/
8226 :
8227 254255 : static void OGRGeoPackageSTIsEmpty(sqlite3_context *pContext, int argc,
8228 : sqlite3_value **argv)
8229 : {
8230 : GPkgHeader sHeader;
8231 254255 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8232 : {
8233 2 : sqlite3_result_null(pContext);
8234 2 : return;
8235 : }
8236 254253 : sqlite3_result_int(pContext, sHeader.bEmpty);
8237 : }
8238 :
8239 : /************************************************************************/
8240 : /* OGRGeoPackageSTGeometryType() */
8241 : /************************************************************************/
8242 :
8243 7 : static void OGRGeoPackageSTGeometryType(sqlite3_context *pContext, int /*argc*/,
8244 : sqlite3_value **argv)
8245 : {
8246 : GPkgHeader sHeader;
8247 :
8248 7 : int nBLOBLen = sqlite3_value_bytes(argv[0]);
8249 : const GByte *pabyBLOB =
8250 7 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8251 : OGRwkbGeometryType eGeometryType;
8252 :
8253 13 : if (nBLOBLen < 8 ||
8254 6 : GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
8255 : {
8256 2 : if (OGRSQLiteGetSpatialiteGeometryHeader(
8257 : pabyBLOB, nBLOBLen, nullptr, &eGeometryType, nullptr, nullptr,
8258 2 : nullptr, nullptr, nullptr) == OGRERR_NONE)
8259 : {
8260 1 : sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
8261 : SQLITE_TRANSIENT);
8262 4 : return;
8263 : }
8264 : else
8265 : {
8266 1 : sqlite3_result_null(pContext);
8267 1 : return;
8268 : }
8269 : }
8270 :
8271 5 : if (static_cast<size_t>(nBLOBLen) < sHeader.nHeaderLen + 5)
8272 : {
8273 2 : sqlite3_result_null(pContext);
8274 2 : return;
8275 : }
8276 :
8277 3 : OGRErr err = OGRReadWKBGeometryType(pabyBLOB + sHeader.nHeaderLen,
8278 : wkbVariantIso, &eGeometryType);
8279 3 : if (err != OGRERR_NONE)
8280 1 : sqlite3_result_null(pContext);
8281 : else
8282 2 : sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
8283 : SQLITE_TRANSIENT);
8284 : }
8285 :
8286 : /************************************************************************/
8287 : /* OGRGeoPackageSTEnvelopesIntersects() */
8288 : /************************************************************************/
8289 :
8290 118 : static void OGRGeoPackageSTEnvelopesIntersects(sqlite3_context *pContext,
8291 : int argc, sqlite3_value **argv)
8292 : {
8293 : GPkgHeader sHeader;
8294 118 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8295 : {
8296 2 : sqlite3_result_int(pContext, FALSE);
8297 107 : return;
8298 : }
8299 116 : const double dfMinX = sqlite3_value_double(argv[1]);
8300 116 : if (sHeader.MaxX < dfMinX)
8301 : {
8302 93 : sqlite3_result_int(pContext, FALSE);
8303 93 : return;
8304 : }
8305 23 : const double dfMinY = sqlite3_value_double(argv[2]);
8306 23 : if (sHeader.MaxY < dfMinY)
8307 : {
8308 11 : sqlite3_result_int(pContext, FALSE);
8309 11 : return;
8310 : }
8311 12 : const double dfMaxX = sqlite3_value_double(argv[3]);
8312 12 : if (sHeader.MinX > dfMaxX)
8313 : {
8314 1 : sqlite3_result_int(pContext, FALSE);
8315 1 : return;
8316 : }
8317 11 : const double dfMaxY = sqlite3_value_double(argv[4]);
8318 11 : sqlite3_result_int(pContext, sHeader.MinY <= dfMaxY);
8319 : }
8320 :
8321 : /************************************************************************/
8322 : /* OGRGeoPackageSTEnvelopesIntersectsTwoParams() */
8323 : /************************************************************************/
8324 :
8325 : static void
8326 3 : OGRGeoPackageSTEnvelopesIntersectsTwoParams(sqlite3_context *pContext, int argc,
8327 : sqlite3_value **argv)
8328 : {
8329 : GPkgHeader sHeader;
8330 3 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false, 0))
8331 : {
8332 0 : sqlite3_result_int(pContext, FALSE);
8333 2 : return;
8334 : }
8335 : GPkgHeader sHeader2;
8336 3 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader2, true, false,
8337 : 1))
8338 : {
8339 0 : sqlite3_result_int(pContext, FALSE);
8340 0 : return;
8341 : }
8342 3 : if (sHeader.MaxX < sHeader2.MinX)
8343 : {
8344 1 : sqlite3_result_int(pContext, FALSE);
8345 1 : return;
8346 : }
8347 2 : if (sHeader.MaxY < sHeader2.MinY)
8348 : {
8349 0 : sqlite3_result_int(pContext, FALSE);
8350 0 : return;
8351 : }
8352 2 : if (sHeader.MinX > sHeader2.MaxX)
8353 : {
8354 1 : sqlite3_result_int(pContext, FALSE);
8355 1 : return;
8356 : }
8357 1 : sqlite3_result_int(pContext, sHeader.MinY <= sHeader2.MaxY);
8358 : }
8359 :
8360 : /************************************************************************/
8361 : /* OGRGeoPackageGPKGIsAssignable() */
8362 : /************************************************************************/
8363 :
8364 8 : static void OGRGeoPackageGPKGIsAssignable(sqlite3_context *pContext,
8365 : int /*argc*/, sqlite3_value **argv)
8366 : {
8367 15 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8368 7 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
8369 : {
8370 2 : sqlite3_result_int(pContext, 0);
8371 2 : return;
8372 : }
8373 :
8374 : const char *pszExpected =
8375 6 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8376 : const char *pszActual =
8377 6 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8378 6 : int bIsAssignable = OGR_GT_IsSubClassOf(OGRFromOGCGeomType(pszActual),
8379 : OGRFromOGCGeomType(pszExpected));
8380 6 : sqlite3_result_int(pContext, bIsAssignable);
8381 : }
8382 :
8383 : /************************************************************************/
8384 : /* OGRGeoPackageSTSRID() */
8385 : /************************************************************************/
8386 :
8387 12 : static void OGRGeoPackageSTSRID(sqlite3_context *pContext, int argc,
8388 : sqlite3_value **argv)
8389 : {
8390 : GPkgHeader sHeader;
8391 12 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8392 : {
8393 2 : sqlite3_result_null(pContext);
8394 2 : return;
8395 : }
8396 10 : sqlite3_result_int(pContext, sHeader.iSrsId);
8397 : }
8398 :
8399 : /************************************************************************/
8400 : /* OGRGeoPackageSetSRID() */
8401 : /************************************************************************/
8402 :
8403 28 : static void OGRGeoPackageSetSRID(sqlite3_context *pContext, int /* argc */,
8404 : sqlite3_value **argv)
8405 : {
8406 28 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8407 : {
8408 1 : sqlite3_result_null(pContext);
8409 1 : return;
8410 : }
8411 27 : const int nDestSRID = sqlite3_value_int(argv[1]);
8412 : GPkgHeader sHeader;
8413 27 : int nBLOBLen = sqlite3_value_bytes(argv[0]);
8414 : const GByte *pabyBLOB =
8415 27 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8416 :
8417 54 : if (nBLOBLen < 8 ||
8418 27 : GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
8419 : {
8420 : // Try also spatialite geometry blobs
8421 0 : OGRGeometry *poGeom = nullptr;
8422 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeom) !=
8423 : OGRERR_NONE)
8424 : {
8425 0 : sqlite3_result_null(pContext);
8426 0 : return;
8427 : }
8428 0 : size_t nBLOBDestLen = 0;
8429 : GByte *pabyDestBLOB =
8430 0 : GPkgGeometryFromOGR(poGeom, nDestSRID, nullptr, &nBLOBDestLen);
8431 0 : if (!pabyDestBLOB)
8432 : {
8433 0 : sqlite3_result_null(pContext);
8434 0 : return;
8435 : }
8436 0 : sqlite3_result_blob(pContext, pabyDestBLOB,
8437 : static_cast<int>(nBLOBDestLen), VSIFree);
8438 0 : return;
8439 : }
8440 :
8441 27 : GByte *pabyDestBLOB = static_cast<GByte *>(CPLMalloc(nBLOBLen));
8442 27 : memcpy(pabyDestBLOB, pabyBLOB, nBLOBLen);
8443 27 : int32_t nSRIDToSerialize = nDestSRID;
8444 27 : if (OGR_SWAP(sHeader.eByteOrder))
8445 0 : nSRIDToSerialize = CPL_SWAP32(nSRIDToSerialize);
8446 27 : memcpy(pabyDestBLOB + 4, &nSRIDToSerialize, 4);
8447 27 : sqlite3_result_blob(pContext, pabyDestBLOB, nBLOBLen, VSIFree);
8448 : }
8449 :
8450 : /************************************************************************/
8451 : /* OGRGeoPackageSTMakeValid() */
8452 : /************************************************************************/
8453 :
8454 3 : static void OGRGeoPackageSTMakeValid(sqlite3_context *pContext, int argc,
8455 : sqlite3_value **argv)
8456 : {
8457 3 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8458 : {
8459 2 : sqlite3_result_null(pContext);
8460 2 : return;
8461 : }
8462 1 : int nBLOBLen = sqlite3_value_bytes(argv[0]);
8463 : const GByte *pabyBLOB =
8464 1 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8465 :
8466 : GPkgHeader sHeader;
8467 1 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8468 : {
8469 0 : sqlite3_result_null(pContext);
8470 0 : return;
8471 : }
8472 :
8473 : auto poGeom = std::unique_ptr<OGRGeometry>(
8474 1 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8475 1 : if (poGeom == nullptr)
8476 : {
8477 : // Try also spatialite geometry blobs
8478 0 : OGRGeometry *poGeomPtr = nullptr;
8479 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
8480 : OGRERR_NONE)
8481 : {
8482 0 : sqlite3_result_null(pContext);
8483 0 : return;
8484 : }
8485 0 : poGeom.reset(poGeomPtr);
8486 : }
8487 1 : auto poValid = std::unique_ptr<OGRGeometry>(poGeom->MakeValid());
8488 1 : if (poValid == nullptr)
8489 : {
8490 0 : sqlite3_result_null(pContext);
8491 0 : return;
8492 : }
8493 :
8494 1 : size_t nBLOBDestLen = 0;
8495 1 : GByte *pabyDestBLOB = GPkgGeometryFromOGR(poValid.get(), sHeader.iSrsId,
8496 : nullptr, &nBLOBDestLen);
8497 1 : if (!pabyDestBLOB)
8498 : {
8499 0 : sqlite3_result_null(pContext);
8500 0 : return;
8501 : }
8502 1 : sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
8503 : VSIFree);
8504 : }
8505 :
8506 : /************************************************************************/
8507 : /* OGRGeoPackageSTArea() */
8508 : /************************************************************************/
8509 :
8510 19 : static void OGRGeoPackageSTArea(sqlite3_context *pContext, int /*argc*/,
8511 : sqlite3_value **argv)
8512 : {
8513 19 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8514 : {
8515 1 : sqlite3_result_null(pContext);
8516 15 : return;
8517 : }
8518 18 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8519 : const GByte *pabyBLOB =
8520 18 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8521 :
8522 : GPkgHeader sHeader;
8523 0 : std::unique_ptr<OGRGeometry> poGeom;
8524 18 : if (GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) == OGRERR_NONE)
8525 : {
8526 16 : if (sHeader.bEmpty)
8527 : {
8528 3 : sqlite3_result_double(pContext, 0);
8529 13 : return;
8530 : }
8531 13 : const GByte *pabyWkb = pabyBLOB + sHeader.nHeaderLen;
8532 13 : size_t nWKBSize = nBLOBLen - sHeader.nHeaderLen;
8533 : bool bNeedSwap;
8534 : uint32_t nType;
8535 13 : if (OGRWKBGetGeomType(pabyWkb, nWKBSize, bNeedSwap, nType))
8536 : {
8537 13 : if (nType == wkbPolygon || nType == wkbPolygon25D ||
8538 11 : nType == wkbPolygon + 1000 || // wkbPolygonZ
8539 10 : nType == wkbPolygonM || nType == wkbPolygonZM)
8540 : {
8541 : double dfArea;
8542 5 : if (OGRWKBPolygonGetArea(pabyWkb, nWKBSize, dfArea))
8543 : {
8544 5 : sqlite3_result_double(pContext, dfArea);
8545 5 : return;
8546 0 : }
8547 : }
8548 8 : else if (nType == wkbMultiPolygon || nType == wkbMultiPolygon25D ||
8549 6 : nType == wkbMultiPolygon + 1000 || // wkbMultiPolygonZ
8550 5 : nType == wkbMultiPolygonM || nType == wkbMultiPolygonZM)
8551 : {
8552 : double dfArea;
8553 5 : if (OGRWKBMultiPolygonGetArea(pabyWkb, nWKBSize, dfArea))
8554 : {
8555 5 : sqlite3_result_double(pContext, dfArea);
8556 5 : return;
8557 : }
8558 : }
8559 : }
8560 :
8561 : // For curve geometries, fallback to OGRGeometry methods
8562 3 : poGeom.reset(GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8563 : }
8564 : else
8565 : {
8566 : // Try also spatialite geometry blobs
8567 2 : OGRGeometry *poGeomPtr = nullptr;
8568 2 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
8569 : OGRERR_NONE)
8570 : {
8571 1 : sqlite3_result_null(pContext);
8572 1 : return;
8573 : }
8574 1 : poGeom.reset(poGeomPtr);
8575 : }
8576 4 : auto poSurface = dynamic_cast<OGRSurface *>(poGeom.get());
8577 4 : if (poSurface == nullptr)
8578 : {
8579 2 : auto poMultiSurface = dynamic_cast<OGRMultiSurface *>(poGeom.get());
8580 2 : if (poMultiSurface == nullptr)
8581 : {
8582 1 : sqlite3_result_double(pContext, 0);
8583 : }
8584 : else
8585 : {
8586 1 : sqlite3_result_double(pContext, poMultiSurface->get_Area());
8587 : }
8588 : }
8589 : else
8590 : {
8591 2 : sqlite3_result_double(pContext, poSurface->get_Area());
8592 : }
8593 : }
8594 :
8595 : /************************************************************************/
8596 : /* OGRGeoPackageGeodesicArea() */
8597 : /************************************************************************/
8598 :
8599 5 : static void OGRGeoPackageGeodesicArea(sqlite3_context *pContext, int argc,
8600 : sqlite3_value **argv)
8601 : {
8602 5 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8603 : {
8604 1 : sqlite3_result_null(pContext);
8605 3 : return;
8606 : }
8607 4 : if (sqlite3_value_int(argv[1]) != 1)
8608 : {
8609 2 : CPLError(CE_Warning, CPLE_NotSupported,
8610 : "ST_Area(geom, use_ellipsoid) is only supported for "
8611 : "use_ellipsoid = 1");
8612 : }
8613 :
8614 4 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8615 : const GByte *pabyBLOB =
8616 4 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8617 : GPkgHeader sHeader;
8618 4 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8619 : {
8620 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8621 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8622 1 : return;
8623 : }
8624 :
8625 : GDALGeoPackageDataset *poDS =
8626 3 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8627 :
8628 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSrcSRS(
8629 3 : poDS->GetSpatialRef(sHeader.iSrsId, true));
8630 3 : if (poSrcSRS == nullptr)
8631 : {
8632 1 : CPLError(CE_Failure, CPLE_AppDefined,
8633 : "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8634 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8635 1 : return;
8636 : }
8637 :
8638 : auto poGeom = std::unique_ptr<OGRGeometry>(
8639 2 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8640 2 : if (poGeom == nullptr)
8641 : {
8642 : // Try also spatialite geometry blobs
8643 0 : OGRGeometry *poGeomSpatialite = nullptr;
8644 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8645 0 : &poGeomSpatialite) != OGRERR_NONE)
8646 : {
8647 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8648 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8649 0 : return;
8650 : }
8651 0 : poGeom.reset(poGeomSpatialite);
8652 : }
8653 :
8654 2 : poGeom->assignSpatialReference(poSrcSRS.get());
8655 2 : sqlite3_result_double(
8656 : pContext, OGR_G_GeodesicArea(OGRGeometry::ToHandle(poGeom.get())));
8657 : }
8658 :
8659 : /************************************************************************/
8660 : /* OGRGeoPackageLengthOrGeodesicLength() */
8661 : /************************************************************************/
8662 :
8663 8 : static void OGRGeoPackageLengthOrGeodesicLength(sqlite3_context *pContext,
8664 : int argc, sqlite3_value **argv)
8665 : {
8666 8 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8667 : {
8668 2 : sqlite3_result_null(pContext);
8669 5 : return;
8670 : }
8671 6 : if (argc == 2 && sqlite3_value_int(argv[1]) != 1)
8672 : {
8673 2 : CPLError(CE_Warning, CPLE_NotSupported,
8674 : "ST_Length(geom, use_ellipsoid) is only supported for "
8675 : "use_ellipsoid = 1");
8676 : }
8677 :
8678 6 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8679 : const GByte *pabyBLOB =
8680 6 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8681 : GPkgHeader sHeader;
8682 6 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8683 : {
8684 2 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8685 2 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8686 2 : return;
8687 : }
8688 :
8689 : GDALGeoPackageDataset *poDS =
8690 4 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8691 :
8692 0 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSrcSRS;
8693 4 : if (argc == 2)
8694 : {
8695 3 : poSrcSRS = poDS->GetSpatialRef(sHeader.iSrsId, true);
8696 3 : if (!poSrcSRS)
8697 : {
8698 1 : CPLError(CE_Failure, CPLE_AppDefined,
8699 : "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8700 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8701 1 : return;
8702 : }
8703 : }
8704 :
8705 : auto poGeom = std::unique_ptr<OGRGeometry>(
8706 3 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8707 3 : if (poGeom == nullptr)
8708 : {
8709 : // Try also spatialite geometry blobs
8710 0 : OGRGeometry *poGeomSpatialite = nullptr;
8711 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8712 0 : &poGeomSpatialite) != OGRERR_NONE)
8713 : {
8714 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8715 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8716 0 : return;
8717 : }
8718 0 : poGeom.reset(poGeomSpatialite);
8719 : }
8720 :
8721 3 : if (argc == 2)
8722 2 : poGeom->assignSpatialReference(poSrcSRS.get());
8723 :
8724 6 : sqlite3_result_double(
8725 : pContext,
8726 1 : argc == 1 ? OGR_G_Length(OGRGeometry::ToHandle(poGeom.get()))
8727 2 : : OGR_G_GeodesicLength(OGRGeometry::ToHandle(poGeom.get())));
8728 : }
8729 :
8730 : /************************************************************************/
8731 : /* OGRGeoPackageTransform() */
8732 : /************************************************************************/
8733 :
8734 : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
8735 : sqlite3_value **argv);
8736 :
8737 32 : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
8738 : sqlite3_value **argv)
8739 : {
8740 63 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB ||
8741 31 : sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
8742 : {
8743 2 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8744 32 : return;
8745 : }
8746 :
8747 30 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8748 : const GByte *pabyBLOB =
8749 30 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8750 : GPkgHeader sHeader;
8751 30 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8752 : {
8753 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8754 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8755 1 : return;
8756 : }
8757 :
8758 29 : const int nDestSRID = sqlite3_value_int(argv[1]);
8759 29 : if (sHeader.iSrsId == nDestSRID)
8760 : {
8761 : // Return blob unmodified
8762 3 : sqlite3_result_blob(pContext, pabyBLOB, nBLOBLen, SQLITE_TRANSIENT);
8763 3 : return;
8764 : }
8765 :
8766 : GDALGeoPackageDataset *poDS =
8767 26 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8768 :
8769 : // Try to get the cached coordinate transformation
8770 : OGRCoordinateTransformation *poCT;
8771 26 : if (poDS->m_nLastCachedCTSrcSRId == sHeader.iSrsId &&
8772 20 : poDS->m_nLastCachedCTDstSRId == nDestSRID)
8773 : {
8774 20 : poCT = poDS->m_poLastCachedCT.get();
8775 : }
8776 : else
8777 : {
8778 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
8779 6 : poSrcSRS(poDS->GetSpatialRef(sHeader.iSrsId, true));
8780 6 : if (poSrcSRS == nullptr)
8781 : {
8782 0 : CPLError(CE_Failure, CPLE_AppDefined,
8783 : "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8784 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8785 0 : return;
8786 : }
8787 :
8788 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
8789 6 : poDstSRS(poDS->GetSpatialRef(nDestSRID, true));
8790 6 : if (poDstSRS == nullptr)
8791 : {
8792 0 : CPLError(CE_Failure, CPLE_AppDefined, "Target SRID (%d) is invalid",
8793 : nDestSRID);
8794 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8795 0 : return;
8796 : }
8797 : poCT =
8798 6 : OGRCreateCoordinateTransformation(poSrcSRS.get(), poDstSRS.get());
8799 6 : if (poCT == nullptr)
8800 : {
8801 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8802 0 : return;
8803 : }
8804 :
8805 : // Cache coordinate transformation for potential later reuse
8806 6 : poDS->m_nLastCachedCTSrcSRId = sHeader.iSrsId;
8807 6 : poDS->m_nLastCachedCTDstSRId = nDestSRID;
8808 6 : poDS->m_poLastCachedCT.reset(poCT);
8809 6 : poCT = poDS->m_poLastCachedCT.get();
8810 : }
8811 :
8812 26 : if (sHeader.nHeaderLen >= 8)
8813 : {
8814 26 : std::vector<GByte> &abyNewBLOB = poDS->m_abyWKBTransformCache;
8815 26 : abyNewBLOB.resize(nBLOBLen);
8816 26 : memcpy(abyNewBLOB.data(), pabyBLOB, nBLOBLen);
8817 :
8818 26 : OGREnvelope3D oEnv3d;
8819 26 : if (!OGRWKBTransform(abyNewBLOB.data() + sHeader.nHeaderLen,
8820 26 : nBLOBLen - sHeader.nHeaderLen, poCT,
8821 78 : poDS->m_oWKBTransformCache, oEnv3d) ||
8822 26 : !GPkgUpdateHeader(abyNewBLOB.data(), nBLOBLen, nDestSRID,
8823 : oEnv3d.MinX, oEnv3d.MaxX, oEnv3d.MinY,
8824 : oEnv3d.MaxY, oEnv3d.MinZ, oEnv3d.MaxZ))
8825 : {
8826 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8827 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8828 0 : return;
8829 : }
8830 :
8831 26 : sqlite3_result_blob(pContext, abyNewBLOB.data(), nBLOBLen,
8832 : SQLITE_TRANSIENT);
8833 26 : return;
8834 : }
8835 :
8836 : // Try also spatialite geometry blobs
8837 0 : OGRGeometry *poGeomSpatialite = nullptr;
8838 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8839 0 : &poGeomSpatialite) != OGRERR_NONE)
8840 : {
8841 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8842 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8843 0 : return;
8844 : }
8845 0 : auto poGeom = std::unique_ptr<OGRGeometry>(poGeomSpatialite);
8846 :
8847 0 : if (poGeom->transform(poCT) != OGRERR_NONE)
8848 : {
8849 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8850 0 : return;
8851 : }
8852 :
8853 0 : size_t nBLOBDestLen = 0;
8854 : GByte *pabyDestBLOB =
8855 0 : GPkgGeometryFromOGR(poGeom.get(), nDestSRID, nullptr, &nBLOBDestLen);
8856 0 : if (!pabyDestBLOB)
8857 : {
8858 0 : sqlite3_result_null(pContext);
8859 0 : return;
8860 : }
8861 0 : sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
8862 : VSIFree);
8863 : }
8864 :
8865 : /************************************************************************/
8866 : /* OGRGeoPackageSridFromAuthCRS() */
8867 : /************************************************************************/
8868 :
8869 4 : static void OGRGeoPackageSridFromAuthCRS(sqlite3_context *pContext,
8870 : int /*argc*/, sqlite3_value **argv)
8871 : {
8872 7 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8873 3 : sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
8874 : {
8875 2 : sqlite3_result_int(pContext, -1);
8876 2 : return;
8877 : }
8878 :
8879 : GDALGeoPackageDataset *poDS =
8880 2 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8881 :
8882 2 : char *pszSQL = sqlite3_mprintf(
8883 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
8884 : "lower(organization) = lower('%q') AND organization_coordsys_id = %d",
8885 2 : sqlite3_value_text(argv[0]), sqlite3_value_int(argv[1]));
8886 2 : OGRErr err = OGRERR_NONE;
8887 2 : int nSRSId = SQLGetInteger(poDS->GetDB(), pszSQL, &err);
8888 2 : sqlite3_free(pszSQL);
8889 2 : if (err != OGRERR_NONE)
8890 1 : nSRSId = -1;
8891 2 : sqlite3_result_int(pContext, nSRSId);
8892 : }
8893 :
8894 : /************************************************************************/
8895 : /* OGRGeoPackageImportFromEPSG() */
8896 : /************************************************************************/
8897 :
8898 4 : static void OGRGeoPackageImportFromEPSG(sqlite3_context *pContext, int /*argc*/,
8899 : sqlite3_value **argv)
8900 : {
8901 4 : if (sqlite3_value_type(argv[0]) != SQLITE_INTEGER)
8902 : {
8903 1 : sqlite3_result_int(pContext, -1);
8904 2 : return;
8905 : }
8906 :
8907 : GDALGeoPackageDataset *poDS =
8908 3 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8909 3 : OGRSpatialReference oSRS;
8910 3 : if (oSRS.importFromEPSG(sqlite3_value_int(argv[0])) != OGRERR_NONE)
8911 : {
8912 1 : sqlite3_result_int(pContext, -1);
8913 1 : return;
8914 : }
8915 :
8916 2 : sqlite3_result_int(pContext, poDS->GetSrsId(&oSRS));
8917 : }
8918 :
8919 : /************************************************************************/
8920 : /* OGRGeoPackageRegisterGeometryExtension() */
8921 : /************************************************************************/
8922 :
8923 1 : static void OGRGeoPackageRegisterGeometryExtension(sqlite3_context *pContext,
8924 : int /*argc*/,
8925 : sqlite3_value **argv)
8926 : {
8927 1 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8928 2 : sqlite3_value_type(argv[1]) != SQLITE_TEXT ||
8929 1 : sqlite3_value_type(argv[2]) != SQLITE_TEXT)
8930 : {
8931 0 : sqlite3_result_int(pContext, 0);
8932 0 : return;
8933 : }
8934 :
8935 : const char *pszTableName =
8936 1 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8937 : const char *pszGeomName =
8938 1 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8939 : const char *pszGeomType =
8940 1 : reinterpret_cast<const char *>(sqlite3_value_text(argv[2]));
8941 :
8942 : GDALGeoPackageDataset *poDS =
8943 1 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8944 :
8945 1 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
8946 1 : poDS->GetLayerByName(pszTableName));
8947 1 : if (poLyr == nullptr)
8948 : {
8949 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
8950 0 : sqlite3_result_int(pContext, 0);
8951 0 : return;
8952 : }
8953 1 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
8954 : {
8955 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
8956 0 : sqlite3_result_int(pContext, 0);
8957 0 : return;
8958 : }
8959 1 : const OGRwkbGeometryType eGeomType = OGRFromOGCGeomType(pszGeomType);
8960 1 : if (eGeomType == wkbUnknown)
8961 : {
8962 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry type name");
8963 0 : sqlite3_result_int(pContext, 0);
8964 0 : return;
8965 : }
8966 :
8967 1 : sqlite3_result_int(
8968 : pContext,
8969 1 : static_cast<int>(poLyr->CreateGeometryExtensionIfNecessary(eGeomType)));
8970 : }
8971 :
8972 : /************************************************************************/
8973 : /* OGRGeoPackageCreateSpatialIndex() */
8974 : /************************************************************************/
8975 :
8976 14 : static void OGRGeoPackageCreateSpatialIndex(sqlite3_context *pContext,
8977 : int /*argc*/, sqlite3_value **argv)
8978 : {
8979 27 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8980 13 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
8981 : {
8982 2 : sqlite3_result_int(pContext, 0);
8983 2 : return;
8984 : }
8985 :
8986 : const char *pszTableName =
8987 12 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8988 : const char *pszGeomName =
8989 12 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8990 : GDALGeoPackageDataset *poDS =
8991 12 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8992 :
8993 12 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
8994 12 : poDS->GetLayerByName(pszTableName));
8995 12 : if (poLyr == nullptr)
8996 : {
8997 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
8998 1 : sqlite3_result_int(pContext, 0);
8999 1 : return;
9000 : }
9001 11 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
9002 : {
9003 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
9004 1 : sqlite3_result_int(pContext, 0);
9005 1 : return;
9006 : }
9007 :
9008 10 : sqlite3_result_int(pContext, poLyr->CreateSpatialIndex());
9009 : }
9010 :
9011 : /************************************************************************/
9012 : /* OGRGeoPackageDisableSpatialIndex() */
9013 : /************************************************************************/
9014 :
9015 12 : static void OGRGeoPackageDisableSpatialIndex(sqlite3_context *pContext,
9016 : int /*argc*/, sqlite3_value **argv)
9017 : {
9018 23 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9019 11 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9020 : {
9021 2 : sqlite3_result_int(pContext, 0);
9022 2 : return;
9023 : }
9024 :
9025 : const char *pszTableName =
9026 10 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9027 : const char *pszGeomName =
9028 10 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9029 : GDALGeoPackageDataset *poDS =
9030 10 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9031 :
9032 10 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
9033 10 : poDS->GetLayerByName(pszTableName));
9034 10 : if (poLyr == nullptr)
9035 : {
9036 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
9037 1 : sqlite3_result_int(pContext, 0);
9038 1 : return;
9039 : }
9040 9 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
9041 : {
9042 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
9043 1 : sqlite3_result_int(pContext, 0);
9044 1 : return;
9045 : }
9046 :
9047 8 : sqlite3_result_int(pContext, poLyr->DropSpatialIndex(true));
9048 : }
9049 :
9050 : /************************************************************************/
9051 : /* OGRGeoPackageHasSpatialIndex() */
9052 : /************************************************************************/
9053 :
9054 29 : static void OGRGeoPackageHasSpatialIndex(sqlite3_context *pContext,
9055 : int /*argc*/, sqlite3_value **argv)
9056 : {
9057 57 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9058 28 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9059 : {
9060 2 : sqlite3_result_int(pContext, 0);
9061 2 : return;
9062 : }
9063 :
9064 : const char *pszTableName =
9065 27 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9066 : const char *pszGeomName =
9067 27 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9068 : GDALGeoPackageDataset *poDS =
9069 27 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9070 :
9071 27 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
9072 27 : poDS->GetLayerByName(pszTableName));
9073 27 : if (poLyr == nullptr)
9074 : {
9075 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
9076 1 : sqlite3_result_int(pContext, 0);
9077 1 : return;
9078 : }
9079 26 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
9080 : {
9081 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
9082 1 : sqlite3_result_int(pContext, 0);
9083 1 : return;
9084 : }
9085 :
9086 25 : poLyr->RunDeferredCreationIfNecessary();
9087 25 : poLyr->CreateSpatialIndexIfNecessary();
9088 :
9089 25 : sqlite3_result_int(pContext, poLyr->HasSpatialIndex());
9090 : }
9091 :
9092 : /************************************************************************/
9093 : /* GPKG_hstore_get_value() */
9094 : /************************************************************************/
9095 :
9096 4 : static void GPKG_hstore_get_value(sqlite3_context *pContext, int /*argc*/,
9097 : sqlite3_value **argv)
9098 : {
9099 7 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9100 3 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9101 : {
9102 2 : sqlite3_result_null(pContext);
9103 2 : return;
9104 : }
9105 :
9106 : const char *pszHStore =
9107 2 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9108 : const char *pszSearchedKey =
9109 2 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9110 2 : char *pszValue = OGRHStoreGetValue(pszHStore, pszSearchedKey);
9111 2 : if (pszValue != nullptr)
9112 1 : sqlite3_result_text(pContext, pszValue, -1, CPLFree);
9113 : else
9114 1 : sqlite3_result_null(pContext);
9115 : }
9116 :
9117 : /************************************************************************/
9118 : /* GPKG_GDAL_GetMemFileFromBlob() */
9119 : /************************************************************************/
9120 :
9121 105 : static CPLString GPKG_GDAL_GetMemFileFromBlob(sqlite3_value **argv)
9122 : {
9123 105 : int nBytes = sqlite3_value_bytes(argv[0]);
9124 : const GByte *pabyBLOB =
9125 105 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
9126 : CPLString osMemFileName(
9127 105 : VSIMemGenerateHiddenFilename("GPKG_GDAL_GetMemFileFromBlob"));
9128 105 : VSILFILE *fp = VSIFileFromMemBuffer(
9129 : osMemFileName.c_str(), const_cast<GByte *>(pabyBLOB), nBytes, FALSE);
9130 105 : VSIFCloseL(fp);
9131 105 : return osMemFileName;
9132 : }
9133 :
9134 : /************************************************************************/
9135 : /* GPKG_GDAL_GetMimeType() */
9136 : /************************************************************************/
9137 :
9138 35 : static void GPKG_GDAL_GetMimeType(sqlite3_context *pContext, int /*argc*/,
9139 : sqlite3_value **argv)
9140 : {
9141 35 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
9142 : {
9143 0 : sqlite3_result_null(pContext);
9144 0 : return;
9145 : }
9146 :
9147 70 : CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
9148 : GDALDriver *poDriver =
9149 35 : GDALDriver::FromHandle(GDALIdentifyDriver(osMemFileName, nullptr));
9150 35 : if (poDriver != nullptr)
9151 : {
9152 35 : const char *pszRes = nullptr;
9153 35 : if (EQUAL(poDriver->GetDescription(), "PNG"))
9154 23 : pszRes = "image/png";
9155 12 : else if (EQUAL(poDriver->GetDescription(), "JPEG"))
9156 6 : pszRes = "image/jpeg";
9157 6 : else if (EQUAL(poDriver->GetDescription(), "WEBP"))
9158 6 : pszRes = "image/x-webp";
9159 0 : else if (EQUAL(poDriver->GetDescription(), "GTIFF"))
9160 0 : pszRes = "image/tiff";
9161 : else
9162 0 : pszRes = CPLSPrintf("gdal/%s", poDriver->GetDescription());
9163 35 : sqlite3_result_text(pContext, pszRes, -1, SQLITE_TRANSIENT);
9164 : }
9165 : else
9166 0 : sqlite3_result_null(pContext);
9167 35 : VSIUnlink(osMemFileName);
9168 : }
9169 :
9170 : /************************************************************************/
9171 : /* GPKG_GDAL_GetBandCount() */
9172 : /************************************************************************/
9173 :
9174 35 : static void GPKG_GDAL_GetBandCount(sqlite3_context *pContext, int /*argc*/,
9175 : sqlite3_value **argv)
9176 : {
9177 35 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
9178 : {
9179 0 : sqlite3_result_null(pContext);
9180 0 : return;
9181 : }
9182 :
9183 70 : CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
9184 : auto poDS = std::unique_ptr<GDALDataset>(
9185 : GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
9186 70 : nullptr, nullptr, nullptr));
9187 35 : if (poDS != nullptr)
9188 : {
9189 35 : sqlite3_result_int(pContext, poDS->GetRasterCount());
9190 : }
9191 : else
9192 0 : sqlite3_result_null(pContext);
9193 35 : VSIUnlink(osMemFileName);
9194 : }
9195 :
9196 : /************************************************************************/
9197 : /* GPKG_GDAL_HasColorTable() */
9198 : /************************************************************************/
9199 :
9200 35 : static void GPKG_GDAL_HasColorTable(sqlite3_context *pContext, int /*argc*/,
9201 : sqlite3_value **argv)
9202 : {
9203 35 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
9204 : {
9205 0 : sqlite3_result_null(pContext);
9206 0 : return;
9207 : }
9208 :
9209 70 : CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
9210 : auto poDS = std::unique_ptr<GDALDataset>(
9211 : GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
9212 70 : nullptr, nullptr, nullptr));
9213 35 : if (poDS != nullptr)
9214 : {
9215 35 : sqlite3_result_int(
9216 46 : pContext, poDS->GetRasterCount() == 1 &&
9217 11 : poDS->GetRasterBand(1)->GetColorTable() != nullptr);
9218 : }
9219 : else
9220 0 : sqlite3_result_null(pContext);
9221 35 : VSIUnlink(osMemFileName);
9222 : }
9223 :
9224 : /************************************************************************/
9225 : /* GetRasterLayerDataset() */
9226 : /************************************************************************/
9227 :
9228 : GDALDataset *
9229 12 : GDALGeoPackageDataset::GetRasterLayerDataset(const char *pszLayerName)
9230 : {
9231 12 : const auto oIter = m_oCachedRasterDS.find(pszLayerName);
9232 12 : if (oIter != m_oCachedRasterDS.end())
9233 10 : return oIter->second.get();
9234 :
9235 : auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
9236 4 : (std::string("GPKG:\"") + m_pszFilename + "\":" + pszLayerName).c_str(),
9237 4 : GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
9238 2 : if (!poDS)
9239 : {
9240 0 : return nullptr;
9241 : }
9242 2 : m_oCachedRasterDS[pszLayerName] = std::move(poDS);
9243 2 : return m_oCachedRasterDS[pszLayerName].get();
9244 : }
9245 :
9246 : /************************************************************************/
9247 : /* GPKG_gdal_get_layer_pixel_value() */
9248 : /************************************************************************/
9249 :
9250 : // NOTE: keep in sync implementations in ogrsqlitesqlfunctionscommon.cpp
9251 : // and ogrgeopackagedatasource.cpp
9252 13 : static void GPKG_gdal_get_layer_pixel_value(sqlite3_context *pContext, int argc,
9253 : sqlite3_value **argv)
9254 : {
9255 13 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
9256 : {
9257 1 : CPLError(CE_Failure, CPLE_AppDefined,
9258 : "Invalid arguments to gdal_get_layer_pixel_value()");
9259 1 : sqlite3_result_null(pContext);
9260 1 : return;
9261 : }
9262 :
9263 : const char *pszLayerName =
9264 12 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9265 :
9266 : GDALGeoPackageDataset *poGlobalDS =
9267 12 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9268 12 : auto poDS = poGlobalDS->GetRasterLayerDataset(pszLayerName);
9269 12 : if (!poDS)
9270 : {
9271 0 : sqlite3_result_null(pContext);
9272 0 : return;
9273 : }
9274 :
9275 12 : OGRSQLite_gdal_get_pixel_value_common("gdal_get_layer_pixel_value",
9276 : pContext, argc, argv, poDS);
9277 : }
9278 :
9279 : /************************************************************************/
9280 : /* GPKG_ogr_layer_Extent() */
9281 : /************************************************************************/
9282 :
9283 3 : static void GPKG_ogr_layer_Extent(sqlite3_context *pContext, int /*argc*/,
9284 : sqlite3_value **argv)
9285 : {
9286 3 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
9287 : {
9288 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s: Invalid argument type",
9289 : "ogr_layer_Extent");
9290 1 : sqlite3_result_null(pContext);
9291 2 : return;
9292 : }
9293 :
9294 : const char *pszLayerName =
9295 2 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9296 : GDALGeoPackageDataset *poDS =
9297 2 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9298 2 : OGRLayer *poLayer = poDS->GetLayerByName(pszLayerName);
9299 2 : if (!poLayer)
9300 : {
9301 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s: unknown layer",
9302 : "ogr_layer_Extent");
9303 1 : sqlite3_result_null(pContext);
9304 1 : return;
9305 : }
9306 :
9307 1 : if (poLayer->GetGeomType() == wkbNone)
9308 : {
9309 0 : sqlite3_result_null(pContext);
9310 0 : return;
9311 : }
9312 :
9313 1 : OGREnvelope sExtent;
9314 1 : if (poLayer->GetExtent(&sExtent, true) != OGRERR_NONE)
9315 : {
9316 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s: Cannot fetch layer extent",
9317 : "ogr_layer_Extent");
9318 0 : sqlite3_result_null(pContext);
9319 0 : return;
9320 : }
9321 :
9322 1 : OGRPolygon oPoly;
9323 1 : auto poRing = std::make_unique<OGRLinearRing>();
9324 1 : poRing->addPoint(sExtent.MinX, sExtent.MinY);
9325 1 : poRing->addPoint(sExtent.MaxX, sExtent.MinY);
9326 1 : poRing->addPoint(sExtent.MaxX, sExtent.MaxY);
9327 1 : poRing->addPoint(sExtent.MinX, sExtent.MaxY);
9328 1 : poRing->addPoint(sExtent.MinX, sExtent.MinY);
9329 1 : oPoly.addRing(std::move(poRing));
9330 :
9331 1 : const auto poSRS = poLayer->GetSpatialRef();
9332 1 : const int nSRID = poDS->GetSrsId(poSRS);
9333 1 : size_t nBLOBDestLen = 0;
9334 : GByte *pabyDestBLOB =
9335 1 : GPkgGeometryFromOGR(&oPoly, nSRID, nullptr, &nBLOBDestLen);
9336 1 : if (!pabyDestBLOB)
9337 : {
9338 0 : sqlite3_result_null(pContext);
9339 0 : return;
9340 : }
9341 1 : sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
9342 : VSIFree);
9343 : }
9344 :
9345 : /************************************************************************/
9346 : /* GPKG_ST_Hilbert_X_Y_TableName() */
9347 : /************************************************************************/
9348 :
9349 8 : static void GPKG_ST_Hilbert_X_Y_TableName(sqlite3_context *pContext,
9350 : [[maybe_unused]] int argc,
9351 : sqlite3_value **argv)
9352 : {
9353 8 : CPLAssert(argc == 3);
9354 8 : const double dfX = sqlite3_value_double(argv[0]);
9355 8 : const double dfY = sqlite3_value_double(argv[1]);
9356 :
9357 8 : if (sqlite3_value_type(argv[2]) != SQLITE_TEXT)
9358 : {
9359 1 : CPLError(CE_Failure, CPLE_AppDefined,
9360 : "%s: Invalid argument type for 3rd argument. Text expected",
9361 : "ST_Hilbert()");
9362 1 : sqlite3_result_null(pContext);
9363 6 : return;
9364 : }
9365 :
9366 : const char *pszLayerName =
9367 7 : reinterpret_cast<const char *>(sqlite3_value_text(argv[2]));
9368 : GDALGeoPackageDataset *poDS =
9369 7 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9370 7 : OGRLayer *poLayer = poDS->GetLayerByName(pszLayerName);
9371 7 : if (!poLayer)
9372 : {
9373 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s: unknown layer '%s'",
9374 : "ST_Hilbert()", pszLayerName);
9375 1 : sqlite3_result_null(pContext);
9376 1 : return;
9377 : }
9378 :
9379 6 : OGREnvelope sExtent;
9380 6 : if (poLayer->GetExtent(&sExtent, true) != OGRERR_NONE)
9381 : {
9382 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s: Cannot fetch layer extent",
9383 : "ST_Hilbert()");
9384 0 : sqlite3_result_null(pContext);
9385 0 : return;
9386 : }
9387 6 : if (!(dfX >= sExtent.MinX && dfY >= sExtent.MinY && dfX <= sExtent.MaxX &&
9388 3 : dfY <= sExtent.MaxY))
9389 : {
9390 4 : CPLError(CE_Warning, CPLE_AppDefined,
9391 : "ST_Hilbert(): (%g, %g) is not within passed bounding box",
9392 : dfX, dfY);
9393 4 : sqlite3_result_null(pContext);
9394 4 : return;
9395 : }
9396 2 : sqlite3_result_int64(pContext, GDALHilbertCode(&sExtent, dfX, dfY));
9397 : }
9398 :
9399 : /************************************************************************/
9400 : /* GPKG_ST_Hilbert_Geom_BBOX() */
9401 : /************************************************************************/
9402 :
9403 6 : static void GPKG_ST_Hilbert_Geom_BBOX(sqlite3_context *pContext, int argc,
9404 : sqlite3_value **argv)
9405 : {
9406 6 : CPLAssert(argc == 5);
9407 : GPkgHeader sHeader;
9408 6 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
9409 : {
9410 1 : sqlite3_result_null(pContext);
9411 5 : return;
9412 : }
9413 5 : const double dfX = (sHeader.MinX + sHeader.MaxX) / 2;
9414 5 : const double dfY = (sHeader.MinY + sHeader.MaxY) / 2;
9415 :
9416 5 : OGREnvelope sExtent;
9417 5 : sExtent.MinX = sqlite3_value_double(argv[1]);
9418 5 : sExtent.MinY = sqlite3_value_double(argv[2]);
9419 5 : sExtent.MaxX = sqlite3_value_double(argv[3]);
9420 5 : sExtent.MaxY = sqlite3_value_double(argv[4]);
9421 5 : if (!(dfX >= sExtent.MinX && dfY >= sExtent.MinY && dfX <= sExtent.MaxX &&
9422 2 : dfY <= sExtent.MaxY))
9423 : {
9424 4 : CPLError(CE_Warning, CPLE_AppDefined,
9425 : "ST_Hilbert(): (%g, %g) is not within passed bounding box",
9426 : dfX, dfY);
9427 4 : sqlite3_result_null(pContext);
9428 4 : return;
9429 : }
9430 1 : sqlite3_result_int64(pContext, GDALHilbertCode(&sExtent, dfX, dfY));
9431 : }
9432 :
9433 : /************************************************************************/
9434 : /* GPKG_ST_Hilbert_Geom_TableName() */
9435 : /************************************************************************/
9436 :
9437 4 : static void GPKG_ST_Hilbert_Geom_TableName(sqlite3_context *pContext, int argc,
9438 : sqlite3_value **argv)
9439 : {
9440 4 : CPLAssert(argc == 2);
9441 : GPkgHeader sHeader;
9442 4 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
9443 : {
9444 1 : sqlite3_result_null(pContext);
9445 3 : return;
9446 : }
9447 3 : const double dfX = (sHeader.MinX + sHeader.MaxX) / 2;
9448 3 : const double dfY = (sHeader.MinY + sHeader.MaxY) / 2;
9449 :
9450 3 : if (sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9451 : {
9452 1 : CPLError(CE_Failure, CPLE_AppDefined,
9453 : "%s: Invalid argument type for 2nd argument. Text expected",
9454 : "ST_Hilbert()");
9455 1 : sqlite3_result_null(pContext);
9456 1 : return;
9457 : }
9458 :
9459 : const char *pszLayerName =
9460 2 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9461 : GDALGeoPackageDataset *poDS =
9462 2 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9463 2 : OGRLayer *poLayer = poDS->GetLayerByName(pszLayerName);
9464 2 : if (!poLayer)
9465 : {
9466 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s: unknown layer '%s'",
9467 : "ST_Hilbert()", pszLayerName);
9468 1 : sqlite3_result_null(pContext);
9469 1 : return;
9470 : }
9471 :
9472 1 : OGREnvelope sExtent;
9473 1 : if (poLayer->GetExtent(&sExtent, true) != OGRERR_NONE)
9474 : {
9475 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s: Cannot fetch layer extent",
9476 : "ST_Hilbert()");
9477 0 : sqlite3_result_null(pContext);
9478 0 : return;
9479 : }
9480 1 : if (!(dfX >= sExtent.MinX && dfY >= sExtent.MinY && dfX <= sExtent.MaxX &&
9481 1 : dfY <= sExtent.MaxY))
9482 : {
9483 0 : CPLError(CE_Warning, CPLE_AppDefined,
9484 : "ST_Hilbert(): (%g, %g) is not within passed bounding box",
9485 : dfX, dfY);
9486 0 : sqlite3_result_null(pContext);
9487 0 : return;
9488 : }
9489 1 : sqlite3_result_int64(pContext, GDALHilbertCode(&sExtent, dfX, dfY));
9490 : }
9491 :
9492 : /************************************************************************/
9493 : /* InstallSQLFunctions() */
9494 : /************************************************************************/
9495 :
9496 : #ifndef SQLITE_DETERMINISTIC
9497 : #define SQLITE_DETERMINISTIC 0
9498 : #endif
9499 :
9500 : #ifndef SQLITE_INNOCUOUS
9501 : #define SQLITE_INNOCUOUS 0
9502 : #endif
9503 :
9504 : #ifndef UTF8_INNOCUOUS
9505 : #define UTF8_INNOCUOUS (SQLITE_UTF8 | SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS)
9506 : #endif
9507 :
9508 2307 : void GDALGeoPackageDataset::InstallSQLFunctions()
9509 : {
9510 2307 : InitSpatialite();
9511 :
9512 : // Enable SpatiaLite 4.3 GPKG mode, i.e. that SpatiaLite functions
9513 : // that take geometries will accept and return GPKG encoded geometries without
9514 : // explicit conversion.
9515 : // Use sqlite3_exec() instead of SQLCommand() since we don't want verbose
9516 : // error.
9517 2307 : sqlite3_exec(hDB, "SELECT EnableGpkgMode()", nullptr, nullptr, nullptr);
9518 :
9519 : /* Used by RTree Spatial Index Extension */
9520 2307 : sqlite3_create_function(hDB, "ST_MinX", 1, UTF8_INNOCUOUS, nullptr,
9521 : OGRGeoPackageSTMinX, nullptr, nullptr);
9522 2307 : sqlite3_create_function(hDB, "ST_MinY", 1, UTF8_INNOCUOUS, nullptr,
9523 : OGRGeoPackageSTMinY, nullptr, nullptr);
9524 2307 : sqlite3_create_function(hDB, "ST_MaxX", 1, UTF8_INNOCUOUS, nullptr,
9525 : OGRGeoPackageSTMaxX, nullptr, nullptr);
9526 2307 : sqlite3_create_function(hDB, "ST_MaxY", 1, UTF8_INNOCUOUS, nullptr,
9527 : OGRGeoPackageSTMaxY, nullptr, nullptr);
9528 2307 : sqlite3_create_function(hDB, "ST_IsEmpty", 1, UTF8_INNOCUOUS, nullptr,
9529 : OGRGeoPackageSTIsEmpty, nullptr, nullptr);
9530 :
9531 : /* Used by Geometry Type Triggers Extension */
9532 2307 : sqlite3_create_function(hDB, "ST_GeometryType", 1, UTF8_INNOCUOUS, nullptr,
9533 : OGRGeoPackageSTGeometryType, nullptr, nullptr);
9534 2307 : sqlite3_create_function(hDB, "GPKG_IsAssignable", 2, UTF8_INNOCUOUS,
9535 : nullptr, OGRGeoPackageGPKGIsAssignable, nullptr,
9536 : nullptr);
9537 :
9538 : /* Used by Geometry SRS ID Triggers Extension */
9539 2307 : sqlite3_create_function(hDB, "ST_SRID", 1, UTF8_INNOCUOUS, nullptr,
9540 : OGRGeoPackageSTSRID, nullptr, nullptr);
9541 :
9542 : /* Spatialite-like functions */
9543 2307 : sqlite3_create_function(hDB, "CreateSpatialIndex", 2, SQLITE_UTF8, this,
9544 : OGRGeoPackageCreateSpatialIndex, nullptr, nullptr);
9545 2307 : sqlite3_create_function(hDB, "DisableSpatialIndex", 2, SQLITE_UTF8, this,
9546 : OGRGeoPackageDisableSpatialIndex, nullptr, nullptr);
9547 2307 : sqlite3_create_function(hDB, "HasSpatialIndex", 2, SQLITE_UTF8, this,
9548 : OGRGeoPackageHasSpatialIndex, nullptr, nullptr);
9549 :
9550 : // HSTORE functions
9551 2307 : sqlite3_create_function(hDB, "hstore_get_value", 2, UTF8_INNOCUOUS, nullptr,
9552 : GPKG_hstore_get_value, nullptr, nullptr);
9553 :
9554 : // Override a few Spatialite functions to work with gpkg_spatial_ref_sys
9555 2307 : sqlite3_create_function(hDB, "ST_Transform", 2, UTF8_INNOCUOUS, this,
9556 : OGRGeoPackageTransform, nullptr, nullptr);
9557 2307 : sqlite3_create_function(hDB, "Transform", 2, UTF8_INNOCUOUS, this,
9558 : OGRGeoPackageTransform, nullptr, nullptr);
9559 2307 : sqlite3_create_function(hDB, "SridFromAuthCRS", 2, SQLITE_UTF8, this,
9560 : OGRGeoPackageSridFromAuthCRS, nullptr, nullptr);
9561 :
9562 2307 : sqlite3_create_function(hDB, "ST_EnvIntersects", 2, UTF8_INNOCUOUS, nullptr,
9563 : OGRGeoPackageSTEnvelopesIntersectsTwoParams,
9564 : nullptr, nullptr);
9565 2307 : sqlite3_create_function(
9566 : hDB, "ST_EnvelopesIntersects", 2, UTF8_INNOCUOUS, nullptr,
9567 : OGRGeoPackageSTEnvelopesIntersectsTwoParams, nullptr, nullptr);
9568 :
9569 2307 : sqlite3_create_function(hDB, "ST_EnvIntersects", 5, UTF8_INNOCUOUS, nullptr,
9570 : OGRGeoPackageSTEnvelopesIntersects, nullptr,
9571 : nullptr);
9572 2307 : sqlite3_create_function(hDB, "ST_EnvelopesIntersects", 5, UTF8_INNOCUOUS,
9573 : nullptr, OGRGeoPackageSTEnvelopesIntersects,
9574 : nullptr, nullptr);
9575 :
9576 : // Implementation that directly hacks the GeoPackage geometry blob header
9577 2307 : sqlite3_create_function(hDB, "SetSRID", 2, UTF8_INNOCUOUS, nullptr,
9578 : OGRGeoPackageSetSRID, nullptr, nullptr);
9579 :
9580 : // GDAL specific function
9581 2307 : sqlite3_create_function(hDB, "ImportFromEPSG", 1, SQLITE_UTF8, this,
9582 : OGRGeoPackageImportFromEPSG, nullptr, nullptr);
9583 :
9584 : // May be used by ogrmerge.py
9585 2307 : sqlite3_create_function(hDB, "RegisterGeometryExtension", 3, SQLITE_UTF8,
9586 : this, OGRGeoPackageRegisterGeometryExtension,
9587 : nullptr, nullptr);
9588 :
9589 2307 : if (OGRGeometryFactory::haveGEOS())
9590 : {
9591 2307 : sqlite3_create_function(hDB, "ST_MakeValid", 1, UTF8_INNOCUOUS, nullptr,
9592 : OGRGeoPackageSTMakeValid, nullptr, nullptr);
9593 : }
9594 :
9595 2307 : sqlite3_create_function(hDB, "ST_Length", 1, UTF8_INNOCUOUS, nullptr,
9596 : OGRGeoPackageLengthOrGeodesicLength, nullptr,
9597 : nullptr);
9598 2307 : sqlite3_create_function(hDB, "ST_Length", 2, UTF8_INNOCUOUS, this,
9599 : OGRGeoPackageLengthOrGeodesicLength, nullptr,
9600 : nullptr);
9601 :
9602 2307 : sqlite3_create_function(hDB, "ST_Area", 1, UTF8_INNOCUOUS, nullptr,
9603 : OGRGeoPackageSTArea, nullptr, nullptr);
9604 2307 : sqlite3_create_function(hDB, "ST_Area", 2, UTF8_INNOCUOUS, this,
9605 : OGRGeoPackageGeodesicArea, nullptr, nullptr);
9606 :
9607 : // Debug functions
9608 2307 : if (CPLTestBool(CPLGetConfigOption("GPKG_DEBUG", "FALSE")))
9609 : {
9610 422 : sqlite3_create_function(hDB, "GDAL_GetMimeType", 1,
9611 : SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9612 : GPKG_GDAL_GetMimeType, nullptr, nullptr);
9613 422 : sqlite3_create_function(hDB, "GDAL_GetBandCount", 1,
9614 : SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9615 : GPKG_GDAL_GetBandCount, nullptr, nullptr);
9616 422 : sqlite3_create_function(hDB, "GDAL_HasColorTable", 1,
9617 : SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9618 : GPKG_GDAL_HasColorTable, nullptr, nullptr);
9619 : }
9620 :
9621 2307 : sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 5, SQLITE_UTF8,
9622 : this, GPKG_gdal_get_layer_pixel_value, nullptr,
9623 : nullptr);
9624 2307 : sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 6, SQLITE_UTF8,
9625 : this, GPKG_gdal_get_layer_pixel_value, nullptr,
9626 : nullptr);
9627 :
9628 : // Function from VirtualOGR
9629 2307 : sqlite3_create_function(hDB, "ogr_layer_Extent", 1, SQLITE_UTF8, this,
9630 : GPKG_ogr_layer_Extent, nullptr, nullptr);
9631 :
9632 2307 : m_pSQLFunctionData = OGRSQLiteRegisterSQLFunctionsCommon(hDB);
9633 :
9634 : // ST_Hilbert() inspired from https://duckdb.org/docs/stable/core_extensions/spatial/functions#st_hilbert
9635 : // Override the generic version of OGRSQLiteRegisterSQLFunctionsCommon()
9636 :
9637 : // X,Y,table_name
9638 2307 : sqlite3_create_function(hDB, "ST_Hilbert", 2 + 1, UTF8_INNOCUOUS, this,
9639 : GPKG_ST_Hilbert_X_Y_TableName, nullptr, nullptr);
9640 :
9641 : // geometry,minX,minY,maxX,maxY
9642 2307 : sqlite3_create_function(hDB, "ST_Hilbert", 1 + 4, UTF8_INNOCUOUS, nullptr,
9643 : GPKG_ST_Hilbert_Geom_BBOX, nullptr, nullptr);
9644 :
9645 : // geometry,table_name
9646 2307 : sqlite3_create_function(hDB, "ST_Hilbert", 1 + 1, UTF8_INNOCUOUS, this,
9647 : GPKG_ST_Hilbert_Geom_TableName, nullptr, nullptr);
9648 2307 : }
9649 :
9650 : /************************************************************************/
9651 : /* OpenOrCreateDB() */
9652 : /************************************************************************/
9653 :
9654 2312 : bool GDALGeoPackageDataset::OpenOrCreateDB(int flags)
9655 : {
9656 2312 : const bool bSuccess = OGRSQLiteBaseDataSource::OpenOrCreateDB(
9657 : flags, /*bRegisterOGR2SQLiteExtensions=*/false,
9658 : /*bLoadExtensions=*/true);
9659 2312 : if (!bSuccess)
9660 10 : return false;
9661 :
9662 : // Turning on recursive_triggers is needed so that DELETE triggers fire
9663 : // in a INSERT OR REPLACE statement. In particular this is needed to
9664 : // make sure gpkg_ogr_contents.feature_count is properly updated.
9665 2302 : SQLCommand(hDB, "PRAGMA recursive_triggers = 1");
9666 :
9667 2302 : InstallSQLFunctions();
9668 :
9669 : const char *pszSqlitePragma =
9670 2302 : CPLGetConfigOption("OGR_SQLITE_PRAGMA", nullptr);
9671 2302 : OGRErr eErr = OGRERR_NONE;
9672 6 : if ((!pszSqlitePragma || !strstr(pszSqlitePragma, "trusted_schema")) &&
9673 : // Older sqlite versions don't have this pragma
9674 4610 : SQLGetInteger(hDB, "PRAGMA trusted_schema", &eErr) == 0 &&
9675 2302 : eErr == OGRERR_NONE)
9676 : {
9677 2302 : bool bNeedsTrustedSchema = false;
9678 :
9679 : // Current SQLite versions require PRAGMA trusted_schema = 1 to be
9680 : // able to use the RTree from triggers, which is only needed when
9681 : // modifying the RTree.
9682 5656 : if (((flags & SQLITE_OPEN_READWRITE) != 0 ||
9683 3552 : (flags & SQLITE_OPEN_CREATE) != 0) &&
9684 1250 : OGRSQLiteRTreeRequiresTrustedSchemaOn())
9685 : {
9686 1250 : bNeedsTrustedSchema = true;
9687 : }
9688 :
9689 : #ifdef HAVE_SPATIALITE
9690 : // Spatialite <= 5.1.0 doesn't declare its functions as SQLITE_INNOCUOUS
9691 1052 : if (!bNeedsTrustedSchema && HasExtensionsTable() &&
9692 961 : SQLGetInteger(
9693 : hDB,
9694 : "SELECT 1 FROM gpkg_extensions WHERE "
9695 : "extension_name ='gdal_spatialite_computed_geom_column'",
9696 1 : nullptr) == 1 &&
9697 3354 : SpatialiteRequiresTrustedSchemaOn() && AreSpatialiteTriggersSafe())
9698 : {
9699 1 : bNeedsTrustedSchema = true;
9700 : }
9701 : #endif
9702 :
9703 2302 : if (bNeedsTrustedSchema)
9704 : {
9705 1251 : CPLDebug("GPKG", "Setting PRAGMA trusted_schema = 1");
9706 1251 : SQLCommand(hDB, "PRAGMA trusted_schema = 1");
9707 : }
9708 : }
9709 :
9710 : const char *pszPreludeStatements =
9711 2302 : CSLFetchNameValue(papszOpenOptions, "PRELUDE_STATEMENTS");
9712 2302 : if (pszPreludeStatements)
9713 : {
9714 2 : if (SQLCommand(hDB, pszPreludeStatements) != OGRERR_NONE)
9715 0 : return false;
9716 : }
9717 :
9718 2302 : return true;
9719 : }
9720 :
9721 : /************************************************************************/
9722 : /* GetLayerWithGetSpatialWhereByName() */
9723 : /************************************************************************/
9724 :
9725 : std::pair<OGRLayer *, IOGRSQLiteGetSpatialWhere *>
9726 90 : GDALGeoPackageDataset::GetLayerWithGetSpatialWhereByName(const char *pszName)
9727 : {
9728 : OGRGeoPackageLayer *poRet =
9729 90 : cpl::down_cast<OGRGeoPackageLayer *>(GetLayerByName(pszName));
9730 90 : return std::pair(poRet, poRet);
9731 : }
9732 :
9733 : /************************************************************************/
9734 : /* CommitTransaction() */
9735 : /************************************************************************/
9736 :
9737 340 : OGRErr GDALGeoPackageDataset::CommitTransaction()
9738 :
9739 : {
9740 340 : if (m_nSoftTransactionLevel == 1)
9741 : {
9742 334 : FlushMetadata();
9743 720 : for (auto &poLayer : m_apoLayers)
9744 : {
9745 386 : poLayer->DoJobAtTransactionCommit();
9746 : }
9747 : }
9748 :
9749 340 : return OGRSQLiteBaseDataSource::CommitTransaction();
9750 : }
9751 :
9752 : /************************************************************************/
9753 : /* RollbackTransaction() */
9754 : /************************************************************************/
9755 :
9756 35 : OGRErr GDALGeoPackageDataset::RollbackTransaction()
9757 :
9758 : {
9759 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9760 70 : std::vector<bool> abAddTriggers;
9761 35 : std::vector<bool> abTriggersDeletedInTransaction;
9762 : #endif
9763 35 : if (m_nSoftTransactionLevel == 1)
9764 : {
9765 34 : FlushMetadata();
9766 70 : for (auto &poLayer : m_apoLayers)
9767 : {
9768 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9769 36 : abAddTriggers.push_back(poLayer->GetAddOGRFeatureCountTriggers());
9770 36 : abTriggersDeletedInTransaction.push_back(
9771 36 : poLayer->GetOGRFeatureCountTriggersDeletedInTransaction());
9772 36 : poLayer->SetAddOGRFeatureCountTriggers(false);
9773 : #endif
9774 36 : poLayer->DoJobAtTransactionRollback();
9775 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9776 36 : poLayer->DisableFeatureCount();
9777 : #endif
9778 : }
9779 : }
9780 :
9781 35 : const OGRErr eErr = OGRSQLiteBaseDataSource::RollbackTransaction();
9782 :
9783 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9784 35 : if (!abAddTriggers.empty())
9785 : {
9786 68 : for (size_t i = 0; i < m_apoLayers.size(); ++i)
9787 : {
9788 36 : auto &poLayer = m_apoLayers[i];
9789 36 : if (abTriggersDeletedInTransaction[i])
9790 : {
9791 7 : poLayer->SetOGRFeatureCountTriggersEnabled(true);
9792 : }
9793 : else
9794 : {
9795 29 : poLayer->SetAddOGRFeatureCountTriggers(abAddTriggers[i]);
9796 : }
9797 : }
9798 : }
9799 : #endif
9800 70 : return eErr;
9801 : }
9802 :
9803 : /************************************************************************/
9804 : /* GetGeometryTypeString() */
9805 : /************************************************************************/
9806 :
9807 : const char *
9808 1653 : GDALGeoPackageDataset::GetGeometryTypeString(OGRwkbGeometryType eType)
9809 : {
9810 1653 : const char *pszGPKGGeomType = OGRToOGCGeomType(eType);
9811 1665 : if (EQUAL(pszGPKGGeomType, "GEOMETRYCOLLECTION") &&
9812 12 : CPLTestBool(CPLGetConfigOption("OGR_GPKG_GEOMCOLLECTION", "NO")))
9813 : {
9814 0 : pszGPKGGeomType = "GEOMCOLLECTION";
9815 : }
9816 1653 : return pszGPKGGeomType;
9817 : }
9818 :
9819 : /************************************************************************/
9820 : /* GetFieldDomainNames() */
9821 : /************************************************************************/
9822 :
9823 : std::vector<std::string>
9824 12 : GDALGeoPackageDataset::GetFieldDomainNames(CSLConstList) const
9825 : {
9826 12 : if (!HasDataColumnConstraintsTable())
9827 3 : return std::vector<std::string>();
9828 :
9829 18 : std::vector<std::string> oDomainNamesList;
9830 :
9831 9 : std::unique_ptr<SQLResult> oResultTable;
9832 : {
9833 : std::string osSQL =
9834 : "SELECT DISTINCT constraint_name "
9835 : "FROM gpkg_data_column_constraints "
9836 : "WHERE constraint_name NOT LIKE '_%_domain_description' "
9837 : "ORDER BY constraint_name "
9838 9 : "LIMIT 10000" // to avoid denial of service
9839 : ;
9840 9 : oResultTable = SQLQuery(hDB, osSQL.c_str());
9841 9 : if (!oResultTable)
9842 0 : return oDomainNamesList;
9843 : }
9844 :
9845 9 : if (oResultTable->RowCount() == 10000)
9846 : {
9847 0 : CPLError(CE_Warning, CPLE_AppDefined,
9848 : "Number of rows returned for field domain names has been "
9849 : "truncated.");
9850 : }
9851 9 : else if (oResultTable->RowCount() > 0)
9852 : {
9853 8 : oDomainNamesList.reserve(oResultTable->RowCount());
9854 105 : for (int i = 0; i < oResultTable->RowCount(); i++)
9855 : {
9856 97 : const char *pszConstraintName = oResultTable->GetValue(0, i);
9857 97 : if (!pszConstraintName)
9858 0 : continue;
9859 :
9860 97 : oDomainNamesList.emplace_back(pszConstraintName);
9861 : }
9862 : }
9863 :
9864 9 : return oDomainNamesList;
9865 : }
9866 :
9867 : /************************************************************************/
9868 : /* GetFieldDomain() */
9869 : /************************************************************************/
9870 :
9871 : const OGRFieldDomain *
9872 138 : GDALGeoPackageDataset::GetFieldDomain(const std::string &name) const
9873 : {
9874 138 : const auto baseRet = GDALDataset::GetFieldDomain(name);
9875 138 : if (baseRet)
9876 43 : return baseRet;
9877 :
9878 95 : if (!HasDataColumnConstraintsTable())
9879 4 : return nullptr;
9880 :
9881 91 : const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
9882 91 : const char *min_is_inclusive =
9883 91 : bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
9884 91 : const char *max_is_inclusive =
9885 91 : bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
9886 :
9887 91 : std::unique_ptr<SQLResult> oResultTable;
9888 : // Note: for coded domains, we use a little trick by using a dummy
9889 : // _{domainname}_domain_description enum that has a single entry whose
9890 : // description is the description of the main domain.
9891 : {
9892 91 : char *pszSQL = sqlite3_mprintf(
9893 : "SELECT constraint_type, value, min, %s, "
9894 : "max, %s, description, constraint_name "
9895 : "FROM gpkg_data_column_constraints "
9896 : "WHERE constraint_name IN ('%q', "
9897 : "'_%q_domain_description') "
9898 : "AND length(constraint_type) < 100 " // to
9899 : // avoid
9900 : // denial
9901 : // of
9902 : // service
9903 : "AND (value IS NULL OR length(value) < "
9904 : "10000) " // to avoid denial
9905 : // of service
9906 : "AND (description IS NULL OR "
9907 : "length(description) < 10000) " // to
9908 : // avoid
9909 : // denial
9910 : // of
9911 : // service
9912 : "ORDER BY value "
9913 : "LIMIT 10000", // to avoid denial of
9914 : // service
9915 : min_is_inclusive, max_is_inclusive, name.c_str(), name.c_str());
9916 91 : oResultTable = SQLQuery(hDB, pszSQL);
9917 91 : sqlite3_free(pszSQL);
9918 91 : if (!oResultTable)
9919 0 : return nullptr;
9920 : }
9921 91 : if (oResultTable->RowCount() == 0)
9922 : {
9923 33 : return nullptr;
9924 : }
9925 58 : if (oResultTable->RowCount() == 10000)
9926 : {
9927 0 : CPLError(CE_Warning, CPLE_AppDefined,
9928 : "Number of rows returned for field domain %s has been "
9929 : "truncated.",
9930 : name.c_str());
9931 : }
9932 :
9933 : // Try to find the field domain data type from fields that implement it
9934 58 : int nFieldType = -1;
9935 58 : OGRFieldSubType eSubType = OFSTNone;
9936 58 : if (HasDataColumnsTable())
9937 : {
9938 53 : char *pszSQL = sqlite3_mprintf(
9939 : "SELECT table_name, column_name FROM gpkg_data_columns WHERE "
9940 : "constraint_name = '%q' LIMIT 10",
9941 : name.c_str());
9942 106 : auto oResultTable2 = SQLQuery(hDB, pszSQL);
9943 53 : sqlite3_free(pszSQL);
9944 53 : if (oResultTable2 && oResultTable2->RowCount() >= 1)
9945 : {
9946 58 : for (int iRecord = 0; iRecord < oResultTable2->RowCount();
9947 : iRecord++)
9948 : {
9949 29 : const char *pszTableName = oResultTable2->GetValue(0, iRecord);
9950 29 : const char *pszColumnName = oResultTable2->GetValue(1, iRecord);
9951 29 : if (pszTableName == nullptr || pszColumnName == nullptr)
9952 0 : continue;
9953 : OGRLayer *poLayer =
9954 58 : const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
9955 29 : pszTableName);
9956 29 : if (poLayer)
9957 : {
9958 29 : const auto poFDefn = poLayer->GetLayerDefn();
9959 29 : int nIdx = poFDefn->GetFieldIndex(pszColumnName);
9960 29 : if (nIdx >= 0)
9961 : {
9962 29 : const auto poFieldDefn = poFDefn->GetFieldDefn(nIdx);
9963 29 : const auto eType = poFieldDefn->GetType();
9964 29 : if (nFieldType < 0)
9965 : {
9966 29 : nFieldType = eType;
9967 29 : eSubType = poFieldDefn->GetSubType();
9968 : }
9969 0 : else if ((eType == OFTInteger64 || eType == OFTReal) &&
9970 : nFieldType == OFTInteger)
9971 : {
9972 : // ok
9973 : }
9974 0 : else if (eType == OFTInteger &&
9975 0 : (nFieldType == OFTInteger64 ||
9976 : nFieldType == OFTReal))
9977 : {
9978 0 : nFieldType = OFTInteger;
9979 0 : eSubType = OFSTNone;
9980 : }
9981 0 : else if (nFieldType != eType)
9982 : {
9983 0 : nFieldType = -1;
9984 0 : eSubType = OFSTNone;
9985 0 : break;
9986 : }
9987 : }
9988 : }
9989 : }
9990 : }
9991 : }
9992 :
9993 58 : std::unique_ptr<OGRFieldDomain> poDomain;
9994 116 : std::vector<OGRCodedValue> asValues;
9995 58 : bool error = false;
9996 116 : CPLString osLastConstraintType;
9997 58 : int nFieldTypeFromEnumCode = -1;
9998 116 : std::string osConstraintDescription;
9999 116 : std::string osDescrConstraintName("_");
10000 58 : osDescrConstraintName += name;
10001 58 : osDescrConstraintName += "_domain_description";
10002 145 : for (int iRecord = 0; iRecord < oResultTable->RowCount(); iRecord++)
10003 : {
10004 91 : const char *pszConstraintType = oResultTable->GetValue(0, iRecord);
10005 91 : if (pszConstraintType == nullptr)
10006 2 : continue;
10007 91 : const char *pszValue = oResultTable->GetValue(1, iRecord);
10008 91 : const char *pszMin = oResultTable->GetValue(2, iRecord);
10009 : const bool bIsMinIncluded =
10010 91 : oResultTable->GetValueAsInteger(3, iRecord) == 1;
10011 91 : const char *pszMax = oResultTable->GetValue(4, iRecord);
10012 : const bool bIsMaxIncluded =
10013 91 : oResultTable->GetValueAsInteger(5, iRecord) == 1;
10014 91 : const char *pszDescription = oResultTable->GetValue(6, iRecord);
10015 91 : const char *pszConstraintName = oResultTable->GetValue(7, iRecord);
10016 :
10017 91 : if (!osLastConstraintType.empty() && osLastConstraintType != "enum")
10018 : {
10019 1 : CPLError(CE_Failure, CPLE_AppDefined,
10020 : "Only constraint of type 'enum' can have multiple rows");
10021 1 : error = true;
10022 4 : break;
10023 : }
10024 :
10025 90 : if (strcmp(pszConstraintType, "enum") == 0)
10026 : {
10027 63 : if (pszValue == nullptr)
10028 : {
10029 1 : CPLError(CE_Failure, CPLE_AppDefined,
10030 : "NULL in 'value' column of enumeration");
10031 1 : error = true;
10032 1 : break;
10033 : }
10034 62 : if (osDescrConstraintName == pszConstraintName)
10035 : {
10036 2 : if (pszDescription)
10037 : {
10038 2 : osConstraintDescription = pszDescription;
10039 : }
10040 2 : continue;
10041 : }
10042 60 : if (asValues.empty())
10043 : {
10044 30 : asValues.reserve(oResultTable->RowCount() + 1);
10045 : }
10046 : OGRCodedValue cv;
10047 : // intended: the 'value' column in GPKG is actually the code
10048 60 : cv.pszCode = VSI_STRDUP_VERBOSE(pszValue);
10049 60 : if (cv.pszCode == nullptr)
10050 : {
10051 0 : error = true;
10052 0 : break;
10053 : }
10054 60 : if (pszDescription)
10055 : {
10056 48 : cv.pszValue = VSI_STRDUP_VERBOSE(pszDescription);
10057 48 : if (cv.pszValue == nullptr)
10058 : {
10059 0 : VSIFree(cv.pszCode);
10060 0 : error = true;
10061 0 : break;
10062 : }
10063 : }
10064 : else
10065 : {
10066 12 : cv.pszValue = nullptr;
10067 : }
10068 :
10069 : // If we can't get the data type from field definition, guess it
10070 : // from code.
10071 60 : if (nFieldType < 0 && nFieldTypeFromEnumCode != OFTString)
10072 : {
10073 36 : switch (CPLGetValueType(cv.pszCode))
10074 : {
10075 26 : case CPL_VALUE_INTEGER:
10076 : {
10077 26 : if (nFieldTypeFromEnumCode != OFTReal &&
10078 : nFieldTypeFromEnumCode != OFTInteger64)
10079 : {
10080 18 : const auto nVal = CPLAtoGIntBig(cv.pszCode);
10081 34 : if (nVal < std::numeric_limits<int>::min() ||
10082 16 : nVal > std::numeric_limits<int>::max())
10083 : {
10084 6 : nFieldTypeFromEnumCode = OFTInteger64;
10085 : }
10086 : else
10087 : {
10088 12 : nFieldTypeFromEnumCode = OFTInteger;
10089 : }
10090 : }
10091 26 : break;
10092 : }
10093 :
10094 6 : case CPL_VALUE_REAL:
10095 6 : nFieldTypeFromEnumCode = OFTReal;
10096 6 : break;
10097 :
10098 4 : case CPL_VALUE_STRING:
10099 4 : nFieldTypeFromEnumCode = OFTString;
10100 4 : break;
10101 : }
10102 : }
10103 :
10104 60 : asValues.emplace_back(cv);
10105 : }
10106 27 : else if (strcmp(pszConstraintType, "range") == 0)
10107 : {
10108 : OGRField sMin;
10109 : OGRField sMax;
10110 20 : OGR_RawField_SetUnset(&sMin);
10111 20 : OGR_RawField_SetUnset(&sMax);
10112 20 : if (nFieldType != OFTInteger && nFieldType != OFTInteger64)
10113 11 : nFieldType = OFTReal;
10114 39 : if (pszMin != nullptr &&
10115 19 : CPLAtof(pszMin) != -std::numeric_limits<double>::infinity())
10116 : {
10117 15 : if (nFieldType == OFTInteger)
10118 6 : sMin.Integer = atoi(pszMin);
10119 9 : else if (nFieldType == OFTInteger64)
10120 3 : sMin.Integer64 = CPLAtoGIntBig(pszMin);
10121 : else /* if( nFieldType == OFTReal ) */
10122 6 : sMin.Real = CPLAtof(pszMin);
10123 : }
10124 39 : if (pszMax != nullptr &&
10125 19 : CPLAtof(pszMax) != std::numeric_limits<double>::infinity())
10126 : {
10127 15 : if (nFieldType == OFTInteger)
10128 6 : sMax.Integer = atoi(pszMax);
10129 9 : else if (nFieldType == OFTInteger64)
10130 3 : sMax.Integer64 = CPLAtoGIntBig(pszMax);
10131 : else /* if( nFieldType == OFTReal ) */
10132 6 : sMax.Real = CPLAtof(pszMax);
10133 : }
10134 20 : poDomain = std::make_unique<OGRRangeFieldDomain>(
10135 20 : name, pszDescription ? pszDescription : "",
10136 40 : static_cast<OGRFieldType>(nFieldType), eSubType, sMin,
10137 20 : bIsMinIncluded, sMax, bIsMaxIncluded);
10138 : }
10139 7 : else if (strcmp(pszConstraintType, "glob") == 0)
10140 : {
10141 6 : if (pszValue == nullptr)
10142 : {
10143 1 : CPLError(CE_Failure, CPLE_AppDefined,
10144 : "NULL in 'value' column of glob");
10145 1 : error = true;
10146 1 : break;
10147 : }
10148 5 : if (nFieldType < 0)
10149 1 : nFieldType = OFTString;
10150 5 : poDomain = std::make_unique<OGRGlobFieldDomain>(
10151 5 : name, pszDescription ? pszDescription : "",
10152 15 : static_cast<OGRFieldType>(nFieldType), eSubType, pszValue);
10153 : }
10154 : else
10155 : {
10156 1 : CPLError(CE_Failure, CPLE_AppDefined,
10157 : "Unhandled constraint_type: %s", pszConstraintType);
10158 1 : error = true;
10159 1 : break;
10160 : }
10161 :
10162 85 : osLastConstraintType = pszConstraintType;
10163 : }
10164 :
10165 58 : if (!asValues.empty())
10166 : {
10167 30 : if (nFieldType < 0)
10168 18 : nFieldType = nFieldTypeFromEnumCode;
10169 30 : poDomain = std::make_unique<OGRCodedFieldDomain>(
10170 : name, osConstraintDescription,
10171 60 : static_cast<OGRFieldType>(nFieldType), eSubType,
10172 60 : std::move(asValues));
10173 : }
10174 :
10175 58 : if (error)
10176 : {
10177 4 : return nullptr;
10178 : }
10179 :
10180 54 : m_oMapFieldDomains[name] = std::move(poDomain);
10181 54 : return GDALDataset::GetFieldDomain(name);
10182 : }
10183 :
10184 : /************************************************************************/
10185 : /* AddFieldDomain() */
10186 : /************************************************************************/
10187 :
10188 19 : bool GDALGeoPackageDataset::AddFieldDomain(
10189 : std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
10190 : {
10191 38 : const std::string domainName(domain->GetName());
10192 19 : if (!GetUpdate())
10193 : {
10194 0 : CPLError(CE_Failure, CPLE_NotSupported,
10195 : "AddFieldDomain() not supported on read-only dataset");
10196 0 : return false;
10197 : }
10198 19 : if (GetFieldDomain(domainName) != nullptr)
10199 : {
10200 1 : failureReason = "A domain of identical name already exists";
10201 1 : return false;
10202 : }
10203 18 : if (!CreateColumnsTableAndColumnConstraintsTablesIfNecessary())
10204 0 : return false;
10205 :
10206 18 : const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
10207 18 : const char *min_is_inclusive =
10208 18 : bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
10209 18 : const char *max_is_inclusive =
10210 18 : bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
10211 :
10212 18 : const auto &osDescription = domain->GetDescription();
10213 18 : switch (domain->GetDomainType())
10214 : {
10215 11 : case OFDT_CODED:
10216 : {
10217 : const auto poCodedDomain =
10218 11 : cpl::down_cast<const OGRCodedFieldDomain *>(domain.get());
10219 11 : if (!osDescription.empty())
10220 : {
10221 : // We use a little trick by using a dummy
10222 : // _{domainname}_domain_description enum that has a single
10223 : // entry whose description is the description of the main
10224 : // domain.
10225 1 : char *pszSQL = sqlite3_mprintf(
10226 : "INSERT INTO gpkg_data_column_constraints ("
10227 : "constraint_name, constraint_type, value, "
10228 : "min, %s, max, %s, "
10229 : "description) VALUES ("
10230 : "'_%q_domain_description', 'enum', '', NULL, NULL, NULL, "
10231 : "NULL, %Q)",
10232 : min_is_inclusive, max_is_inclusive, domainName.c_str(),
10233 : osDescription.c_str());
10234 1 : CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
10235 1 : sqlite3_free(pszSQL);
10236 : }
10237 11 : const auto &enumeration = poCodedDomain->GetEnumeration();
10238 33 : for (int i = 0; enumeration[i].pszCode != nullptr; ++i)
10239 : {
10240 22 : char *pszSQL = sqlite3_mprintf(
10241 : "INSERT INTO gpkg_data_column_constraints ("
10242 : "constraint_name, constraint_type, value, "
10243 : "min, %s, max, %s, "
10244 : "description) VALUES ("
10245 : "'%q', 'enum', '%q', NULL, NULL, NULL, NULL, %Q)",
10246 : min_is_inclusive, max_is_inclusive, domainName.c_str(),
10247 22 : enumeration[i].pszCode, enumeration[i].pszValue);
10248 22 : bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
10249 22 : sqlite3_free(pszSQL);
10250 22 : if (!ok)
10251 0 : return false;
10252 : }
10253 11 : break;
10254 : }
10255 :
10256 6 : case OFDT_RANGE:
10257 : {
10258 : const auto poRangeDomain =
10259 6 : cpl::down_cast<const OGRRangeFieldDomain *>(domain.get());
10260 6 : const auto eFieldType = poRangeDomain->GetFieldType();
10261 6 : if (eFieldType != OFTInteger && eFieldType != OFTInteger64 &&
10262 : eFieldType != OFTReal)
10263 : {
10264 : failureReason = "Only range domains of numeric type are "
10265 0 : "supported in GeoPackage";
10266 0 : return false;
10267 : }
10268 :
10269 6 : double dfMin = -std::numeric_limits<double>::infinity();
10270 6 : double dfMax = std::numeric_limits<double>::infinity();
10271 6 : bool bMinIsInclusive = true;
10272 6 : const auto &sMin = poRangeDomain->GetMin(bMinIsInclusive);
10273 6 : bool bMaxIsInclusive = true;
10274 6 : const auto &sMax = poRangeDomain->GetMax(bMaxIsInclusive);
10275 6 : if (eFieldType == OFTInteger)
10276 : {
10277 2 : if (!OGR_RawField_IsUnset(&sMin))
10278 2 : dfMin = sMin.Integer;
10279 2 : if (!OGR_RawField_IsUnset(&sMax))
10280 2 : dfMax = sMax.Integer;
10281 : }
10282 4 : else if (eFieldType == OFTInteger64)
10283 : {
10284 1 : if (!OGR_RawField_IsUnset(&sMin))
10285 1 : dfMin = static_cast<double>(sMin.Integer64);
10286 1 : if (!OGR_RawField_IsUnset(&sMax))
10287 1 : dfMax = static_cast<double>(sMax.Integer64);
10288 : }
10289 : else /* if( eFieldType == OFTReal ) */
10290 : {
10291 3 : if (!OGR_RawField_IsUnset(&sMin))
10292 3 : dfMin = sMin.Real;
10293 3 : if (!OGR_RawField_IsUnset(&sMax))
10294 3 : dfMax = sMax.Real;
10295 : }
10296 :
10297 6 : sqlite3_stmt *hInsertStmt = nullptr;
10298 : const char *pszSQL =
10299 6 : CPLSPrintf("INSERT INTO gpkg_data_column_constraints ("
10300 : "constraint_name, constraint_type, value, "
10301 : "min, %s, max, %s, "
10302 : "description) VALUES ("
10303 : "?, 'range', NULL, ?, ?, ?, ?, ?)",
10304 : min_is_inclusive, max_is_inclusive);
10305 6 : if (SQLPrepareWithError(hDB, pszSQL, -1, &hInsertStmt, nullptr) !=
10306 : SQLITE_OK)
10307 : {
10308 0 : return false;
10309 : }
10310 6 : sqlite3_bind_text(hInsertStmt, 1, domainName.c_str(),
10311 6 : static_cast<int>(domainName.size()),
10312 : SQLITE_TRANSIENT);
10313 6 : sqlite3_bind_double(hInsertStmt, 2, dfMin);
10314 6 : sqlite3_bind_int(hInsertStmt, 3, bMinIsInclusive ? 1 : 0);
10315 6 : sqlite3_bind_double(hInsertStmt, 4, dfMax);
10316 6 : sqlite3_bind_int(hInsertStmt, 5, bMaxIsInclusive ? 1 : 0);
10317 6 : if (osDescription.empty())
10318 : {
10319 3 : sqlite3_bind_null(hInsertStmt, 6);
10320 : }
10321 : else
10322 : {
10323 3 : sqlite3_bind_text(hInsertStmt, 6, osDescription.c_str(),
10324 3 : static_cast<int>(osDescription.size()),
10325 : SQLITE_TRANSIENT);
10326 : }
10327 6 : const int sqlite_err = sqlite3_step(hInsertStmt);
10328 6 : sqlite3_finalize(hInsertStmt);
10329 6 : if (sqlite_err != SQLITE_OK && sqlite_err != SQLITE_DONE)
10330 : {
10331 0 : CPLError(CE_Failure, CPLE_AppDefined,
10332 : "failed to execute insertion '%s': %s", pszSQL,
10333 : sqlite3_errmsg(hDB));
10334 0 : return false;
10335 : }
10336 :
10337 6 : break;
10338 : }
10339 :
10340 1 : case OFDT_GLOB:
10341 : {
10342 : const auto poGlobDomain =
10343 1 : cpl::down_cast<const OGRGlobFieldDomain *>(domain.get());
10344 2 : char *pszSQL = sqlite3_mprintf(
10345 : "INSERT INTO gpkg_data_column_constraints ("
10346 : "constraint_name, constraint_type, value, "
10347 : "min, %s, max, %s, "
10348 : "description) VALUES ("
10349 : "'%q', 'glob', '%q', NULL, NULL, NULL, NULL, %Q)",
10350 : min_is_inclusive, max_is_inclusive, domainName.c_str(),
10351 1 : poGlobDomain->GetGlob().c_str(),
10352 2 : osDescription.empty() ? nullptr : osDescription.c_str());
10353 1 : bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
10354 1 : sqlite3_free(pszSQL);
10355 1 : if (!ok)
10356 0 : return false;
10357 :
10358 1 : break;
10359 : }
10360 : }
10361 :
10362 18 : m_oMapFieldDomains[domainName] = std::move(domain);
10363 18 : return true;
10364 : }
10365 :
10366 : /************************************************************************/
10367 : /* UpdateFieldDomain() */
10368 : /************************************************************************/
10369 :
10370 3 : bool GDALGeoPackageDataset::UpdateFieldDomain(
10371 : std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
10372 : {
10373 6 : const std::string domainName(domain->GetName());
10374 3 : if (eAccess != GA_Update)
10375 : {
10376 1 : CPLError(CE_Failure, CPLE_NotSupported,
10377 : "UpdateFieldDomain() not supported on read-only dataset");
10378 1 : return false;
10379 : }
10380 :
10381 2 : if (GetFieldDomain(domainName) == nullptr)
10382 : {
10383 1 : failureReason = "The domain should already exist to be updated";
10384 1 : return false;
10385 : }
10386 :
10387 1 : bool bRet = SoftStartTransaction() == OGRERR_NONE;
10388 1 : if (bRet)
10389 : {
10390 2 : bRet = DeleteFieldDomain(domainName, failureReason) &&
10391 1 : AddFieldDomain(std::move(domain), failureReason);
10392 1 : if (bRet)
10393 1 : bRet = SoftCommitTransaction() == OGRERR_NONE;
10394 : else
10395 0 : SoftRollbackTransaction();
10396 : }
10397 1 : return bRet;
10398 : }
10399 :
10400 : /************************************************************************/
10401 : /* DeleteFieldDomain() */
10402 : /************************************************************************/
10403 :
10404 18 : bool GDALGeoPackageDataset::DeleteFieldDomain(const std::string &name,
10405 : std::string &failureReason)
10406 : {
10407 18 : if (eAccess != GA_Update)
10408 : {
10409 1 : CPLError(CE_Failure, CPLE_NotSupported,
10410 : "DeleteFieldDomain() not supported on read-only dataset");
10411 1 : return false;
10412 : }
10413 17 : if (GetFieldDomain(name) == nullptr)
10414 : {
10415 1 : failureReason = "Domain does not exist";
10416 1 : return false;
10417 : }
10418 :
10419 : char *pszSQL =
10420 16 : sqlite3_mprintf("DELETE FROM gpkg_data_column_constraints WHERE "
10421 : "constraint_name IN ('%q', '_%q_domain_description')",
10422 : name.c_str(), name.c_str());
10423 16 : const bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
10424 16 : sqlite3_free(pszSQL);
10425 16 : if (ok)
10426 16 : m_oMapFieldDomains.erase(name);
10427 16 : return ok;
10428 : }
10429 :
10430 : /************************************************************************/
10431 : /* AddRelationship() */
10432 : /************************************************************************/
10433 :
10434 24 : bool GDALGeoPackageDataset::AddRelationship(
10435 : std::unique_ptr<GDALRelationship> &&relationship,
10436 : std::string &failureReason)
10437 : {
10438 24 : if (!GetUpdate())
10439 : {
10440 0 : CPLError(CE_Failure, CPLE_NotSupported,
10441 : "AddRelationship() not supported on read-only dataset");
10442 0 : return false;
10443 : }
10444 :
10445 : const std::string osRelationshipName = GenerateNameForRelationship(
10446 24 : relationship->GetLeftTableName().c_str(),
10447 24 : relationship->GetRightTableName().c_str(),
10448 96 : relationship->GetRelatedTableType().c_str());
10449 : // sanity checks
10450 24 : if (GetRelationship(osRelationshipName) != nullptr)
10451 : {
10452 1 : failureReason = "A relationship of identical name already exists";
10453 1 : return false;
10454 : }
10455 :
10456 23 : if (!ValidateRelationship(relationship.get(), failureReason))
10457 : {
10458 14 : return false;
10459 : }
10460 :
10461 9 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
10462 : {
10463 0 : return false;
10464 : }
10465 9 : if (!CreateRelationsTableIfNecessary())
10466 : {
10467 0 : failureReason = "Could not create gpkgext_relations table";
10468 0 : return false;
10469 : }
10470 9 : if (SQLGetInteger(GetDB(),
10471 : "SELECT 1 FROM gpkg_extensions WHERE "
10472 : "table_name = 'gpkgext_relations'",
10473 9 : nullptr) != 1)
10474 : {
10475 4 : if (OGRERR_NONE !=
10476 4 : SQLCommand(
10477 : GetDB(),
10478 : "INSERT INTO gpkg_extensions "
10479 : "(table_name,column_name,extension_name,definition,scope) "
10480 : "VALUES ('gpkgext_relations', NULL, 'gpkg_related_tables', "
10481 : "'http://www.geopackage.org/18-000.html', "
10482 : "'read-write')"))
10483 : {
10484 : failureReason =
10485 0 : "Could not create gpkg_extensions entry for gpkgext_relations";
10486 0 : return false;
10487 : }
10488 : }
10489 :
10490 9 : const std::string &osLeftTableName = relationship->GetLeftTableName();
10491 9 : const std::string &osRightTableName = relationship->GetRightTableName();
10492 9 : const auto &aosLeftTableFields = relationship->GetLeftTableFields();
10493 9 : const auto &aosRightTableFields = relationship->GetRightTableFields();
10494 :
10495 18 : std::string osRelatedTableType = relationship->GetRelatedTableType();
10496 9 : if (osRelatedTableType.empty())
10497 : {
10498 5 : osRelatedTableType = "features";
10499 : }
10500 :
10501 : // generate mapping table if not set
10502 18 : CPLString osMappingTableName = relationship->GetMappingTableName();
10503 9 : if (osMappingTableName.empty())
10504 : {
10505 3 : int nIndex = 1;
10506 3 : osMappingTableName = osLeftTableName + "_" + osRightTableName;
10507 3 : while (FindLayerIndex(osMappingTableName.c_str()) >= 0)
10508 : {
10509 0 : nIndex += 1;
10510 : osMappingTableName.Printf("%s_%s_%d", osLeftTableName.c_str(),
10511 0 : osRightTableName.c_str(), nIndex);
10512 : }
10513 :
10514 : // determine whether base/related keys are unique
10515 3 : bool bBaseKeyIsUnique = false;
10516 : {
10517 : const std::set<std::string> uniqueBaseFieldsUC =
10518 : SQLGetUniqueFieldUCConstraints(GetDB(),
10519 6 : osLeftTableName.c_str());
10520 6 : if (uniqueBaseFieldsUC.find(
10521 3 : CPLString(aosLeftTableFields[0]).toupper()) !=
10522 6 : uniqueBaseFieldsUC.end())
10523 : {
10524 2 : bBaseKeyIsUnique = true;
10525 : }
10526 : }
10527 3 : bool bRelatedKeyIsUnique = false;
10528 : {
10529 : const std::set<std::string> uniqueRelatedFieldsUC =
10530 : SQLGetUniqueFieldUCConstraints(GetDB(),
10531 6 : osRightTableName.c_str());
10532 6 : if (uniqueRelatedFieldsUC.find(
10533 3 : CPLString(aosRightTableFields[0]).toupper()) !=
10534 6 : uniqueRelatedFieldsUC.end())
10535 : {
10536 2 : bRelatedKeyIsUnique = true;
10537 : }
10538 : }
10539 :
10540 : // create mapping table
10541 :
10542 3 : std::string osBaseIdDefinition = "base_id INTEGER";
10543 3 : if (bBaseKeyIsUnique)
10544 : {
10545 2 : char *pszSQL = sqlite3_mprintf(
10546 : " CONSTRAINT 'fk_base_id_%q' REFERENCES \"%w\"(\"%w\") ON "
10547 : "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
10548 : "DEFERRED",
10549 : osMappingTableName.c_str(), osLeftTableName.c_str(),
10550 2 : aosLeftTableFields[0].c_str());
10551 2 : osBaseIdDefinition += pszSQL;
10552 2 : sqlite3_free(pszSQL);
10553 : }
10554 :
10555 3 : std::string osRelatedIdDefinition = "related_id INTEGER";
10556 3 : if (bRelatedKeyIsUnique)
10557 : {
10558 2 : char *pszSQL = sqlite3_mprintf(
10559 : " CONSTRAINT 'fk_related_id_%q' REFERENCES \"%w\"(\"%w\") ON "
10560 : "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
10561 : "DEFERRED",
10562 : osMappingTableName.c_str(), osRightTableName.c_str(),
10563 2 : aosRightTableFields[0].c_str());
10564 2 : osRelatedIdDefinition += pszSQL;
10565 2 : sqlite3_free(pszSQL);
10566 : }
10567 :
10568 3 : char *pszSQL = sqlite3_mprintf("CREATE TABLE \"%w\" ("
10569 : "id INTEGER PRIMARY KEY AUTOINCREMENT, "
10570 : "%s, %s);",
10571 : osMappingTableName.c_str(),
10572 : osBaseIdDefinition.c_str(),
10573 : osRelatedIdDefinition.c_str());
10574 3 : OGRErr eErr = SQLCommand(hDB, pszSQL);
10575 3 : sqlite3_free(pszSQL);
10576 3 : if (eErr != OGRERR_NONE)
10577 : {
10578 : failureReason =
10579 0 : ("Could not create mapping table " + osMappingTableName)
10580 0 : .c_str();
10581 0 : return false;
10582 : }
10583 :
10584 : /*
10585 : * Strictly speaking we should NOT be inserting the mapping table into gpkg_contents.
10586 : * The related tables extension explicitly states that the mapping table should only be
10587 : * in the gpkgext_relations table and not in gpkg_contents. (See also discussion at
10588 : * https://github.com/opengeospatial/geopackage/issues/679).
10589 : *
10590 : * However, if we don't insert the mapping table into gpkg_contents then it is no longer
10591 : * visible to some clients (eg ESRI software only allows opening tables that are present
10592 : * in gpkg_contents). So we'll do this anyway, for maximum compatibility and flexibility.
10593 : *
10594 : * More related discussion is at https://github.com/OSGeo/gdal/pull/9258
10595 : */
10596 3 : pszSQL = sqlite3_mprintf(
10597 : "INSERT INTO gpkg_contents "
10598 : "(table_name,data_type,identifier,description,last_change,srs_id) "
10599 : "VALUES "
10600 : "('%q','attributes','%q','Mapping table for relationship between "
10601 : "%q and %q',%s,0)",
10602 : osMappingTableName.c_str(), /*table_name*/
10603 : osMappingTableName.c_str(), /*identifier*/
10604 : osLeftTableName.c_str(), /*description left table name*/
10605 : osRightTableName.c_str(), /*description right table name*/
10606 6 : GDALGeoPackageDataset::GetCurrentDateEscapedSQL().c_str());
10607 :
10608 : // Note -- we explicitly ignore failures here, because hey, we aren't really
10609 : // supposed to be adding this table to gpkg_contents anyway!
10610 3 : (void)SQLCommand(hDB, pszSQL);
10611 3 : sqlite3_free(pszSQL);
10612 :
10613 3 : pszSQL = sqlite3_mprintf(
10614 : "CREATE INDEX \"idx_%w_base_id\" ON \"%w\" (base_id);",
10615 : osMappingTableName.c_str(), osMappingTableName.c_str());
10616 3 : eErr = SQLCommand(hDB, pszSQL);
10617 3 : sqlite3_free(pszSQL);
10618 3 : if (eErr != OGRERR_NONE)
10619 : {
10620 0 : failureReason = ("Could not create index for " +
10621 0 : osMappingTableName + " (base_id)")
10622 0 : .c_str();
10623 0 : return false;
10624 : }
10625 :
10626 3 : pszSQL = sqlite3_mprintf(
10627 : "CREATE INDEX \"idx_%qw_related_id\" ON \"%w\" (related_id);",
10628 : osMappingTableName.c_str(), osMappingTableName.c_str());
10629 3 : eErr = SQLCommand(hDB, pszSQL);
10630 3 : sqlite3_free(pszSQL);
10631 3 : if (eErr != OGRERR_NONE)
10632 : {
10633 0 : failureReason = ("Could not create index for " +
10634 0 : osMappingTableName + " (related_id)")
10635 0 : .c_str();
10636 0 : return false;
10637 : }
10638 : }
10639 : else
10640 : {
10641 : // validate mapping table structure
10642 6 : if (OGRGeoPackageTableLayer *poLayer =
10643 6 : cpl::down_cast<OGRGeoPackageTableLayer *>(
10644 6 : GetLayerByName(osMappingTableName)))
10645 : {
10646 4 : if (poLayer->GetLayerDefn()->GetFieldIndex("base_id") < 0)
10647 : {
10648 : failureReason =
10649 2 : ("Field base_id must exist in " + osMappingTableName)
10650 1 : .c_str();
10651 1 : return false;
10652 : }
10653 3 : if (poLayer->GetLayerDefn()->GetFieldIndex("related_id") < 0)
10654 : {
10655 : failureReason =
10656 2 : ("Field related_id must exist in " + osMappingTableName)
10657 1 : .c_str();
10658 1 : return false;
10659 : }
10660 : }
10661 : else
10662 : {
10663 : failureReason =
10664 2 : ("Could not retrieve table " + osMappingTableName).c_str();
10665 2 : return false;
10666 : }
10667 : }
10668 :
10669 5 : char *pszSQL = sqlite3_mprintf(
10670 : "INSERT INTO gpkg_extensions "
10671 : "(table_name,column_name,extension_name,definition,scope) "
10672 : "VALUES ('%q', NULL, 'gpkg_related_tables', "
10673 : "'http://www.geopackage.org/18-000.html', "
10674 : "'read-write')",
10675 : osMappingTableName.c_str());
10676 5 : OGRErr eErr = SQLCommand(hDB, pszSQL);
10677 5 : sqlite3_free(pszSQL);
10678 5 : if (eErr != OGRERR_NONE)
10679 : {
10680 0 : failureReason = ("Could not insert mapping table " +
10681 0 : osMappingTableName + " into gpkg_extensions")
10682 0 : .c_str();
10683 0 : return false;
10684 : }
10685 :
10686 15 : pszSQL = sqlite3_mprintf(
10687 : "INSERT INTO gpkgext_relations "
10688 : "(base_table_name,base_primary_column,related_table_name,related_"
10689 : "primary_column,relation_name,mapping_table_name) "
10690 : "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
10691 5 : osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
10692 5 : osRightTableName.c_str(), aosRightTableFields[0].c_str(),
10693 : osRelatedTableType.c_str(), osMappingTableName.c_str());
10694 5 : eErr = SQLCommand(hDB, pszSQL);
10695 5 : sqlite3_free(pszSQL);
10696 5 : if (eErr != OGRERR_NONE)
10697 : {
10698 0 : failureReason = "Could not insert relationship into gpkgext_relations";
10699 0 : return false;
10700 : }
10701 :
10702 5 : ClearCachedRelationships();
10703 5 : LoadRelationships();
10704 5 : return true;
10705 : }
10706 :
10707 : /************************************************************************/
10708 : /* DeleteRelationship() */
10709 : /************************************************************************/
10710 :
10711 4 : bool GDALGeoPackageDataset::DeleteRelationship(const std::string &name,
10712 : std::string &failureReason)
10713 : {
10714 4 : if (eAccess != GA_Update)
10715 : {
10716 0 : CPLError(CE_Failure, CPLE_NotSupported,
10717 : "DeleteRelationship() not supported on read-only dataset");
10718 0 : return false;
10719 : }
10720 :
10721 : // ensure relationships are up to date before we try to remove one
10722 4 : ClearCachedRelationships();
10723 4 : LoadRelationships();
10724 :
10725 8 : std::string osMappingTableName;
10726 : {
10727 4 : const GDALRelationship *poRelationship = GetRelationship(name);
10728 4 : if (poRelationship == nullptr)
10729 : {
10730 1 : failureReason = "Could not find relationship with name " + name;
10731 1 : return false;
10732 : }
10733 :
10734 3 : osMappingTableName = poRelationship->GetMappingTableName();
10735 : }
10736 :
10737 : // DeleteLayerCommon will delete existing relationship objects, so we can't
10738 : // refer to poRelationship or any of its members previously obtained here
10739 3 : if (DeleteLayerCommon(osMappingTableName.c_str()) != OGRERR_NONE)
10740 : {
10741 : failureReason =
10742 0 : "Could not remove mapping layer name " + osMappingTableName;
10743 :
10744 : // relationships may have been left in an inconsistent state -- reload
10745 : // them now
10746 0 : ClearCachedRelationships();
10747 0 : LoadRelationships();
10748 0 : return false;
10749 : }
10750 :
10751 3 : ClearCachedRelationships();
10752 3 : LoadRelationships();
10753 3 : return true;
10754 : }
10755 :
10756 : /************************************************************************/
10757 : /* UpdateRelationship() */
10758 : /************************************************************************/
10759 :
10760 6 : bool GDALGeoPackageDataset::UpdateRelationship(
10761 : std::unique_ptr<GDALRelationship> &&relationship,
10762 : std::string &failureReason)
10763 : {
10764 6 : if (eAccess != GA_Update)
10765 : {
10766 0 : CPLError(CE_Failure, CPLE_NotSupported,
10767 : "UpdateRelationship() not supported on read-only dataset");
10768 0 : return false;
10769 : }
10770 :
10771 : // ensure relationships are up to date before we try to update one
10772 6 : ClearCachedRelationships();
10773 6 : LoadRelationships();
10774 :
10775 6 : const std::string &osRelationshipName = relationship->GetName();
10776 6 : const std::string &osLeftTableName = relationship->GetLeftTableName();
10777 6 : const std::string &osRightTableName = relationship->GetRightTableName();
10778 6 : const std::string &osMappingTableName = relationship->GetMappingTableName();
10779 6 : const auto &aosLeftTableFields = relationship->GetLeftTableFields();
10780 6 : const auto &aosRightTableFields = relationship->GetRightTableFields();
10781 :
10782 : // sanity checks
10783 : {
10784 : const GDALRelationship *poExistingRelationship =
10785 6 : GetRelationship(osRelationshipName);
10786 6 : if (poExistingRelationship == nullptr)
10787 : {
10788 : failureReason =
10789 1 : "The relationship should already exist to be updated";
10790 1 : return false;
10791 : }
10792 :
10793 5 : if (!ValidateRelationship(relationship.get(), failureReason))
10794 : {
10795 2 : return false;
10796 : }
10797 :
10798 : // we don't permit changes to the participating tables
10799 3 : if (osLeftTableName != poExistingRelationship->GetLeftTableName())
10800 : {
10801 0 : failureReason = ("Cannot change base table from " +
10802 0 : poExistingRelationship->GetLeftTableName() +
10803 0 : " to " + osLeftTableName)
10804 0 : .c_str();
10805 0 : return false;
10806 : }
10807 3 : if (osRightTableName != poExistingRelationship->GetRightTableName())
10808 : {
10809 0 : failureReason = ("Cannot change related table from " +
10810 0 : poExistingRelationship->GetRightTableName() +
10811 0 : " to " + osRightTableName)
10812 0 : .c_str();
10813 0 : return false;
10814 : }
10815 3 : if (osMappingTableName != poExistingRelationship->GetMappingTableName())
10816 : {
10817 0 : failureReason = ("Cannot change mapping table from " +
10818 0 : poExistingRelationship->GetMappingTableName() +
10819 0 : " to " + osMappingTableName)
10820 0 : .c_str();
10821 0 : return false;
10822 : }
10823 : }
10824 :
10825 6 : std::string osRelatedTableType = relationship->GetRelatedTableType();
10826 3 : if (osRelatedTableType.empty())
10827 : {
10828 0 : osRelatedTableType = "features";
10829 : }
10830 :
10831 3 : char *pszSQL = sqlite3_mprintf(
10832 : "DELETE FROM gpkgext_relations WHERE mapping_table_name='%q'",
10833 : osMappingTableName.c_str());
10834 3 : OGRErr eErr = SQLCommand(hDB, pszSQL);
10835 3 : sqlite3_free(pszSQL);
10836 3 : if (eErr != OGRERR_NONE)
10837 : {
10838 : failureReason =
10839 0 : "Could not delete old relationship from gpkgext_relations";
10840 0 : return false;
10841 : }
10842 :
10843 9 : pszSQL = sqlite3_mprintf(
10844 : "INSERT INTO gpkgext_relations "
10845 : "(base_table_name,base_primary_column,related_table_name,related_"
10846 : "primary_column,relation_name,mapping_table_name) "
10847 : "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
10848 3 : osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
10849 3 : osRightTableName.c_str(), aosRightTableFields[0].c_str(),
10850 : osRelatedTableType.c_str(), osMappingTableName.c_str());
10851 3 : eErr = SQLCommand(hDB, pszSQL);
10852 3 : sqlite3_free(pszSQL);
10853 3 : if (eErr != OGRERR_NONE)
10854 : {
10855 : failureReason =
10856 0 : "Could not insert updated relationship into gpkgext_relations";
10857 0 : return false;
10858 : }
10859 :
10860 3 : ClearCachedRelationships();
10861 3 : LoadRelationships();
10862 3 : return true;
10863 : }
10864 :
10865 : /************************************************************************/
10866 : /* GetSqliteMasterContent() */
10867 : /************************************************************************/
10868 :
10869 : const std::vector<SQLSqliteMasterContent> &
10870 2 : GDALGeoPackageDataset::GetSqliteMasterContent()
10871 : {
10872 2 : if (m_aoSqliteMasterContent.empty())
10873 : {
10874 : auto oResultTable =
10875 2 : SQLQuery(hDB, "SELECT sql, type, tbl_name FROM sqlite_master");
10876 1 : if (oResultTable)
10877 : {
10878 58 : for (int rowCnt = 0; rowCnt < oResultTable->RowCount(); ++rowCnt)
10879 : {
10880 114 : SQLSqliteMasterContent row;
10881 57 : const char *pszSQL = oResultTable->GetValue(0, rowCnt);
10882 57 : row.osSQL = pszSQL ? pszSQL : "";
10883 57 : const char *pszType = oResultTable->GetValue(1, rowCnt);
10884 57 : row.osType = pszType ? pszType : "";
10885 57 : const char *pszTableName = oResultTable->GetValue(2, rowCnt);
10886 57 : row.osTableName = pszTableName ? pszTableName : "";
10887 57 : m_aoSqliteMasterContent.emplace_back(std::move(row));
10888 : }
10889 : }
10890 : }
10891 2 : return m_aoSqliteMasterContent;
10892 : }
|