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 588 : GetTilingScheme(const char *pszName)
83 : {
84 588 : if (EQUAL(pszName, "CUSTOM"))
85 460 : 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();
144 53 : const char *pszAuthCode = oSRS.GetAuthorityCode();
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 1123 : OGRErr GDALGeoPackageDataset::SetApplicationAndUserVersionId()
192 : {
193 1123 : CPLAssert(hDB != nullptr);
194 :
195 1123 : const CPLString osPragma(CPLString().Printf("PRAGMA application_id = %u;"
196 : "PRAGMA user_version = %u",
197 : m_nApplicationId,
198 2246 : m_nUserVersion));
199 2246 : return SQLCommand(hDB, osPragma.c_str());
200 : }
201 :
202 2969 : bool GDALGeoPackageDataset::CloseDB()
203 : {
204 2969 : OGRSQLiteUnregisterSQLFunctions(m_pSQLFunctionData);
205 2969 : m_pSQLFunctionData = nullptr;
206 2969 : 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 1001 : static OGRErr GDALGPKGImportFromEPSG(OGRSpatialReference *poSpatialRef,
223 : int nEPSGCode)
224 : {
225 1001 : CPLPushErrorHandler(CPLQuietErrorHandler);
226 1001 : const OGRErr eErr = poSpatialRef->importFromEPSG(nEPSGCode);
227 1001 : CPLPopErrorHandler();
228 1001 : CPLErrorReset();
229 1001 : return eErr;
230 : }
231 :
232 : OGRSpatialReferenceRefCountedPtr
233 1456 : GDALGeoPackageDataset::GetSpatialRef(int iSrsId, bool bFallbackToEPSG,
234 : bool bEmitErrorIfNotFound)
235 : {
236 1456 : const auto oIter = m_oMapSrsIdToSrs.find(iSrsId);
237 1456 : if (oIter != m_oMapSrsIdToSrs.end())
238 : {
239 103 : return oIter->second;
240 : }
241 :
242 1353 : if (iSrsId == 0 || iSrsId == -1)
243 : {
244 121 : auto poSpatialRef = OGRSpatialReferenceRefCountedPtr::makeInstance();
245 121 : poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
246 :
247 : // See corresponding tests in GDALGeoPackageDataset::GetSrsId
248 121 : if (iSrsId == 0)
249 : {
250 31 : poSpatialRef->SetGeogCS("Undefined geographic SRS", "unknown",
251 : "unknown", SRS_WGS84_SEMIMAJOR,
252 : SRS_WGS84_INVFLATTENING);
253 : }
254 90 : else if (iSrsId == -1)
255 : {
256 90 : poSpatialRef->SetLocalCS("Undefined Cartesian SRS");
257 90 : poSpatialRef->SetLinearUnits(SRS_UL_METER, 1.0);
258 : }
259 :
260 242 : return m_oMapSrsIdToSrs.insert({iSrsId, std::move(poSpatialRef)})
261 121 : .first->second;
262 : }
263 :
264 2464 : CPLString oSQL;
265 1232 : oSQL.Printf("SELECT srs_name, definition, organization, "
266 : "organization_coordsys_id%s%s "
267 : "FROM gpkg_spatial_ref_sys WHERE "
268 : "srs_id = %d LIMIT 2",
269 1232 : m_bHasDefinition12_063 ? ", definition_12_063" : "",
270 1232 : m_bHasEpochColumn ? ", epoch" : "", iSrsId);
271 :
272 2464 : auto oResult = SQLQuery(hDB, oSQL.c_str());
273 :
274 1232 : if (!oResult || oResult->RowCount() != 1)
275 : {
276 12 : if (bFallbackToEPSG)
277 : {
278 7 : CPLDebug("GPKG",
279 : "unable to read srs_id '%d' from gpkg_spatial_ref_sys",
280 : iSrsId);
281 7 : auto poSRS = OGRSpatialReferenceRefCountedPtr::makeInstance();
282 7 : if (poSRS->importFromEPSG(iSrsId) == OGRERR_NONE)
283 : {
284 5 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
285 5 : return poSRS;
286 : }
287 : }
288 5 : else if (bEmitErrorIfNotFound)
289 : {
290 2 : CPLError(CE_Warning, CPLE_AppDefined,
291 : "unable to read srs_id '%d' from gpkg_spatial_ref_sys",
292 : iSrsId);
293 2 : m_oMapSrsIdToSrs[iSrsId] = nullptr;
294 : }
295 7 : return nullptr;
296 : }
297 :
298 1220 : const char *pszName = oResult->GetValue(0, 0);
299 1220 : if (pszName && EQUAL(pszName, "Undefined SRS"))
300 : {
301 511 : m_oMapSrsIdToSrs[iSrsId] = nullptr;
302 511 : return nullptr;
303 : }
304 709 : const char *pszWkt = oResult->GetValue(1, 0);
305 709 : if (pszWkt == nullptr)
306 0 : return nullptr;
307 709 : const char *pszOrganization = oResult->GetValue(2, 0);
308 709 : const char *pszOrganizationCoordsysID = oResult->GetValue(3, 0);
309 : const char *pszWkt2 =
310 709 : m_bHasDefinition12_063 ? oResult->GetValue(4, 0) : nullptr;
311 709 : if (pszWkt2 && !EQUAL(pszWkt2, "undefined"))
312 76 : pszWkt = pszWkt2;
313 : const char *pszCoordinateEpoch =
314 709 : m_bHasEpochColumn ? oResult->GetValue(5, 0) : nullptr;
315 : const double dfCoordinateEpoch =
316 709 : pszCoordinateEpoch ? CPLAtof(pszCoordinateEpoch) : 0.0;
317 :
318 1418 : auto poSpatialRef = OGRSpatialReferenceRefCountedPtr::makeInstance();
319 709 : poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
320 : // Try to import first from EPSG code, and then from WKT
321 709 : if (!(pszOrganization && pszOrganizationCoordsysID &&
322 709 : EQUAL(pszOrganization, "EPSG") &&
323 685 : (atoi(pszOrganizationCoordsysID) == iSrsId ||
324 4 : (dfCoordinateEpoch > 0 && strstr(pszWkt, "DYNAMIC[") == nullptr)) &&
325 685 : GDALGPKGImportFromEPSG(poSpatialRef.get(),
326 : atoi(pszOrganizationCoordsysID)) ==
327 1418 : OGRERR_NONE) &&
328 24 : poSpatialRef->importFromWkt(pszWkt) != OGRERR_NONE)
329 : {
330 0 : CPLError(CE_Warning, CPLE_AppDefined,
331 : "Unable to parse srs_id '%d' well-known text '%s'", iSrsId,
332 : pszWkt);
333 0 : m_oMapSrsIdToSrs[iSrsId] = nullptr;
334 0 : return nullptr;
335 : }
336 :
337 709 : poSpatialRef->StripTOWGS84IfKnownDatumAndAllowed();
338 709 : poSpatialRef->SetCoordinateEpoch(dfCoordinateEpoch);
339 1418 : return m_oMapSrsIdToSrs.insert({iSrsId, std::move(poSpatialRef)})
340 709 : .first->second;
341 : }
342 :
343 347 : const char *GDALGeoPackageDataset::GetSrsName(const OGRSpatialReference &oSRS)
344 : {
345 347 : const char *pszName = oSRS.GetName();
346 347 : if (pszName)
347 347 : return pszName;
348 :
349 : // Something odd. Return empty.
350 0 : return "Unnamed SRS";
351 : }
352 :
353 : /* Add the definition_12_063 column to an existing gpkg_spatial_ref_sys table */
354 7 : bool GDALGeoPackageDataset::ConvertGpkgSpatialRefSysToExtensionWkt2(
355 : bool bForceEpoch)
356 : {
357 7 : const bool bAddEpoch = (m_nUserVersion >= GPKG_1_4_VERSION || bForceEpoch);
358 : auto oResultTable = SQLQuery(
359 : hDB, "SELECT srs_name, srs_id, organization, organization_coordsys_id, "
360 14 : "definition, description FROM gpkg_spatial_ref_sys LIMIT 100000");
361 7 : if (!oResultTable)
362 0 : return false;
363 :
364 : // Temporary remove foreign key checks
365 : const GPKGTemporaryForeignKeyCheckDisabler
366 7 : oGPKGTemporaryForeignKeyCheckDisabler(this);
367 :
368 7 : bool bRet = SoftStartTransaction() == OGRERR_NONE;
369 :
370 7 : if (bRet)
371 : {
372 : std::string osSQL("CREATE TABLE gpkg_spatial_ref_sys_temp ("
373 : "srs_name TEXT NOT NULL,"
374 : "srs_id INTEGER NOT NULL PRIMARY KEY,"
375 : "organization TEXT NOT NULL,"
376 : "organization_coordsys_id INTEGER NOT NULL,"
377 : "definition TEXT NOT NULL,"
378 : "description TEXT, "
379 7 : "definition_12_063 TEXT NOT NULL");
380 7 : if (bAddEpoch)
381 6 : osSQL += ", epoch DOUBLE";
382 7 : osSQL += ")";
383 7 : bRet = SQLCommand(hDB, osSQL.c_str()) == OGRERR_NONE;
384 : }
385 :
386 7 : if (bRet)
387 : {
388 32 : for (int i = 0; bRet && i < oResultTable->RowCount(); i++)
389 : {
390 25 : const char *pszSrsName = oResultTable->GetValue(0, i);
391 25 : const char *pszSrsId = oResultTable->GetValue(1, i);
392 25 : const char *pszOrganization = oResultTable->GetValue(2, i);
393 : const char *pszOrganizationCoordsysID =
394 25 : oResultTable->GetValue(3, i);
395 25 : const char *pszDefinition = oResultTable->GetValue(4, i);
396 : if (pszSrsName == nullptr || pszSrsId == nullptr ||
397 : pszOrganization == nullptr ||
398 : pszOrganizationCoordsysID == nullptr)
399 : {
400 : // should not happen as there are NOT NULL constraints
401 : // But a database could lack such NOT NULL constraints or have
402 : // large values that would cause a memory allocation failure.
403 : }
404 25 : const char *pszDescription = oResultTable->GetValue(5, i);
405 : char *pszSQL;
406 :
407 50 : OGRSpatialReference oSRS;
408 25 : if (pszOrganization && pszOrganizationCoordsysID &&
409 25 : EQUAL(pszOrganization, "EPSG"))
410 : {
411 9 : oSRS.importFromEPSG(atoi(pszOrganizationCoordsysID));
412 : }
413 34 : if (!oSRS.IsEmpty() && pszDefinition &&
414 9 : !EQUAL(pszDefinition, "undefined"))
415 : {
416 9 : oSRS.SetFromUserInput(pszDefinition);
417 : }
418 25 : char *pszWKT2 = nullptr;
419 25 : if (!oSRS.IsEmpty())
420 : {
421 9 : const char *const apszOptionsWkt2[] = {"FORMAT=WKT2_2015",
422 : nullptr};
423 9 : oSRS.exportToWkt(&pszWKT2, apszOptionsWkt2);
424 9 : if (pszWKT2 && pszWKT2[0] == '\0')
425 : {
426 0 : CPLFree(pszWKT2);
427 0 : pszWKT2 = nullptr;
428 : }
429 : }
430 25 : if (pszWKT2 == nullptr)
431 : {
432 16 : pszWKT2 = CPLStrdup("undefined");
433 : }
434 :
435 25 : if (pszDescription)
436 : {
437 22 : pszSQL = sqlite3_mprintf(
438 : "INSERT INTO gpkg_spatial_ref_sys_temp(srs_name, srs_id, "
439 : "organization, organization_coordsys_id, definition, "
440 : "description, definition_12_063) VALUES ('%q', '%q', '%q', "
441 : "'%q', '%q', '%q', '%q')",
442 : pszSrsName, pszSrsId, pszOrganization,
443 : pszOrganizationCoordsysID, pszDefinition, pszDescription,
444 : pszWKT2);
445 : }
446 : else
447 : {
448 3 : pszSQL = sqlite3_mprintf(
449 : "INSERT INTO gpkg_spatial_ref_sys_temp(srs_name, srs_id, "
450 : "organization, organization_coordsys_id, definition, "
451 : "description, definition_12_063) VALUES ('%q', '%q', '%q', "
452 : "'%q', '%q', NULL, '%q')",
453 : pszSrsName, pszSrsId, pszOrganization,
454 : pszOrganizationCoordsysID, pszDefinition, pszWKT2);
455 : }
456 :
457 25 : CPLFree(pszWKT2);
458 25 : bRet &= SQLCommand(hDB, pszSQL) == OGRERR_NONE;
459 25 : sqlite3_free(pszSQL);
460 : }
461 : }
462 :
463 7 : if (bRet)
464 : {
465 7 : bRet =
466 7 : SQLCommand(hDB, "DROP TABLE gpkg_spatial_ref_sys") == OGRERR_NONE;
467 : }
468 7 : if (bRet)
469 : {
470 7 : bRet = SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys_temp RENAME "
471 : "TO gpkg_spatial_ref_sys") == OGRERR_NONE;
472 : }
473 7 : if (bRet)
474 : {
475 14 : bRet = OGRERR_NONE == CreateExtensionsTableIfNecessary() &&
476 7 : OGRERR_NONE == SQLCommand(hDB,
477 : "INSERT INTO gpkg_extensions "
478 : "(table_name, column_name, "
479 : "extension_name, definition, scope) "
480 : "VALUES "
481 : "('gpkg_spatial_ref_sys', "
482 : "'definition_12_063', 'gpkg_crs_wkt', "
483 : "'http://www.geopackage.org/spec120/"
484 : "#extension_crs_wkt', 'read-write')");
485 : }
486 7 : if (bRet && bAddEpoch)
487 : {
488 6 : bRet =
489 : OGRERR_NONE ==
490 6 : SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
491 : "'gpkg_crs_wkt_1_1' "
492 12 : "WHERE extension_name = 'gpkg_crs_wkt'") &&
493 : OGRERR_NONE ==
494 6 : SQLCommand(
495 : hDB,
496 : "INSERT INTO gpkg_extensions "
497 : "(table_name, column_name, extension_name, definition, "
498 : "scope) "
499 : "VALUES "
500 : "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
501 : "'http://www.geopackage.org/spec/#extension_crs_wkt', "
502 : "'read-write')");
503 : }
504 7 : if (bRet)
505 : {
506 7 : SoftCommitTransaction();
507 7 : m_bHasDefinition12_063 = true;
508 7 : if (bAddEpoch)
509 6 : m_bHasEpochColumn = true;
510 : }
511 : else
512 : {
513 0 : SoftRollbackTransaction();
514 : }
515 :
516 7 : return bRet;
517 : }
518 :
519 1100 : int GDALGeoPackageDataset::GetSrsId(const OGRSpatialReference *poSRSIn)
520 : {
521 1100 : const char *pszName = poSRSIn ? poSRSIn->GetName() : nullptr;
522 1604 : if (!poSRSIn || poSRSIn->IsEmpty() ||
523 504 : (pszName && EQUAL(pszName, "Undefined SRS")))
524 : {
525 598 : OGRErr err = OGRERR_NONE;
526 598 : const int nSRSId = SQLGetInteger(
527 : hDB,
528 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE srs_name = "
529 : "'Undefined SRS' AND organization = 'GDAL'",
530 : &err);
531 598 : if (err == OGRERR_NONE)
532 60 : return nSRSId;
533 :
534 : // The below WKT definitions are somehow questionable (using a unknown
535 : // unit). For GDAL >= 3.9, they won't be used. They will only be used
536 : // for earlier versions.
537 : const char *pszSQL;
538 : #define UNDEFINED_CRS_SRS_ID 99999
539 : static_assert(UNDEFINED_CRS_SRS_ID == FIRST_CUSTOM_SRSID - 1);
540 : #define STRINGIFY(x) #x
541 : #define XSTRINGIFY(x) STRINGIFY(x)
542 538 : if (m_bHasDefinition12_063)
543 : {
544 : /* clang-format off */
545 1 : pszSQL =
546 : "INSERT INTO gpkg_spatial_ref_sys "
547 : "(srs_name,srs_id,organization,organization_coordsys_id,"
548 : "definition, definition_12_063, description) VALUES "
549 : "('Undefined SRS'," XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ",'GDAL',"
550 : XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ","
551 : "'LOCAL_CS[\"Undefined SRS\",LOCAL_DATUM[\"unknown\",32767],"
552 : "UNIT[\"unknown\",0],AXIS[\"Easting\",EAST],"
553 : "AXIS[\"Northing\",NORTH]]',"
554 : "'ENGCRS[\"Undefined SRS\",EDATUM[\"unknown\"],CS[Cartesian,2],"
555 : "AXIS[\"easting\",east,ORDER[1],LENGTHUNIT[\"unknown\",0]],"
556 : "AXIS[\"northing\",north,ORDER[2],LENGTHUNIT[\"unknown\",0]]]',"
557 : "'Custom undefined coordinate reference system')";
558 : /* clang-format on */
559 : }
560 : else
561 : {
562 : /* clang-format off */
563 537 : pszSQL =
564 : "INSERT INTO gpkg_spatial_ref_sys "
565 : "(srs_name,srs_id,organization,organization_coordsys_id,"
566 : "definition, description) VALUES "
567 : "('Undefined SRS'," XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ",'GDAL',"
568 : XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ","
569 : "'LOCAL_CS[\"Undefined SRS\",LOCAL_DATUM[\"unknown\",32767],"
570 : "UNIT[\"unknown\",0],AXIS[\"Easting\",EAST],"
571 : "AXIS[\"Northing\",NORTH]]',"
572 : "'Custom undefined coordinate reference system')";
573 : /* clang-format on */
574 : }
575 538 : if (SQLCommand(hDB, pszSQL) == OGRERR_NONE)
576 538 : return UNDEFINED_CRS_SRS_ID;
577 : #undef UNDEFINED_CRS_SRS_ID
578 : #undef XSTRINGIFY
579 : #undef STRINGIFY
580 0 : return -1;
581 : }
582 :
583 1004 : auto poSRS = OGRSpatialReferenceRefCountedPtr::makeClone(poSRSIn);
584 502 : if (poSRS->IsGeographic() || poSRS->IsLocal())
585 : {
586 : // See corresponding tests in GDALGeoPackageDataset::GetSpatialRef
587 173 : if (pszName != nullptr && strlen(pszName) > 0)
588 : {
589 173 : if (EQUAL(pszName, "Undefined geographic SRS"))
590 2 : return 0;
591 :
592 171 : if (EQUAL(pszName, "Undefined Cartesian SRS"))
593 1 : return -1;
594 : }
595 : }
596 :
597 499 : const char *pszAuthorityName = poSRS->GetAuthorityName();
598 :
599 499 : if (pszAuthorityName == nullptr || strlen(pszAuthorityName) == 0)
600 : {
601 : // Try to force identify an EPSG code.
602 28 : poSRS->AutoIdentifyEPSG();
603 :
604 28 : pszAuthorityName = poSRS->GetAuthorityName();
605 28 : if (pszAuthorityName != nullptr && EQUAL(pszAuthorityName, "EPSG"))
606 : {
607 0 : const char *pszAuthorityCode = poSRS->GetAuthorityCode();
608 0 : if (pszAuthorityCode != nullptr && strlen(pszAuthorityCode) > 0)
609 : {
610 : /* Import 'clean' SRS */
611 0 : poSRS->importFromEPSG(atoi(pszAuthorityCode));
612 :
613 0 : pszAuthorityName = poSRS->GetAuthorityName();
614 : }
615 : }
616 :
617 28 : poSRS->SetCoordinateEpoch(poSRSIn->GetCoordinateEpoch());
618 : }
619 :
620 : // Check whether the EPSG authority code is already mapped to a
621 : // SRS ID.
622 499 : char *pszSQL = nullptr;
623 499 : int nSRSId = DEFAULT_SRID;
624 499 : int nAuthorityCode = 0;
625 499 : OGRErr err = OGRERR_NONE;
626 499 : bool bCanUseAuthorityCode = false;
627 499 : const char *const apszIsSameOptions[] = {
628 : "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES",
629 : "IGNORE_COORDINATE_EPOCH=YES", nullptr};
630 499 : if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0)
631 : {
632 471 : const char *pszAuthorityCode = poSRS->GetAuthorityCode();
633 471 : if (pszAuthorityCode)
634 : {
635 471 : if (CPLGetValueType(pszAuthorityCode) == CPL_VALUE_INTEGER)
636 : {
637 471 : nAuthorityCode = atoi(pszAuthorityCode);
638 : }
639 : else
640 : {
641 0 : CPLDebug("GPKG",
642 : "SRS has %s:%s identification, but the code not "
643 : "being an integer value cannot be stored as such "
644 : "in the database.",
645 : pszAuthorityName, pszAuthorityCode);
646 0 : pszAuthorityName = nullptr;
647 : }
648 : }
649 : }
650 :
651 970 : if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
652 471 : poSRSIn->GetCoordinateEpoch() == 0)
653 : {
654 : pszSQL =
655 466 : sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
656 : "upper(organization) = upper('%q') AND "
657 : "organization_coordsys_id = %d",
658 : pszAuthorityName, nAuthorityCode);
659 :
660 466 : nSRSId = SQLGetInteger(hDB, pszSQL, &err);
661 466 : sqlite3_free(pszSQL);
662 :
663 : // Got a match? Return it!
664 466 : if (OGRERR_NONE == err)
665 : {
666 148 : auto poRefSRS = GetSpatialRef(nSRSId);
667 : bool bOK =
668 148 : (poRefSRS == nullptr ||
669 149 : poSRS->IsSame(poRefSRS.get(), apszIsSameOptions) ||
670 1 : !CPLTestBool(CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")));
671 148 : if (bOK)
672 : {
673 147 : return nSRSId;
674 : }
675 : else
676 : {
677 1 : CPLError(CE_Warning, CPLE_AppDefined,
678 : "Passed SRS uses %s:%d identification, but its "
679 : "definition is not compatible with the "
680 : "definition of that object already in the database. "
681 : "Registering it as a new entry into the database.",
682 : pszAuthorityName, nAuthorityCode);
683 1 : pszAuthorityName = nullptr;
684 1 : nAuthorityCode = 0;
685 : }
686 : }
687 : }
688 :
689 : // Translate SRS to WKT.
690 352 : CPLCharUniquePtr pszWKT1;
691 352 : CPLCharUniquePtr pszWKT2_2015;
692 352 : CPLCharUniquePtr pszWKT2_2019;
693 352 : const char *const apszOptionsWkt1[] = {"FORMAT=WKT1_GDAL", nullptr};
694 352 : const char *const apszOptionsWkt2_2015[] = {"FORMAT=WKT2_2015", nullptr};
695 352 : const char *const apszOptionsWkt2_2019[] = {"FORMAT=WKT2_2019", nullptr};
696 :
697 704 : std::string osEpochTest;
698 352 : if (poSRSIn->GetCoordinateEpoch() > 0 && m_bHasEpochColumn)
699 : {
700 : osEpochTest =
701 3 : CPLSPrintf(" AND epoch = %.17g", poSRSIn->GetCoordinateEpoch());
702 : }
703 :
704 695 : if (!(poSRS->IsGeographic() && poSRS->GetAxesCount() == 3) &&
705 343 : !poSRS->IsDerivedGeographic())
706 : {
707 686 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
708 343 : char *pszTmp = nullptr;
709 343 : poSRS->exportToWkt(&pszTmp, apszOptionsWkt1);
710 343 : pszWKT1.reset(pszTmp);
711 343 : if (pszWKT1 && pszWKT1.get()[0] == '\0')
712 : {
713 0 : pszWKT1.reset();
714 : }
715 : }
716 : {
717 704 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
718 352 : char *pszTmp = nullptr;
719 352 : poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2015);
720 352 : pszWKT2_2015.reset(pszTmp);
721 352 : if (pszWKT2_2015 && pszWKT2_2015.get()[0] == '\0')
722 : {
723 0 : pszWKT2_2015.reset();
724 : }
725 : }
726 : {
727 352 : char *pszTmp = nullptr;
728 352 : poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2019);
729 352 : pszWKT2_2019.reset(pszTmp);
730 352 : if (pszWKT2_2019 && pszWKT2_2019.get()[0] == '\0')
731 : {
732 0 : pszWKT2_2019.reset();
733 : }
734 : }
735 :
736 352 : if (!pszWKT1 && !pszWKT2_2015 && !pszWKT2_2019)
737 : {
738 0 : return DEFAULT_SRID;
739 : }
740 :
741 352 : if (poSRSIn->GetCoordinateEpoch() == 0 || m_bHasEpochColumn)
742 : {
743 : // Search if there is already an existing entry with this WKT
744 349 : if (m_bHasDefinition12_063 && (pszWKT2_2015 || pszWKT2_2019))
745 : {
746 42 : if (pszWKT1)
747 : {
748 144 : pszSQL = sqlite3_mprintf(
749 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
750 : "(definition = '%q' OR definition_12_063 IN ('%q','%q'))%s",
751 : pszWKT1.get(),
752 72 : pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
753 72 : pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
754 : osEpochTest.c_str());
755 : }
756 : else
757 : {
758 24 : pszSQL = sqlite3_mprintf(
759 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
760 : "definition_12_063 IN ('%q', '%q')%s",
761 12 : pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
762 12 : pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
763 : osEpochTest.c_str());
764 : }
765 : }
766 307 : else if (pszWKT1)
767 : {
768 : pszSQL =
769 304 : sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
770 : "definition = '%q'%s",
771 : pszWKT1.get(), osEpochTest.c_str());
772 : }
773 : else
774 : {
775 3 : pszSQL = nullptr;
776 : }
777 349 : if (pszSQL)
778 : {
779 346 : nSRSId = SQLGetInteger(hDB, pszSQL, &err);
780 346 : sqlite3_free(pszSQL);
781 346 : if (OGRERR_NONE == err)
782 : {
783 5 : return nSRSId;
784 : }
785 : }
786 : }
787 :
788 668 : if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
789 321 : poSRSIn->GetCoordinateEpoch() == 0)
790 : {
791 317 : bool bTryToReuseSRSId = true;
792 317 : if (EQUAL(pszAuthorityName, "EPSG"))
793 : {
794 632 : OGRSpatialReference oSRS_EPSG;
795 316 : if (GDALGPKGImportFromEPSG(&oSRS_EPSG, nAuthorityCode) ==
796 : OGRERR_NONE)
797 : {
798 317 : if (!poSRS->IsSame(&oSRS_EPSG, apszIsSameOptions) &&
799 1 : CPLTestBool(
800 : CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")))
801 : {
802 1 : bTryToReuseSRSId = false;
803 1 : CPLError(
804 : CE_Warning, CPLE_AppDefined,
805 : "Passed SRS uses %s:%d identification, but its "
806 : "definition is not compatible with the "
807 : "official definition of the object. "
808 : "Registering it as a non-%s entry into the database.",
809 : pszAuthorityName, nAuthorityCode, pszAuthorityName);
810 1 : pszAuthorityName = nullptr;
811 1 : nAuthorityCode = 0;
812 : }
813 : }
814 : }
815 317 : if (bTryToReuseSRSId)
816 : {
817 : // No match, but maybe we can use the nAuthorityCode as the nSRSId?
818 316 : pszSQL = sqlite3_mprintf(
819 : "SELECT Count(*) FROM gpkg_spatial_ref_sys WHERE "
820 : "srs_id = %d",
821 : nAuthorityCode);
822 :
823 : // Yep, we can!
824 316 : if (SQLGetInteger(hDB, pszSQL, nullptr) == 0)
825 315 : bCanUseAuthorityCode = true;
826 316 : sqlite3_free(pszSQL);
827 : }
828 : }
829 :
830 347 : bool bConvertGpkgSpatialRefSysToExtensionWkt2 = false;
831 347 : bool bForceEpoch = false;
832 350 : if (!m_bHasDefinition12_063 && pszWKT1 == nullptr &&
833 3 : (pszWKT2_2015 != nullptr || pszWKT2_2019 != nullptr))
834 : {
835 3 : bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
836 : }
837 :
838 : // Add epoch column if needed
839 347 : if (poSRSIn->GetCoordinateEpoch() > 0 && !m_bHasEpochColumn)
840 : {
841 3 : if (m_bHasDefinition12_063)
842 : {
843 0 : if (SoftStartTransaction() != OGRERR_NONE)
844 0 : return DEFAULT_SRID;
845 0 : if (SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys "
846 0 : "ADD COLUMN epoch DOUBLE") != OGRERR_NONE ||
847 0 : SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
848 : "'gpkg_crs_wkt_1_1' "
849 : "WHERE extension_name = 'gpkg_crs_wkt'") !=
850 0 : OGRERR_NONE ||
851 0 : SQLCommand(
852 : hDB,
853 : "INSERT INTO gpkg_extensions "
854 : "(table_name, column_name, extension_name, definition, "
855 : "scope) "
856 : "VALUES "
857 : "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
858 : "'http://www.geopackage.org/spec/#extension_crs_wkt', "
859 : "'read-write')") != OGRERR_NONE)
860 : {
861 0 : SoftRollbackTransaction();
862 0 : return DEFAULT_SRID;
863 : }
864 :
865 0 : if (SoftCommitTransaction() != OGRERR_NONE)
866 0 : return DEFAULT_SRID;
867 :
868 0 : m_bHasEpochColumn = true;
869 : }
870 : else
871 : {
872 3 : bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
873 3 : bForceEpoch = true;
874 : }
875 : }
876 :
877 353 : if (bConvertGpkgSpatialRefSysToExtensionWkt2 &&
878 6 : !ConvertGpkgSpatialRefSysToExtensionWkt2(bForceEpoch))
879 : {
880 0 : return DEFAULT_SRID;
881 : }
882 :
883 : // Reuse the authority code number as SRS_ID if we can
884 347 : if (bCanUseAuthorityCode)
885 : {
886 315 : nSRSId = nAuthorityCode;
887 : }
888 : // Otherwise, generate a new SRS_ID number (max + 1)
889 : else
890 : {
891 : // Get the current maximum srid in the srs table.
892 32 : const int nMaxSRSId = SQLGetInteger(
893 : hDB, "SELECT MAX(srs_id) FROM gpkg_spatial_ref_sys", nullptr);
894 32 : nSRSId = std::max(FIRST_CUSTOM_SRSID, nMaxSRSId + 1);
895 : }
896 :
897 694 : std::string osEpochColumn;
898 347 : std::string osEpochVal;
899 347 : if (poSRSIn->GetCoordinateEpoch() > 0)
900 : {
901 5 : osEpochColumn = ", epoch";
902 5 : osEpochVal = CPLSPrintf(", %.17g", poSRSIn->GetCoordinateEpoch());
903 : }
904 :
905 : // Add new SRS row to gpkg_spatial_ref_sys.
906 347 : if (m_bHasDefinition12_063)
907 : {
908 : // Force WKT2_2019 when we have a dynamic CRS and coordinate epoch
909 45 : const char *pszWKT2 = poSRSIn->IsDynamic() &&
910 10 : poSRSIn->GetCoordinateEpoch() > 0 &&
911 1 : pszWKT2_2019
912 1 : ? pszWKT2_2019.get()
913 44 : : pszWKT2_2015 ? pszWKT2_2015.get()
914 97 : : pszWKT2_2019.get();
915 :
916 45 : if (pszAuthorityName != nullptr && nAuthorityCode > 0)
917 : {
918 99 : pszSQL = sqlite3_mprintf(
919 : "INSERT INTO gpkg_spatial_ref_sys "
920 : "(srs_name,srs_id,organization,organization_coordsys_id,"
921 : "definition, definition_12_063%s) VALUES "
922 : "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
923 33 : osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId,
924 : pszAuthorityName, nAuthorityCode,
925 62 : pszWKT1 ? pszWKT1.get() : "undefined",
926 : pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
927 : }
928 : else
929 : {
930 36 : pszSQL = sqlite3_mprintf(
931 : "INSERT INTO gpkg_spatial_ref_sys "
932 : "(srs_name,srs_id,organization,organization_coordsys_id,"
933 : "definition, definition_12_063%s) VALUES "
934 : "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
935 12 : osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId, "NONE",
936 21 : nSRSId, pszWKT1 ? pszWKT1.get() : "undefined",
937 : pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
938 : }
939 : }
940 : else
941 : {
942 302 : if (pszAuthorityName != nullptr && nAuthorityCode > 0)
943 : {
944 574 : pszSQL = sqlite3_mprintf(
945 : "INSERT INTO gpkg_spatial_ref_sys "
946 : "(srs_name,srs_id,organization,organization_coordsys_id,"
947 : "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
948 287 : GetSrsName(*poSRS), nSRSId, pszAuthorityName, nAuthorityCode,
949 574 : pszWKT1 ? pszWKT1.get() : "undefined");
950 : }
951 : else
952 : {
953 30 : pszSQL = sqlite3_mprintf(
954 : "INSERT INTO gpkg_spatial_ref_sys "
955 : "(srs_name,srs_id,organization,organization_coordsys_id,"
956 : "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
957 15 : GetSrsName(*poSRS), nSRSId, "NONE", nSRSId,
958 30 : pszWKT1 ? pszWKT1.get() : "undefined");
959 : }
960 : }
961 :
962 : // Add new row to gpkg_spatial_ref_sys.
963 347 : CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
964 :
965 : // Free everything that was allocated.
966 347 : sqlite3_free(pszSQL);
967 :
968 347 : return nSRSId;
969 : }
970 :
971 : /************************************************************************/
972 : /* ~GDALGeoPackageDataset() */
973 : /************************************************************************/
974 :
975 5916 : GDALGeoPackageDataset::~GDALGeoPackageDataset()
976 : {
977 2958 : GDALGeoPackageDataset::Close();
978 5916 : }
979 :
980 : /************************************************************************/
981 : /* Close() */
982 : /************************************************************************/
983 :
984 4957 : CPLErr GDALGeoPackageDataset::Close(GDALProgressFunc, void *)
985 : {
986 4957 : CPLErr eErr = CE_None;
987 4957 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
988 : {
989 1725 : if (eAccess == GA_Update && m_poParentDS == nullptr &&
990 4683 : !m_osRasterTable.empty() && !m_bGeoTransformValid)
991 : {
992 3 : CPLError(CE_Failure, CPLE_AppDefined,
993 : "Raster table %s not correctly initialized due to missing "
994 : "call to SetGeoTransform()",
995 : m_osRasterTable.c_str());
996 : }
997 :
998 5897 : if (!IsMarkedSuppressOnClose() &&
999 2939 : GDALGeoPackageDataset::FlushCache(true) != CE_None)
1000 : {
1001 7 : eErr = CE_Failure;
1002 : }
1003 :
1004 : // Destroy bands now since we don't want
1005 : // GDALGPKGMBTilesLikeRasterBand::FlushCache() to run after dataset
1006 : // destruction
1007 4806 : for (int i = 0; i < nBands; i++)
1008 1848 : delete papoBands[i];
1009 2958 : nBands = 0;
1010 2958 : CPLFree(papoBands);
1011 2958 : papoBands = nullptr;
1012 :
1013 : // Destroy overviews before cleaning m_hTempDB as they could still
1014 : // need it
1015 2958 : m_apoOverviewDS.clear();
1016 :
1017 2958 : if (m_poParentDS)
1018 : {
1019 330 : hDB = nullptr;
1020 : }
1021 :
1022 2958 : m_apoLayers.clear();
1023 :
1024 2958 : m_oMapSrsIdToSrs.clear();
1025 :
1026 2958 : if (!CloseDB())
1027 0 : eErr = CE_Failure;
1028 :
1029 2958 : if (OGRSQLiteBaseDataSource::Close() != CE_None)
1030 0 : eErr = CE_Failure;
1031 : }
1032 4957 : return eErr;
1033 : }
1034 :
1035 : /************************************************************************/
1036 : /* ICanIWriteBlock() */
1037 : /************************************************************************/
1038 :
1039 5699 : bool GDALGeoPackageDataset::ICanIWriteBlock()
1040 : {
1041 5699 : if (!GetUpdate())
1042 : {
1043 0 : CPLError(
1044 : CE_Failure, CPLE_NotSupported,
1045 : "IWriteBlock() not supported on dataset opened in read-only mode");
1046 0 : return false;
1047 : }
1048 :
1049 5699 : if (m_pabyCachedTiles == nullptr)
1050 : {
1051 0 : return false;
1052 : }
1053 :
1054 5699 : if (!m_bGeoTransformValid || m_nSRID == UNKNOWN_SRID)
1055 : {
1056 0 : CPLError(CE_Failure, CPLE_NotSupported,
1057 : "IWriteBlock() not supported if georeferencing not set");
1058 0 : return false;
1059 : }
1060 5699 : return true;
1061 : }
1062 :
1063 : /************************************************************************/
1064 : /* IRasterIO() */
1065 : /************************************************************************/
1066 :
1067 137 : CPLErr GDALGeoPackageDataset::IRasterIO(
1068 : GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
1069 : void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
1070 : int nBandCount, BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
1071 : GSpacing nLineSpace, GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
1072 :
1073 : {
1074 137 : CPLErr eErr = OGRSQLiteBaseDataSource::IRasterIO(
1075 : eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
1076 : eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace, nBandSpace,
1077 : psExtraArg);
1078 :
1079 : // If writing all bands, in non-shifted mode, flush all entirely written
1080 : // tiles This can avoid "stressing" the block cache with too many dirty
1081 : // blocks. Note: this logic would be useless with a per-dataset block cache.
1082 137 : if (eErr == CE_None && eRWFlag == GF_Write && nXSize == nBufXSize &&
1083 126 : nYSize == nBufYSize && nBandCount == nBands &&
1084 123 : m_nShiftXPixelsMod == 0 && m_nShiftYPixelsMod == 0)
1085 : {
1086 : auto poBand =
1087 119 : cpl::down_cast<GDALGPKGMBTilesLikeRasterBand *>(GetRasterBand(1));
1088 : int nBlockXSize, nBlockYSize;
1089 119 : poBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
1090 119 : const int nBlockXStart = DIV_ROUND_UP(nXOff, nBlockXSize);
1091 119 : const int nBlockYStart = DIV_ROUND_UP(nYOff, nBlockYSize);
1092 119 : const int nBlockXEnd = (nXOff + nXSize) / nBlockXSize;
1093 119 : const int nBlockYEnd = (nYOff + nYSize) / nBlockYSize;
1094 273 : for (int nBlockY = nBlockXStart; nBlockY < nBlockYEnd; nBlockY++)
1095 : {
1096 4371 : for (int nBlockX = nBlockYStart; nBlockX < nBlockXEnd; nBlockX++)
1097 : {
1098 : GDALRasterBlock *poBlock =
1099 4217 : poBand->AccessibleTryGetLockedBlockRef(nBlockX, nBlockY);
1100 4217 : if (poBlock)
1101 : {
1102 : // GetDirty() should be true in most situation (otherwise
1103 : // it means the block cache is under extreme pressure!)
1104 4215 : if (poBlock->GetDirty())
1105 : {
1106 : // IWriteBlock() on one band will check the dirty state
1107 : // of the corresponding blocks in other bands, to decide
1108 : // if it can call WriteTile(), so we have only to do
1109 : // that on one of the bands
1110 4215 : if (poBlock->Write() != CE_None)
1111 250 : eErr = CE_Failure;
1112 : }
1113 4215 : poBlock->DropLock();
1114 : }
1115 : }
1116 : }
1117 : }
1118 :
1119 137 : return eErr;
1120 : }
1121 :
1122 : /************************************************************************/
1123 : /* GetOGRTableLimit() */
1124 : /************************************************************************/
1125 :
1126 4967 : static int GetOGRTableLimit()
1127 : {
1128 4967 : return atoi(CPLGetConfigOption("OGR_TABLE_LIMIT", "10000"));
1129 : }
1130 :
1131 : /************************************************************************/
1132 : /* GetNameTypeMapFromSQliteMaster() */
1133 : /************************************************************************/
1134 :
1135 : const std::map<CPLString, CPLString> &
1136 1591 : GDALGeoPackageDataset::GetNameTypeMapFromSQliteMaster()
1137 : {
1138 1591 : if (!m_oMapNameToType.empty())
1139 437 : return m_oMapNameToType;
1140 :
1141 : CPLString osSQL(
1142 : "SELECT name, type FROM sqlite_master WHERE "
1143 : "type IN ('view', 'table') OR "
1144 2308 : "(name LIKE 'trigger_%_feature_count_%' AND type = 'trigger')");
1145 1154 : const int nTableLimit = GetOGRTableLimit();
1146 1154 : if (nTableLimit > 0)
1147 : {
1148 1154 : osSQL += " LIMIT ";
1149 1154 : osSQL += CPLSPrintf("%d", 1 + 3 * nTableLimit);
1150 : }
1151 :
1152 1154 : auto oResult = SQLQuery(hDB, osSQL);
1153 1154 : if (oResult)
1154 : {
1155 19171 : for (int i = 0; i < oResult->RowCount(); i++)
1156 : {
1157 18017 : const char *pszName = oResult->GetValue(0, i);
1158 18017 : const char *pszType = oResult->GetValue(1, i);
1159 18017 : m_oMapNameToType[CPLString(pszName).toupper()] = pszType;
1160 : }
1161 : }
1162 :
1163 1154 : return m_oMapNameToType;
1164 : }
1165 :
1166 : /************************************************************************/
1167 : /* RemoveTableFromSQLiteMasterCache() */
1168 : /************************************************************************/
1169 :
1170 58 : void GDALGeoPackageDataset::RemoveTableFromSQLiteMasterCache(
1171 : const char *pszTableName)
1172 : {
1173 58 : m_oMapNameToType.erase(CPLString(pszTableName).toupper());
1174 58 : }
1175 :
1176 : /************************************************************************/
1177 : /* GetUnknownExtensionsTableSpecific() */
1178 : /************************************************************************/
1179 :
1180 : const std::map<CPLString, std::vector<GPKGExtensionDesc>> &
1181 1065 : GDALGeoPackageDataset::GetUnknownExtensionsTableSpecific()
1182 : {
1183 1065 : if (m_bMapTableToExtensionsBuilt)
1184 107 : return m_oMapTableToExtensions;
1185 958 : m_bMapTableToExtensionsBuilt = true;
1186 :
1187 958 : if (!HasExtensionsTable())
1188 52 : return m_oMapTableToExtensions;
1189 :
1190 : CPLString osSQL(
1191 : "SELECT table_name, extension_name, definition, scope "
1192 : "FROM gpkg_extensions WHERE "
1193 : "table_name IS NOT NULL "
1194 : "AND extension_name IS NOT NULL "
1195 : "AND definition IS NOT NULL "
1196 : "AND scope IS NOT NULL "
1197 : "AND extension_name NOT IN ('gpkg_geom_CIRCULARSTRING', "
1198 : "'gpkg_geom_COMPOUNDCURVE', 'gpkg_geom_CURVEPOLYGON', "
1199 : "'gpkg_geom_MULTICURVE', "
1200 : "'gpkg_geom_MULTISURFACE', 'gpkg_geom_CURVE', 'gpkg_geom_SURFACE', "
1201 : "'gpkg_geom_POLYHEDRALSURFACE', 'gpkg_geom_TIN', 'gpkg_geom_TRIANGLE', "
1202 : "'gpkg_rtree_index', 'gpkg_geometry_type_trigger', "
1203 : "'gpkg_srs_id_trigger', "
1204 : "'gpkg_crs_wkt', 'gpkg_crs_wkt_1_1', 'gpkg_schema', "
1205 : "'gpkg_related_tables', 'related_tables'"
1206 : #ifdef HAVE_SPATIALITE
1207 : ", 'gdal_spatialite_computed_geom_column'"
1208 : #endif
1209 1812 : ")");
1210 906 : const int nTableLimit = GetOGRTableLimit();
1211 906 : if (nTableLimit > 0)
1212 : {
1213 906 : osSQL += " LIMIT ";
1214 906 : osSQL += CPLSPrintf("%d", 1 + 10 * nTableLimit);
1215 : }
1216 :
1217 906 : auto oResult = SQLQuery(hDB, osSQL);
1218 906 : if (oResult)
1219 : {
1220 1725 : for (int i = 0; i < oResult->RowCount(); i++)
1221 : {
1222 819 : const char *pszTableName = oResult->GetValue(0, i);
1223 819 : const char *pszExtensionName = oResult->GetValue(1, i);
1224 819 : const char *pszDefinition = oResult->GetValue(2, i);
1225 819 : const char *pszScope = oResult->GetValue(3, i);
1226 819 : if (pszTableName && pszExtensionName && pszDefinition && pszScope)
1227 : {
1228 819 : GPKGExtensionDesc oDesc;
1229 819 : oDesc.osExtensionName = pszExtensionName;
1230 819 : oDesc.osDefinition = pszDefinition;
1231 819 : oDesc.osScope = pszScope;
1232 1638 : m_oMapTableToExtensions[CPLString(pszTableName).toupper()]
1233 819 : .push_back(std::move(oDesc));
1234 : }
1235 : }
1236 : }
1237 :
1238 906 : return m_oMapTableToExtensions;
1239 : }
1240 :
1241 : /************************************************************************/
1242 : /* GetContents() */
1243 : /************************************************************************/
1244 :
1245 : const std::map<CPLString, GPKGContentsDesc> &
1246 1046 : GDALGeoPackageDataset::GetContents()
1247 : {
1248 1046 : if (m_bMapTableToContentsBuilt)
1249 90 : return m_oMapTableToContents;
1250 956 : m_bMapTableToContentsBuilt = true;
1251 :
1252 : CPLString osSQL("SELECT table_name, data_type, identifier, "
1253 : "description, min_x, min_y, max_x, max_y "
1254 1912 : "FROM gpkg_contents");
1255 956 : const int nTableLimit = GetOGRTableLimit();
1256 956 : if (nTableLimit > 0)
1257 : {
1258 956 : osSQL += " LIMIT ";
1259 956 : osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1260 : }
1261 :
1262 956 : auto oResult = SQLQuery(hDB, osSQL);
1263 956 : if (oResult)
1264 : {
1265 2059 : for (int i = 0; i < oResult->RowCount(); i++)
1266 : {
1267 1103 : const char *pszTableName = oResult->GetValue(0, i);
1268 1103 : if (pszTableName == nullptr)
1269 0 : continue;
1270 1103 : const char *pszDataType = oResult->GetValue(1, i);
1271 1103 : const char *pszIdentifier = oResult->GetValue(2, i);
1272 1103 : const char *pszDescription = oResult->GetValue(3, i);
1273 1103 : const char *pszMinX = oResult->GetValue(4, i);
1274 1103 : const char *pszMinY = oResult->GetValue(5, i);
1275 1103 : const char *pszMaxX = oResult->GetValue(6, i);
1276 1103 : const char *pszMaxY = oResult->GetValue(7, i);
1277 1103 : GPKGContentsDesc oDesc;
1278 1103 : if (pszDataType)
1279 1103 : oDesc.osDataType = pszDataType;
1280 1103 : if (pszIdentifier)
1281 1103 : oDesc.osIdentifier = pszIdentifier;
1282 1103 : if (pszDescription)
1283 1102 : oDesc.osDescription = pszDescription;
1284 1103 : if (pszMinX)
1285 761 : oDesc.osMinX = pszMinX;
1286 1103 : if (pszMinY)
1287 761 : oDesc.osMinY = pszMinY;
1288 1103 : if (pszMaxX)
1289 761 : oDesc.osMaxX = pszMaxX;
1290 1103 : if (pszMaxY)
1291 761 : oDesc.osMaxY = pszMaxY;
1292 2206 : m_oMapTableToContents[CPLString(pszTableName).toupper()] =
1293 2206 : std::move(oDesc);
1294 : }
1295 : }
1296 :
1297 956 : return m_oMapTableToContents;
1298 : }
1299 :
1300 : /************************************************************************/
1301 : /* Open() */
1302 : /************************************************************************/
1303 :
1304 1477 : int GDALGeoPackageDataset::Open(GDALOpenInfo *poOpenInfo,
1305 : const std::string &osFilenameInZip)
1306 : {
1307 1477 : m_osFilenameInZip = osFilenameInZip;
1308 1477 : CPLAssert(m_apoLayers.empty());
1309 1477 : CPLAssert(hDB == nullptr);
1310 :
1311 1477 : SetDescription(poOpenInfo->pszFilename);
1312 2954 : CPLString osFilename(poOpenInfo->pszFilename);
1313 2954 : CPLString osSubdatasetTableName;
1314 : GByte abyHeaderLetMeHerePlease[100];
1315 1477 : const GByte *pabyHeader = poOpenInfo->pabyHeader;
1316 1477 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GPKG:"))
1317 : {
1318 313 : char **papszTokens = CSLTokenizeString2(poOpenInfo->pszFilename, ":",
1319 : CSLT_HONOURSTRINGS);
1320 313 : int nCount = CSLCount(papszTokens);
1321 313 : if (nCount < 2)
1322 : {
1323 0 : CSLDestroy(papszTokens);
1324 0 : return FALSE;
1325 : }
1326 :
1327 313 : if (nCount <= 3)
1328 : {
1329 311 : osFilename = papszTokens[1];
1330 : }
1331 : /* GPKG:C:\BLA.GPKG:foo */
1332 2 : else if (nCount == 4 && strlen(papszTokens[1]) == 1 &&
1333 2 : (papszTokens[2][0] == '/' || papszTokens[2][0] == '\\'))
1334 : {
1335 2 : osFilename = CPLString(papszTokens[1]) + ":" + papszTokens[2];
1336 : }
1337 : // GPKG:/vsicurl/http[s]://[user:passwd@]example.com[:8080]/foo.gpkg:bar
1338 0 : else if (/*nCount >= 4 && */
1339 0 : (EQUAL(papszTokens[1], "/vsicurl/http") ||
1340 0 : EQUAL(papszTokens[1], "/vsicurl/https")))
1341 : {
1342 0 : osFilename = CPLString(papszTokens[1]);
1343 0 : for (int i = 2; i < nCount - 1; i++)
1344 : {
1345 0 : osFilename += ':';
1346 0 : osFilename += papszTokens[i];
1347 : }
1348 : }
1349 313 : if (nCount >= 3)
1350 14 : osSubdatasetTableName = papszTokens[nCount - 1];
1351 :
1352 313 : CSLDestroy(papszTokens);
1353 313 : VSILFILE *fp = VSIFOpenL(osFilename, "rb");
1354 313 : if (fp != nullptr)
1355 : {
1356 313 : VSIFReadL(abyHeaderLetMeHerePlease, 1, 100, fp);
1357 313 : VSIFCloseL(fp);
1358 : }
1359 313 : pabyHeader = abyHeaderLetMeHerePlease;
1360 : }
1361 1164 : else if (poOpenInfo->pabyHeader &&
1362 1164 : STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
1363 : "SQLite format 3"))
1364 : {
1365 1157 : m_bCallUndeclareFileNotToOpen = true;
1366 1157 : GDALOpenInfoDeclareFileNotToOpen(osFilename, poOpenInfo->pabyHeader,
1367 : poOpenInfo->nHeaderBytes);
1368 : }
1369 :
1370 1477 : eAccess = poOpenInfo->eAccess;
1371 1477 : if (!m_osFilenameInZip.empty())
1372 : {
1373 2 : m_pszFilename = CPLStrdup(CPLSPrintf(
1374 : "/vsizip/{%s}/%s", osFilename.c_str(), m_osFilenameInZip.c_str()));
1375 : }
1376 : else
1377 : {
1378 1475 : m_pszFilename = CPLStrdup(osFilename);
1379 : }
1380 :
1381 1477 : if (poOpenInfo->papszOpenOptions)
1382 : {
1383 100 : CSLDestroy(papszOpenOptions);
1384 100 : papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
1385 : }
1386 :
1387 : #ifdef ENABLE_SQL_GPKG_FORMAT
1388 1477 : if (poOpenInfo->pabyHeader &&
1389 1164 : STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
1390 5 : "-- SQL GPKG") &&
1391 5 : poOpenInfo->fpL != nullptr)
1392 : {
1393 5 : if (sqlite3_open_v2(":memory:", &hDB, SQLITE_OPEN_READWRITE, nullptr) !=
1394 : SQLITE_OK)
1395 : {
1396 0 : return FALSE;
1397 : }
1398 :
1399 5 : InstallSQLFunctions();
1400 :
1401 : // Ingest the lines of the dump
1402 5 : VSIFSeekL(poOpenInfo->fpL, 0, SEEK_SET);
1403 : const char *pszLine;
1404 76 : while ((pszLine = CPLReadLineL(poOpenInfo->fpL)) != nullptr)
1405 : {
1406 71 : if (STARTS_WITH(pszLine, "--"))
1407 5 : continue;
1408 :
1409 66 : if (!SQLCheckLineIsSafe(pszLine))
1410 0 : return false;
1411 :
1412 66 : char *pszErrMsg = nullptr;
1413 66 : if (sqlite3_exec(hDB, pszLine, nullptr, nullptr, &pszErrMsg) !=
1414 : SQLITE_OK)
1415 : {
1416 0 : if (pszErrMsg)
1417 0 : CPLDebug("SQLITE", "Error %s", pszErrMsg);
1418 : }
1419 66 : sqlite3_free(pszErrMsg);
1420 5 : }
1421 : }
1422 :
1423 1472 : else if (pabyHeader != nullptr)
1424 : #endif
1425 : {
1426 1472 : if (poOpenInfo->fpL)
1427 : {
1428 : // See above comment about -wal locking for the importance of
1429 : // closing that file, prior to calling sqlite3_open()
1430 1040 : VSIFCloseL(poOpenInfo->fpL);
1431 1040 : poOpenInfo->fpL = nullptr;
1432 : }
1433 :
1434 : /* See if we can open the SQLite database */
1435 1472 : if (!OpenOrCreateDB(GetUpdate() ? SQLITE_OPEN_READWRITE
1436 : : SQLITE_OPEN_READONLY))
1437 3 : return FALSE;
1438 :
1439 1469 : memcpy(&m_nApplicationId, pabyHeader + knApplicationIdPos, 4);
1440 1469 : m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
1441 1469 : memcpy(&m_nUserVersion, pabyHeader + knUserVersionPos, 4);
1442 1469 : m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
1443 1469 : if (m_nApplicationId == GP10_APPLICATION_ID)
1444 : {
1445 9 : CPLDebug("GPKG", "GeoPackage v1.0");
1446 : }
1447 1460 : else if (m_nApplicationId == GP11_APPLICATION_ID)
1448 : {
1449 2 : CPLDebug("GPKG", "GeoPackage v1.1");
1450 : }
1451 1458 : else if (m_nApplicationId == GPKG_APPLICATION_ID &&
1452 1454 : m_nUserVersion >= GPKG_1_2_VERSION)
1453 : {
1454 1452 : CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
1455 1452 : (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
1456 : }
1457 : }
1458 :
1459 : /* Requirement 6: The SQLite PRAGMA integrity_check SQL command SHALL return
1460 : * “ok” */
1461 : /* http://opengis.github.io/geopackage/#_file_integrity */
1462 : /* Disable integrity check by default, since it is expensive on big files */
1463 1474 : if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_INTEGRITY_CHECK", "NO")) &&
1464 0 : OGRERR_NONE != PragmaCheck("integrity_check", "ok", 1))
1465 : {
1466 0 : CPLError(CE_Failure, CPLE_AppDefined,
1467 : "pragma integrity_check on '%s' failed", m_pszFilename);
1468 0 : return FALSE;
1469 : }
1470 :
1471 : /* Requirement 7: The SQLite PRAGMA foreign_key_check() SQL with no */
1472 : /* parameter value SHALL return an empty result set */
1473 : /* http://opengis.github.io/geopackage/#_file_integrity */
1474 : /* Disable the check by default, since it is to corrupt databases, and */
1475 : /* that causes issues to downstream software that can't open them. */
1476 1474 : if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_FOREIGN_KEY_CHECK", "NO")) &&
1477 0 : OGRERR_NONE != PragmaCheck("foreign_key_check", "", 0))
1478 : {
1479 0 : CPLError(CE_Failure, CPLE_AppDefined,
1480 : "pragma foreign_key_check on '%s' failed.", m_pszFilename);
1481 0 : return FALSE;
1482 : }
1483 :
1484 : /* Check for requirement metadata tables */
1485 : /* Requirement 10: gpkg_spatial_ref_sys must exist */
1486 : /* Requirement 13: gpkg_contents must exist */
1487 1474 : if (SQLGetInteger(hDB,
1488 : "SELECT COUNT(*) FROM sqlite_master WHERE "
1489 : "name IN ('gpkg_spatial_ref_sys', 'gpkg_contents') AND "
1490 : "type IN ('table', 'view')",
1491 1474 : nullptr) != 2)
1492 : {
1493 0 : CPLError(CE_Failure, CPLE_AppDefined,
1494 : "At least one of the required GeoPackage tables, "
1495 : "gpkg_spatial_ref_sys or gpkg_contents, is missing");
1496 0 : return FALSE;
1497 : }
1498 :
1499 1474 : DetectSpatialRefSysColumns();
1500 :
1501 : #ifdef ENABLE_GPKG_OGR_CONTENTS
1502 1474 : if (SQLGetInteger(hDB,
1503 : "SELECT 1 FROM sqlite_master WHERE "
1504 : "name = 'gpkg_ogr_contents' AND type = 'table'",
1505 1474 : nullptr) == 1)
1506 : {
1507 1463 : m_bHasGPKGOGRContents = true;
1508 : }
1509 : #endif
1510 :
1511 1474 : CheckUnknownExtensions();
1512 :
1513 1474 : int bRet = FALSE;
1514 1474 : bool bHasGPKGExtRelations = false;
1515 1474 : if (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR)
1516 : {
1517 1281 : m_bHasGPKGGeometryColumns =
1518 1281 : SQLGetInteger(hDB,
1519 : "SELECT 1 FROM sqlite_master WHERE "
1520 : "name = 'gpkg_geometry_columns' AND "
1521 : "type IN ('table', 'view')",
1522 1281 : nullptr) == 1;
1523 1281 : bHasGPKGExtRelations = HasGpkgextRelationsTable();
1524 : }
1525 1474 : if (m_bHasGPKGGeometryColumns)
1526 : {
1527 : /* Load layer definitions for all tables in gpkg_contents &
1528 : * gpkg_geometry_columns */
1529 : /* and non-spatial tables as well */
1530 : std::string osSQL =
1531 : "SELECT c.table_name, c.identifier, 1 as is_spatial, "
1532 : "g.column_name, g.geometry_type_name, g.z, g.m, c.min_x, c.min_y, "
1533 : "c.max_x, c.max_y, 1 AS is_in_gpkg_contents, "
1534 : "(SELECT type FROM sqlite_master WHERE lower(name) = "
1535 : "lower(c.table_name) AND type IN ('table', 'view')) AS object_type "
1536 : " FROM gpkg_geometry_columns g "
1537 : " JOIN gpkg_contents c ON (g.table_name = c.table_name)"
1538 : " WHERE "
1539 : " c.table_name <> 'ogr_empty_table' AND"
1540 : " c.data_type = 'features' "
1541 : // aspatial: Was the only method available in OGR 2.0 and 2.1
1542 : // attributes: GPKG 1.2 or later
1543 : "UNION ALL "
1544 : "SELECT table_name, identifier, 0 as is_spatial, NULL, NULL, 0, 0, "
1545 : "0 AS xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 1 AS "
1546 : "is_in_gpkg_contents, "
1547 : "(SELECT type FROM sqlite_master WHERE lower(name) = "
1548 : "lower(table_name) AND type IN ('table', 'view')) AS object_type "
1549 : " FROM gpkg_contents"
1550 1279 : " WHERE data_type IN ('aspatial', 'attributes') ";
1551 :
1552 2558 : const char *pszListAllTables = CSLFetchNameValueDef(
1553 1279 : poOpenInfo->papszOpenOptions, "LIST_ALL_TABLES", "AUTO");
1554 1279 : bool bHasASpatialOrAttributes = HasGDALAspatialExtension();
1555 1279 : if (!bHasASpatialOrAttributes)
1556 : {
1557 : auto oResultTable =
1558 : SQLQuery(hDB, "SELECT * FROM gpkg_contents WHERE "
1559 1278 : "data_type = 'attributes' LIMIT 1");
1560 1278 : bHasASpatialOrAttributes =
1561 1278 : (oResultTable && oResultTable->RowCount() == 1);
1562 : }
1563 1279 : if (bHasGPKGExtRelations)
1564 : {
1565 : osSQL += "UNION ALL "
1566 : "SELECT mapping_table_name, mapping_table_name, 0 as "
1567 : "is_spatial, NULL, NULL, 0, 0, 0 AS "
1568 : "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
1569 : "is_in_gpkg_contents, 'table' AS object_type "
1570 : "FROM gpkgext_relations WHERE "
1571 : "lower(mapping_table_name) NOT IN (SELECT "
1572 : "lower(table_name) FROM gpkg_contents) AND "
1573 : "EXISTS (SELECT 1 FROM sqlite_master WHERE "
1574 : "type IN ('table', 'view') AND "
1575 20 : "lower(name) = lower(mapping_table_name))";
1576 : }
1577 1279 : if (EQUAL(pszListAllTables, "YES") ||
1578 1278 : (!bHasASpatialOrAttributes && EQUAL(pszListAllTables, "AUTO")))
1579 : {
1580 : // vgpkg_ is Spatialite virtual table
1581 : osSQL +=
1582 : "UNION ALL "
1583 : "SELECT name, name, 0 as is_spatial, NULL, NULL, 0, 0, 0 AS "
1584 : "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
1585 : "is_in_gpkg_contents, type AS object_type "
1586 : "FROM sqlite_master WHERE type IN ('table', 'view') "
1587 : "AND name NOT LIKE 'gpkg_%' "
1588 : "AND name NOT LIKE 'vgpkg_%' "
1589 : "AND name NOT LIKE 'rtree_%' AND name NOT LIKE 'sqlite_%' "
1590 : // Avoid reading those views from simple_sewer_features.gpkg
1591 : "AND name NOT IN ('st_spatial_ref_sys', 'spatial_ref_sys', "
1592 : "'st_geometry_columns', 'geometry_columns') "
1593 : "AND lower(name) NOT IN (SELECT lower(table_name) FROM "
1594 1209 : "gpkg_contents)";
1595 1209 : if (bHasGPKGExtRelations)
1596 : {
1597 : osSQL += " AND lower(name) NOT IN (SELECT "
1598 : "lower(mapping_table_name) FROM "
1599 15 : "gpkgext_relations)";
1600 : }
1601 : }
1602 1279 : const int nTableLimit = GetOGRTableLimit();
1603 1279 : if (nTableLimit > 0)
1604 : {
1605 1279 : osSQL += " LIMIT ";
1606 1279 : osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1607 : }
1608 :
1609 1279 : auto oResult = SQLQuery(hDB, osSQL.c_str());
1610 1279 : if (!oResult)
1611 : {
1612 0 : return FALSE;
1613 : }
1614 :
1615 1279 : if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
1616 : {
1617 1 : CPLError(CE_Warning, CPLE_AppDefined,
1618 : "File has more than %d vector tables. "
1619 : "Limiting to first %d (can be overridden with "
1620 : "OGR_TABLE_LIMIT config option)",
1621 : nTableLimit, nTableLimit);
1622 1 : oResult->LimitRowCount(nTableLimit);
1623 : }
1624 :
1625 1279 : if (oResult->RowCount() > 0)
1626 : {
1627 1162 : bRet = TRUE;
1628 :
1629 1162 : m_apoLayers.reserve(oResult->RowCount());
1630 :
1631 2324 : std::map<std::string, int> oMapTableRefCount;
1632 4552 : for (int i = 0; i < oResult->RowCount(); i++)
1633 : {
1634 3390 : const char *pszTableName = oResult->GetValue(0, i);
1635 3390 : if (pszTableName == nullptr)
1636 0 : continue;
1637 3390 : if (++oMapTableRefCount[pszTableName] == 2)
1638 : {
1639 : // This should normally not happen if all constraints are
1640 : // properly set
1641 2 : CPLError(CE_Warning, CPLE_AppDefined,
1642 : "Table %s appearing several times in "
1643 : "gpkg_contents and/or gpkg_geometry_columns",
1644 : pszTableName);
1645 : }
1646 : }
1647 :
1648 2324 : std::set<std::string> oExistingLayers;
1649 4552 : for (int i = 0; i < oResult->RowCount(); i++)
1650 : {
1651 3390 : const char *pszTableName = oResult->GetValue(0, i);
1652 3390 : if (pszTableName == nullptr)
1653 2 : continue;
1654 : const bool bTableHasSeveralGeomColumns =
1655 3390 : oMapTableRefCount[pszTableName] > 1;
1656 3390 : bool bIsSpatial = CPL_TO_BOOL(oResult->GetValueAsInteger(2, i));
1657 3390 : const char *pszGeomColName = oResult->GetValue(3, i);
1658 3390 : const char *pszGeomType = oResult->GetValue(4, i);
1659 3390 : const char *pszZ = oResult->GetValue(5, i);
1660 3390 : const char *pszM = oResult->GetValue(6, i);
1661 : bool bIsInGpkgContents =
1662 3390 : CPL_TO_BOOL(oResult->GetValueAsInteger(11, i));
1663 3390 : if (!bIsInGpkgContents)
1664 46 : m_bNonSpatialTablesNonRegisteredInGpkgContentsFound = true;
1665 3390 : const char *pszObjectType = oResult->GetValue(12, i);
1666 3390 : if (pszObjectType == nullptr ||
1667 3389 : !(EQUAL(pszObjectType, "table") ||
1668 21 : EQUAL(pszObjectType, "view")))
1669 : {
1670 1 : CPLError(CE_Warning, CPLE_AppDefined,
1671 : "Table/view %s is referenced in gpkg_contents, "
1672 : "but does not exist",
1673 : pszTableName);
1674 1 : continue;
1675 : }
1676 : // Non-standard and undocumented behavior:
1677 : // if the same table appears to have several geometry columns,
1678 : // handle it for now as multiple layers named
1679 : // "table_name (geom_col_name)"
1680 : // The way we handle that might change in the future (e.g
1681 : // could be a single layer with multiple geometry columns)
1682 : std::string osLayerNameWithGeomColName =
1683 7196 : pszGeomColName ? std::string(pszTableName) + " (" +
1684 : pszGeomColName + ')'
1685 6778 : : std::string(pszTableName);
1686 3389 : if (cpl::contains(oExistingLayers, osLayerNameWithGeomColName))
1687 1 : continue;
1688 3388 : oExistingLayers.insert(osLayerNameWithGeomColName);
1689 : const std::string osLayerName =
1690 : bTableHasSeveralGeomColumns
1691 3 : ? std::move(osLayerNameWithGeomColName)
1692 6779 : : std::string(pszTableName);
1693 : auto poLayer = std::make_unique<OGRGeoPackageTableLayer>(
1694 6776 : this, osLayerName.c_str());
1695 3388 : bool bHasZ = pszZ && atoi(pszZ) > 0;
1696 3388 : bool bHasM = pszM && atoi(pszM) > 0;
1697 3388 : if (pszGeomType && EQUAL(pszGeomType, "GEOMETRY"))
1698 : {
1699 726 : if (pszZ && atoi(pszZ) == 2)
1700 14 : bHasZ = false;
1701 726 : if (pszM && atoi(pszM) == 2)
1702 6 : bHasM = false;
1703 : }
1704 3388 : poLayer->SetOpeningParameters(
1705 : pszTableName, pszObjectType, bIsInGpkgContents, bIsSpatial,
1706 : pszGeomColName, pszGeomType, bHasZ, bHasM);
1707 3388 : m_apoLayers.push_back(std::move(poLayer));
1708 : }
1709 : }
1710 : }
1711 :
1712 1474 : bool bHasTileMatrixSet = false;
1713 1474 : if (poOpenInfo->nOpenFlags & GDAL_OF_RASTER)
1714 : {
1715 675 : bHasTileMatrixSet = SQLGetInteger(hDB,
1716 : "SELECT 1 FROM sqlite_master WHERE "
1717 : "name = 'gpkg_tile_matrix_set' AND "
1718 : "type IN ('table', 'view')",
1719 : nullptr) == 1;
1720 : }
1721 1474 : if (bHasTileMatrixSet)
1722 : {
1723 : std::string osSQL =
1724 : "SELECT c.table_name, c.identifier, c.description, c.srs_id, "
1725 : "c.min_x, c.min_y, c.max_x, c.max_y, "
1726 : "tms.min_x, tms.min_y, tms.max_x, tms.max_y, c.data_type "
1727 : "FROM gpkg_contents c JOIN gpkg_tile_matrix_set tms ON "
1728 : "c.table_name = tms.table_name WHERE "
1729 672 : "data_type IN ('tiles', '2d-gridded-coverage')";
1730 672 : if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE"))
1731 : osSubdatasetTableName =
1732 2 : CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE");
1733 672 : if (!osSubdatasetTableName.empty())
1734 : {
1735 16 : char *pszTmp = sqlite3_mprintf(" AND c.table_name='%q'",
1736 : osSubdatasetTableName.c_str());
1737 16 : osSQL += pszTmp;
1738 16 : sqlite3_free(pszTmp);
1739 16 : SetPhysicalFilename(osFilename.c_str());
1740 : }
1741 672 : const int nTableLimit = GetOGRTableLimit();
1742 672 : if (nTableLimit > 0)
1743 : {
1744 672 : osSQL += " LIMIT ";
1745 672 : osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1746 : }
1747 :
1748 672 : auto oResult = SQLQuery(hDB, osSQL.c_str());
1749 672 : if (!oResult)
1750 : {
1751 0 : return FALSE;
1752 : }
1753 :
1754 672 : if (oResult->RowCount() == 0 && !osSubdatasetTableName.empty())
1755 : {
1756 1 : CPLError(CE_Failure, CPLE_AppDefined,
1757 : "Cannot find table '%s' in GeoPackage dataset",
1758 : osSubdatasetTableName.c_str());
1759 : }
1760 671 : else if (oResult->RowCount() == 1)
1761 : {
1762 285 : const char *pszTableName = oResult->GetValue(0, 0);
1763 285 : const char *pszIdentifier = oResult->GetValue(1, 0);
1764 285 : const char *pszDescription = oResult->GetValue(2, 0);
1765 285 : const char *pszSRSId = oResult->GetValue(3, 0);
1766 285 : const char *pszMinX = oResult->GetValue(4, 0);
1767 285 : const char *pszMinY = oResult->GetValue(5, 0);
1768 285 : const char *pszMaxX = oResult->GetValue(6, 0);
1769 285 : const char *pszMaxY = oResult->GetValue(7, 0);
1770 285 : const char *pszTMSMinX = oResult->GetValue(8, 0);
1771 285 : const char *pszTMSMinY = oResult->GetValue(9, 0);
1772 285 : const char *pszTMSMaxX = oResult->GetValue(10, 0);
1773 285 : const char *pszTMSMaxY = oResult->GetValue(11, 0);
1774 285 : const char *pszDataType = oResult->GetValue(12, 0);
1775 285 : if (pszTableName && pszTMSMinX && pszTMSMinY && pszTMSMaxX &&
1776 : pszTMSMaxY)
1777 : {
1778 570 : bRet = OpenRaster(
1779 : pszTableName, pszIdentifier, pszDescription,
1780 285 : pszSRSId ? atoi(pszSRSId) : 0, CPLAtof(pszTMSMinX),
1781 : CPLAtof(pszTMSMinY), CPLAtof(pszTMSMaxX),
1782 : CPLAtof(pszTMSMaxY), pszMinX, pszMinY, pszMaxX, pszMaxY,
1783 285 : EQUAL(pszDataType, "tiles"), poOpenInfo->papszOpenOptions);
1784 : }
1785 : }
1786 386 : else if (oResult->RowCount() >= 1)
1787 : {
1788 5 : bRet = TRUE;
1789 :
1790 5 : if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
1791 : {
1792 1 : CPLError(CE_Warning, CPLE_AppDefined,
1793 : "File has more than %d raster tables. "
1794 : "Limiting to first %d (can be overridden with "
1795 : "OGR_TABLE_LIMIT config option)",
1796 : nTableLimit, nTableLimit);
1797 1 : oResult->LimitRowCount(nTableLimit);
1798 : }
1799 :
1800 5 : int nSDSCount = 0;
1801 2013 : for (int i = 0; i < oResult->RowCount(); i++)
1802 : {
1803 2008 : const char *pszTableName = oResult->GetValue(0, i);
1804 2008 : const char *pszIdentifier = oResult->GetValue(1, i);
1805 2008 : if (pszTableName == nullptr)
1806 0 : continue;
1807 : m_aosSubDatasets.AddNameValue(
1808 : CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
1809 2008 : CPLSPrintf("GPKG:%s:%s", m_pszFilename, pszTableName));
1810 : m_aosSubDatasets.AddNameValue(
1811 : CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
1812 : pszIdentifier
1813 2008 : ? CPLSPrintf("%s - %s", pszTableName, pszIdentifier)
1814 4016 : : pszTableName);
1815 2008 : nSDSCount++;
1816 : }
1817 : }
1818 : }
1819 :
1820 1474 : if (!bRet && (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR))
1821 : {
1822 34 : if ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE))
1823 : {
1824 23 : bRet = TRUE;
1825 : }
1826 : else
1827 : {
1828 11 : CPLDebug("GPKG",
1829 : "This GeoPackage has no vector content and is opened "
1830 : "in read-only mode. If you open it in update mode, "
1831 : "opening will be successful.");
1832 : }
1833 : }
1834 :
1835 1474 : if (eAccess == GA_Update)
1836 : {
1837 285 : FixupWrongRTreeTrigger();
1838 285 : FixupWrongMedataReferenceColumnNameUpdate();
1839 : }
1840 :
1841 1474 : SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
1842 :
1843 1474 : return bRet;
1844 : }
1845 :
1846 : /************************************************************************/
1847 : /* DetectSpatialRefSysColumns() */
1848 : /************************************************************************/
1849 :
1850 1484 : void GDALGeoPackageDataset::DetectSpatialRefSysColumns()
1851 : {
1852 : // Detect definition_12_063 column
1853 : {
1854 1484 : sqlite3_stmt *hSQLStmt = nullptr;
1855 1484 : int rc = sqlite3_prepare_v2(
1856 : hDB, "SELECT definition_12_063 FROM gpkg_spatial_ref_sys ", -1,
1857 : &hSQLStmt, nullptr);
1858 1484 : if (rc == SQLITE_OK)
1859 : {
1860 85 : m_bHasDefinition12_063 = true;
1861 85 : sqlite3_finalize(hSQLStmt);
1862 : }
1863 : }
1864 :
1865 : // Detect epoch column
1866 1484 : if (m_bHasDefinition12_063)
1867 : {
1868 85 : sqlite3_stmt *hSQLStmt = nullptr;
1869 : int rc =
1870 85 : sqlite3_prepare_v2(hDB, "SELECT epoch FROM gpkg_spatial_ref_sys ",
1871 : -1, &hSQLStmt, nullptr);
1872 85 : if (rc == SQLITE_OK)
1873 : {
1874 76 : m_bHasEpochColumn = true;
1875 76 : sqlite3_finalize(hSQLStmt);
1876 : }
1877 : }
1878 1484 : }
1879 :
1880 : /************************************************************************/
1881 : /* FixupWrongRTreeTrigger() */
1882 : /************************************************************************/
1883 :
1884 285 : void GDALGeoPackageDataset::FixupWrongRTreeTrigger()
1885 : {
1886 : auto oResult = SQLQuery(
1887 : hDB,
1888 : "SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND "
1889 285 : "NAME LIKE 'rtree_%_update3' AND sql LIKE '% AFTER UPDATE OF % ON %'");
1890 285 : if (oResult == nullptr)
1891 0 : return;
1892 285 : if (oResult->RowCount() > 0)
1893 : {
1894 1 : CPLDebug("GPKG", "Fixing incorrect trigger(s) related to RTree");
1895 : }
1896 287 : for (int i = 0; i < oResult->RowCount(); i++)
1897 : {
1898 2 : const char *pszName = oResult->GetValue(0, i);
1899 2 : const char *pszSQL = oResult->GetValue(1, i);
1900 2 : const char *pszPtr1 = strstr(pszSQL, " AFTER UPDATE OF ");
1901 2 : if (pszPtr1)
1902 : {
1903 2 : const char *pszPtr = pszPtr1 + strlen(" AFTER UPDATE OF ");
1904 : // Skipping over geometry column name
1905 4 : while (*pszPtr == ' ')
1906 2 : pszPtr++;
1907 2 : if (pszPtr[0] == '"' || pszPtr[0] == '\'')
1908 : {
1909 1 : char chStringDelim = pszPtr[0];
1910 1 : pszPtr++;
1911 9 : while (*pszPtr != '\0' && *pszPtr != chStringDelim)
1912 : {
1913 8 : if (*pszPtr == '\\' && pszPtr[1] == chStringDelim)
1914 0 : pszPtr += 2;
1915 : else
1916 8 : pszPtr += 1;
1917 : }
1918 1 : if (*pszPtr == chStringDelim)
1919 1 : pszPtr++;
1920 : }
1921 : else
1922 : {
1923 1 : pszPtr++;
1924 8 : while (*pszPtr != ' ')
1925 7 : pszPtr++;
1926 : }
1927 2 : if (*pszPtr == ' ')
1928 : {
1929 2 : SQLCommand(hDB,
1930 4 : ("DROP TRIGGER \"" + SQLEscapeName(pszName) + "\"")
1931 : .c_str());
1932 4 : CPLString newSQL;
1933 2 : newSQL.assign(pszSQL, pszPtr1 - pszSQL);
1934 2 : newSQL += " AFTER UPDATE";
1935 2 : newSQL += pszPtr;
1936 2 : SQLCommand(hDB, newSQL);
1937 : }
1938 : }
1939 : }
1940 : }
1941 :
1942 : /************************************************************************/
1943 : /* FixupWrongMedataReferenceColumnNameUpdate() */
1944 : /************************************************************************/
1945 :
1946 285 : void GDALGeoPackageDataset::FixupWrongMedataReferenceColumnNameUpdate()
1947 : {
1948 : // Fix wrong trigger that was generated by GDAL < 2.4.0
1949 : // See https://github.com/qgis/QGIS/issues/42768
1950 : auto oResult = SQLQuery(
1951 : hDB, "SELECT sql FROM sqlite_master WHERE type = 'trigger' AND "
1952 : "NAME ='gpkg_metadata_reference_column_name_update' AND "
1953 285 : "sql LIKE '%column_nameIS%'");
1954 285 : if (oResult == nullptr)
1955 0 : return;
1956 285 : if (oResult->RowCount() == 1)
1957 : {
1958 1 : CPLDebug("GPKG", "Fixing incorrect trigger "
1959 : "gpkg_metadata_reference_column_name_update");
1960 1 : const char *pszSQL = oResult->GetValue(0, 0);
1961 : std::string osNewSQL(
1962 3 : CPLString(pszSQL).replaceAll("column_nameIS", "column_name IS"));
1963 :
1964 1 : SQLCommand(hDB,
1965 : "DROP TRIGGER gpkg_metadata_reference_column_name_update");
1966 1 : SQLCommand(hDB, osNewSQL.c_str());
1967 : }
1968 : }
1969 :
1970 : /************************************************************************/
1971 : /* ClearCachedRelationships() */
1972 : /************************************************************************/
1973 :
1974 38 : void GDALGeoPackageDataset::ClearCachedRelationships()
1975 : {
1976 38 : m_bHasPopulatedRelationships = false;
1977 38 : m_osMapRelationships.clear();
1978 38 : }
1979 :
1980 : /************************************************************************/
1981 : /* LoadRelationships() */
1982 : /************************************************************************/
1983 :
1984 106 : void GDALGeoPackageDataset::LoadRelationships() const
1985 : {
1986 106 : m_osMapRelationships.clear();
1987 :
1988 106 : std::vector<std::string> oExcludedTables;
1989 106 : if (HasGpkgextRelationsTable())
1990 : {
1991 41 : LoadRelationshipsUsingRelatedTablesExtension();
1992 :
1993 98 : for (const auto &oRelationship : m_osMapRelationships)
1994 : {
1995 : oExcludedTables.emplace_back(
1996 57 : oRelationship.second->GetMappingTableName());
1997 : }
1998 : }
1999 :
2000 : // Also load relationships defined using foreign keys (i.e. one-to-many
2001 : // relationships). Here we must exclude any relationships defined from the
2002 : // related tables extension, we don't want them included twice.
2003 106 : LoadRelationshipsFromForeignKeys(oExcludedTables);
2004 106 : m_bHasPopulatedRelationships = true;
2005 106 : }
2006 :
2007 : /************************************************************************/
2008 : /* LoadRelationshipsUsingRelatedTablesExtension() */
2009 : /************************************************************************/
2010 :
2011 41 : void GDALGeoPackageDataset::LoadRelationshipsUsingRelatedTablesExtension() const
2012 : {
2013 41 : m_osMapRelationships.clear();
2014 :
2015 : auto oResultTable = SQLQuery(
2016 41 : hDB, "SELECT base_table_name, base_primary_column, "
2017 : "related_table_name, related_primary_column, relation_name, "
2018 82 : "mapping_table_name FROM gpkgext_relations");
2019 41 : if (oResultTable && oResultTable->RowCount() > 0)
2020 : {
2021 95 : for (int i = 0; i < oResultTable->RowCount(); i++)
2022 : {
2023 58 : const char *pszBaseTableName = oResultTable->GetValue(0, i);
2024 58 : if (!pszBaseTableName)
2025 : {
2026 0 : CPLError(CE_Warning, CPLE_AppDefined,
2027 : "Could not retrieve base_table_name from "
2028 : "gpkgext_relations");
2029 1 : continue;
2030 : }
2031 58 : const char *pszBasePrimaryColumn = oResultTable->GetValue(1, i);
2032 58 : if (!pszBasePrimaryColumn)
2033 : {
2034 0 : CPLError(CE_Warning, CPLE_AppDefined,
2035 : "Could not retrieve base_primary_column from "
2036 : "gpkgext_relations");
2037 0 : continue;
2038 : }
2039 58 : const char *pszRelatedTableName = oResultTable->GetValue(2, i);
2040 58 : if (!pszRelatedTableName)
2041 : {
2042 0 : CPLError(CE_Warning, CPLE_AppDefined,
2043 : "Could not retrieve related_table_name from "
2044 : "gpkgext_relations");
2045 0 : continue;
2046 : }
2047 58 : const char *pszRelatedPrimaryColumn = oResultTable->GetValue(3, i);
2048 58 : if (!pszRelatedPrimaryColumn)
2049 : {
2050 0 : CPLError(CE_Warning, CPLE_AppDefined,
2051 : "Could not retrieve related_primary_column from "
2052 : "gpkgext_relations");
2053 0 : continue;
2054 : }
2055 58 : const char *pszRelationName = oResultTable->GetValue(4, i);
2056 58 : if (!pszRelationName)
2057 : {
2058 0 : CPLError(
2059 : CE_Warning, CPLE_AppDefined,
2060 : "Could not retrieve relation_name from gpkgext_relations");
2061 0 : continue;
2062 : }
2063 58 : const char *pszMappingTableName = oResultTable->GetValue(5, i);
2064 58 : if (!pszMappingTableName)
2065 : {
2066 0 : CPLError(CE_Warning, CPLE_AppDefined,
2067 : "Could not retrieve mapping_table_name from "
2068 : "gpkgext_relations");
2069 0 : continue;
2070 : }
2071 :
2072 : // confirm that mapping table exists
2073 : char *pszSQL =
2074 58 : sqlite3_mprintf("SELECT 1 FROM sqlite_master WHERE "
2075 : "name='%q' AND type IN ('table', 'view')",
2076 : pszMappingTableName);
2077 58 : const int nMappingTableCount = SQLGetInteger(hDB, pszSQL, nullptr);
2078 58 : sqlite3_free(pszSQL);
2079 :
2080 59 : if (nMappingTableCount < 1 &&
2081 1 : !const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
2082 1 : pszMappingTableName))
2083 : {
2084 1 : CPLError(CE_Warning, CPLE_AppDefined,
2085 : "Relationship mapping table %s does not exist",
2086 : pszMappingTableName);
2087 1 : continue;
2088 : }
2089 :
2090 : const std::string osRelationName = GenerateNameForRelationship(
2091 114 : pszBaseTableName, pszRelatedTableName, pszRelationName);
2092 :
2093 114 : std::string osType{};
2094 : // defined requirement classes -- for these types the relation name
2095 : // will be specific string value from the related tables extension.
2096 : // In this case we need to construct a unique relationship name
2097 : // based on the related tables
2098 57 : if (EQUAL(pszRelationName, "media") ||
2099 42 : EQUAL(pszRelationName, "simple_attributes") ||
2100 42 : EQUAL(pszRelationName, "features") ||
2101 20 : EQUAL(pszRelationName, "attributes") ||
2102 2 : EQUAL(pszRelationName, "tiles"))
2103 : {
2104 55 : osType = pszRelationName;
2105 : }
2106 : else
2107 : {
2108 : // user defined types default to features
2109 2 : osType = "features";
2110 : }
2111 :
2112 : auto poRelationship = std::make_unique<GDALRelationship>(
2113 : osRelationName, pszBaseTableName, pszRelatedTableName,
2114 114 : GRC_MANY_TO_MANY);
2115 :
2116 114 : poRelationship->SetLeftTableFields({pszBasePrimaryColumn});
2117 114 : poRelationship->SetRightTableFields({pszRelatedPrimaryColumn});
2118 114 : poRelationship->SetLeftMappingTableFields({"base_id"});
2119 114 : poRelationship->SetRightMappingTableFields({"related_id"});
2120 57 : poRelationship->SetMappingTableName(pszMappingTableName);
2121 57 : poRelationship->SetRelatedTableType(osType);
2122 :
2123 57 : m_osMapRelationships[osRelationName] = std::move(poRelationship);
2124 : }
2125 : }
2126 41 : }
2127 :
2128 : /************************************************************************/
2129 : /* GenerateNameForRelationship() */
2130 : /************************************************************************/
2131 :
2132 83 : std::string GDALGeoPackageDataset::GenerateNameForRelationship(
2133 : const char *pszBaseTableName, const char *pszRelatedTableName,
2134 : const char *pszType)
2135 : {
2136 : // defined requirement classes -- for these types the relation name will be
2137 : // specific string value from the related tables extension. In this case we
2138 : // need to construct a unique relationship name based on the related tables
2139 83 : if (EQUAL(pszType, "media") || EQUAL(pszType, "simple_attributes") ||
2140 55 : EQUAL(pszType, "features") || EQUAL(pszType, "attributes") ||
2141 8 : EQUAL(pszType, "tiles"))
2142 : {
2143 150 : std::ostringstream stream;
2144 : stream << pszBaseTableName << '_' << pszRelatedTableName << '_'
2145 75 : << pszType;
2146 75 : return stream.str();
2147 : }
2148 : else
2149 : {
2150 : // user defined types default to features
2151 8 : return pszType;
2152 : }
2153 : }
2154 :
2155 : /************************************************************************/
2156 : /* ValidateRelationship() */
2157 : /************************************************************************/
2158 :
2159 30 : bool GDALGeoPackageDataset::ValidateRelationship(
2160 : const GDALRelationship *poRelationship, std::string &failureReason)
2161 : {
2162 :
2163 30 : if (poRelationship->GetCardinality() !=
2164 : GDALRelationshipCardinality::GRC_MANY_TO_MANY)
2165 : {
2166 3 : failureReason = "Only many to many relationships are supported";
2167 3 : return false;
2168 : }
2169 :
2170 54 : std::string osRelatedTableType = poRelationship->GetRelatedTableType();
2171 71 : if (!osRelatedTableType.empty() && osRelatedTableType != "features" &&
2172 32 : osRelatedTableType != "media" &&
2173 20 : osRelatedTableType != "simple_attributes" &&
2174 59 : osRelatedTableType != "attributes" && osRelatedTableType != "tiles")
2175 : {
2176 : failureReason =
2177 4 : ("Related table type " + osRelatedTableType +
2178 : " is not a valid value for the GeoPackage specification. "
2179 : "Valid values are: features, media, simple_attributes, "
2180 : "attributes, tiles.")
2181 2 : .c_str();
2182 2 : return false;
2183 : }
2184 :
2185 25 : const std::string &osLeftTableName = poRelationship->GetLeftTableName();
2186 25 : OGRGeoPackageLayer *poLeftTable = cpl::down_cast<OGRGeoPackageLayer *>(
2187 25 : GetLayerByName(osLeftTableName.c_str()));
2188 25 : if (!poLeftTable)
2189 : {
2190 4 : failureReason = ("Left table " + osLeftTableName +
2191 : " is not an existing layer in the dataset")
2192 2 : .c_str();
2193 2 : return false;
2194 : }
2195 23 : const std::string &osRightTableName = poRelationship->GetRightTableName();
2196 23 : OGRGeoPackageLayer *poRightTable = cpl::down_cast<OGRGeoPackageLayer *>(
2197 23 : GetLayerByName(osRightTableName.c_str()));
2198 23 : if (!poRightTable)
2199 : {
2200 4 : failureReason = ("Right table " + osRightTableName +
2201 : " is not an existing layer in the dataset")
2202 2 : .c_str();
2203 2 : return false;
2204 : }
2205 :
2206 21 : const auto &aosLeftTableFields = poRelationship->GetLeftTableFields();
2207 21 : if (aosLeftTableFields.empty())
2208 : {
2209 1 : failureReason = "No left table fields were specified";
2210 1 : return false;
2211 : }
2212 20 : else if (aosLeftTableFields.size() > 1)
2213 : {
2214 : failureReason = "Only a single left table field is permitted for the "
2215 1 : "GeoPackage specification";
2216 1 : return false;
2217 : }
2218 : else
2219 : {
2220 : // validate left field exists
2221 38 : if (poLeftTable->GetLayerDefn()->GetFieldIndex(
2222 43 : aosLeftTableFields[0].c_str()) < 0 &&
2223 5 : !EQUAL(poLeftTable->GetFIDColumn(), aosLeftTableFields[0].c_str()))
2224 : {
2225 2 : failureReason = ("Left table field " + aosLeftTableFields[0] +
2226 2 : " does not exist in " + osLeftTableName)
2227 1 : .c_str();
2228 1 : return false;
2229 : }
2230 : }
2231 :
2232 18 : const auto &aosRightTableFields = poRelationship->GetRightTableFields();
2233 18 : if (aosRightTableFields.empty())
2234 : {
2235 1 : failureReason = "No right table fields were specified";
2236 1 : return false;
2237 : }
2238 17 : else if (aosRightTableFields.size() > 1)
2239 : {
2240 : failureReason = "Only a single right table field is permitted for the "
2241 1 : "GeoPackage specification";
2242 1 : return false;
2243 : }
2244 : else
2245 : {
2246 : // validate right field exists
2247 32 : if (poRightTable->GetLayerDefn()->GetFieldIndex(
2248 38 : aosRightTableFields[0].c_str()) < 0 &&
2249 6 : !EQUAL(poRightTable->GetFIDColumn(),
2250 : aosRightTableFields[0].c_str()))
2251 : {
2252 4 : failureReason = ("Right table field " + aosRightTableFields[0] +
2253 4 : " does not exist in " + osRightTableName)
2254 2 : .c_str();
2255 2 : return false;
2256 : }
2257 : }
2258 :
2259 14 : return true;
2260 : }
2261 :
2262 : /************************************************************************/
2263 : /* InitRaster() */
2264 : /************************************************************************/
2265 :
2266 369 : bool GDALGeoPackageDataset::InitRaster(
2267 : GDALGeoPackageDataset *poParentDS, const char *pszTableName, double dfMinX,
2268 : double dfMinY, double dfMaxX, double dfMaxY, const char *pszContentsMinX,
2269 : const char *pszContentsMinY, const char *pszContentsMaxX,
2270 : const char *pszContentsMaxY, CSLConstList papszOpenOptionsIn,
2271 : const SQLResult &oResult, int nIdxInResult)
2272 : {
2273 369 : m_osRasterTable = pszTableName;
2274 369 : m_dfTMSMinX = dfMinX;
2275 369 : m_dfTMSMaxY = dfMaxY;
2276 :
2277 : // Despite prior checking, the type might be Binary and
2278 : // SQLResultGetValue() not working properly on it
2279 369 : int nZoomLevel = atoi(oResult.GetValue(0, nIdxInResult));
2280 369 : if (nZoomLevel < 0 || nZoomLevel > 65536)
2281 : {
2282 0 : return false;
2283 : }
2284 369 : double dfPixelXSize = CPLAtof(oResult.GetValue(1, nIdxInResult));
2285 369 : double dfPixelYSize = CPLAtof(oResult.GetValue(2, nIdxInResult));
2286 369 : if (dfPixelXSize <= 0 || dfPixelYSize <= 0)
2287 : {
2288 0 : return false;
2289 : }
2290 369 : int nTileWidth = atoi(oResult.GetValue(3, nIdxInResult));
2291 369 : int nTileHeight = atoi(oResult.GetValue(4, nIdxInResult));
2292 369 : if (nTileWidth <= 0 || nTileWidth > 65536 || nTileHeight <= 0 ||
2293 : nTileHeight > 65536)
2294 : {
2295 0 : return false;
2296 : }
2297 : int nTileMatrixWidth = static_cast<int>(
2298 738 : std::min(static_cast<GIntBig>(INT_MAX),
2299 369 : CPLAtoGIntBig(oResult.GetValue(5, nIdxInResult))));
2300 : int nTileMatrixHeight = static_cast<int>(
2301 738 : std::min(static_cast<GIntBig>(INT_MAX),
2302 369 : CPLAtoGIntBig(oResult.GetValue(6, nIdxInResult))));
2303 369 : if (nTileMatrixWidth <= 0 || nTileMatrixHeight <= 0)
2304 : {
2305 0 : return false;
2306 : }
2307 :
2308 : /* Use content bounds in priority over tile_matrix_set bounds */
2309 369 : double dfGDALMinX = dfMinX;
2310 369 : double dfGDALMinY = dfMinY;
2311 369 : double dfGDALMaxX = dfMaxX;
2312 369 : double dfGDALMaxY = dfMaxY;
2313 : pszContentsMinX =
2314 369 : CSLFetchNameValueDef(papszOpenOptionsIn, "MINX", pszContentsMinX);
2315 : pszContentsMinY =
2316 369 : CSLFetchNameValueDef(papszOpenOptionsIn, "MINY", pszContentsMinY);
2317 : pszContentsMaxX =
2318 369 : CSLFetchNameValueDef(papszOpenOptionsIn, "MAXX", pszContentsMaxX);
2319 : pszContentsMaxY =
2320 369 : CSLFetchNameValueDef(papszOpenOptionsIn, "MAXY", pszContentsMaxY);
2321 369 : if (pszContentsMinX != nullptr && pszContentsMinY != nullptr &&
2322 369 : pszContentsMaxX != nullptr && pszContentsMaxY != nullptr)
2323 : {
2324 737 : if (CPLAtof(pszContentsMinX) < CPLAtof(pszContentsMaxX) &&
2325 368 : CPLAtof(pszContentsMinY) < CPLAtof(pszContentsMaxY))
2326 : {
2327 368 : dfGDALMinX = CPLAtof(pszContentsMinX);
2328 368 : dfGDALMinY = CPLAtof(pszContentsMinY);
2329 368 : dfGDALMaxX = CPLAtof(pszContentsMaxX);
2330 368 : dfGDALMaxY = CPLAtof(pszContentsMaxY);
2331 : }
2332 : else
2333 : {
2334 1 : CPLError(CE_Warning, CPLE_AppDefined,
2335 : "Illegal min_x/min_y/max_x/max_y values for %s in open "
2336 : "options and/or gpkg_contents. Using bounds of "
2337 : "gpkg_tile_matrix_set instead",
2338 : pszTableName);
2339 : }
2340 : }
2341 369 : if (dfGDALMinX >= dfGDALMaxX || dfGDALMinY >= dfGDALMaxY)
2342 : {
2343 0 : CPLError(CE_Failure, CPLE_AppDefined,
2344 : "Illegal min_x/min_y/max_x/max_y values for %s", pszTableName);
2345 0 : return false;
2346 : }
2347 :
2348 369 : int nBandCount = 0;
2349 : const char *pszBAND_COUNT =
2350 369 : CSLFetchNameValue(papszOpenOptionsIn, "BAND_COUNT");
2351 369 : if (poParentDS)
2352 : {
2353 86 : nBandCount = poParentDS->GetRasterCount();
2354 : }
2355 283 : else if (m_eDT != GDT_UInt8)
2356 : {
2357 65 : if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO") &&
2358 0 : !EQUAL(pszBAND_COUNT, "1"))
2359 : {
2360 0 : CPLError(CE_Warning, CPLE_AppDefined,
2361 : "BAND_COUNT ignored for non-Byte data");
2362 : }
2363 65 : nBandCount = 1;
2364 : }
2365 : else
2366 : {
2367 218 : if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO"))
2368 : {
2369 69 : nBandCount = atoi(pszBAND_COUNT);
2370 69 : if (nBandCount == 1)
2371 5 : GetMetadata(GDAL_MDD_IMAGE_STRUCTURE);
2372 : }
2373 : else
2374 : {
2375 149 : GetMetadata(GDAL_MDD_IMAGE_STRUCTURE);
2376 149 : nBandCount = m_nBandCountFromMetadata;
2377 149 : if (nBandCount == 1)
2378 47 : m_eTF = GPKG_TF_PNG;
2379 : }
2380 218 : if (nBandCount == 1 && !m_osTFFromMetadata.empty())
2381 : {
2382 2 : m_eTF = GDALGPKGMBTilesGetTileFormat(m_osTFFromMetadata.c_str());
2383 : }
2384 218 : if (nBandCount <= 0 || nBandCount > 4)
2385 88 : nBandCount = 4;
2386 : }
2387 :
2388 369 : return InitRaster(poParentDS, pszTableName, nZoomLevel, nBandCount, dfMinX,
2389 : dfMaxY, dfPixelXSize, dfPixelYSize, nTileWidth,
2390 : nTileHeight, nTileMatrixWidth, nTileMatrixHeight,
2391 369 : dfGDALMinX, dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
2392 : }
2393 :
2394 : /************************************************************************/
2395 : /* ComputeTileAndPixelShifts() */
2396 : /************************************************************************/
2397 :
2398 806 : bool GDALGeoPackageDataset::ComputeTileAndPixelShifts()
2399 : {
2400 : int nTileWidth, nTileHeight;
2401 806 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
2402 :
2403 : // Compute shift between GDAL origin and TileMatrixSet origin
2404 806 : const double dfShiftXPixels = (m_gt[0] - m_dfTMSMinX) / m_gt[1];
2405 806 : if (!(dfShiftXPixels / nTileWidth >= INT_MIN &&
2406 803 : dfShiftXPixels / nTileWidth < INT_MAX))
2407 : {
2408 3 : return false;
2409 : }
2410 803 : const int64_t nShiftXPixels =
2411 803 : static_cast<int64_t>(floor(0.5 + dfShiftXPixels));
2412 803 : m_nShiftXTiles = static_cast<int>(nShiftXPixels / nTileWidth);
2413 803 : if (nShiftXPixels < 0 && (nShiftXPixels % nTileWidth) != 0)
2414 11 : m_nShiftXTiles--;
2415 803 : m_nShiftXPixelsMod =
2416 803 : (static_cast<int>(nShiftXPixels % nTileWidth) + nTileWidth) %
2417 : nTileWidth;
2418 :
2419 803 : const double dfShiftYPixels = (m_gt[3] - m_dfTMSMaxY) / m_gt[5];
2420 803 : if (!(dfShiftYPixels / nTileHeight >= INT_MIN &&
2421 803 : dfShiftYPixels / nTileHeight < INT_MAX))
2422 : {
2423 1 : return false;
2424 : }
2425 802 : const int64_t nShiftYPixels =
2426 802 : static_cast<int64_t>(floor(0.5 + dfShiftYPixels));
2427 802 : m_nShiftYTiles = static_cast<int>(nShiftYPixels / nTileHeight);
2428 802 : if (nShiftYPixels < 0 && (nShiftYPixels % nTileHeight) != 0)
2429 11 : m_nShiftYTiles--;
2430 802 : m_nShiftYPixelsMod =
2431 802 : (static_cast<int>(nShiftYPixels % nTileHeight) + nTileHeight) %
2432 : nTileHeight;
2433 802 : return true;
2434 : }
2435 :
2436 : /************************************************************************/
2437 : /* AllocCachedTiles() */
2438 : /************************************************************************/
2439 :
2440 802 : bool GDALGeoPackageDataset::AllocCachedTiles()
2441 : {
2442 : int nTileWidth, nTileHeight;
2443 802 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
2444 :
2445 : // We currently need 4 caches because of
2446 : // GDALGPKGMBTilesLikePseudoDataset::ReadTile(int nRow, int nCol)
2447 802 : const int nCacheCount = 4;
2448 : /*
2449 : (m_nShiftXPixelsMod != 0 || m_nShiftYPixelsMod != 0) ? 4 :
2450 : (GetUpdate() && m_eDT == GDT_UInt8) ? 2 : 1;
2451 : */
2452 802 : m_pabyCachedTiles = static_cast<GByte *>(VSI_MALLOC3_VERBOSE(
2453 : cpl::fits_on<int>(nCacheCount * (m_eDT == GDT_UInt8 ? 4 : 1) *
2454 : m_nDTSize),
2455 : nTileWidth, nTileHeight));
2456 802 : if (m_pabyCachedTiles == nullptr)
2457 : {
2458 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too big tiles: %d x %d",
2459 : nTileWidth, nTileHeight);
2460 0 : return false;
2461 : }
2462 :
2463 802 : return true;
2464 : }
2465 :
2466 : /************************************************************************/
2467 : /* InitRaster() */
2468 : /************************************************************************/
2469 :
2470 613 : bool GDALGeoPackageDataset::InitRaster(
2471 : GDALGeoPackageDataset *poParentDS, const char *pszTableName, int nZoomLevel,
2472 : int nBandCount, double dfTMSMinX, double dfTMSMaxY, double dfPixelXSize,
2473 : double dfPixelYSize, int nTileWidth, int nTileHeight, int nTileMatrixWidth,
2474 : int nTileMatrixHeight, double dfGDALMinX, double dfGDALMinY,
2475 : double dfGDALMaxX, double dfGDALMaxY)
2476 : {
2477 613 : m_osRasterTable = pszTableName;
2478 613 : m_dfTMSMinX = dfTMSMinX;
2479 613 : m_dfTMSMaxY = dfTMSMaxY;
2480 613 : m_nZoomLevel = nZoomLevel;
2481 613 : m_nTileMatrixWidth = nTileMatrixWidth;
2482 613 : m_nTileMatrixHeight = nTileMatrixHeight;
2483 :
2484 613 : m_bGeoTransformValid = true;
2485 613 : m_gt[0] = dfGDALMinX;
2486 613 : m_gt[1] = dfPixelXSize;
2487 613 : m_gt[3] = dfGDALMaxY;
2488 613 : m_gt[5] = -dfPixelYSize;
2489 613 : double dfRasterXSize = 0.5 + (dfGDALMaxX - dfGDALMinX) / dfPixelXSize;
2490 613 : double dfRasterYSize = 0.5 + (dfGDALMaxY - dfGDALMinY) / dfPixelYSize;
2491 613 : if (dfRasterXSize > INT_MAX || dfRasterYSize > INT_MAX)
2492 : {
2493 0 : CPLError(CE_Failure, CPLE_NotSupported, "Too big raster: %f x %f",
2494 : dfRasterXSize, dfRasterYSize);
2495 0 : return false;
2496 : }
2497 613 : nRasterXSize = std::max(1, static_cast<int>(dfRasterXSize));
2498 613 : nRasterYSize = std::max(1, static_cast<int>(dfRasterYSize));
2499 :
2500 613 : if (poParentDS)
2501 : {
2502 330 : m_poParentDS = poParentDS;
2503 330 : eAccess = poParentDS->eAccess;
2504 330 : hDB = poParentDS->hDB;
2505 330 : m_eTF = poParentDS->m_eTF;
2506 330 : m_eDT = poParentDS->m_eDT;
2507 330 : m_nDTSize = poParentDS->m_nDTSize;
2508 330 : m_dfScale = poParentDS->m_dfScale;
2509 330 : m_dfOffset = poParentDS->m_dfOffset;
2510 330 : m_dfPrecision = poParentDS->m_dfPrecision;
2511 330 : m_usGPKGNull = poParentDS->m_usGPKGNull;
2512 330 : m_nQuality = poParentDS->m_nQuality;
2513 330 : m_nZLevel = poParentDS->m_nZLevel;
2514 330 : m_bDither = poParentDS->m_bDither;
2515 : /*m_nSRID = poParentDS->m_nSRID;*/
2516 330 : m_osWHERE = poParentDS->m_osWHERE;
2517 330 : SetDescription(CPLSPrintf("%s - zoom_level=%d",
2518 330 : poParentDS->GetDescription(), m_nZoomLevel));
2519 : }
2520 :
2521 2132 : for (int i = 1; i <= nBandCount; i++)
2522 : {
2523 : auto poNewBand = std::make_unique<GDALGeoPackageRasterBand>(
2524 1519 : this, nTileWidth, nTileHeight);
2525 1519 : if (poParentDS)
2526 : {
2527 766 : int bHasNoData = FALSE;
2528 : double dfNoDataValue =
2529 766 : poParentDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
2530 766 : if (bHasNoData)
2531 24 : poNewBand->SetNoDataValueInternal(dfNoDataValue);
2532 : }
2533 :
2534 1519 : if (nBandCount == 1 && m_poCTFromMetadata)
2535 : {
2536 3 : poNewBand->AssignColorTable(m_poCTFromMetadata.get());
2537 : }
2538 1519 : if (!m_osNodataValueFromMetadata.empty())
2539 : {
2540 8 : poNewBand->SetNoDataValueInternal(
2541 : CPLAtof(m_osNodataValueFromMetadata.c_str()));
2542 : }
2543 :
2544 1519 : SetBand(i, std::move(poNewBand));
2545 : }
2546 :
2547 613 : if (!ComputeTileAndPixelShifts())
2548 : {
2549 3 : CPLError(CE_Failure, CPLE_AppDefined,
2550 : "Overflow occurred in ComputeTileAndPixelShifts()");
2551 3 : return false;
2552 : }
2553 :
2554 610 : GDALPamDataset::SetMetadataItem(GDALMD_INTERLEAVE, "PIXEL",
2555 : GDAL_MDD_IMAGE_STRUCTURE);
2556 610 : GDALPamDataset::SetMetadataItem("ZOOM_LEVEL",
2557 : CPLSPrintf("%d", m_nZoomLevel));
2558 :
2559 610 : return AllocCachedTiles();
2560 : }
2561 :
2562 : /************************************************************************/
2563 : /* GDALGPKGMBTilesGetTileFormat() */
2564 : /************************************************************************/
2565 :
2566 90 : GPKGTileFormat GDALGPKGMBTilesGetTileFormat(const char *pszTF)
2567 : {
2568 90 : GPKGTileFormat eTF = GPKG_TF_PNG_JPEG;
2569 90 : if (pszTF)
2570 : {
2571 90 : if (EQUAL(pszTF, "PNG_JPEG") || EQUAL(pszTF, "AUTO"))
2572 1 : eTF = GPKG_TF_PNG_JPEG;
2573 89 : else if (EQUAL(pszTF, "PNG"))
2574 48 : eTF = GPKG_TF_PNG;
2575 41 : else if (EQUAL(pszTF, "PNG8"))
2576 10 : eTF = GPKG_TF_PNG8;
2577 31 : else if (EQUAL(pszTF, "JPEG"))
2578 16 : eTF = GPKG_TF_JPEG;
2579 15 : else if (EQUAL(pszTF, "WEBP"))
2580 15 : eTF = GPKG_TF_WEBP;
2581 : else
2582 : {
2583 0 : CPLError(CE_Failure, CPLE_NotSupported,
2584 : "Unsuppoted value for TILE_FORMAT: %s", pszTF);
2585 : }
2586 : }
2587 90 : return eTF;
2588 : }
2589 :
2590 38 : const char *GDALMBTilesGetTileFormatName(GPKGTileFormat eTF)
2591 : {
2592 38 : switch (eTF)
2593 : {
2594 32 : case GPKG_TF_PNG:
2595 : case GPKG_TF_PNG8:
2596 32 : return "png";
2597 3 : case GPKG_TF_JPEG:
2598 3 : return "jpg";
2599 3 : case GPKG_TF_WEBP:
2600 3 : return "webp";
2601 0 : default:
2602 0 : break;
2603 : }
2604 0 : CPLError(CE_Failure, CPLE_NotSupported,
2605 : "Unsuppoted value for TILE_FORMAT: %d", static_cast<int>(eTF));
2606 0 : return nullptr;
2607 : }
2608 :
2609 : /************************************************************************/
2610 : /* OpenRaster() */
2611 : /************************************************************************/
2612 :
2613 285 : bool GDALGeoPackageDataset::OpenRaster(
2614 : const char *pszTableName, const char *pszIdentifier,
2615 : const char *pszDescription, int nSRSId, double dfMinX, double dfMinY,
2616 : double dfMaxX, double dfMaxY, const char *pszContentsMinX,
2617 : const char *pszContentsMinY, const char *pszContentsMaxX,
2618 : const char *pszContentsMaxY, bool bIsTiles, CSLConstList papszOpenOptionsIn)
2619 : {
2620 285 : if (dfMinX >= dfMaxX || dfMinY >= dfMaxY)
2621 0 : return false;
2622 :
2623 : // Config option just for debug, and for example force set to NaN
2624 : // which is not supported
2625 570 : CPLString osDataNull = CPLGetConfigOption("GPKG_NODATA", "");
2626 570 : CPLString osUom;
2627 570 : CPLString osFieldName;
2628 570 : CPLString osGridCellEncoding;
2629 285 : if (!bIsTiles)
2630 : {
2631 65 : char *pszSQL = sqlite3_mprintf(
2632 : "SELECT datatype, scale, offset, data_null, precision FROM "
2633 : "gpkg_2d_gridded_coverage_ancillary "
2634 : "WHERE tile_matrix_set_name = '%q' "
2635 : "AND datatype IN ('integer', 'float')"
2636 : "AND (scale > 0 OR scale IS NULL)",
2637 : pszTableName);
2638 65 : auto oResult = SQLQuery(hDB, pszSQL);
2639 65 : sqlite3_free(pszSQL);
2640 65 : if (!oResult || oResult->RowCount() == 0)
2641 : {
2642 0 : return false;
2643 : }
2644 65 : const char *pszDataType = oResult->GetValue(0, 0);
2645 65 : const char *pszScale = oResult->GetValue(1, 0);
2646 65 : const char *pszOffset = oResult->GetValue(2, 0);
2647 65 : const char *pszDataNull = oResult->GetValue(3, 0);
2648 65 : const char *pszPrecision = oResult->GetValue(4, 0);
2649 65 : if (pszDataNull)
2650 23 : osDataNull = pszDataNull;
2651 65 : if (EQUAL(pszDataType, "float"))
2652 : {
2653 6 : SetDataType(GDT_Float32);
2654 6 : m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
2655 : }
2656 : else
2657 : {
2658 59 : SetDataType(GDT_Float32);
2659 59 : m_eTF = GPKG_TF_PNG_16BIT;
2660 59 : const double dfScale = pszScale ? CPLAtof(pszScale) : 1.0;
2661 59 : const double dfOffset = pszOffset ? CPLAtof(pszOffset) : 0.0;
2662 59 : if (dfScale == 1.0)
2663 : {
2664 59 : if (dfOffset == 0.0)
2665 : {
2666 24 : SetDataType(GDT_UInt16);
2667 : }
2668 35 : else if (dfOffset == -32768.0)
2669 : {
2670 35 : SetDataType(GDT_Int16);
2671 : }
2672 : // coverity[tainted_data]
2673 0 : else if (dfOffset == -32767.0 && !osDataNull.empty() &&
2674 0 : CPLAtof(osDataNull) == 65535.0)
2675 : // Given that we will map the nodata value to -32768
2676 : {
2677 0 : SetDataType(GDT_Int16);
2678 : }
2679 : }
2680 :
2681 : // Check that the tile offset and scales are compatible with a
2682 : // final integer result.
2683 59 : if (m_eDT != GDT_Float32)
2684 : {
2685 : // coverity[tainted_data]
2686 59 : if (dfScale == 1.0 && dfOffset == -32768.0 &&
2687 118 : !osDataNull.empty() && CPLAtof(osDataNull) == 65535.0)
2688 : {
2689 : // Given that we will map the nodata value to -32768
2690 9 : pszSQL = sqlite3_mprintf(
2691 : "SELECT 1 FROM "
2692 : "gpkg_2d_gridded_tile_ancillary WHERE "
2693 : "tpudt_name = '%q' "
2694 : "AND NOT ((offset = 0.0 or offset = 1.0) "
2695 : "AND scale = 1.0) "
2696 : "LIMIT 1",
2697 : pszTableName);
2698 : }
2699 : else
2700 : {
2701 50 : pszSQL = sqlite3_mprintf(
2702 : "SELECT 1 FROM "
2703 : "gpkg_2d_gridded_tile_ancillary WHERE "
2704 : "tpudt_name = '%q' "
2705 : "AND NOT (offset = 0.0 AND scale = 1.0) LIMIT 1",
2706 : pszTableName);
2707 : }
2708 59 : sqlite3_stmt *hSQLStmt = nullptr;
2709 : int rc =
2710 59 : SQLPrepareWithError(hDB, pszSQL, -1, &hSQLStmt, nullptr);
2711 :
2712 59 : if (rc == SQLITE_OK)
2713 : {
2714 59 : if (sqlite3_step(hSQLStmt) == SQLITE_ROW)
2715 : {
2716 8 : SetDataType(GDT_Float32);
2717 : }
2718 59 : sqlite3_finalize(hSQLStmt);
2719 : }
2720 59 : sqlite3_free(pszSQL);
2721 : }
2722 :
2723 59 : SetGlobalOffsetScale(dfOffset, dfScale);
2724 : }
2725 65 : if (pszPrecision)
2726 65 : m_dfPrecision = CPLAtof(pszPrecision);
2727 :
2728 : // Request those columns in a separate query, so as to keep
2729 : // compatibility with pre OGC 17-066r1 databases
2730 : pszSQL =
2731 65 : sqlite3_mprintf("SELECT uom, field_name, grid_cell_encoding FROM "
2732 : "gpkg_2d_gridded_coverage_ancillary "
2733 : "WHERE tile_matrix_set_name = '%q'",
2734 : pszTableName);
2735 65 : CPLPushErrorHandler(CPLQuietErrorHandler);
2736 65 : oResult = SQLQuery(hDB, pszSQL);
2737 65 : CPLPopErrorHandler();
2738 65 : sqlite3_free(pszSQL);
2739 65 : if (oResult && oResult->RowCount() == 1)
2740 : {
2741 64 : const char *pszUom = oResult->GetValue(0, 0);
2742 64 : if (pszUom)
2743 2 : osUom = pszUom;
2744 64 : const char *pszFieldName = oResult->GetValue(1, 0);
2745 64 : if (pszFieldName)
2746 64 : osFieldName = pszFieldName;
2747 64 : const char *pszGridCellEncoding = oResult->GetValue(2, 0);
2748 64 : if (pszGridCellEncoding)
2749 64 : osGridCellEncoding = pszGridCellEncoding;
2750 : }
2751 : }
2752 :
2753 285 : m_bRecordInsertedInGPKGContent = true;
2754 285 : m_nSRID = nSRSId;
2755 :
2756 569 : if (auto poSRS = GetSpatialRef(nSRSId))
2757 : {
2758 284 : m_oSRS = *(poSRS.get());
2759 : }
2760 :
2761 : /* Various sanity checks added in the SELECT */
2762 285 : char *pszQuotedTableName = sqlite3_mprintf("'%q'", pszTableName);
2763 570 : CPLString osQuotedTableName(pszQuotedTableName);
2764 285 : sqlite3_free(pszQuotedTableName);
2765 285 : char *pszSQL = sqlite3_mprintf(
2766 : "SELECT zoom_level, pixel_x_size, pixel_y_size, tile_width, "
2767 : "tile_height, matrix_width, matrix_height "
2768 : "FROM gpkg_tile_matrix tm "
2769 : "WHERE table_name = %s "
2770 : // INT_MAX would be the theoretical maximum value to avoid
2771 : // overflows, but that's already a insane value.
2772 : "AND zoom_level >= 0 AND zoom_level <= 65536 "
2773 : "AND pixel_x_size > 0 AND pixel_y_size > 0 "
2774 : "AND tile_width >= 1 AND tile_width <= 65536 "
2775 : "AND tile_height >= 1 AND tile_height <= 65536 "
2776 : "AND matrix_width >= 1 AND matrix_height >= 1",
2777 : osQuotedTableName.c_str());
2778 570 : CPLString osSQL(pszSQL);
2779 : const char *pszZoomLevel =
2780 285 : CSLFetchNameValue(papszOpenOptionsIn, "ZOOM_LEVEL");
2781 285 : if (pszZoomLevel)
2782 : {
2783 5 : if (GetUpdate())
2784 1 : osSQL += CPLSPrintf(" AND zoom_level <= %d", atoi(pszZoomLevel));
2785 : else
2786 : {
2787 : osSQL += CPLSPrintf(
2788 : " AND (zoom_level = %d OR (zoom_level < %d AND EXISTS(SELECT 1 "
2789 : "FROM %s WHERE zoom_level = tm.zoom_level LIMIT 1)))",
2790 : atoi(pszZoomLevel), atoi(pszZoomLevel),
2791 4 : osQuotedTableName.c_str());
2792 : }
2793 : }
2794 : // In read-only mode, only lists non empty zoom levels
2795 280 : else if (!GetUpdate())
2796 : {
2797 : osSQL += CPLSPrintf(" AND EXISTS(SELECT 1 FROM %s WHERE zoom_level = "
2798 : "tm.zoom_level LIMIT 1)",
2799 226 : osQuotedTableName.c_str());
2800 : }
2801 : else // if( pszZoomLevel == nullptr )
2802 : {
2803 : osSQL +=
2804 : CPLSPrintf(" AND zoom_level <= (SELECT MAX(zoom_level) FROM %s)",
2805 54 : osQuotedTableName.c_str());
2806 : }
2807 285 : osSQL += " ORDER BY zoom_level DESC";
2808 : // To avoid denial of service.
2809 285 : osSQL += " LIMIT 100";
2810 :
2811 570 : auto oResult = SQLQuery(hDB, osSQL.c_str());
2812 285 : if (!oResult || oResult->RowCount() == 0)
2813 : {
2814 120 : if (oResult && oResult->RowCount() == 0 && pszContentsMinX != nullptr &&
2815 120 : pszContentsMinY != nullptr && pszContentsMaxX != nullptr &&
2816 : pszContentsMaxY != nullptr)
2817 : {
2818 59 : osSQL = pszSQL;
2819 59 : osSQL += " ORDER BY zoom_level DESC";
2820 59 : if (!GetUpdate())
2821 33 : osSQL += " LIMIT 1";
2822 59 : oResult = SQLQuery(hDB, osSQL.c_str());
2823 : }
2824 60 : if (!oResult || oResult->RowCount() == 0)
2825 : {
2826 1 : if (oResult && pszZoomLevel != nullptr)
2827 : {
2828 1 : CPLError(CE_Failure, CPLE_AppDefined,
2829 : "ZOOM_LEVEL is probably not valid w.r.t tile "
2830 : "table content");
2831 : }
2832 1 : sqlite3_free(pszSQL);
2833 1 : return false;
2834 : }
2835 : }
2836 284 : sqlite3_free(pszSQL);
2837 :
2838 : // If USE_TILE_EXTENT=YES, then query the tile table to find which tiles
2839 : // actually exist.
2840 :
2841 : // CAUTION: Do not move those variables inside inner scope !
2842 568 : CPLString osContentsMinX, osContentsMinY, osContentsMaxX, osContentsMaxY;
2843 :
2844 284 : if (CPLTestBool(
2845 : CSLFetchNameValueDef(papszOpenOptionsIn, "USE_TILE_EXTENT", "NO")))
2846 : {
2847 13 : pszSQL = sqlite3_mprintf(
2848 : "SELECT MIN(tile_column), MIN(tile_row), MAX(tile_column), "
2849 : "MAX(tile_row) FROM \"%w\" WHERE zoom_level = %d",
2850 : pszTableName, atoi(oResult->GetValue(0, 0)));
2851 13 : auto oResult2 = SQLQuery(hDB, pszSQL);
2852 13 : sqlite3_free(pszSQL);
2853 26 : if (!oResult2 || oResult2->RowCount() == 0 ||
2854 : // Can happen if table is empty
2855 38 : oResult2->GetValue(0, 0) == nullptr ||
2856 : // Can happen if table has no NOT NULL constraint on tile_row
2857 : // and that all tile_row are NULL
2858 12 : oResult2->GetValue(1, 0) == nullptr)
2859 : {
2860 1 : return false;
2861 : }
2862 12 : const double dfPixelXSize = CPLAtof(oResult->GetValue(1, 0));
2863 12 : const double dfPixelYSize = CPLAtof(oResult->GetValue(2, 0));
2864 12 : const int nTileWidth = atoi(oResult->GetValue(3, 0));
2865 12 : const int nTileHeight = atoi(oResult->GetValue(4, 0));
2866 : osContentsMinX =
2867 24 : CPLSPrintf("%.17g", dfMinX + dfPixelXSize * nTileWidth *
2868 12 : atoi(oResult2->GetValue(0, 0)));
2869 : osContentsMaxY =
2870 24 : CPLSPrintf("%.17g", dfMaxY - dfPixelYSize * nTileHeight *
2871 12 : atoi(oResult2->GetValue(1, 0)));
2872 : osContentsMaxX = CPLSPrintf(
2873 24 : "%.17g", dfMinX + dfPixelXSize * nTileWidth *
2874 12 : (1 + atoi(oResult2->GetValue(2, 0))));
2875 : osContentsMinY = CPLSPrintf(
2876 24 : "%.17g", dfMaxY - dfPixelYSize * nTileHeight *
2877 12 : (1 + atoi(oResult2->GetValue(3, 0))));
2878 12 : pszContentsMinX = osContentsMinX.c_str();
2879 12 : pszContentsMinY = osContentsMinY.c_str();
2880 12 : pszContentsMaxX = osContentsMaxX.c_str();
2881 12 : pszContentsMaxY = osContentsMaxY.c_str();
2882 : }
2883 :
2884 283 : if (!InitRaster(nullptr, pszTableName, dfMinX, dfMinY, dfMaxX, dfMaxY,
2885 : pszContentsMinX, pszContentsMinY, pszContentsMaxX,
2886 283 : pszContentsMaxY, papszOpenOptionsIn, *oResult, 0))
2887 : {
2888 3 : return false;
2889 : }
2890 :
2891 280 : auto poBand = cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(1));
2892 280 : if (!osDataNull.empty())
2893 : {
2894 23 : double dfGPKGNoDataValue = CPLAtof(osDataNull);
2895 23 : if (m_eTF == GPKG_TF_PNG_16BIT)
2896 : {
2897 21 : if (dfGPKGNoDataValue < 0 || dfGPKGNoDataValue > 65535 ||
2898 21 : static_cast<int>(dfGPKGNoDataValue) != dfGPKGNoDataValue)
2899 : {
2900 0 : CPLError(CE_Warning, CPLE_AppDefined,
2901 : "data_null = %.17g is invalid for integer data_type",
2902 : dfGPKGNoDataValue);
2903 : }
2904 : else
2905 : {
2906 21 : m_usGPKGNull = static_cast<GUInt16>(dfGPKGNoDataValue);
2907 21 : if (m_eDT == GDT_Int16 && m_usGPKGNull > 32767)
2908 9 : dfGPKGNoDataValue = -32768.0;
2909 12 : else if (m_eDT == GDT_Float32)
2910 : {
2911 : // Pick a value that is unlikely to be hit with offset &
2912 : // scale
2913 4 : dfGPKGNoDataValue = -std::numeric_limits<float>::max();
2914 : }
2915 21 : poBand->SetNoDataValueInternal(dfGPKGNoDataValue);
2916 : }
2917 : }
2918 : else
2919 : {
2920 2 : poBand->SetNoDataValueInternal(
2921 2 : static_cast<float>(dfGPKGNoDataValue));
2922 : }
2923 : }
2924 280 : if (!osUom.empty())
2925 : {
2926 2 : poBand->SetUnitTypeInternal(osUom);
2927 : }
2928 280 : if (!osFieldName.empty())
2929 : {
2930 64 : GetRasterBand(1)->GDALRasterBand::SetDescription(osFieldName);
2931 : }
2932 280 : if (!osGridCellEncoding.empty())
2933 : {
2934 64 : if (osGridCellEncoding == "grid-value-is-center")
2935 : {
2936 15 : GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
2937 : GDALMD_AOP_POINT);
2938 : }
2939 49 : else if (osGridCellEncoding == "grid-value-is-area")
2940 : {
2941 45 : GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
2942 : GDALMD_AOP_AREA);
2943 : }
2944 : else
2945 : {
2946 4 : GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
2947 : GDALMD_AOP_POINT);
2948 4 : GetRasterBand(1)->GDALRasterBand::SetMetadataItem(
2949 : "GRID_CELL_ENCODING", osGridCellEncoding);
2950 : }
2951 : }
2952 :
2953 280 : CheckUnknownExtensions(true);
2954 :
2955 : // Do this after CheckUnknownExtensions() so that m_eTF is set to
2956 : // GPKG_TF_WEBP if the table already registers the gpkg_webp extension
2957 280 : const char *pszTF = CSLFetchNameValue(papszOpenOptionsIn, "TILE_FORMAT");
2958 280 : if (pszTF)
2959 : {
2960 4 : if (!GetUpdate())
2961 : {
2962 0 : CPLError(CE_Warning, CPLE_AppDefined,
2963 : "TILE_FORMAT open option ignored in read-only mode");
2964 : }
2965 4 : else if (m_eTF == GPKG_TF_PNG_16BIT ||
2966 4 : m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
2967 : {
2968 0 : CPLError(CE_Warning, CPLE_AppDefined,
2969 : "TILE_FORMAT open option ignored on gridded coverages");
2970 : }
2971 : else
2972 : {
2973 4 : GPKGTileFormat eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
2974 4 : if (eTF == GPKG_TF_WEBP && m_eTF != eTF)
2975 : {
2976 1 : if (!RegisterWebPExtension())
2977 0 : return false;
2978 : }
2979 4 : m_eTF = eTF;
2980 : }
2981 : }
2982 :
2983 280 : ParseCompressionOptions(papszOpenOptionsIn);
2984 :
2985 280 : m_osWHERE = CSLFetchNameValueDef(papszOpenOptionsIn, "WHERE", "");
2986 :
2987 : // Set metadata
2988 280 : if (pszIdentifier && pszIdentifier[0])
2989 280 : GDALPamDataset::SetMetadataItem("IDENTIFIER", pszIdentifier);
2990 280 : if (pszDescription && pszDescription[0])
2991 21 : GDALPamDataset::SetMetadataItem("DESCRIPTION", pszDescription);
2992 :
2993 : // Add overviews
2994 365 : for (int i = 1; i < oResult->RowCount(); i++)
2995 : {
2996 86 : auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
2997 86 : poOvrDS->ShareLockWithParentDataset(this);
2998 172 : if (!poOvrDS->InitRaster(this, pszTableName, dfMinX, dfMinY, dfMaxX,
2999 : dfMaxY, pszContentsMinX, pszContentsMinY,
3000 : pszContentsMaxX, pszContentsMaxY,
3001 86 : papszOpenOptionsIn, *oResult, i))
3002 : {
3003 0 : break;
3004 : }
3005 :
3006 : int nTileWidth, nTileHeight;
3007 86 : poOvrDS->GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3008 : const bool bStop =
3009 87 : (eAccess == GA_ReadOnly && poOvrDS->GetRasterXSize() < nTileWidth &&
3010 1 : poOvrDS->GetRasterYSize() < nTileHeight);
3011 :
3012 86 : m_apoOverviewDS.push_back(std::move(poOvrDS));
3013 :
3014 86 : if (bStop)
3015 : {
3016 1 : break;
3017 : }
3018 : }
3019 :
3020 280 : return true;
3021 : }
3022 :
3023 : /************************************************************************/
3024 : /* GetSpatialRef() */
3025 : /************************************************************************/
3026 :
3027 25 : const OGRSpatialReference *GDALGeoPackageDataset::GetSpatialRef() const
3028 : {
3029 25 : if (GetLayerCount())
3030 3 : return GDALDataset::GetSpatialRef();
3031 22 : return GetSpatialRefRasterOnly();
3032 : }
3033 :
3034 : /************************************************************************/
3035 : /* GetSpatialRefRasterOnly() */
3036 : /************************************************************************/
3037 :
3038 : const OGRSpatialReference *
3039 23 : GDALGeoPackageDataset::GetSpatialRefRasterOnly() const
3040 :
3041 : {
3042 23 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
3043 : }
3044 :
3045 : /************************************************************************/
3046 : /* SetSpatialRef() */
3047 : /************************************************************************/
3048 :
3049 158 : CPLErr GDALGeoPackageDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
3050 : {
3051 158 : if (nBands == 0)
3052 : {
3053 1 : CPLError(CE_Failure, CPLE_NotSupported,
3054 : "SetProjection() not supported on a dataset with 0 band");
3055 1 : return CE_Failure;
3056 : }
3057 157 : if (eAccess != GA_Update)
3058 : {
3059 1 : CPLError(CE_Failure, CPLE_NotSupported,
3060 : "SetProjection() not supported on read-only dataset");
3061 1 : return CE_Failure;
3062 : }
3063 :
3064 156 : const int nSRID = GetSrsId(poSRS);
3065 312 : const auto poTS = GetTilingScheme(m_osTilingScheme);
3066 156 : if (poTS && nSRID != poTS->nEPSGCode)
3067 : {
3068 2 : CPLError(CE_Failure, CPLE_NotSupported,
3069 : "Projection should be EPSG:%d for %s tiling scheme",
3070 1 : poTS->nEPSGCode, m_osTilingScheme.c_str());
3071 1 : return CE_Failure;
3072 : }
3073 :
3074 155 : m_nSRID = nSRID;
3075 155 : m_oSRS.Clear();
3076 155 : if (poSRS)
3077 154 : m_oSRS = *poSRS;
3078 :
3079 155 : if (m_bRecordInsertedInGPKGContent)
3080 : {
3081 124 : char *pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET srs_id = %d "
3082 : "WHERE lower(table_name) = lower('%q')",
3083 : m_nSRID, m_osRasterTable.c_str());
3084 124 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3085 124 : sqlite3_free(pszSQL);
3086 124 : if (eErr != OGRERR_NONE)
3087 0 : return CE_Failure;
3088 :
3089 124 : pszSQL = sqlite3_mprintf("UPDATE gpkg_tile_matrix_set SET srs_id = %d "
3090 : "WHERE lower(table_name) = lower('%q')",
3091 : m_nSRID, m_osRasterTable.c_str());
3092 124 : eErr = SQLCommand(hDB, pszSQL);
3093 124 : sqlite3_free(pszSQL);
3094 124 : if (eErr != OGRERR_NONE)
3095 0 : return CE_Failure;
3096 : }
3097 :
3098 155 : return CE_None;
3099 : }
3100 :
3101 : /************************************************************************/
3102 : /* GetGeoTransform() */
3103 : /************************************************************************/
3104 :
3105 35 : CPLErr GDALGeoPackageDataset::GetGeoTransform(GDALGeoTransform >) const
3106 : {
3107 35 : gt = m_gt;
3108 35 : if (!m_bGeoTransformValid)
3109 2 : return CE_Failure;
3110 : else
3111 33 : return CE_None;
3112 : }
3113 :
3114 : /************************************************************************/
3115 : /* SetGeoTransform() */
3116 : /************************************************************************/
3117 :
3118 198 : CPLErr GDALGeoPackageDataset::SetGeoTransform(const GDALGeoTransform >)
3119 : {
3120 198 : if (nBands == 0)
3121 : {
3122 2 : CPLError(CE_Failure, CPLE_NotSupported,
3123 : "SetGeoTransform() not supported on a dataset with 0 band");
3124 2 : return CE_Failure;
3125 : }
3126 196 : if (eAccess != GA_Update)
3127 : {
3128 1 : CPLError(CE_Failure, CPLE_NotSupported,
3129 : "SetGeoTransform() not supported on read-only dataset");
3130 1 : return CE_Failure;
3131 : }
3132 195 : if (m_bGeoTransformValid)
3133 : {
3134 1 : CPLError(CE_Failure, CPLE_NotSupported,
3135 : "Cannot modify geotransform once set");
3136 1 : return CE_Failure;
3137 : }
3138 194 : if (gt[2] != 0.0 || gt[4] != 0 || gt[5] > 0.0)
3139 : {
3140 0 : CPLError(CE_Failure, CPLE_NotSupported,
3141 : "Only north-up non rotated geotransform supported");
3142 0 : return CE_Failure;
3143 : }
3144 :
3145 194 : if (m_nZoomLevel < 0)
3146 : {
3147 193 : const auto poTS = GetTilingScheme(m_osTilingScheme);
3148 193 : if (poTS)
3149 : {
3150 20 : double dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
3151 20 : double dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
3152 199 : for (m_nZoomLevel = 0; m_nZoomLevel < MAX_ZOOM_LEVEL;
3153 179 : m_nZoomLevel++)
3154 : {
3155 198 : double dfExpectedPixelXSize =
3156 198 : dfPixelXSizeZoomLevel0 / (1 << m_nZoomLevel);
3157 198 : double dfExpectedPixelYSize =
3158 198 : dfPixelYSizeZoomLevel0 / (1 << m_nZoomLevel);
3159 198 : if (fabs(gt[1] - dfExpectedPixelXSize) <
3160 217 : 1e-8 * dfExpectedPixelXSize &&
3161 19 : fabs(fabs(gt[5]) - dfExpectedPixelYSize) <
3162 19 : 1e-8 * dfExpectedPixelYSize)
3163 : {
3164 19 : break;
3165 : }
3166 : }
3167 20 : if (m_nZoomLevel == MAX_ZOOM_LEVEL)
3168 : {
3169 1 : m_nZoomLevel = -1;
3170 1 : CPLError(
3171 : CE_Failure, CPLE_NotSupported,
3172 : "Could not find an appropriate zoom level of %s tiling "
3173 : "scheme that matches raster pixel size",
3174 : m_osTilingScheme.c_str());
3175 1 : return CE_Failure;
3176 : }
3177 : }
3178 : }
3179 :
3180 193 : m_gt = gt;
3181 193 : m_bGeoTransformValid = true;
3182 :
3183 193 : return FinalizeRasterRegistration();
3184 : }
3185 :
3186 : /************************************************************************/
3187 : /* FinalizeRasterRegistration() */
3188 : /************************************************************************/
3189 :
3190 193 : CPLErr GDALGeoPackageDataset::FinalizeRasterRegistration()
3191 : {
3192 : OGRErr eErr;
3193 :
3194 193 : m_dfTMSMinX = m_gt[0];
3195 193 : m_dfTMSMaxY = m_gt[3];
3196 :
3197 : int nTileWidth, nTileHeight;
3198 193 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3199 :
3200 193 : if (m_nZoomLevel < 0)
3201 : {
3202 173 : m_nZoomLevel = 0;
3203 252 : while ((nRasterXSize >> m_nZoomLevel) > nTileWidth ||
3204 173 : (nRasterYSize >> m_nZoomLevel) > nTileHeight)
3205 79 : m_nZoomLevel++;
3206 : }
3207 :
3208 193 : double dfPixelXSizeZoomLevel0 = m_gt[1] * (1 << m_nZoomLevel);
3209 193 : double dfPixelYSizeZoomLevel0 = fabs(m_gt[5]) * (1 << m_nZoomLevel);
3210 : int nTileXCountZoomLevel0 =
3211 193 : std::max(1, DIV_ROUND_UP((nRasterXSize >> m_nZoomLevel), nTileWidth));
3212 : int nTileYCountZoomLevel0 =
3213 193 : std::max(1, DIV_ROUND_UP((nRasterYSize >> m_nZoomLevel), nTileHeight));
3214 :
3215 386 : const auto poTS = GetTilingScheme(m_osTilingScheme);
3216 193 : if (poTS)
3217 : {
3218 20 : CPLAssert(m_nZoomLevel >= 0);
3219 20 : m_dfTMSMinX = poTS->dfMinX;
3220 20 : m_dfTMSMaxY = poTS->dfMaxY;
3221 20 : dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
3222 20 : dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
3223 20 : nTileXCountZoomLevel0 = poTS->nTileXCountZoomLevel0;
3224 20 : nTileYCountZoomLevel0 = poTS->nTileYCountZoomLevel0;
3225 : }
3226 193 : m_nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << m_nZoomLevel);
3227 193 : m_nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << m_nZoomLevel);
3228 :
3229 193 : if (!ComputeTileAndPixelShifts())
3230 : {
3231 1 : CPLError(CE_Failure, CPLE_AppDefined,
3232 : "Overflow occurred in ComputeTileAndPixelShifts()");
3233 1 : return CE_Failure;
3234 : }
3235 :
3236 192 : if (!AllocCachedTiles())
3237 : {
3238 0 : return CE_Failure;
3239 : }
3240 :
3241 192 : double dfGDALMinX = m_gt[0];
3242 192 : double dfGDALMinY = m_gt[3] + nRasterYSize * m_gt[5];
3243 192 : double dfGDALMaxX = m_gt[0] + nRasterXSize * m_gt[1];
3244 192 : double dfGDALMaxY = m_gt[3];
3245 :
3246 192 : if (SoftStartTransaction() != OGRERR_NONE)
3247 0 : return CE_Failure;
3248 :
3249 : const char *pszCurrentDate =
3250 192 : CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
3251 : CPLString osInsertGpkgContentsFormatting(
3252 : "INSERT INTO gpkg_contents "
3253 : "(table_name,data_type,identifier,description,min_x,min_y,max_x,max_y,"
3254 : "last_change,srs_id) VALUES "
3255 384 : "('%q','%q','%q','%q',%.17g,%.17g,%.17g,%.17g,");
3256 192 : osInsertGpkgContentsFormatting += (pszCurrentDate) ? "'%q'" : "%s";
3257 192 : osInsertGpkgContentsFormatting += ",%d)";
3258 384 : char *pszSQL = sqlite3_mprintf(
3259 : osInsertGpkgContentsFormatting.c_str(), m_osRasterTable.c_str(),
3260 192 : (m_eDT == GDT_UInt8) ? "tiles" : "2d-gridded-coverage",
3261 : m_osIdentifier.c_str(), m_osDescription.c_str(), dfGDALMinX, dfGDALMinY,
3262 : dfGDALMaxX, dfGDALMaxY,
3263 : pszCurrentDate ? pszCurrentDate
3264 : : "strftime('%Y-%m-%dT%H:%M:%fZ','now')",
3265 : m_nSRID);
3266 :
3267 192 : eErr = SQLCommand(hDB, pszSQL);
3268 192 : sqlite3_free(pszSQL);
3269 192 : if (eErr != OGRERR_NONE)
3270 : {
3271 8 : SoftRollbackTransaction();
3272 8 : return CE_Failure;
3273 : }
3274 :
3275 184 : double dfTMSMaxX = m_dfTMSMinX + nTileXCountZoomLevel0 * nTileWidth *
3276 : dfPixelXSizeZoomLevel0;
3277 184 : double dfTMSMinY = m_dfTMSMaxY - nTileYCountZoomLevel0 * nTileHeight *
3278 : dfPixelYSizeZoomLevel0;
3279 :
3280 : pszSQL =
3281 184 : sqlite3_mprintf("INSERT INTO gpkg_tile_matrix_set "
3282 : "(table_name,srs_id,min_x,min_y,max_x,max_y) VALUES "
3283 : "('%q',%d,%.17g,%.17g,%.17g,%.17g)",
3284 : m_osRasterTable.c_str(), m_nSRID, m_dfTMSMinX,
3285 : dfTMSMinY, dfTMSMaxX, m_dfTMSMaxY);
3286 184 : eErr = SQLCommand(hDB, pszSQL);
3287 184 : sqlite3_free(pszSQL);
3288 184 : if (eErr != OGRERR_NONE)
3289 : {
3290 0 : SoftRollbackTransaction();
3291 0 : return CE_Failure;
3292 : }
3293 :
3294 184 : m_apoOverviewDS.resize(m_nZoomLevel);
3295 :
3296 608 : for (int i = 0; i <= m_nZoomLevel; i++)
3297 : {
3298 424 : double dfPixelXSizeZoomLevel = 0.0;
3299 424 : double dfPixelYSizeZoomLevel = 0.0;
3300 424 : int nTileMatrixWidth = 0;
3301 424 : int nTileMatrixHeight = 0;
3302 424 : if (EQUAL(m_osTilingScheme, "CUSTOM"))
3303 : {
3304 243 : dfPixelXSizeZoomLevel = m_gt[1] * (1 << (m_nZoomLevel - i));
3305 243 : dfPixelYSizeZoomLevel = fabs(m_gt[5]) * (1 << (m_nZoomLevel - i));
3306 : }
3307 : else
3308 : {
3309 181 : dfPixelXSizeZoomLevel = dfPixelXSizeZoomLevel0 / (1 << i);
3310 181 : dfPixelYSizeZoomLevel = dfPixelYSizeZoomLevel0 / (1 << i);
3311 : }
3312 424 : nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << i);
3313 424 : nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << i);
3314 :
3315 424 : pszSQL = sqlite3_mprintf(
3316 : "INSERT INTO gpkg_tile_matrix "
3317 : "(table_name,zoom_level,matrix_width,matrix_height,tile_width,tile_"
3318 : "height,pixel_x_size,pixel_y_size) VALUES "
3319 : "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
3320 : m_osRasterTable.c_str(), i, nTileMatrixWidth, nTileMatrixHeight,
3321 : nTileWidth, nTileHeight, dfPixelXSizeZoomLevel,
3322 : dfPixelYSizeZoomLevel);
3323 424 : eErr = SQLCommand(hDB, pszSQL);
3324 424 : sqlite3_free(pszSQL);
3325 424 : if (eErr != OGRERR_NONE)
3326 : {
3327 0 : SoftRollbackTransaction();
3328 0 : return CE_Failure;
3329 : }
3330 :
3331 424 : if (i < m_nZoomLevel)
3332 : {
3333 480 : auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
3334 240 : poOvrDS->ShareLockWithParentDataset(this);
3335 240 : poOvrDS->InitRaster(this, m_osRasterTable, i, nBands, m_dfTMSMinX,
3336 : m_dfTMSMaxY, dfPixelXSizeZoomLevel,
3337 : dfPixelYSizeZoomLevel, nTileWidth, nTileHeight,
3338 : nTileMatrixWidth, nTileMatrixHeight, dfGDALMinX,
3339 : dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
3340 :
3341 240 : m_apoOverviewDS[m_nZoomLevel - 1 - i] = std::move(poOvrDS);
3342 : }
3343 : }
3344 :
3345 184 : if (!m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.empty())
3346 : {
3347 40 : eErr = SQLCommand(
3348 : hDB, m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.c_str());
3349 40 : m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.clear();
3350 40 : if (eErr != OGRERR_NONE)
3351 : {
3352 0 : SoftRollbackTransaction();
3353 0 : return CE_Failure;
3354 : }
3355 : }
3356 :
3357 184 : SoftCommitTransaction();
3358 :
3359 184 : m_apoOverviewDS.resize(m_nZoomLevel);
3360 184 : m_bRecordInsertedInGPKGContent = true;
3361 :
3362 184 : return CE_None;
3363 : }
3364 :
3365 : /************************************************************************/
3366 : /* FlushCache() */
3367 : /************************************************************************/
3368 :
3369 3190 : CPLErr GDALGeoPackageDataset::FlushCache(bool bAtClosing)
3370 : {
3371 3190 : if (m_bInFlushCache)
3372 0 : return CE_None;
3373 :
3374 3190 : if (eAccess == GA_Update || !m_bMetadataDirty)
3375 : {
3376 3187 : SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
3377 : }
3378 :
3379 3190 : if (m_bRemoveOGREmptyTable)
3380 : {
3381 916 : m_bRemoveOGREmptyTable = false;
3382 916 : RemoveOGREmptyTable();
3383 : }
3384 :
3385 3190 : CPLErr eErr = IFlushCacheWithErrCode(bAtClosing);
3386 :
3387 3190 : FlushMetadata();
3388 :
3389 3190 : if (eAccess == GA_Update || !m_bMetadataDirty)
3390 : {
3391 : // Needed again as above IFlushCacheWithErrCode()
3392 : // may have call GDALGeoPackageRasterBand::InvalidateStatistics()
3393 : // which modifies metadata
3394 3190 : SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
3395 : }
3396 :
3397 3190 : return eErr;
3398 : }
3399 :
3400 5459 : CPLErr GDALGeoPackageDataset::IFlushCacheWithErrCode(bool bAtClosing)
3401 :
3402 : {
3403 5459 : if (m_bInFlushCache)
3404 2202 : return CE_None;
3405 3257 : m_bInFlushCache = true;
3406 3257 : if (hDB && eAccess == GA_ReadOnly && bAtClosing)
3407 : {
3408 : // Clean-up metadata that will go to PAM by removing items that
3409 : // are reconstructed.
3410 2394 : CPLStringList aosMD;
3411 1850 : for (CSLConstList papszIter = GetMetadata(); papszIter && *papszIter;
3412 : ++papszIter)
3413 : {
3414 653 : char *pszKey = nullptr;
3415 653 : CPLParseNameValue(*papszIter, &pszKey);
3416 1306 : if (pszKey &&
3417 653 : (EQUAL(pszKey, "AREA_OR_POINT") ||
3418 499 : EQUAL(pszKey, "IDENTIFIER") || EQUAL(pszKey, "DESCRIPTION") ||
3419 267 : EQUAL(pszKey, "ZOOM_LEVEL") ||
3420 683 : STARTS_WITH(pszKey, "GPKG_METADATA_ITEM_")))
3421 : {
3422 : // remove it
3423 : }
3424 : else
3425 : {
3426 30 : aosMD.AddString(*papszIter);
3427 : }
3428 653 : CPLFree(pszKey);
3429 : }
3430 1197 : oMDMD.SetMetadata(aosMD.List());
3431 1197 : oMDMD.SetMetadata(nullptr, GDAL_MDD_IMAGE_STRUCTURE);
3432 :
3433 2394 : GDALPamDataset::FlushCache(bAtClosing);
3434 : }
3435 : else
3436 : {
3437 : // Short circuit GDALPamDataset to avoid serialization to .aux.xml
3438 2060 : GDALDataset::FlushCache(bAtClosing);
3439 : }
3440 :
3441 7807 : for (auto &poLayer : m_apoLayers)
3442 : {
3443 4550 : poLayer->RunDeferredCreationIfNecessary();
3444 4550 : poLayer->CreateSpatialIndexIfNecessary();
3445 : }
3446 :
3447 : // Update raster table last_change column in gpkg_contents if needed
3448 3257 : if (m_bHasModifiedTiles)
3449 : {
3450 546 : for (int i = 1; i <= nBands; ++i)
3451 : {
3452 : auto poBand =
3453 362 : cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
3454 362 : if (!poBand->HaveStatsMetadataBeenSetInThisSession())
3455 : {
3456 348 : poBand->InvalidateStatistics();
3457 348 : if (psPam && psPam->pszPamFilename)
3458 348 : VSIUnlink(psPam->pszPamFilename);
3459 : }
3460 : }
3461 :
3462 184 : UpdateGpkgContentsLastChange(m_osRasterTable);
3463 :
3464 184 : m_bHasModifiedTiles = false;
3465 : }
3466 :
3467 3257 : CPLErr eErr = FlushTiles();
3468 :
3469 3257 : m_bInFlushCache = false;
3470 3257 : return eErr;
3471 : }
3472 :
3473 : /************************************************************************/
3474 : /* GetCurrentDateEscapedSQL() */
3475 : /************************************************************************/
3476 :
3477 2495 : std::string GDALGeoPackageDataset::GetCurrentDateEscapedSQL()
3478 : {
3479 : const char *pszCurrentDate =
3480 2495 : CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
3481 2495 : if (pszCurrentDate)
3482 14 : return '\'' + SQLEscapeLiteral(pszCurrentDate) + '\'';
3483 2488 : return "strftime('%Y-%m-%dT%H:%M:%fZ','now')";
3484 : }
3485 :
3486 : /************************************************************************/
3487 : /* UpdateGpkgContentsLastChange() */
3488 : /************************************************************************/
3489 :
3490 : OGRErr
3491 1057 : GDALGeoPackageDataset::UpdateGpkgContentsLastChange(const char *pszTableName)
3492 : {
3493 : char *pszSQL =
3494 1057 : sqlite3_mprintf("UPDATE gpkg_contents SET "
3495 : "last_change = %s "
3496 : "WHERE lower(table_name) = lower('%q')",
3497 2114 : GetCurrentDateEscapedSQL().c_str(), pszTableName);
3498 1057 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3499 1057 : sqlite3_free(pszSQL);
3500 1057 : return eErr;
3501 : }
3502 :
3503 : /************************************************************************/
3504 : /* IBuildOverviews() */
3505 : /************************************************************************/
3506 :
3507 20 : CPLErr GDALGeoPackageDataset::IBuildOverviews(
3508 : const char *pszResampling, int nOverviews, const int *panOverviewList,
3509 : int nBandsIn, const int * /*panBandList*/, GDALProgressFunc pfnProgress,
3510 : void *pProgressData, CSLConstList papszOptions)
3511 : {
3512 20 : if (GetAccess() != GA_Update)
3513 : {
3514 1 : CPLError(CE_Failure, CPLE_NotSupported,
3515 : "Overview building not supported on a database opened in "
3516 : "read-only mode");
3517 1 : return CE_Failure;
3518 : }
3519 19 : if (m_poParentDS != nullptr)
3520 : {
3521 1 : CPLError(CE_Failure, CPLE_NotSupported,
3522 : "Overview building not supported on overview dataset");
3523 1 : return CE_Failure;
3524 : }
3525 :
3526 18 : if (nOverviews == 0)
3527 : {
3528 5 : for (auto &poOvrDS : m_apoOverviewDS)
3529 3 : poOvrDS->FlushCache(false);
3530 :
3531 2 : SoftStartTransaction();
3532 :
3533 2 : if (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
3534 : {
3535 1 : char *pszSQL = sqlite3_mprintf(
3536 : "DELETE FROM gpkg_2d_gridded_tile_ancillary WHERE id IN "
3537 : "(SELECT y.id FROM \"%w\" x "
3538 : "JOIN gpkg_2d_gridded_tile_ancillary y "
3539 : "ON x.id = y.tpudt_id AND y.tpudt_name = '%q' AND "
3540 : "x.zoom_level < %d)",
3541 : m_osRasterTable.c_str(), m_osRasterTable.c_str(), m_nZoomLevel);
3542 1 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3543 1 : sqlite3_free(pszSQL);
3544 1 : if (eErr != OGRERR_NONE)
3545 : {
3546 0 : SoftRollbackTransaction();
3547 0 : return CE_Failure;
3548 : }
3549 : }
3550 :
3551 : char *pszSQL =
3552 2 : sqlite3_mprintf("DELETE FROM \"%w\" WHERE zoom_level < %d",
3553 : m_osRasterTable.c_str(), m_nZoomLevel);
3554 2 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3555 2 : sqlite3_free(pszSQL);
3556 2 : if (eErr != OGRERR_NONE)
3557 : {
3558 0 : SoftRollbackTransaction();
3559 0 : return CE_Failure;
3560 : }
3561 :
3562 2 : SoftCommitTransaction();
3563 :
3564 2 : return CE_None;
3565 : }
3566 :
3567 16 : if (nBandsIn != nBands)
3568 : {
3569 0 : CPLError(CE_Failure, CPLE_NotSupported,
3570 : "Generation of overviews in GPKG only"
3571 : "supported when operating on all bands.");
3572 0 : return CE_Failure;
3573 : }
3574 :
3575 16 : if (m_apoOverviewDS.empty())
3576 : {
3577 0 : CPLError(CE_Failure, CPLE_AppDefined,
3578 : "Image too small to support overviews");
3579 0 : return CE_Failure;
3580 : }
3581 :
3582 16 : FlushCache(false);
3583 60 : for (int i = 0; i < nOverviews; i++)
3584 : {
3585 47 : if (panOverviewList[i] < 2)
3586 : {
3587 1 : CPLError(CE_Failure, CPLE_IllegalArg,
3588 : "Overview factor must be >= 2");
3589 1 : return CE_Failure;
3590 : }
3591 :
3592 46 : bool bFound = false;
3593 46 : int jCandidate = -1;
3594 46 : int nMaxOvFactor = 0;
3595 196 : for (int j = 0; j < static_cast<int>(m_apoOverviewDS.size()); j++)
3596 : {
3597 190 : const auto poODS = m_apoOverviewDS[j].get();
3598 : const int nOvFactor =
3599 190 : static_cast<int>(0.5 + poODS->m_gt[1] / m_gt[1]);
3600 :
3601 190 : nMaxOvFactor = nOvFactor;
3602 :
3603 190 : if (nOvFactor == panOverviewList[i])
3604 : {
3605 40 : bFound = true;
3606 40 : break;
3607 : }
3608 :
3609 150 : if (jCandidate < 0 && nOvFactor > panOverviewList[i])
3610 1 : jCandidate = j;
3611 : }
3612 :
3613 46 : if (!bFound)
3614 : {
3615 : /* Mostly for debug */
3616 6 : if (!CPLTestBool(CPLGetConfigOption(
3617 : "ALLOW_GPKG_ZOOM_OTHER_EXTENSION", "YES")))
3618 : {
3619 2 : CPLString osOvrList;
3620 4 : for (const auto &poODS : m_apoOverviewDS)
3621 : {
3622 : const int nOvFactor =
3623 2 : static_cast<int>(0.5 + poODS->m_gt[1] / m_gt[1]);
3624 :
3625 2 : if (!osOvrList.empty())
3626 0 : osOvrList += ' ';
3627 2 : osOvrList += CPLSPrintf("%d", nOvFactor);
3628 : }
3629 2 : CPLError(CE_Failure, CPLE_NotSupported,
3630 : "Only overviews %s can be computed",
3631 : osOvrList.c_str());
3632 2 : return CE_Failure;
3633 : }
3634 : else
3635 : {
3636 4 : int nOvFactor = panOverviewList[i];
3637 4 : if (jCandidate < 0)
3638 3 : jCandidate = static_cast<int>(m_apoOverviewDS.size());
3639 :
3640 4 : int nOvXSize = std::max(1, GetRasterXSize() / nOvFactor);
3641 4 : int nOvYSize = std::max(1, GetRasterYSize() / nOvFactor);
3642 4 : if (!(jCandidate == static_cast<int>(m_apoOverviewDS.size()) &&
3643 5 : nOvFactor == 2 * nMaxOvFactor) &&
3644 1 : !m_bZoomOther)
3645 : {
3646 1 : CPLError(CE_Warning, CPLE_AppDefined,
3647 : "Use of overview factor %d causes gpkg_zoom_other "
3648 : "extension to be needed",
3649 : nOvFactor);
3650 1 : RegisterZoomOtherExtension();
3651 1 : m_bZoomOther = true;
3652 : }
3653 :
3654 4 : SoftStartTransaction();
3655 :
3656 4 : CPLAssert(jCandidate > 0);
3657 : const int nNewZoomLevel =
3658 4 : m_apoOverviewDS[jCandidate - 1]->m_nZoomLevel;
3659 :
3660 : char *pszSQL;
3661 : OGRErr eErr;
3662 24 : for (int k = 0; k <= jCandidate; k++)
3663 : {
3664 60 : pszSQL = sqlite3_mprintf(
3665 : "UPDATE gpkg_tile_matrix SET zoom_level = %d "
3666 : "WHERE lower(table_name) = lower('%q') AND zoom_level "
3667 : "= %d",
3668 20 : m_nZoomLevel - k + 1, m_osRasterTable.c_str(),
3669 20 : m_nZoomLevel - k);
3670 20 : eErr = SQLCommand(hDB, pszSQL);
3671 20 : sqlite3_free(pszSQL);
3672 20 : if (eErr != OGRERR_NONE)
3673 : {
3674 0 : SoftRollbackTransaction();
3675 0 : return CE_Failure;
3676 : }
3677 :
3678 : pszSQL =
3679 20 : sqlite3_mprintf("UPDATE \"%w\" SET zoom_level = %d "
3680 : "WHERE zoom_level = %d",
3681 : m_osRasterTable.c_str(),
3682 20 : m_nZoomLevel - k + 1, m_nZoomLevel - k);
3683 20 : eErr = SQLCommand(hDB, pszSQL);
3684 20 : sqlite3_free(pszSQL);
3685 20 : if (eErr != OGRERR_NONE)
3686 : {
3687 0 : SoftRollbackTransaction();
3688 0 : return CE_Failure;
3689 : }
3690 : }
3691 :
3692 4 : double dfGDALMinX = m_gt[0];
3693 4 : double dfGDALMinY = m_gt[3] + nRasterYSize * m_gt[5];
3694 4 : double dfGDALMaxX = m_gt[0] + nRasterXSize * m_gt[1];
3695 4 : double dfGDALMaxY = m_gt[3];
3696 4 : double dfPixelXSizeZoomLevel = m_gt[1] * nOvFactor;
3697 4 : double dfPixelYSizeZoomLevel = fabs(m_gt[5]) * nOvFactor;
3698 : int nTileWidth, nTileHeight;
3699 4 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3700 4 : int nTileMatrixWidth = DIV_ROUND_UP(nOvXSize, nTileWidth);
3701 4 : int nTileMatrixHeight = DIV_ROUND_UP(nOvYSize, nTileHeight);
3702 4 : pszSQL = sqlite3_mprintf(
3703 : "INSERT INTO gpkg_tile_matrix "
3704 : "(table_name,zoom_level,matrix_width,matrix_height,tile_"
3705 : "width,tile_height,pixel_x_size,pixel_y_size) VALUES "
3706 : "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
3707 : m_osRasterTable.c_str(), nNewZoomLevel, nTileMatrixWidth,
3708 : nTileMatrixHeight, nTileWidth, nTileHeight,
3709 : dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel);
3710 4 : eErr = SQLCommand(hDB, pszSQL);
3711 4 : sqlite3_free(pszSQL);
3712 4 : if (eErr != OGRERR_NONE)
3713 : {
3714 0 : SoftRollbackTransaction();
3715 0 : return CE_Failure;
3716 : }
3717 :
3718 4 : SoftCommitTransaction();
3719 :
3720 4 : m_nZoomLevel++; /* this change our zoom level as well as
3721 : previous overviews */
3722 20 : for (int k = 0; k < jCandidate; k++)
3723 16 : m_apoOverviewDS[k]->m_nZoomLevel++;
3724 :
3725 4 : auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
3726 4 : poOvrDS->ShareLockWithParentDataset(this);
3727 4 : poOvrDS->InitRaster(
3728 : this, m_osRasterTable, nNewZoomLevel, nBands, m_dfTMSMinX,
3729 : m_dfTMSMaxY, dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel,
3730 : nTileWidth, nTileHeight, nTileMatrixWidth,
3731 : nTileMatrixHeight, dfGDALMinX, dfGDALMinY, dfGDALMaxX,
3732 : dfGDALMaxY);
3733 4 : m_apoOverviewDS.insert(m_apoOverviewDS.begin() + jCandidate,
3734 8 : std::move(poOvrDS));
3735 : }
3736 : }
3737 : }
3738 :
3739 : GDALRasterBand ***papapoOverviewBands = static_cast<GDALRasterBand ***>(
3740 13 : CPLCalloc(sizeof(GDALRasterBand **), nBands));
3741 13 : CPLErr eErr = CE_None;
3742 49 : for (int iBand = 0; eErr == CE_None && iBand < nBands; iBand++)
3743 : {
3744 72 : papapoOverviewBands[iBand] = static_cast<GDALRasterBand **>(
3745 36 : CPLCalloc(sizeof(GDALRasterBand *), nOverviews));
3746 36 : int iCurOverview = 0;
3747 185 : for (int i = 0; i < nOverviews; i++)
3748 : {
3749 149 : bool bFound = false;
3750 724 : for (const auto &poODS : m_apoOverviewDS)
3751 : {
3752 : const int nOvFactor =
3753 724 : static_cast<int>(0.5 + poODS->m_gt[1] / m_gt[1]);
3754 :
3755 724 : if (nOvFactor == panOverviewList[i])
3756 : {
3757 298 : papapoOverviewBands[iBand][iCurOverview] =
3758 149 : poODS->GetRasterBand(iBand + 1);
3759 149 : iCurOverview++;
3760 149 : bFound = true;
3761 149 : break;
3762 : }
3763 : }
3764 149 : if (!bFound)
3765 : {
3766 0 : CPLError(CE_Failure, CPLE_AppDefined,
3767 : "Could not find dataset corresponding to ov factor %d",
3768 0 : panOverviewList[i]);
3769 0 : eErr = CE_Failure;
3770 : }
3771 : }
3772 36 : if (eErr == CE_None)
3773 : {
3774 36 : CPLAssert(iCurOverview == nOverviews);
3775 : }
3776 : }
3777 :
3778 13 : if (eErr == CE_None)
3779 13 : eErr = GDALRegenerateOverviewsMultiBand(
3780 13 : nBands, papoBands, nOverviews, papapoOverviewBands, pszResampling,
3781 : pfnProgress, pProgressData, papszOptions);
3782 :
3783 49 : for (int iBand = 0; iBand < nBands; iBand++)
3784 : {
3785 36 : CPLFree(papapoOverviewBands[iBand]);
3786 : }
3787 13 : CPLFree(papapoOverviewBands);
3788 :
3789 13 : return eErr;
3790 : }
3791 :
3792 : /************************************************************************/
3793 : /* GetFileList() */
3794 : /************************************************************************/
3795 :
3796 38 : char **GDALGeoPackageDataset::GetFileList()
3797 : {
3798 38 : TryLoadXML();
3799 38 : return GDALPamDataset::GetFileList();
3800 : }
3801 :
3802 : /************************************************************************/
3803 : /* GetMetadataDomainList() */
3804 : /************************************************************************/
3805 :
3806 53 : char **GDALGeoPackageDataset::GetMetadataDomainList()
3807 : {
3808 53 : GetMetadata();
3809 53 : if (!m_osRasterTable.empty())
3810 5 : GetMetadata("GEOPACKAGE");
3811 53 : return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
3812 53 : TRUE, GDAL_MDD_SUBDATASETS, nullptr);
3813 : }
3814 :
3815 : /************************************************************************/
3816 : /* CheckMetadataDomain() */
3817 : /************************************************************************/
3818 :
3819 6875 : const char *GDALGeoPackageDataset::CheckMetadataDomain(const char *pszDomain)
3820 : {
3821 7063 : if (pszDomain != nullptr && EQUAL(pszDomain, "GEOPACKAGE") &&
3822 188 : m_osRasterTable.empty())
3823 : {
3824 4 : CPLError(
3825 : CE_Warning, CPLE_IllegalArg,
3826 : "Using GEOPACKAGE for a non-raster geopackage is not supported. "
3827 : "Using default domain instead");
3828 4 : return nullptr;
3829 : }
3830 6871 : return pszDomain;
3831 : }
3832 :
3833 : /************************************************************************/
3834 : /* HasMetadataTables() */
3835 : /************************************************************************/
3836 :
3837 6542 : bool GDALGeoPackageDataset::HasMetadataTables() const
3838 : {
3839 6542 : if (m_nHasMetadataTables < 0)
3840 : {
3841 : const int nCount =
3842 2483 : SQLGetInteger(hDB,
3843 : "SELECT COUNT(*) FROM sqlite_master WHERE name IN "
3844 : "('gpkg_metadata', 'gpkg_metadata_reference') "
3845 : "AND type IN ('table', 'view')",
3846 : nullptr);
3847 2483 : m_nHasMetadataTables = nCount == 2;
3848 : }
3849 6542 : return CPL_TO_BOOL(m_nHasMetadataTables);
3850 : }
3851 :
3852 : /************************************************************************/
3853 : /* HasDataColumnsTable() */
3854 : /************************************************************************/
3855 :
3856 1424 : bool GDALGeoPackageDataset::HasDataColumnsTable() const
3857 : {
3858 2848 : const int nCount = SQLGetInteger(
3859 1424 : hDB,
3860 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_data_columns'"
3861 : "AND type IN ('table', 'view')",
3862 : nullptr);
3863 1424 : return nCount == 1;
3864 : }
3865 :
3866 : /************************************************************************/
3867 : /* HasDataColumnConstraintsTable() */
3868 : /************************************************************************/
3869 :
3870 168 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTable() const
3871 : {
3872 168 : const int nCount = SQLGetInteger(hDB,
3873 : "SELECT 1 FROM sqlite_master WHERE name = "
3874 : "'gpkg_data_column_constraints'"
3875 : "AND type IN ('table', 'view')",
3876 : nullptr);
3877 168 : return nCount == 1;
3878 : }
3879 :
3880 : /************************************************************************/
3881 : /* HasDataColumnConstraintsTableGPKG_1_0() */
3882 : /************************************************************************/
3883 :
3884 111 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTableGPKG_1_0() const
3885 : {
3886 111 : if (m_nApplicationId != GP10_APPLICATION_ID)
3887 109 : return false;
3888 : // In GPKG 1.0, the columns were named minIsInclusive, maxIsInclusive
3889 : // They were changed in 1.1 to min_is_inclusive, max_is_inclusive
3890 2 : bool bRet = false;
3891 2 : sqlite3_stmt *hSQLStmt = nullptr;
3892 2 : int rc = sqlite3_prepare_v2(hDB,
3893 : "SELECT minIsInclusive, maxIsInclusive FROM "
3894 : "gpkg_data_column_constraints",
3895 : -1, &hSQLStmt, nullptr);
3896 2 : if (rc == SQLITE_OK)
3897 : {
3898 2 : bRet = true;
3899 2 : sqlite3_finalize(hSQLStmt);
3900 : }
3901 2 : return bRet;
3902 : }
3903 :
3904 : /************************************************************************/
3905 : /* CreateColumnsTableAndColumnConstraintsTablesIfNecessary() */
3906 : /************************************************************************/
3907 :
3908 53 : bool GDALGeoPackageDataset::
3909 : CreateColumnsTableAndColumnConstraintsTablesIfNecessary()
3910 : {
3911 53 : if (!HasDataColumnsTable())
3912 : {
3913 : // Geopackage < 1.3 had
3914 : // CONSTRAINT fk_gdc_tn FOREIGN KEY (table_name) REFERENCES
3915 : // gpkg_contents(table_name) instead of the unique constraint.
3916 13 : if (OGRERR_NONE !=
3917 13 : SQLCommand(
3918 : GetDB(),
3919 : "CREATE TABLE gpkg_data_columns ("
3920 : "table_name TEXT NOT NULL,"
3921 : "column_name TEXT NOT NULL,"
3922 : "name TEXT,"
3923 : "title TEXT,"
3924 : "description TEXT,"
3925 : "mime_type TEXT,"
3926 : "constraint_name TEXT,"
3927 : "CONSTRAINT pk_gdc PRIMARY KEY (table_name, column_name),"
3928 : "CONSTRAINT gdc_tn UNIQUE (table_name, name));"))
3929 : {
3930 0 : return false;
3931 : }
3932 : }
3933 53 : if (!HasDataColumnConstraintsTable())
3934 : {
3935 28 : const char *min_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
3936 14 : ? "min_is_inclusive"
3937 : : "minIsInclusive";
3938 28 : const char *max_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
3939 14 : ? "max_is_inclusive"
3940 : : "maxIsInclusive";
3941 :
3942 : const std::string osSQL(
3943 : CPLSPrintf("CREATE TABLE gpkg_data_column_constraints ("
3944 : "constraint_name TEXT NOT NULL,"
3945 : "constraint_type TEXT NOT NULL,"
3946 : "value TEXT,"
3947 : "min NUMERIC,"
3948 : "%s BOOLEAN,"
3949 : "max NUMERIC,"
3950 : "%s BOOLEAN,"
3951 : "description TEXT,"
3952 : "CONSTRAINT gdcc_ntv UNIQUE (constraint_name, "
3953 : "constraint_type, value));",
3954 14 : min_is_inclusive, max_is_inclusive));
3955 14 : if (OGRERR_NONE != SQLCommand(GetDB(), osSQL.c_str()))
3956 : {
3957 0 : return false;
3958 : }
3959 : }
3960 53 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
3961 : {
3962 0 : return false;
3963 : }
3964 53 : if (SQLGetInteger(GetDB(),
3965 : "SELECT 1 FROM gpkg_extensions WHERE "
3966 : "table_name = 'gpkg_data_columns'",
3967 53 : nullptr) != 1)
3968 : {
3969 14 : if (OGRERR_NONE !=
3970 14 : SQLCommand(
3971 : GetDB(),
3972 : "INSERT INTO gpkg_extensions "
3973 : "(table_name,column_name,extension_name,definition,scope) "
3974 : "VALUES ('gpkg_data_columns', NULL, 'gpkg_schema', "
3975 : "'http://www.geopackage.org/spec121/#extension_schema', "
3976 : "'read-write')"))
3977 : {
3978 0 : return false;
3979 : }
3980 : }
3981 53 : if (SQLGetInteger(GetDB(),
3982 : "SELECT 1 FROM gpkg_extensions WHERE "
3983 : "table_name = 'gpkg_data_column_constraints'",
3984 53 : nullptr) != 1)
3985 : {
3986 14 : if (OGRERR_NONE !=
3987 14 : SQLCommand(
3988 : GetDB(),
3989 : "INSERT INTO gpkg_extensions "
3990 : "(table_name,column_name,extension_name,definition,scope) "
3991 : "VALUES ('gpkg_data_column_constraints', NULL, 'gpkg_schema', "
3992 : "'http://www.geopackage.org/spec121/#extension_schema', "
3993 : "'read-write')"))
3994 : {
3995 0 : return false;
3996 : }
3997 : }
3998 :
3999 53 : return true;
4000 : }
4001 :
4002 : /************************************************************************/
4003 : /* HasGpkgextRelationsTable() */
4004 : /************************************************************************/
4005 :
4006 1464 : bool GDALGeoPackageDataset::HasGpkgextRelationsTable() const
4007 : {
4008 2928 : const int nCount = SQLGetInteger(
4009 1464 : hDB,
4010 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkgext_relations'"
4011 : "AND type IN ('table', 'view')",
4012 : nullptr);
4013 1464 : return nCount == 1;
4014 : }
4015 :
4016 : /************************************************************************/
4017 : /* CreateRelationsTableIfNecessary() */
4018 : /************************************************************************/
4019 :
4020 11 : bool GDALGeoPackageDataset::CreateRelationsTableIfNecessary()
4021 : {
4022 11 : if (HasGpkgextRelationsTable())
4023 : {
4024 6 : return true;
4025 : }
4026 :
4027 5 : if (OGRERR_NONE !=
4028 5 : SQLCommand(GetDB(), "CREATE TABLE gpkgext_relations ("
4029 : "id INTEGER PRIMARY KEY AUTOINCREMENT,"
4030 : "base_table_name TEXT NOT NULL,"
4031 : "base_primary_column TEXT NOT NULL DEFAULT 'id',"
4032 : "related_table_name TEXT NOT NULL,"
4033 : "related_primary_column TEXT NOT NULL DEFAULT 'id',"
4034 : "relation_name TEXT NOT NULL,"
4035 : "mapping_table_name TEXT NOT NULL UNIQUE);"))
4036 : {
4037 0 : return false;
4038 : }
4039 :
4040 5 : return true;
4041 : }
4042 :
4043 : /************************************************************************/
4044 : /* HasQGISLayerStyles() */
4045 : /************************************************************************/
4046 :
4047 11 : bool GDALGeoPackageDataset::HasQGISLayerStyles() const
4048 : {
4049 : // QGIS layer_styles extension:
4050 : // https://github.com/pka/qgpkg/blob/master/qgis_geopackage_extension.md
4051 11 : bool bRet = false;
4052 : const int nCount =
4053 11 : SQLGetInteger(hDB,
4054 : "SELECT 1 FROM sqlite_master WHERE name = 'layer_styles'"
4055 : "AND type = 'table'",
4056 : nullptr);
4057 11 : if (nCount == 1)
4058 : {
4059 1 : sqlite3_stmt *hSQLStmt = nullptr;
4060 2 : int rc = sqlite3_prepare_v2(
4061 1 : hDB, "SELECT f_table_name, f_geometry_column FROM layer_styles", -1,
4062 : &hSQLStmt, nullptr);
4063 1 : if (rc == SQLITE_OK)
4064 : {
4065 1 : bRet = true;
4066 1 : sqlite3_finalize(hSQLStmt);
4067 : }
4068 : }
4069 11 : return bRet;
4070 : }
4071 :
4072 : /************************************************************************/
4073 : /* GetMetadata() */
4074 : /************************************************************************/
4075 :
4076 4509 : CSLConstList GDALGeoPackageDataset::GetMetadata(const char *pszDomain)
4077 :
4078 : {
4079 4509 : pszDomain = CheckMetadataDomain(pszDomain);
4080 4509 : if (pszDomain != nullptr && EQUAL(pszDomain, GDAL_MDD_SUBDATASETS))
4081 83 : return m_aosSubDatasets.List();
4082 :
4083 4426 : if (m_bHasReadMetadataFromStorage)
4084 2013 : return GDALPamDataset::GetMetadata(pszDomain);
4085 :
4086 2413 : m_bHasReadMetadataFromStorage = true;
4087 :
4088 2413 : TryLoadXML();
4089 :
4090 2413 : if (!HasMetadataTables())
4091 1823 : return GDALPamDataset::GetMetadata(pszDomain);
4092 :
4093 590 : char *pszSQL = nullptr;
4094 590 : if (!m_osRasterTable.empty())
4095 : {
4096 178 : pszSQL = sqlite3_mprintf(
4097 : "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
4098 : "mdr.reference_scope FROM gpkg_metadata md "
4099 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4100 : "WHERE "
4101 : "(mdr.reference_scope = 'geopackage' OR "
4102 : "(mdr.reference_scope = 'table' AND lower(mdr.table_name) = "
4103 : "lower('%q'))) ORDER BY md.id "
4104 : "LIMIT 1000", // to avoid denial of service
4105 : m_osRasterTable.c_str());
4106 : }
4107 : else
4108 : {
4109 412 : pszSQL = sqlite3_mprintf(
4110 : "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
4111 : "mdr.reference_scope FROM gpkg_metadata md "
4112 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4113 : "WHERE "
4114 : "mdr.reference_scope = 'geopackage' ORDER BY md.id "
4115 : "LIMIT 1000" // to avoid denial of service
4116 : );
4117 : }
4118 :
4119 1180 : auto oResult = SQLQuery(hDB, pszSQL);
4120 590 : sqlite3_free(pszSQL);
4121 590 : if (!oResult)
4122 : {
4123 0 : return GDALPamDataset::GetMetadata(pszDomain);
4124 : }
4125 :
4126 590 : char **papszMetadata = CSLDuplicate(GDALPamDataset::GetMetadata());
4127 :
4128 : /* GDAL metadata */
4129 788 : for (int i = 0; i < oResult->RowCount(); i++)
4130 : {
4131 198 : const char *pszMetadata = oResult->GetValue(0, i);
4132 198 : const char *pszMDStandardURI = oResult->GetValue(1, i);
4133 198 : const char *pszMimeType = oResult->GetValue(2, i);
4134 198 : const char *pszReferenceScope = oResult->GetValue(3, i);
4135 198 : if (pszMetadata && pszMDStandardURI && pszMimeType &&
4136 198 : pszReferenceScope && EQUAL(pszMDStandardURI, "http://gdal.org") &&
4137 182 : EQUAL(pszMimeType, "text/xml"))
4138 : {
4139 182 : CPLXMLNode *psXMLNode = CPLParseXMLString(pszMetadata);
4140 182 : if (psXMLNode)
4141 : {
4142 364 : GDALMultiDomainMetadata oLocalMDMD;
4143 182 : oLocalMDMD.XMLInit(psXMLNode, FALSE);
4144 349 : if (!m_osRasterTable.empty() &&
4145 167 : EQUAL(pszReferenceScope, "geopackage"))
4146 : {
4147 6 : oMDMD.SetMetadata(oLocalMDMD.GetMetadata(), "GEOPACKAGE");
4148 : }
4149 : else
4150 : {
4151 : papszMetadata =
4152 176 : CSLMerge(papszMetadata, oLocalMDMD.GetMetadata());
4153 176 : CSLConstList papszDomainList = oLocalMDMD.GetDomainList();
4154 176 : CSLConstList papszIter = papszDomainList;
4155 473 : while (papszIter && *papszIter)
4156 : {
4157 297 : if (EQUAL(*papszIter, GDAL_MDD_IMAGE_STRUCTURE))
4158 : {
4159 : CSLConstList papszMD =
4160 134 : oLocalMDMD.GetMetadata(*papszIter);
4161 : const char *pszBAND_COUNT =
4162 134 : CSLFetchNameValue(papszMD, "BAND_COUNT");
4163 134 : if (pszBAND_COUNT)
4164 132 : m_nBandCountFromMetadata = atoi(pszBAND_COUNT);
4165 :
4166 : const char *pszCOLOR_TABLE =
4167 134 : CSLFetchNameValue(papszMD, "COLOR_TABLE");
4168 134 : if (pszCOLOR_TABLE)
4169 : {
4170 : const CPLStringList aosTokens(
4171 : CSLTokenizeString2(pszCOLOR_TABLE, "{,",
4172 26 : 0));
4173 13 : if ((aosTokens.size() % 4) == 0)
4174 : {
4175 13 : const int nColors = aosTokens.size() / 4;
4176 : m_poCTFromMetadata =
4177 13 : std::make_unique<GDALColorTable>();
4178 3341 : for (int iColor = 0; iColor < nColors;
4179 : ++iColor)
4180 : {
4181 : GDALColorEntry sEntry;
4182 3328 : sEntry.c1 = static_cast<short>(
4183 3328 : atoi(aosTokens[4 * iColor + 0]));
4184 3328 : sEntry.c2 = static_cast<short>(
4185 3328 : atoi(aosTokens[4 * iColor + 1]));
4186 3328 : sEntry.c3 = static_cast<short>(
4187 3328 : atoi(aosTokens[4 * iColor + 2]));
4188 3328 : sEntry.c4 = static_cast<short>(
4189 3328 : atoi(aosTokens[4 * iColor + 3]));
4190 3328 : m_poCTFromMetadata->SetColorEntry(
4191 : iColor, &sEntry);
4192 : }
4193 : }
4194 : }
4195 :
4196 : const char *pszTILE_FORMAT =
4197 134 : CSLFetchNameValue(papszMD, "TILE_FORMAT");
4198 134 : if (pszTILE_FORMAT)
4199 : {
4200 8 : m_osTFFromMetadata = pszTILE_FORMAT;
4201 8 : oMDMD.SetMetadataItem("TILE_FORMAT",
4202 : pszTILE_FORMAT,
4203 : GDAL_MDD_IMAGE_STRUCTURE);
4204 : }
4205 :
4206 : const char *pszNodataValue =
4207 134 : CSLFetchNameValue(papszMD, "NODATA_VALUE");
4208 134 : if (pszNodataValue)
4209 : {
4210 2 : m_osNodataValueFromMetadata = pszNodataValue;
4211 : }
4212 : }
4213 :
4214 163 : else if (!EQUAL(*papszIter, "") &&
4215 18 : !STARTS_WITH(*papszIter, "BAND_"))
4216 : {
4217 12 : oMDMD.SetMetadata(
4218 6 : oLocalMDMD.GetMetadata(*papszIter), *papszIter);
4219 : }
4220 297 : papszIter++;
4221 : }
4222 : }
4223 182 : CPLDestroyXMLNode(psXMLNode);
4224 : }
4225 : }
4226 : }
4227 :
4228 590 : GDALPamDataset::SetMetadata(papszMetadata);
4229 590 : CSLDestroy(papszMetadata);
4230 590 : papszMetadata = nullptr;
4231 :
4232 : /* Add non-GDAL metadata now */
4233 590 : int nNonGDALMDILocal = 1;
4234 590 : int nNonGDALMDIGeopackage = 1;
4235 788 : for (int i = 0; i < oResult->RowCount(); i++)
4236 : {
4237 198 : const char *pszMetadata = oResult->GetValue(0, i);
4238 198 : const char *pszMDStandardURI = oResult->GetValue(1, i);
4239 198 : const char *pszMimeType = oResult->GetValue(2, i);
4240 198 : const char *pszReferenceScope = oResult->GetValue(3, i);
4241 198 : if (pszMetadata == nullptr || pszMDStandardURI == nullptr ||
4242 198 : pszMimeType == nullptr || pszReferenceScope == nullptr)
4243 : {
4244 : // should not happen as there are NOT NULL constraints
4245 : // But a database could lack such NOT NULL constraints or have
4246 : // large values that would cause a memory allocation failure.
4247 0 : continue;
4248 : }
4249 198 : int bIsGPKGScope = EQUAL(pszReferenceScope, "geopackage");
4250 198 : if (EQUAL(pszMDStandardURI, "http://gdal.org") &&
4251 182 : EQUAL(pszMimeType, "text/xml"))
4252 182 : continue;
4253 :
4254 16 : if (!m_osRasterTable.empty() && bIsGPKGScope)
4255 : {
4256 8 : oMDMD.SetMetadataItem(
4257 : CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDIGeopackage),
4258 : pszMetadata, "GEOPACKAGE");
4259 8 : nNonGDALMDIGeopackage++;
4260 : }
4261 : /*else if( strcmp( pszMDStandardURI, "http://www.isotc211.org/2005/gmd"
4262 : ) == 0 && strcmp( pszMimeType, "text/xml" ) == 0 )
4263 : {
4264 : char* apszMD[2];
4265 : apszMD[0] = (char*)pszMetadata;
4266 : apszMD[1] = NULL;
4267 : oMDMD.SetMetadata(apszMD, "xml:MD_Metadata");
4268 : }*/
4269 : else
4270 : {
4271 8 : oMDMD.SetMetadataItem(
4272 : CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDILocal),
4273 : pszMetadata);
4274 8 : nNonGDALMDILocal++;
4275 : }
4276 : }
4277 :
4278 590 : return GDALPamDataset::GetMetadata(pszDomain);
4279 : }
4280 :
4281 : /************************************************************************/
4282 : /* WriteMetadata() */
4283 : /************************************************************************/
4284 :
4285 880 : void GDALGeoPackageDataset::WriteMetadata(
4286 : CPLXMLNode *psXMLNode, /* will be destroyed by the method */
4287 : const char *pszTableName)
4288 : {
4289 880 : const bool bIsEmpty = (psXMLNode == nullptr);
4290 880 : if (!HasMetadataTables())
4291 : {
4292 673 : if (bIsEmpty || !CreateMetadataTables())
4293 : {
4294 312 : CPLDestroyXMLNode(psXMLNode);
4295 312 : return;
4296 : }
4297 : }
4298 :
4299 568 : char *pszXML = nullptr;
4300 568 : if (!bIsEmpty)
4301 : {
4302 : CPLXMLNode *psMasterXMLNode =
4303 411 : CPLCreateXMLNode(nullptr, CXT_Element, "GDALMultiDomainMetadata");
4304 411 : psMasterXMLNode->psChild = psXMLNode;
4305 411 : pszXML = CPLSerializeXMLTree(psMasterXMLNode);
4306 411 : CPLDestroyXMLNode(psMasterXMLNode);
4307 : }
4308 : // cppcheck-suppress uselessAssignmentPtrArg
4309 568 : psXMLNode = nullptr;
4310 :
4311 568 : char *pszSQL = nullptr;
4312 568 : if (pszTableName && pszTableName[0] != '\0')
4313 : {
4314 419 : pszSQL = sqlite3_mprintf(
4315 : "SELECT md.id FROM gpkg_metadata md "
4316 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4317 : "WHERE md.md_scope = 'dataset' AND "
4318 : "md.md_standard_uri='http://gdal.org' "
4319 : "AND md.mime_type='text/xml' AND mdr.reference_scope = 'table' AND "
4320 : "lower(mdr.table_name) = lower('%q')",
4321 : pszTableName);
4322 : }
4323 : else
4324 : {
4325 149 : pszSQL = sqlite3_mprintf(
4326 : "SELECT md.id FROM gpkg_metadata md "
4327 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4328 : "WHERE md.md_scope = 'dataset' AND "
4329 : "md.md_standard_uri='http://gdal.org' "
4330 : "AND md.mime_type='text/xml' AND mdr.reference_scope = "
4331 : "'geopackage'");
4332 : }
4333 : OGRErr err;
4334 568 : int mdId = SQLGetInteger(hDB, pszSQL, &err);
4335 568 : if (err != OGRERR_NONE)
4336 535 : mdId = -1;
4337 568 : sqlite3_free(pszSQL);
4338 :
4339 568 : if (bIsEmpty)
4340 : {
4341 157 : if (mdId >= 0)
4342 : {
4343 6 : SQLCommand(
4344 : hDB,
4345 : CPLSPrintf(
4346 : "DELETE FROM gpkg_metadata_reference WHERE md_file_id = %d",
4347 : mdId));
4348 6 : SQLCommand(
4349 : hDB,
4350 : CPLSPrintf("DELETE FROM gpkg_metadata WHERE id = %d", mdId));
4351 : }
4352 : }
4353 : else
4354 : {
4355 411 : if (mdId >= 0)
4356 : {
4357 27 : pszSQL = sqlite3_mprintf(
4358 : "UPDATE gpkg_metadata SET metadata = '%q' WHERE id = %d",
4359 : pszXML, mdId);
4360 : }
4361 : else
4362 : {
4363 : pszSQL =
4364 384 : sqlite3_mprintf("INSERT INTO gpkg_metadata (md_scope, "
4365 : "md_standard_uri, mime_type, metadata) VALUES "
4366 : "('dataset','http://gdal.org','text/xml','%q')",
4367 : pszXML);
4368 : }
4369 411 : SQLCommand(hDB, pszSQL);
4370 411 : sqlite3_free(pszSQL);
4371 :
4372 411 : CPLFree(pszXML);
4373 :
4374 411 : if (mdId < 0)
4375 : {
4376 384 : const sqlite_int64 nFID = sqlite3_last_insert_rowid(hDB);
4377 384 : if (pszTableName != nullptr && pszTableName[0] != '\0')
4378 : {
4379 372 : pszSQL = sqlite3_mprintf(
4380 : "INSERT INTO gpkg_metadata_reference (reference_scope, "
4381 : "table_name, timestamp, md_file_id) VALUES "
4382 : "('table', '%q', %s, %d)",
4383 744 : pszTableName, GetCurrentDateEscapedSQL().c_str(),
4384 : static_cast<int>(nFID));
4385 : }
4386 : else
4387 : {
4388 12 : pszSQL = sqlite3_mprintf(
4389 : "INSERT INTO gpkg_metadata_reference (reference_scope, "
4390 : "timestamp, md_file_id) VALUES "
4391 : "('geopackage', %s, %d)",
4392 24 : GetCurrentDateEscapedSQL().c_str(), static_cast<int>(nFID));
4393 : }
4394 : }
4395 : else
4396 : {
4397 27 : pszSQL = sqlite3_mprintf("UPDATE gpkg_metadata_reference SET "
4398 : "timestamp = %s WHERE md_file_id = %d",
4399 54 : GetCurrentDateEscapedSQL().c_str(), mdId);
4400 : }
4401 411 : SQLCommand(hDB, pszSQL);
4402 411 : sqlite3_free(pszSQL);
4403 : }
4404 : }
4405 :
4406 : /************************************************************************/
4407 : /* CreateMetadataTables() */
4408 : /************************************************************************/
4409 :
4410 380 : bool GDALGeoPackageDataset::CreateMetadataTables()
4411 : {
4412 : const bool bCreateTriggers =
4413 380 : CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "NO"));
4414 :
4415 : /* From C.10. gpkg_metadata Table 35. gpkg_metadata Table Definition SQL */
4416 : CPLString osSQL = "CREATE TABLE gpkg_metadata ("
4417 : "id INTEGER CONSTRAINT m_pk PRIMARY KEY ASC NOT NULL,"
4418 : "md_scope TEXT NOT NULL DEFAULT 'dataset',"
4419 : "md_standard_uri TEXT NOT NULL,"
4420 : "mime_type TEXT NOT NULL DEFAULT 'text/xml',"
4421 : "metadata TEXT NOT NULL DEFAULT ''"
4422 760 : ")";
4423 :
4424 : /* From D.2. metadata Table 40. metadata Trigger Definition SQL */
4425 380 : const char *pszMetadataTriggers =
4426 : "CREATE TRIGGER 'gpkg_metadata_md_scope_insert' "
4427 : "BEFORE INSERT ON 'gpkg_metadata' "
4428 : "FOR EACH ROW BEGIN "
4429 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata violates "
4430 : "constraint: md_scope must be one of undefined | fieldSession | "
4431 : "collectionSession | series | dataset | featureType | feature | "
4432 : "attributeType | attribute | tile | model | catalogue | schema | "
4433 : "taxonomy software | service | collectionHardware | "
4434 : "nonGeographicDataset | dimensionGroup') "
4435 : "WHERE NOT(NEW.md_scope IN "
4436 : "('undefined','fieldSession','collectionSession','series','dataset', "
4437 : "'featureType','feature','attributeType','attribute','tile','model', "
4438 : "'catalogue','schema','taxonomy','software','service', "
4439 : "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
4440 : "END; "
4441 : "CREATE TRIGGER 'gpkg_metadata_md_scope_update' "
4442 : "BEFORE UPDATE OF 'md_scope' ON 'gpkg_metadata' "
4443 : "FOR EACH ROW BEGIN "
4444 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata violates "
4445 : "constraint: md_scope must be one of undefined | fieldSession | "
4446 : "collectionSession | series | dataset | featureType | feature | "
4447 : "attributeType | attribute | tile | model | catalogue | schema | "
4448 : "taxonomy software | service | collectionHardware | "
4449 : "nonGeographicDataset | dimensionGroup') "
4450 : "WHERE NOT(NEW.md_scope IN "
4451 : "('undefined','fieldSession','collectionSession','series','dataset', "
4452 : "'featureType','feature','attributeType','attribute','tile','model', "
4453 : "'catalogue','schema','taxonomy','software','service', "
4454 : "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
4455 : "END";
4456 380 : if (bCreateTriggers)
4457 : {
4458 0 : osSQL += ";";
4459 0 : osSQL += pszMetadataTriggers;
4460 : }
4461 :
4462 : /* From C.11. gpkg_metadata_reference Table 36. gpkg_metadata_reference
4463 : * Table Definition SQL */
4464 : osSQL += ";"
4465 : "CREATE TABLE gpkg_metadata_reference ("
4466 : "reference_scope TEXT NOT NULL,"
4467 : "table_name TEXT,"
4468 : "column_name TEXT,"
4469 : "row_id_value INTEGER,"
4470 : "timestamp DATETIME NOT NULL DEFAULT "
4471 : "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
4472 : "md_file_id INTEGER NOT NULL,"
4473 : "md_parent_id INTEGER,"
4474 : "CONSTRAINT crmr_mfi_fk FOREIGN KEY (md_file_id) REFERENCES "
4475 : "gpkg_metadata(id),"
4476 : "CONSTRAINT crmr_mpi_fk FOREIGN KEY (md_parent_id) REFERENCES "
4477 : "gpkg_metadata(id)"
4478 380 : ")";
4479 :
4480 : /* From D.3. metadata_reference Table 41. gpkg_metadata_reference Trigger
4481 : * Definition SQL */
4482 380 : const char *pszMetadataReferenceTriggers =
4483 : "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_insert' "
4484 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4485 : "FOR EACH ROW BEGIN "
4486 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4487 : "violates constraint: reference_scope must be one of \"geopackage\", "
4488 : "table\", \"column\", \"row\", \"row/col\"') "
4489 : "WHERE NOT NEW.reference_scope IN "
4490 : "('geopackage','table','column','row','row/col'); "
4491 : "END; "
4492 : "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_update' "
4493 : "BEFORE UPDATE OF 'reference_scope' ON 'gpkg_metadata_reference' "
4494 : "FOR EACH ROW BEGIN "
4495 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4496 : "violates constraint: reference_scope must be one of \"geopackage\", "
4497 : "\"table\", \"column\", \"row\", \"row/col\"') "
4498 : "WHERE NOT NEW.reference_scope IN "
4499 : "('geopackage','table','column','row','row/col'); "
4500 : "END; "
4501 : "CREATE TRIGGER 'gpkg_metadata_reference_column_name_insert' "
4502 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4503 : "FOR EACH ROW BEGIN "
4504 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4505 : "violates constraint: column name must be NULL when reference_scope "
4506 : "is \"geopackage\", \"table\" or \"row\"') "
4507 : "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
4508 : "AND NEW.column_name IS NOT NULL); "
4509 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4510 : "violates constraint: column name must be defined for the specified "
4511 : "table when reference_scope is \"column\" or \"row/col\"') "
4512 : "WHERE (NEW.reference_scope IN ('column','row/col') "
4513 : "AND NOT NEW.table_name IN ( "
4514 : "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
4515 : "AND name = NEW.table_name "
4516 : "AND sql LIKE ('%' || NEW.column_name || '%'))); "
4517 : "END; "
4518 : "CREATE TRIGGER 'gpkg_metadata_reference_column_name_update' "
4519 : "BEFORE UPDATE OF column_name ON 'gpkg_metadata_reference' "
4520 : "FOR EACH ROW BEGIN "
4521 : "SELECT RAISE(ABORT, 'update 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, 'update 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_row_id_value_insert' "
4536 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4537 : "FOR EACH ROW BEGIN "
4538 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4539 : "violates constraint: row_id_value must be NULL when reference_scope "
4540 : "is \"geopackage\", \"table\" or \"column\"') "
4541 : "WHERE NEW.reference_scope IN ('geopackage','table','column') "
4542 : "AND NEW.row_id_value IS NOT NULL; "
4543 : "END; "
4544 : "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_update' "
4545 : "BEFORE UPDATE OF 'row_id_value' ON 'gpkg_metadata_reference' "
4546 : "FOR EACH ROW BEGIN "
4547 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4548 : "violates constraint: row_id_value must be NULL when reference_scope "
4549 : "is \"geopackage\", \"table\" or \"column\"') "
4550 : "WHERE NEW.reference_scope IN ('geopackage','table','column') "
4551 : "AND NEW.row_id_value IS NOT NULL; "
4552 : "END; "
4553 : "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_insert' "
4554 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4555 : "FOR EACH ROW BEGIN "
4556 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4557 : "violates constraint: timestamp must be a valid time in ISO 8601 "
4558 : "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
4559 : "WHERE NOT (NEW.timestamp GLOB "
4560 : "'[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-"
4561 : "5][0-9].[0-9][0-9][0-9]Z' "
4562 : "AND strftime('%s',NEW.timestamp) NOT NULL); "
4563 : "END; "
4564 : "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_update' "
4565 : "BEFORE UPDATE OF 'timestamp' ON 'gpkg_metadata_reference' "
4566 : "FOR EACH ROW BEGIN "
4567 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4568 : "violates constraint: timestamp must be a valid time in ISO 8601 "
4569 : "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
4570 : "WHERE NOT (NEW.timestamp GLOB "
4571 : "'[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-"
4572 : "5][0-9].[0-9][0-9][0-9]Z' "
4573 : "AND strftime('%s',NEW.timestamp) NOT NULL); "
4574 : "END";
4575 380 : if (bCreateTriggers)
4576 : {
4577 0 : osSQL += ";";
4578 0 : osSQL += pszMetadataReferenceTriggers;
4579 : }
4580 :
4581 380 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
4582 2 : return false;
4583 :
4584 378 : osSQL += ";";
4585 : osSQL += "INSERT INTO gpkg_extensions "
4586 : "(table_name, column_name, extension_name, definition, scope) "
4587 : "VALUES "
4588 : "('gpkg_metadata', NULL, 'gpkg_metadata', "
4589 : "'http://www.geopackage.org/spec120/#extension_metadata', "
4590 378 : "'read-write')";
4591 :
4592 378 : osSQL += ";";
4593 : osSQL += "INSERT INTO gpkg_extensions "
4594 : "(table_name, column_name, extension_name, definition, scope) "
4595 : "VALUES "
4596 : "('gpkg_metadata_reference', NULL, 'gpkg_metadata', "
4597 : "'http://www.geopackage.org/spec120/#extension_metadata', "
4598 378 : "'read-write')";
4599 :
4600 378 : const bool bOK = SQLCommand(hDB, osSQL) == OGRERR_NONE;
4601 378 : m_nHasMetadataTables = bOK;
4602 378 : return bOK;
4603 : }
4604 :
4605 : /************************************************************************/
4606 : /* FlushMetadata() */
4607 : /************************************************************************/
4608 :
4609 9358 : void GDALGeoPackageDataset::FlushMetadata()
4610 : {
4611 9358 : if (!m_bMetadataDirty || m_poParentDS != nullptr ||
4612 440 : m_nCreateMetadataTables == FALSE)
4613 8924 : return;
4614 434 : m_bMetadataDirty = false;
4615 :
4616 434 : if (eAccess == GA_ReadOnly)
4617 : {
4618 3 : return;
4619 : }
4620 :
4621 431 : bool bCanWriteAreaOrPoint =
4622 860 : !m_bGridCellEncodingAsCO &&
4623 429 : (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT);
4624 431 : if (!m_osRasterTable.empty())
4625 : {
4626 : const char *pszIdentifier =
4627 147 : GDALGeoPackageDataset::GetMetadataItem("IDENTIFIER");
4628 : const char *pszDescription =
4629 147 : GDALGeoPackageDataset::GetMetadataItem("DESCRIPTION");
4630 176 : if (!m_bIdentifierAsCO && pszIdentifier != nullptr &&
4631 29 : pszIdentifier != m_osIdentifier)
4632 : {
4633 14 : m_osIdentifier = pszIdentifier;
4634 : char *pszSQL =
4635 14 : sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
4636 : "WHERE lower(table_name) = lower('%q')",
4637 : pszIdentifier, m_osRasterTable.c_str());
4638 14 : SQLCommand(hDB, pszSQL);
4639 14 : sqlite3_free(pszSQL);
4640 : }
4641 154 : if (!m_bDescriptionAsCO && pszDescription != nullptr &&
4642 7 : pszDescription != m_osDescription)
4643 : {
4644 7 : m_osDescription = pszDescription;
4645 : char *pszSQL =
4646 7 : sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
4647 : "WHERE lower(table_name) = lower('%q')",
4648 : pszDescription, m_osRasterTable.c_str());
4649 7 : SQLCommand(hDB, pszSQL);
4650 7 : sqlite3_free(pszSQL);
4651 : }
4652 147 : if (bCanWriteAreaOrPoint)
4653 : {
4654 : const char *pszAreaOrPoint =
4655 28 : GDALGeoPackageDataset::GetMetadataItem(GDALMD_AREA_OR_POINT);
4656 28 : if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_AREA))
4657 : {
4658 23 : bCanWriteAreaOrPoint = false;
4659 23 : char *pszSQL = sqlite3_mprintf(
4660 : "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
4661 : "grid_cell_encoding = 'grid-value-is-area' WHERE "
4662 : "lower(tile_matrix_set_name) = lower('%q')",
4663 : m_osRasterTable.c_str());
4664 23 : SQLCommand(hDB, pszSQL);
4665 23 : sqlite3_free(pszSQL);
4666 : }
4667 5 : else if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_POINT))
4668 : {
4669 1 : bCanWriteAreaOrPoint = false;
4670 1 : char *pszSQL = sqlite3_mprintf(
4671 : "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
4672 : "grid_cell_encoding = 'grid-value-is-center' WHERE "
4673 : "lower(tile_matrix_set_name) = lower('%q')",
4674 : m_osRasterTable.c_str());
4675 1 : SQLCommand(hDB, pszSQL);
4676 1 : sqlite3_free(pszSQL);
4677 : }
4678 : }
4679 : }
4680 :
4681 431 : char **papszMDDup = nullptr;
4682 208 : for (const char *pszKeyValue :
4683 847 : cpl::Iterate(GDALGeoPackageDataset::GetMetadata()))
4684 : {
4685 208 : if (STARTS_WITH_CI(pszKeyValue, "IDENTIFIER="))
4686 29 : continue;
4687 179 : if (STARTS_WITH_CI(pszKeyValue, "DESCRIPTION="))
4688 8 : continue;
4689 171 : if (STARTS_WITH_CI(pszKeyValue, "ZOOM_LEVEL="))
4690 14 : continue;
4691 157 : if (STARTS_WITH_CI(pszKeyValue, "GPKG_METADATA_ITEM_"))
4692 4 : continue;
4693 153 : if ((m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT) &&
4694 29 : !bCanWriteAreaOrPoint &&
4695 26 : STARTS_WITH_CI(pszKeyValue, GDALMD_AREA_OR_POINT))
4696 : {
4697 26 : continue;
4698 : }
4699 127 : papszMDDup = CSLInsertString(papszMDDup, -1, pszKeyValue);
4700 : }
4701 :
4702 431 : CPLXMLNode *psXMLNode = nullptr;
4703 : {
4704 431 : GDALMultiDomainMetadata oLocalMDMD;
4705 431 : CSLConstList papszDomainList = oMDMD.GetDomainList();
4706 431 : CSLConstList papszIter = papszDomainList;
4707 431 : oLocalMDMD.SetMetadata(papszMDDup);
4708 766 : while (papszIter && *papszIter)
4709 : {
4710 335 : if (!EQUAL(*papszIter, "") &&
4711 162 : !EQUAL(*papszIter, GDAL_MDD_IMAGE_STRUCTURE) &&
4712 15 : !EQUAL(*papszIter, "GEOPACKAGE"))
4713 : {
4714 8 : oLocalMDMD.SetMetadata(oMDMD.GetMetadata(*papszIter),
4715 : *papszIter);
4716 : }
4717 335 : papszIter++;
4718 : }
4719 431 : if (m_nBandCountFromMetadata > 0)
4720 : {
4721 77 : oLocalMDMD.SetMetadataItem(
4722 : "BAND_COUNT", CPLSPrintf("%d", m_nBandCountFromMetadata),
4723 : GDAL_MDD_IMAGE_STRUCTURE);
4724 77 : if (nBands == 1)
4725 : {
4726 53 : const auto poCT = GetRasterBand(1)->GetColorTable();
4727 53 : if (poCT)
4728 : {
4729 16 : std::string osVal("{");
4730 8 : const int nColorCount = poCT->GetColorEntryCount();
4731 2056 : for (int i = 0; i < nColorCount; ++i)
4732 : {
4733 2048 : if (i > 0)
4734 2040 : osVal += ',';
4735 2048 : const GDALColorEntry *psEntry = poCT->GetColorEntry(i);
4736 : osVal +=
4737 2048 : CPLSPrintf("{%d,%d,%d,%d}", psEntry->c1,
4738 2048 : psEntry->c2, psEntry->c3, psEntry->c4);
4739 : }
4740 8 : osVal += '}';
4741 8 : oLocalMDMD.SetMetadataItem("COLOR_TABLE", osVal.c_str(),
4742 : GDAL_MDD_IMAGE_STRUCTURE);
4743 : }
4744 : }
4745 77 : if (nBands == 1)
4746 : {
4747 53 : const char *pszTILE_FORMAT = nullptr;
4748 53 : switch (m_eTF)
4749 : {
4750 0 : case GPKG_TF_PNG_JPEG:
4751 0 : pszTILE_FORMAT = "JPEG_PNG";
4752 0 : break;
4753 47 : case GPKG_TF_PNG:
4754 47 : break;
4755 0 : case GPKG_TF_PNG8:
4756 0 : pszTILE_FORMAT = "PNG8";
4757 0 : break;
4758 3 : case GPKG_TF_JPEG:
4759 3 : pszTILE_FORMAT = "JPEG";
4760 3 : break;
4761 3 : case GPKG_TF_WEBP:
4762 3 : pszTILE_FORMAT = "WEBP";
4763 3 : break;
4764 0 : case GPKG_TF_PNG_16BIT:
4765 0 : break;
4766 0 : case GPKG_TF_TIFF_32BIT_FLOAT:
4767 0 : break;
4768 : }
4769 53 : if (pszTILE_FORMAT)
4770 6 : oLocalMDMD.SetMetadataItem("TILE_FORMAT", pszTILE_FORMAT,
4771 : GDAL_MDD_IMAGE_STRUCTURE);
4772 : }
4773 : }
4774 578 : if (GetRasterCount() > 0 &&
4775 147 : GetRasterBand(1)->GetRasterDataType() == GDT_UInt8)
4776 : {
4777 117 : int bHasNoData = FALSE;
4778 : const double dfNoDataValue =
4779 117 : GetRasterBand(1)->GetNoDataValue(&bHasNoData);
4780 117 : if (bHasNoData)
4781 : {
4782 3 : oLocalMDMD.SetMetadataItem("NODATA_VALUE",
4783 : CPLSPrintf("%.17g", dfNoDataValue),
4784 : GDAL_MDD_IMAGE_STRUCTURE);
4785 : }
4786 : }
4787 683 : for (int i = 1; i <= GetRasterCount(); ++i)
4788 : {
4789 : auto poBand =
4790 252 : cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
4791 252 : poBand->AddImplicitStatistics(false);
4792 252 : CSLConstList papszMD = GetRasterBand(i)->GetMetadata();
4793 252 : poBand->AddImplicitStatistics(true);
4794 252 : if (papszMD)
4795 : {
4796 15 : oLocalMDMD.SetMetadata(papszMD, CPLSPrintf("BAND_%d", i));
4797 : }
4798 : }
4799 431 : psXMLNode = oLocalMDMD.Serialize();
4800 : }
4801 :
4802 431 : CSLDestroy(papszMDDup);
4803 431 : papszMDDup = nullptr;
4804 :
4805 431 : WriteMetadata(psXMLNode, m_osRasterTable.c_str());
4806 :
4807 431 : if (!m_osRasterTable.empty())
4808 : {
4809 : CSLConstList papszGeopackageMD =
4810 147 : GDALGeoPackageDataset::GetMetadata("GEOPACKAGE");
4811 :
4812 147 : papszMDDup = nullptr;
4813 147 : for (CSLConstList papszIter = papszGeopackageMD;
4814 156 : papszIter && *papszIter; ++papszIter)
4815 : {
4816 9 : papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
4817 : }
4818 :
4819 294 : GDALMultiDomainMetadata oLocalMDMD;
4820 147 : oLocalMDMD.SetMetadata(papszMDDup);
4821 147 : CSLDestroy(papszMDDup);
4822 147 : papszMDDup = nullptr;
4823 147 : psXMLNode = oLocalMDMD.Serialize();
4824 :
4825 147 : WriteMetadata(psXMLNode, nullptr);
4826 : }
4827 :
4828 733 : for (auto &poLayer : m_apoLayers)
4829 : {
4830 302 : const char *pszIdentifier = poLayer->GetMetadataItem("IDENTIFIER");
4831 302 : const char *pszDescription = poLayer->GetMetadataItem("DESCRIPTION");
4832 302 : if (pszIdentifier != nullptr)
4833 : {
4834 : char *pszSQL =
4835 3 : sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
4836 : "WHERE lower(table_name) = lower('%q')",
4837 : pszIdentifier, poLayer->GetName());
4838 3 : SQLCommand(hDB, pszSQL);
4839 3 : sqlite3_free(pszSQL);
4840 : }
4841 302 : if (pszDescription != nullptr)
4842 : {
4843 : char *pszSQL =
4844 3 : sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
4845 : "WHERE lower(table_name) = lower('%q')",
4846 : pszDescription, poLayer->GetName());
4847 3 : SQLCommand(hDB, pszSQL);
4848 3 : sqlite3_free(pszSQL);
4849 : }
4850 :
4851 302 : papszMDDup = nullptr;
4852 1101 : for (CSLConstList papszIter = poLayer->GetMetadata();
4853 1101 : papszIter && *papszIter; ++papszIter)
4854 : {
4855 799 : if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
4856 3 : continue;
4857 796 : if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
4858 3 : continue;
4859 793 : if (STARTS_WITH_CI(*papszIter, "OLMD_FID64="))
4860 0 : continue;
4861 793 : papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
4862 : }
4863 :
4864 : {
4865 302 : GDALMultiDomainMetadata oLocalMDMD;
4866 302 : char **papszDomainList = poLayer->GetMetadataDomainList();
4867 302 : char **papszIter = papszDomainList;
4868 302 : oLocalMDMD.SetMetadata(papszMDDup);
4869 667 : while (papszIter && *papszIter)
4870 : {
4871 365 : if (!EQUAL(*papszIter, ""))
4872 76 : oLocalMDMD.SetMetadata(poLayer->GetMetadata(*papszIter),
4873 : *papszIter);
4874 365 : papszIter++;
4875 : }
4876 302 : CSLDestroy(papszDomainList);
4877 302 : psXMLNode = oLocalMDMD.Serialize();
4878 : }
4879 :
4880 302 : CSLDestroy(papszMDDup);
4881 302 : papszMDDup = nullptr;
4882 :
4883 302 : WriteMetadata(psXMLNode, poLayer->GetName());
4884 : }
4885 : }
4886 :
4887 : /************************************************************************/
4888 : /* GetMetadataItem() */
4889 : /************************************************************************/
4890 :
4891 2209 : const char *GDALGeoPackageDataset::GetMetadataItem(const char *pszName,
4892 : const char *pszDomain)
4893 : {
4894 2209 : pszDomain = CheckMetadataDomain(pszDomain);
4895 2209 : return CSLFetchNameValue(GetMetadata(pszDomain), pszName);
4896 : }
4897 :
4898 : /************************************************************************/
4899 : /* SetMetadata() */
4900 : /************************************************************************/
4901 :
4902 136 : CPLErr GDALGeoPackageDataset::SetMetadata(CSLConstList papszMetadata,
4903 : const char *pszDomain)
4904 : {
4905 136 : pszDomain = CheckMetadataDomain(pszDomain);
4906 136 : m_bMetadataDirty = true;
4907 136 : GetMetadata(); /* force loading from storage if needed */
4908 136 : return GDALPamDataset::SetMetadata(papszMetadata, pszDomain);
4909 : }
4910 :
4911 : /************************************************************************/
4912 : /* SetMetadataItem() */
4913 : /************************************************************************/
4914 :
4915 21 : CPLErr GDALGeoPackageDataset::SetMetadataItem(const char *pszName,
4916 : const char *pszValue,
4917 : const char *pszDomain)
4918 : {
4919 21 : pszDomain = CheckMetadataDomain(pszDomain);
4920 21 : m_bMetadataDirty = true;
4921 21 : GetMetadata(); /* force loading from storage if needed */
4922 21 : return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
4923 : }
4924 :
4925 : /************************************************************************/
4926 : /* Create() */
4927 : /************************************************************************/
4928 :
4929 1151 : int GDALGeoPackageDataset::Create(const char *pszFilename, int nXSize,
4930 : int nYSize, int nBandsIn, GDALDataType eDT,
4931 : CSLConstList papszOptions)
4932 : {
4933 2302 : CPLString osCommand;
4934 :
4935 : /* First, ensure there isn't any such file yet. */
4936 : VSIStatBufL sStatBuf;
4937 :
4938 1151 : if (nBandsIn != 0)
4939 : {
4940 232 : if (eDT == GDT_UInt8)
4941 : {
4942 162 : if (nBandsIn != 1 && nBandsIn != 2 && nBandsIn != 3 &&
4943 : nBandsIn != 4)
4944 : {
4945 1 : CPLError(CE_Failure, CPLE_NotSupported,
4946 : "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), "
4947 : "3 (RGB) or 4 (RGBA) band dataset supported for "
4948 : "Byte datatype");
4949 1 : return FALSE;
4950 : }
4951 : }
4952 70 : else if (eDT == GDT_Int16 || eDT == GDT_UInt16 || eDT == GDT_Float32)
4953 : {
4954 43 : if (nBandsIn != 1)
4955 : {
4956 3 : CPLError(CE_Failure, CPLE_NotSupported,
4957 : "Only single band dataset supported for non Byte "
4958 : "datatype");
4959 3 : return FALSE;
4960 : }
4961 : }
4962 : else
4963 : {
4964 27 : CPLError(CE_Failure, CPLE_NotSupported,
4965 : "Only Byte, Int16, UInt16 or Float32 supported");
4966 27 : return FALSE;
4967 : }
4968 : }
4969 :
4970 1120 : const size_t nFilenameLen = strlen(pszFilename);
4971 1120 : const bool bGpkgZip =
4972 1115 : (nFilenameLen > strlen(".gpkg.zip") &&
4973 2235 : !STARTS_WITH(pszFilename, "/vsizip/") &&
4974 1115 : EQUAL(pszFilename + nFilenameLen - strlen(".gpkg.zip"), ".gpkg.zip"));
4975 :
4976 : const bool bUseTempFile =
4977 1121 : bGpkgZip || (CPLTestBool(CPLGetConfigOption(
4978 1 : "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", "NO")) &&
4979 1 : (VSIHasOptimizedReadMultiRange(pszFilename) != FALSE ||
4980 1 : EQUAL(CPLGetConfigOption(
4981 : "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", ""),
4982 1120 : "FORCED")));
4983 :
4984 1120 : bool bFileExists = false;
4985 1120 : if (VSIStatL(pszFilename, &sStatBuf) == 0)
4986 : {
4987 10 : bFileExists = true;
4988 20 : if (nBandsIn == 0 || bUseTempFile ||
4989 10 : !CPLTestBool(
4990 : CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")))
4991 : {
4992 0 : CPLError(CE_Failure, CPLE_AppDefined,
4993 : "A file system object called '%s' already exists.",
4994 : pszFilename);
4995 :
4996 0 : return FALSE;
4997 : }
4998 : }
4999 :
5000 1120 : if (bUseTempFile)
5001 : {
5002 3 : if (bGpkgZip)
5003 : {
5004 2 : std::string osFilenameInZip(CPLGetFilename(pszFilename));
5005 2 : osFilenameInZip.resize(osFilenameInZip.size() - strlen(".zip"));
5006 : m_osFinalFilename =
5007 2 : std::string("/vsizip/{") + pszFilename + "}/" + osFilenameInZip;
5008 : }
5009 : else
5010 : {
5011 1 : m_osFinalFilename = pszFilename;
5012 : }
5013 3 : m_pszFilename = CPLStrdup(
5014 6 : CPLGenerateTempFilenameSafe(CPLGetFilename(pszFilename)).c_str());
5015 3 : CPLDebug("GPKG", "Creating temporary file %s", m_pszFilename);
5016 : }
5017 : else
5018 : {
5019 1117 : m_pszFilename = CPLStrdup(pszFilename);
5020 : }
5021 1120 : m_bNew = true;
5022 1120 : eAccess = GA_Update;
5023 1120 : m_bDateTimeWithTZ =
5024 1120 : EQUAL(CSLFetchNameValueDef(papszOptions, "DATETIME_FORMAT", "WITH_TZ"),
5025 : "WITH_TZ");
5026 :
5027 : // for test/debug purposes only. true is the nominal value
5028 1120 : m_bPNGSupports2Bands =
5029 1120 : CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_2BANDS", "TRUE"));
5030 1120 : m_bPNGSupportsCT =
5031 1120 : CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_CT", "TRUE"));
5032 :
5033 1120 : if (!OpenOrCreateDB(bFileExists
5034 : ? SQLITE_OPEN_READWRITE
5035 : : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE))
5036 8 : return FALSE;
5037 :
5038 : /* Default to synchronous=off for performance for new file */
5039 2214 : if (!bFileExists &&
5040 1102 : CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
5041 : {
5042 585 : SQLCommand(hDB, "PRAGMA synchronous = OFF");
5043 : }
5044 :
5045 : /* OGR UTF-8 support. If we set the UTF-8 Pragma early on, it */
5046 : /* will be written into the main file and supported henceforth */
5047 1112 : SQLCommand(hDB, "PRAGMA encoding = \"UTF-8\"");
5048 :
5049 1112 : if (bFileExists)
5050 : {
5051 10 : VSILFILE *fp = VSIFOpenL(pszFilename, "rb");
5052 10 : if (fp)
5053 : {
5054 : GByte abyHeader[100];
5055 10 : VSIFReadL(abyHeader, 1, sizeof(abyHeader), fp);
5056 10 : VSIFCloseL(fp);
5057 :
5058 10 : memcpy(&m_nApplicationId, abyHeader + knApplicationIdPos, 4);
5059 10 : m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
5060 10 : memcpy(&m_nUserVersion, abyHeader + knUserVersionPos, 4);
5061 10 : m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
5062 :
5063 10 : if (m_nApplicationId == GP10_APPLICATION_ID)
5064 : {
5065 0 : CPLDebug("GPKG", "GeoPackage v1.0");
5066 : }
5067 10 : else if (m_nApplicationId == GP11_APPLICATION_ID)
5068 : {
5069 0 : CPLDebug("GPKG", "GeoPackage v1.1");
5070 : }
5071 10 : else if (m_nApplicationId == GPKG_APPLICATION_ID &&
5072 10 : m_nUserVersion >= GPKG_1_2_VERSION)
5073 : {
5074 10 : CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
5075 10 : (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
5076 : }
5077 : }
5078 :
5079 10 : DetectSpatialRefSysColumns();
5080 : }
5081 :
5082 1112 : const char *pszVersion = CSLFetchNameValue(papszOptions, "VERSION");
5083 1112 : if (pszVersion && !EQUAL(pszVersion, "AUTO"))
5084 : {
5085 41 : if (EQUAL(pszVersion, "1.0"))
5086 : {
5087 2 : m_nApplicationId = GP10_APPLICATION_ID;
5088 2 : m_nUserVersion = 0;
5089 : }
5090 39 : else if (EQUAL(pszVersion, "1.1"))
5091 : {
5092 1 : m_nApplicationId = GP11_APPLICATION_ID;
5093 1 : m_nUserVersion = 0;
5094 : }
5095 38 : else if (EQUAL(pszVersion, "1.2"))
5096 : {
5097 15 : m_nApplicationId = GPKG_APPLICATION_ID;
5098 15 : m_nUserVersion = GPKG_1_2_VERSION;
5099 : }
5100 23 : else if (EQUAL(pszVersion, "1.3"))
5101 : {
5102 3 : m_nApplicationId = GPKG_APPLICATION_ID;
5103 3 : m_nUserVersion = GPKG_1_3_VERSION;
5104 : }
5105 20 : else if (EQUAL(pszVersion, "1.4"))
5106 : {
5107 20 : m_nApplicationId = GPKG_APPLICATION_ID;
5108 20 : m_nUserVersion = GPKG_1_4_VERSION;
5109 : }
5110 : }
5111 :
5112 1112 : SoftStartTransaction();
5113 :
5114 2224 : CPLString osSQL;
5115 1112 : if (!bFileExists)
5116 : {
5117 : /* Requirement 10: A GeoPackage SHALL include a gpkg_spatial_ref_sys
5118 : * table */
5119 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5120 : osSQL = "CREATE TABLE gpkg_spatial_ref_sys ("
5121 : "srs_name TEXT NOT NULL,"
5122 : "srs_id INTEGER NOT NULL PRIMARY KEY,"
5123 : "organization TEXT NOT NULL,"
5124 : "organization_coordsys_id INTEGER NOT NULL,"
5125 : "definition TEXT NOT NULL,"
5126 1102 : "description TEXT";
5127 1102 : if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "CRS_WKT_EXTENSION",
5128 1289 : "NO")) ||
5129 187 : (nBandsIn != 0 && eDT != GDT_UInt8))
5130 : {
5131 42 : m_bHasDefinition12_063 = true;
5132 42 : osSQL += ", definition_12_063 TEXT NOT NULL";
5133 42 : if (m_nUserVersion >= GPKG_1_4_VERSION)
5134 : {
5135 40 : osSQL += ", epoch DOUBLE";
5136 40 : m_bHasEpochColumn = true;
5137 : }
5138 : }
5139 : osSQL += ")"
5140 : ";"
5141 : /* Requirement 11: The gpkg_spatial_ref_sys table in a
5142 : GeoPackage SHALL */
5143 : /* contain a record for EPSG:4326, the geodetic WGS84 SRS */
5144 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5145 :
5146 : "INSERT INTO gpkg_spatial_ref_sys ("
5147 : "srs_name, srs_id, organization, organization_coordsys_id, "
5148 1102 : "definition, description";
5149 1102 : if (m_bHasDefinition12_063)
5150 42 : osSQL += ", definition_12_063";
5151 : osSQL +=
5152 : ") VALUES ("
5153 : "'WGS 84 geodetic', 4326, 'EPSG', 4326, '"
5154 : "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS "
5155 : "84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],"
5156 : "AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY["
5157 : "\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY["
5158 : "\"EPSG\",\"9122\"]],AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\","
5159 : "EAST],AUTHORITY[\"EPSG\",\"4326\"]]"
5160 : "', 'longitude/latitude coordinates in decimal degrees on the WGS "
5161 1102 : "84 spheroid'";
5162 1102 : if (m_bHasDefinition12_063)
5163 : osSQL +=
5164 : ", 'GEODCRS[\"WGS 84\", DATUM[\"World Geodetic System 1984\", "
5165 : "ELLIPSOID[\"WGS 84\",6378137, 298.257223563, "
5166 : "LENGTHUNIT[\"metre\", 1.0]]], PRIMEM[\"Greenwich\", 0.0, "
5167 : "ANGLEUNIT[\"degree\",0.0174532925199433]], CS[ellipsoidal, "
5168 : "2], AXIS[\"latitude\", north, ORDER[1]], AXIS[\"longitude\", "
5169 : "east, ORDER[2]], ANGLEUNIT[\"degree\", 0.0174532925199433], "
5170 42 : "ID[\"EPSG\", 4326]]'";
5171 : osSQL +=
5172 : ")"
5173 : ";"
5174 : /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
5175 : SHALL */
5176 : /* contain a record with an srs_id of -1, an organization of “NONE”,
5177 : */
5178 : /* an organization_coordsys_id of -1, and definition “undefined” */
5179 : /* for undefined Cartesian coordinate reference systems */
5180 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5181 : "INSERT INTO gpkg_spatial_ref_sys ("
5182 : "srs_name, srs_id, organization, organization_coordsys_id, "
5183 1102 : "definition, description";
5184 1102 : if (m_bHasDefinition12_063)
5185 42 : osSQL += ", definition_12_063";
5186 : osSQL += ") VALUES ("
5187 : "'Undefined Cartesian SRS', -1, 'NONE', -1, 'undefined', "
5188 1102 : "'undefined Cartesian coordinate reference system'";
5189 1102 : if (m_bHasDefinition12_063)
5190 42 : osSQL += ", 'undefined'";
5191 : osSQL +=
5192 : ")"
5193 : ";"
5194 : /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
5195 : SHALL */
5196 : /* contain a record with an srs_id of 0, an organization of “NONE”,
5197 : */
5198 : /* an organization_coordsys_id of 0, and definition “undefined” */
5199 : /* for undefined geographic coordinate reference systems */
5200 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5201 : "INSERT INTO gpkg_spatial_ref_sys ("
5202 : "srs_name, srs_id, organization, organization_coordsys_id, "
5203 1102 : "definition, description";
5204 1102 : if (m_bHasDefinition12_063)
5205 42 : osSQL += ", definition_12_063";
5206 : osSQL += ") VALUES ("
5207 : "'Undefined geographic SRS', 0, 'NONE', 0, 'undefined', "
5208 1102 : "'undefined geographic coordinate reference system'";
5209 1102 : if (m_bHasDefinition12_063)
5210 42 : osSQL += ", 'undefined'";
5211 : osSQL += ")"
5212 : ";"
5213 : /* Requirement 13: A GeoPackage file SHALL include a
5214 : gpkg_contents table */
5215 : /* http://opengis.github.io/geopackage/#_contents */
5216 : "CREATE TABLE gpkg_contents ("
5217 : "table_name TEXT NOT NULL PRIMARY KEY,"
5218 : "data_type TEXT NOT NULL,"
5219 : "identifier TEXT UNIQUE,"
5220 : "description TEXT DEFAULT '',"
5221 : "last_change DATETIME NOT NULL DEFAULT "
5222 : "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
5223 : "min_x DOUBLE, min_y DOUBLE,"
5224 : "max_x DOUBLE, max_y DOUBLE,"
5225 : "srs_id INTEGER,"
5226 : "CONSTRAINT fk_gc_r_srs_id FOREIGN KEY (srs_id) REFERENCES "
5227 : "gpkg_spatial_ref_sys(srs_id)"
5228 1102 : ")";
5229 :
5230 : #ifdef ENABLE_GPKG_OGR_CONTENTS
5231 1102 : if (CPLFetchBool(papszOptions, "ADD_GPKG_OGR_CONTENTS", true))
5232 : {
5233 1096 : m_bHasGPKGOGRContents = true;
5234 : osSQL += ";"
5235 : "CREATE TABLE gpkg_ogr_contents("
5236 : "table_name TEXT NOT NULL PRIMARY KEY,"
5237 : "feature_count INTEGER DEFAULT NULL"
5238 1096 : ")";
5239 : }
5240 : #endif
5241 :
5242 : /* Requirement 21: A GeoPackage with a gpkg_contents table row with a
5243 : * “features” */
5244 : /* data_type SHALL contain a gpkg_geometry_columns table or updateable
5245 : * view */
5246 : /* http://opengis.github.io/geopackage/#_geometry_columns */
5247 : const bool bCreateGeometryColumns =
5248 1102 : CPLTestBool(CPLGetConfigOption("CREATE_GEOMETRY_COLUMNS", "YES"));
5249 1102 : if (bCreateGeometryColumns)
5250 : {
5251 1101 : m_bHasGPKGGeometryColumns = true;
5252 1101 : osSQL += ";";
5253 1101 : osSQL += pszCREATE_GPKG_GEOMETRY_COLUMNS;
5254 : }
5255 : }
5256 :
5257 : const bool bCreateTriggers =
5258 1112 : CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "YES"));
5259 10 : if ((bFileExists && nBandsIn != 0 &&
5260 10 : SQLGetInteger(
5261 : hDB,
5262 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_tile_matrix_set' "
5263 : "AND type in ('table', 'view')",
5264 2224 : nullptr) == 0) ||
5265 1111 : (!bFileExists &&
5266 1102 : CPLTestBool(CPLGetConfigOption("CREATE_RASTER_TABLES", "YES"))))
5267 : {
5268 1102 : if (!osSQL.empty())
5269 1101 : osSQL += ";";
5270 :
5271 : /* From C.5. gpkg_tile_matrix_set Table 28. gpkg_tile_matrix_set Table
5272 : * Creation SQL */
5273 : osSQL += "CREATE TABLE gpkg_tile_matrix_set ("
5274 : "table_name TEXT NOT NULL PRIMARY KEY,"
5275 : "srs_id INTEGER NOT NULL,"
5276 : "min_x DOUBLE NOT NULL,"
5277 : "min_y DOUBLE NOT NULL,"
5278 : "max_x DOUBLE NOT NULL,"
5279 : "max_y DOUBLE NOT NULL,"
5280 : "CONSTRAINT fk_gtms_table_name FOREIGN KEY (table_name) "
5281 : "REFERENCES gpkg_contents(table_name),"
5282 : "CONSTRAINT fk_gtms_srs FOREIGN KEY (srs_id) REFERENCES "
5283 : "gpkg_spatial_ref_sys (srs_id)"
5284 : ")"
5285 : ";"
5286 :
5287 : /* From C.6. gpkg_tile_matrix Table 29. gpkg_tile_matrix Table
5288 : Creation SQL */
5289 : "CREATE TABLE gpkg_tile_matrix ("
5290 : "table_name TEXT NOT NULL,"
5291 : "zoom_level INTEGER NOT NULL,"
5292 : "matrix_width INTEGER NOT NULL,"
5293 : "matrix_height INTEGER NOT NULL,"
5294 : "tile_width INTEGER NOT NULL,"
5295 : "tile_height INTEGER NOT NULL,"
5296 : "pixel_x_size DOUBLE NOT NULL,"
5297 : "pixel_y_size DOUBLE NOT NULL,"
5298 : "CONSTRAINT pk_ttm PRIMARY KEY (table_name, zoom_level),"
5299 : "CONSTRAINT fk_tmm_table_name FOREIGN KEY (table_name) "
5300 : "REFERENCES gpkg_contents(table_name)"
5301 1102 : ")";
5302 :
5303 1102 : if (bCreateTriggers)
5304 : {
5305 : /* From D.1. gpkg_tile_matrix Table 39. gpkg_tile_matrix Trigger
5306 : * Definition SQL */
5307 1102 : const char *pszTileMatrixTrigger =
5308 : "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_insert' "
5309 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5310 : "FOR EACH ROW BEGIN "
5311 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5312 : "violates constraint: zoom_level cannot be less than 0') "
5313 : "WHERE (NEW.zoom_level < 0); "
5314 : "END; "
5315 : "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_update' "
5316 : "BEFORE UPDATE of zoom_level ON 'gpkg_tile_matrix' "
5317 : "FOR EACH ROW BEGIN "
5318 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5319 : "violates constraint: zoom_level cannot be less than 0') "
5320 : "WHERE (NEW.zoom_level < 0); "
5321 : "END; "
5322 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_insert' "
5323 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5324 : "FOR EACH ROW BEGIN "
5325 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5326 : "violates constraint: matrix_width cannot be less than 1') "
5327 : "WHERE (NEW.matrix_width < 1); "
5328 : "END; "
5329 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_update' "
5330 : "BEFORE UPDATE OF matrix_width ON 'gpkg_tile_matrix' "
5331 : "FOR EACH ROW BEGIN "
5332 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5333 : "violates constraint: matrix_width cannot be less than 1') "
5334 : "WHERE (NEW.matrix_width < 1); "
5335 : "END; "
5336 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_insert' "
5337 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5338 : "FOR EACH ROW BEGIN "
5339 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5340 : "violates constraint: matrix_height cannot be less than 1') "
5341 : "WHERE (NEW.matrix_height < 1); "
5342 : "END; "
5343 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_update' "
5344 : "BEFORE UPDATE OF matrix_height ON 'gpkg_tile_matrix' "
5345 : "FOR EACH ROW BEGIN "
5346 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5347 : "violates constraint: matrix_height cannot be less than 1') "
5348 : "WHERE (NEW.matrix_height < 1); "
5349 : "END; "
5350 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_insert' "
5351 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5352 : "FOR EACH ROW BEGIN "
5353 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5354 : "violates constraint: pixel_x_size must be greater than 0') "
5355 : "WHERE NOT (NEW.pixel_x_size > 0); "
5356 : "END; "
5357 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_update' "
5358 : "BEFORE UPDATE OF pixel_x_size ON 'gpkg_tile_matrix' "
5359 : "FOR EACH ROW BEGIN "
5360 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5361 : "violates constraint: pixel_x_size must be greater than 0') "
5362 : "WHERE NOT (NEW.pixel_x_size > 0); "
5363 : "END; "
5364 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_insert' "
5365 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5366 : "FOR EACH ROW BEGIN "
5367 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5368 : "violates constraint: pixel_y_size must be greater than 0') "
5369 : "WHERE NOT (NEW.pixel_y_size > 0); "
5370 : "END; "
5371 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_update' "
5372 : "BEFORE UPDATE OF pixel_y_size ON 'gpkg_tile_matrix' "
5373 : "FOR EACH ROW BEGIN "
5374 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5375 : "violates constraint: pixel_y_size must be greater than 0') "
5376 : "WHERE NOT (NEW.pixel_y_size > 0); "
5377 : "END;";
5378 1102 : osSQL += ";";
5379 1102 : osSQL += pszTileMatrixTrigger;
5380 : }
5381 : }
5382 :
5383 1112 : if (!osSQL.empty() && OGRERR_NONE != SQLCommand(hDB, osSQL))
5384 1 : return FALSE;
5385 :
5386 1111 : if (!bFileExists)
5387 : {
5388 : const char *pszMetadataTables =
5389 1101 : CSLFetchNameValue(papszOptions, "METADATA_TABLES");
5390 1101 : if (pszMetadataTables)
5391 10 : m_nCreateMetadataTables = int(CPLTestBool(pszMetadataTables));
5392 :
5393 1101 : if (m_nCreateMetadataTables == TRUE && !CreateMetadataTables())
5394 0 : return FALSE;
5395 :
5396 1101 : if (m_bHasDefinition12_063)
5397 : {
5398 84 : if (OGRERR_NONE != CreateExtensionsTableIfNecessary() ||
5399 : OGRERR_NONE !=
5400 42 : SQLCommand(hDB, "INSERT INTO gpkg_extensions "
5401 : "(table_name, column_name, extension_name, "
5402 : "definition, scope) "
5403 : "VALUES "
5404 : "('gpkg_spatial_ref_sys', "
5405 : "'definition_12_063', 'gpkg_crs_wkt', "
5406 : "'http://www.geopackage.org/spec120/"
5407 : "#extension_crs_wkt', 'read-write')"))
5408 : {
5409 0 : return FALSE;
5410 : }
5411 42 : if (m_bHasEpochColumn)
5412 : {
5413 40 : if (OGRERR_NONE !=
5414 40 : SQLCommand(
5415 : hDB, "UPDATE gpkg_extensions SET extension_name = "
5416 : "'gpkg_crs_wkt_1_1' "
5417 80 : "WHERE extension_name = 'gpkg_crs_wkt'") ||
5418 : OGRERR_NONE !=
5419 40 : SQLCommand(hDB, "INSERT INTO gpkg_extensions "
5420 : "(table_name, column_name, "
5421 : "extension_name, definition, scope) "
5422 : "VALUES "
5423 : "('gpkg_spatial_ref_sys', 'epoch', "
5424 : "'gpkg_crs_wkt_1_1', "
5425 : "'http://www.geopackage.org/spec/"
5426 : "#extension_crs_wkt', "
5427 : "'read-write')"))
5428 : {
5429 0 : return FALSE;
5430 : }
5431 : }
5432 : }
5433 : }
5434 :
5435 1111 : if (nBandsIn != 0)
5436 : {
5437 196 : const std::string osTableName = CPLGetBasenameSafe(m_pszFilename);
5438 : m_osRasterTable = CSLFetchNameValueDef(papszOptions, "RASTER_TABLE",
5439 196 : osTableName.c_str());
5440 196 : if (m_osRasterTable.empty())
5441 : {
5442 0 : CPLError(CE_Failure, CPLE_AppDefined,
5443 : "RASTER_TABLE must be set to a non empty value");
5444 0 : return FALSE;
5445 : }
5446 196 : m_bIdentifierAsCO =
5447 196 : CSLFetchNameValue(papszOptions, "RASTER_IDENTIFIER") != nullptr;
5448 : m_osIdentifier = CSLFetchNameValueDef(papszOptions, "RASTER_IDENTIFIER",
5449 196 : m_osRasterTable);
5450 196 : m_bDescriptionAsCO =
5451 196 : CSLFetchNameValue(papszOptions, "RASTER_DESCRIPTION") != nullptr;
5452 : m_osDescription =
5453 196 : CSLFetchNameValueDef(papszOptions, "RASTER_DESCRIPTION", "");
5454 196 : SetDataType(eDT);
5455 196 : if (eDT == GDT_Int16)
5456 16 : SetGlobalOffsetScale(-32768.0, 1.0);
5457 :
5458 : /* From C.7. sample_tile_pyramid (Informative) Table 31. EXAMPLE: tiles
5459 : * table Create Table SQL (Informative) */
5460 : char *pszSQL =
5461 196 : sqlite3_mprintf("CREATE TABLE \"%w\" ("
5462 : "id INTEGER PRIMARY KEY AUTOINCREMENT,"
5463 : "zoom_level INTEGER NOT NULL,"
5464 : "tile_column INTEGER NOT NULL,"
5465 : "tile_row INTEGER NOT NULL,"
5466 : "tile_data BLOB NOT NULL,"
5467 : "UNIQUE (zoom_level, tile_column, tile_row)"
5468 : ")",
5469 : m_osRasterTable.c_str());
5470 196 : osSQL = pszSQL;
5471 196 : sqlite3_free(pszSQL);
5472 :
5473 196 : if (bCreateTriggers)
5474 : {
5475 196 : osSQL += ";";
5476 196 : osSQL += CreateRasterTriggersSQL(m_osRasterTable);
5477 : }
5478 :
5479 196 : OGRErr eErr = SQLCommand(hDB, osSQL);
5480 196 : if (OGRERR_NONE != eErr)
5481 0 : return FALSE;
5482 :
5483 196 : const char *pszTF = CSLFetchNameValue(papszOptions, "TILE_FORMAT");
5484 196 : if (eDT == GDT_Int16 || eDT == GDT_UInt16)
5485 : {
5486 27 : m_eTF = GPKG_TF_PNG_16BIT;
5487 27 : if (pszTF)
5488 : {
5489 1 : if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "PNG"))
5490 : {
5491 0 : CPLError(CE_Warning, CPLE_NotSupported,
5492 : "Only AUTO or PNG supported "
5493 : "as tile format for Int16 / UInt16");
5494 : }
5495 : }
5496 : }
5497 169 : else if (eDT == GDT_Float32)
5498 : {
5499 13 : m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
5500 13 : if (pszTF)
5501 : {
5502 5 : if (EQUAL(pszTF, "PNG"))
5503 5 : m_eTF = GPKG_TF_PNG_16BIT;
5504 0 : else if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "TIFF"))
5505 : {
5506 0 : CPLError(CE_Warning, CPLE_NotSupported,
5507 : "Only AUTO, PNG or TIFF supported "
5508 : "as tile format for Float32");
5509 : }
5510 : }
5511 : }
5512 : else
5513 : {
5514 156 : if (pszTF)
5515 : {
5516 71 : m_eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
5517 71 : if (nBandsIn == 1 && m_eTF != GPKG_TF_PNG)
5518 7 : m_bMetadataDirty = true;
5519 : }
5520 85 : else if (nBandsIn == 1)
5521 74 : m_eTF = GPKG_TF_PNG;
5522 : }
5523 :
5524 196 : if (eDT != GDT_UInt8)
5525 : {
5526 40 : if (!CreateTileGriddedTable(papszOptions))
5527 0 : return FALSE;
5528 : }
5529 :
5530 196 : nRasterXSize = nXSize;
5531 196 : nRasterYSize = nYSize;
5532 :
5533 : const char *pszTileSize =
5534 196 : CSLFetchNameValueDef(papszOptions, "BLOCKSIZE", "256");
5535 : const char *pszTileWidth =
5536 196 : CSLFetchNameValueDef(papszOptions, "BLOCKXSIZE", pszTileSize);
5537 : const char *pszTileHeight =
5538 196 : CSLFetchNameValueDef(papszOptions, "BLOCKYSIZE", pszTileSize);
5539 196 : int nTileWidth = atoi(pszTileWidth);
5540 196 : int nTileHeight = atoi(pszTileHeight);
5541 196 : if ((nTileWidth < 8 || nTileWidth > 4096 || nTileHeight < 8 ||
5542 392 : nTileHeight > 4096) &&
5543 1 : !CPLTestBool(CPLGetConfigOption("GPKG_ALLOW_CRAZY_SETTINGS", "NO")))
5544 : {
5545 0 : CPLError(CE_Failure, CPLE_AppDefined,
5546 : "Invalid block dimensions: %dx%d", nTileWidth,
5547 : nTileHeight);
5548 0 : return FALSE;
5549 : }
5550 :
5551 525 : for (int i = 1; i <= nBandsIn; i++)
5552 : {
5553 329 : SetBand(i, std::make_unique<GDALGeoPackageRasterBand>(
5554 : this, nTileWidth, nTileHeight));
5555 : }
5556 :
5557 196 : GDALPamDataset::SetMetadataItem(GDALMD_INTERLEAVE, "PIXEL",
5558 : GDAL_MDD_IMAGE_STRUCTURE);
5559 196 : GDALPamDataset::SetMetadataItem("IDENTIFIER", m_osIdentifier);
5560 196 : if (!m_osDescription.empty())
5561 1 : GDALPamDataset::SetMetadataItem("DESCRIPTION", m_osDescription);
5562 :
5563 196 : ParseCompressionOptions(papszOptions);
5564 :
5565 196 : if (m_eTF == GPKG_TF_WEBP)
5566 : {
5567 10 : if (!RegisterWebPExtension())
5568 0 : return FALSE;
5569 : }
5570 :
5571 : m_osTilingScheme =
5572 196 : CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
5573 196 : if (!EQUAL(m_osTilingScheme, "CUSTOM"))
5574 : {
5575 22 : const auto poTS = GetTilingScheme(m_osTilingScheme);
5576 22 : if (!poTS)
5577 0 : return FALSE;
5578 :
5579 43 : if (nTileWidth != poTS->nTileWidth ||
5580 21 : nTileHeight != poTS->nTileHeight)
5581 : {
5582 2 : CPLError(CE_Failure, CPLE_NotSupported,
5583 : "Tile dimension should be %dx%d for %s tiling scheme",
5584 1 : poTS->nTileWidth, poTS->nTileHeight,
5585 : m_osTilingScheme.c_str());
5586 1 : return FALSE;
5587 : }
5588 :
5589 : const char *pszZoomLevel =
5590 21 : CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
5591 21 : if (pszZoomLevel)
5592 : {
5593 1 : m_nZoomLevel = atoi(pszZoomLevel);
5594 1 : int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
5595 1 : while ((1 << nMaxZoomLevelForThisTM) >
5596 2 : INT_MAX / poTS->nTileXCountZoomLevel0 ||
5597 1 : (1 << nMaxZoomLevelForThisTM) >
5598 1 : INT_MAX / poTS->nTileYCountZoomLevel0)
5599 : {
5600 0 : --nMaxZoomLevelForThisTM;
5601 : }
5602 :
5603 1 : if (m_nZoomLevel < 0 || m_nZoomLevel > nMaxZoomLevelForThisTM)
5604 : {
5605 0 : CPLError(CE_Failure, CPLE_AppDefined,
5606 : "ZOOM_LEVEL = %s is invalid. It should be in "
5607 : "[0,%d] range",
5608 : pszZoomLevel, nMaxZoomLevelForThisTM);
5609 0 : return FALSE;
5610 : }
5611 : }
5612 :
5613 : // Implicitly sets SRS.
5614 21 : OGRSpatialReference oSRS;
5615 21 : if (oSRS.importFromEPSG(poTS->nEPSGCode) != OGRERR_NONE)
5616 0 : return FALSE;
5617 21 : char *pszWKT = nullptr;
5618 21 : oSRS.exportToWkt(&pszWKT);
5619 21 : SetProjection(pszWKT);
5620 21 : CPLFree(pszWKT);
5621 : }
5622 : else
5623 : {
5624 174 : if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
5625 : {
5626 0 : CPLError(
5627 : CE_Failure, CPLE_NotSupported,
5628 : "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
5629 0 : return false;
5630 : }
5631 : }
5632 : }
5633 :
5634 1110 : if (bFileExists && nBandsIn > 0 && eDT == GDT_UInt8)
5635 : {
5636 : // If there was an ogr_empty_table table, we can remove it
5637 9 : RemoveOGREmptyTable();
5638 : }
5639 :
5640 1110 : SoftCommitTransaction();
5641 :
5642 : /* Requirement 2 */
5643 : /* We have to do this after there's some content so the database file */
5644 : /* is not zero length */
5645 1110 : SetApplicationAndUserVersionId();
5646 :
5647 : /* Default to synchronous=off for performance for new file */
5648 2210 : if (!bFileExists &&
5649 1100 : CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
5650 : {
5651 585 : SQLCommand(hDB, "PRAGMA synchronous = OFF");
5652 : }
5653 :
5654 : // Enable SpatiaLite 4.3 GPKG mode, i.e. that SpatiaLite functions
5655 : // that take geometries will accept and return GPKG encoded geometries without
5656 : // explicit conversion.
5657 : // Note: we need to do that after DB creation, since EnableGpkgMode()
5658 : // checks for the presence of GPKG system tables.
5659 1110 : sqlite3_exec(hDB, "SELECT EnableGpkgMode()", nullptr, nullptr, nullptr);
5660 :
5661 1110 : return TRUE;
5662 : }
5663 :
5664 : /************************************************************************/
5665 : /* RemoveOGREmptyTable() */
5666 : /************************************************************************/
5667 :
5668 925 : void GDALGeoPackageDataset::RemoveOGREmptyTable()
5669 : {
5670 : // Run with sqlite3_exec since we don't want errors to be emitted
5671 925 : sqlite3_exec(hDB, "DROP TABLE IF EXISTS ogr_empty_table", nullptr, nullptr,
5672 : nullptr);
5673 925 : sqlite3_exec(
5674 : hDB, "DELETE FROM gpkg_contents WHERE table_name = 'ogr_empty_table'",
5675 : nullptr, nullptr, nullptr);
5676 : #ifdef ENABLE_GPKG_OGR_CONTENTS
5677 925 : if (m_bHasGPKGOGRContents)
5678 : {
5679 910 : sqlite3_exec(hDB,
5680 : "DELETE FROM gpkg_ogr_contents WHERE "
5681 : "table_name = 'ogr_empty_table'",
5682 : nullptr, nullptr, nullptr);
5683 : }
5684 : #endif
5685 925 : sqlite3_exec(hDB,
5686 : "DELETE FROM gpkg_geometry_columns WHERE "
5687 : "table_name = 'ogr_empty_table'",
5688 : nullptr, nullptr, nullptr);
5689 925 : }
5690 :
5691 : /************************************************************************/
5692 : /* CreateTileGriddedTable() */
5693 : /************************************************************************/
5694 :
5695 40 : bool GDALGeoPackageDataset::CreateTileGriddedTable(CSLConstList papszOptions)
5696 : {
5697 80 : CPLString osSQL;
5698 40 : if (!HasGriddedCoverageAncillaryTable())
5699 : {
5700 : // It doesn't exist. So create gpkg_extensions table if necessary, and
5701 : // gpkg_2d_gridded_coverage_ancillary & gpkg_2d_gridded_tile_ancillary,
5702 : // and register them as extensions.
5703 40 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
5704 0 : return false;
5705 :
5706 : // Req 1 /table-defs/coverage-ancillary
5707 : osSQL = "CREATE TABLE gpkg_2d_gridded_coverage_ancillary ("
5708 : "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
5709 : "tile_matrix_set_name TEXT NOT NULL UNIQUE,"
5710 : "datatype TEXT NOT NULL DEFAULT 'integer',"
5711 : "scale REAL NOT NULL DEFAULT 1.0,"
5712 : "offset REAL NOT NULL DEFAULT 0.0,"
5713 : "precision REAL DEFAULT 1.0,"
5714 : "data_null REAL,"
5715 : "grid_cell_encoding TEXT DEFAULT 'grid-value-is-center',"
5716 : "uom TEXT,"
5717 : "field_name TEXT DEFAULT 'Height',"
5718 : "quantity_definition TEXT DEFAULT 'Height',"
5719 : "CONSTRAINT fk_g2dgtct_name FOREIGN KEY(tile_matrix_set_name) "
5720 : "REFERENCES gpkg_tile_matrix_set ( table_name ) "
5721 : "CHECK (datatype in ('integer','float')))"
5722 : ";"
5723 : // Requirement 2 /table-defs/tile-ancillary
5724 : "CREATE TABLE gpkg_2d_gridded_tile_ancillary ("
5725 : "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
5726 : "tpudt_name TEXT NOT NULL,"
5727 : "tpudt_id INTEGER NOT NULL,"
5728 : "scale REAL NOT NULL DEFAULT 1.0,"
5729 : "offset REAL NOT NULL DEFAULT 0.0,"
5730 : "min REAL DEFAULT NULL,"
5731 : "max REAL DEFAULT NULL,"
5732 : "mean REAL DEFAULT NULL,"
5733 : "std_dev REAL DEFAULT NULL,"
5734 : "CONSTRAINT fk_g2dgtat_name FOREIGN KEY (tpudt_name) "
5735 : "REFERENCES gpkg_contents(table_name),"
5736 : "UNIQUE (tpudt_name, tpudt_id))"
5737 : ";"
5738 : // Requirement 6 /gpkg-extensions
5739 : "INSERT INTO gpkg_extensions "
5740 : "(table_name, column_name, extension_name, definition, scope) "
5741 : "VALUES ('gpkg_2d_gridded_coverage_ancillary', NULL, "
5742 : "'gpkg_2d_gridded_coverage', "
5743 : "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5744 : "'read-write')"
5745 : ";"
5746 : // Requirement 6 /gpkg-extensions
5747 : "INSERT INTO gpkg_extensions "
5748 : "(table_name, column_name, extension_name, definition, scope) "
5749 : "VALUES ('gpkg_2d_gridded_tile_ancillary', NULL, "
5750 : "'gpkg_2d_gridded_coverage', "
5751 : "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5752 : "'read-write')"
5753 40 : ";";
5754 : }
5755 :
5756 : // Requirement 6 /gpkg-extensions
5757 40 : char *pszSQL = sqlite3_mprintf(
5758 : "INSERT INTO gpkg_extensions "
5759 : "(table_name, column_name, extension_name, definition, scope) "
5760 : "VALUES ('%q', 'tile_data', "
5761 : "'gpkg_2d_gridded_coverage', "
5762 : "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5763 : "'read-write')",
5764 : m_osRasterTable.c_str());
5765 40 : osSQL += pszSQL;
5766 40 : osSQL += ";";
5767 40 : sqlite3_free(pszSQL);
5768 :
5769 : // Requirement 7 /gpkg-2d-gridded-coverage-ancillary
5770 : // Requirement 8 /gpkg-2d-gridded-coverage-ancillary-set-name
5771 : // Requirement 9 /gpkg-2d-gridded-coverage-ancillary-datatype
5772 40 : m_dfPrecision =
5773 40 : CPLAtof(CSLFetchNameValueDef(papszOptions, "PRECISION", "1"));
5774 : CPLString osGridCellEncoding(CSLFetchNameValueDef(
5775 80 : papszOptions, "GRID_CELL_ENCODING", "grid-value-is-center"));
5776 40 : m_bGridCellEncodingAsCO =
5777 40 : CSLFetchNameValue(papszOptions, "GRID_CELL_ENCODING") != nullptr;
5778 80 : CPLString osUom(CSLFetchNameValueDef(papszOptions, "UOM", ""));
5779 : CPLString osFieldName(
5780 80 : CSLFetchNameValueDef(papszOptions, "FIELD_NAME", "Height"));
5781 : CPLString osQuantityDefinition(
5782 80 : CSLFetchNameValueDef(papszOptions, "QUANTITY_DEFINITION", "Height"));
5783 :
5784 121 : pszSQL = sqlite3_mprintf(
5785 : "INSERT INTO gpkg_2d_gridded_coverage_ancillary "
5786 : "(tile_matrix_set_name, datatype, scale, offset, precision, "
5787 : "grid_cell_encoding, uom, field_name, quantity_definition) "
5788 : "VALUES (%Q, '%s', %.17g, %.17g, %.17g, %Q, %Q, %Q, %Q)",
5789 : m_osRasterTable.c_str(),
5790 40 : (m_eTF == GPKG_TF_PNG_16BIT) ? "integer" : "float", m_dfScale,
5791 : m_dfOffset, m_dfPrecision, osGridCellEncoding.c_str(),
5792 41 : osUom.empty() ? nullptr : osUom.c_str(), osFieldName.c_str(),
5793 : osQuantityDefinition.c_str());
5794 40 : m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary = pszSQL;
5795 40 : sqlite3_free(pszSQL);
5796 :
5797 : // Requirement 3 /gpkg-spatial-ref-sys-row
5798 : auto oResultTable = SQLQuery(
5799 80 : hDB, "SELECT * FROM gpkg_spatial_ref_sys WHERE srs_id = 4979 LIMIT 2");
5800 40 : bool bHasEPSG4979 = (oResultTable && oResultTable->RowCount() == 1);
5801 40 : if (!bHasEPSG4979)
5802 : {
5803 41 : if (!m_bHasDefinition12_063 &&
5804 1 : !ConvertGpkgSpatialRefSysToExtensionWkt2(/*bForceEpoch=*/false))
5805 : {
5806 0 : return false;
5807 : }
5808 :
5809 : // This is WKT 2...
5810 40 : const char *pszWKT =
5811 : "GEODCRS[\"WGS 84\","
5812 : "DATUM[\"World Geodetic System 1984\","
5813 : " ELLIPSOID[\"WGS 84\",6378137,298.257223563,"
5814 : "LENGTHUNIT[\"metre\",1.0]]],"
5815 : "CS[ellipsoidal,3],"
5816 : " AXIS[\"latitude\",north,ORDER[1],ANGLEUNIT[\"degree\","
5817 : "0.0174532925199433]],"
5818 : " AXIS[\"longitude\",east,ORDER[2],ANGLEUNIT[\"degree\","
5819 : "0.0174532925199433]],"
5820 : " AXIS[\"ellipsoidal height\",up,ORDER[3],"
5821 : "LENGTHUNIT[\"metre\",1.0]],"
5822 : "ID[\"EPSG\",4979]]";
5823 :
5824 40 : pszSQL = sqlite3_mprintf(
5825 : "INSERT INTO gpkg_spatial_ref_sys "
5826 : "(srs_name,srs_id,organization,organization_coordsys_id,"
5827 : "definition,definition_12_063) VALUES "
5828 : "('WGS 84 3D', 4979, 'EPSG', 4979, 'undefined', '%q')",
5829 : pszWKT);
5830 40 : osSQL += ";";
5831 40 : osSQL += pszSQL;
5832 40 : sqlite3_free(pszSQL);
5833 : }
5834 :
5835 40 : return SQLCommand(hDB, osSQL) == OGRERR_NONE;
5836 : }
5837 :
5838 : /************************************************************************/
5839 : /* HasGriddedCoverageAncillaryTable() */
5840 : /************************************************************************/
5841 :
5842 44 : bool GDALGeoPackageDataset::HasGriddedCoverageAncillaryTable()
5843 : {
5844 : auto oResultTable = SQLQuery(
5845 : hDB, "SELECT * FROM sqlite_master WHERE type IN ('table', 'view') AND "
5846 44 : "name = 'gpkg_2d_gridded_coverage_ancillary'");
5847 44 : bool bHasTable = (oResultTable && oResultTable->RowCount() == 1);
5848 88 : return bHasTable;
5849 : }
5850 :
5851 : /************************************************************************/
5852 : /* GetUnderlyingDataset() */
5853 : /************************************************************************/
5854 :
5855 3 : static GDALDataset *GetUnderlyingDataset(GDALDataset *poSrcDS)
5856 : {
5857 3 : if (auto poVRTDS = dynamic_cast<VRTDataset *>(poSrcDS))
5858 : {
5859 0 : auto poTmpDS = poVRTDS->GetSingleSimpleSource();
5860 0 : if (poTmpDS)
5861 0 : return poTmpDS;
5862 : }
5863 :
5864 3 : return poSrcDS;
5865 : }
5866 :
5867 : /************************************************************************/
5868 : /* CreateCopy() */
5869 : /************************************************************************/
5870 :
5871 : typedef struct
5872 : {
5873 : const char *pszName;
5874 : GDALResampleAlg eResampleAlg;
5875 : } WarpResamplingAlg;
5876 :
5877 : static const WarpResamplingAlg asResamplingAlg[] = {
5878 : {"NEAREST", GRA_NearestNeighbour},
5879 : {"BILINEAR", GRA_Bilinear},
5880 : {"CUBIC", GRA_Cubic},
5881 : {"CUBICSPLINE", GRA_CubicSpline},
5882 : {"LANCZOS", GRA_Lanczos},
5883 : {"MODE", GRA_Mode},
5884 : {"AVERAGE", GRA_Average},
5885 : {"RMS", GRA_RMS},
5886 : };
5887 :
5888 165 : GDALDataset *GDALGeoPackageDataset::CreateCopy(const char *pszFilename,
5889 : GDALDataset *poSrcDS,
5890 : int bStrict,
5891 : CSLConstList papszOptions,
5892 : GDALProgressFunc pfnProgress,
5893 : void *pProgressData)
5894 : {
5895 165 : const int nBands = poSrcDS->GetRasterCount();
5896 165 : if (nBands == 0)
5897 : {
5898 2 : GDALDataset *poDS = nullptr;
5899 : GDALDriver *poThisDriver =
5900 2 : GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
5901 2 : if (poThisDriver != nullptr)
5902 : {
5903 2 : poDS = poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS,
5904 : bStrict, papszOptions,
5905 : pfnProgress, pProgressData);
5906 : }
5907 2 : return poDS;
5908 : }
5909 :
5910 : const char *pszTilingScheme =
5911 163 : CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
5912 :
5913 326 : CPLStringList apszUpdatedOptions(CSLDuplicate(papszOptions));
5914 163 : if (CPLTestBool(
5915 169 : CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")) &&
5916 6 : CSLFetchNameValue(papszOptions, "RASTER_TABLE") == nullptr)
5917 : {
5918 : const std::string osBasename(CPLGetBasenameSafe(
5919 6 : GetUnderlyingDataset(poSrcDS)->GetDescription()));
5920 3 : apszUpdatedOptions.SetNameValue("RASTER_TABLE", osBasename.c_str());
5921 : }
5922 :
5923 163 : if (nBands != 1 && nBands != 2 && nBands != 3 && nBands != 4)
5924 : {
5925 1 : CPLError(CE_Failure, CPLE_NotSupported,
5926 : "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), 3 (RGB) or "
5927 : "4 (RGBA) band dataset supported");
5928 1 : return nullptr;
5929 : }
5930 :
5931 162 : const char *pszUnitType = poSrcDS->GetRasterBand(1)->GetUnitType();
5932 324 : if (CSLFetchNameValue(papszOptions, "UOM") == nullptr && pszUnitType &&
5933 162 : !EQUAL(pszUnitType, ""))
5934 : {
5935 1 : apszUpdatedOptions.SetNameValue("UOM", pszUnitType);
5936 : }
5937 :
5938 162 : if (EQUAL(pszTilingScheme, "CUSTOM"))
5939 : {
5940 138 : if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
5941 : {
5942 0 : CPLError(CE_Failure, CPLE_NotSupported,
5943 : "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
5944 0 : return nullptr;
5945 : }
5946 :
5947 138 : GDALGeoPackageDataset *poDS = nullptr;
5948 : GDALDriver *poThisDriver =
5949 138 : GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
5950 138 : if (poThisDriver != nullptr)
5951 : {
5952 138 : apszUpdatedOptions.SetNameValue("SKIP_HOLES", "YES");
5953 138 : poDS = cpl::down_cast<GDALGeoPackageDataset *>(
5954 : poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
5955 : apszUpdatedOptions, pfnProgress,
5956 138 : pProgressData));
5957 :
5958 256 : if (poDS != nullptr &&
5959 138 : poSrcDS->GetRasterBand(1)->GetRasterDataType() == GDT_UInt8 &&
5960 : nBands <= 3)
5961 : {
5962 78 : poDS->m_nBandCountFromMetadata = nBands;
5963 78 : poDS->m_bMetadataDirty = true;
5964 : }
5965 : }
5966 138 : if (poDS)
5967 118 : poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
5968 138 : return poDS;
5969 : }
5970 :
5971 48 : const auto poTS = GetTilingScheme(pszTilingScheme);
5972 24 : if (!poTS)
5973 : {
5974 2 : return nullptr;
5975 : }
5976 22 : const int nEPSGCode = poTS->nEPSGCode;
5977 :
5978 44 : OGRSpatialReference oSRS;
5979 22 : if (oSRS.importFromEPSG(nEPSGCode) != OGRERR_NONE)
5980 : {
5981 0 : return nullptr;
5982 : }
5983 22 : char *pszWKT = nullptr;
5984 22 : oSRS.exportToWkt(&pszWKT);
5985 22 : char **papszTO = CSLSetNameValue(nullptr, "DST_SRS", pszWKT);
5986 :
5987 22 : void *hTransformArg = nullptr;
5988 :
5989 : // Hack to compensate for GDALSuggestedWarpOutput2() failure (or not
5990 : // ideal suggestion with PROJ 8) when reprojecting latitude = +/- 90 to
5991 : // EPSG:3857.
5992 22 : GDALGeoTransform srcGT;
5993 22 : std::unique_ptr<GDALDataset> poTmpDS;
5994 22 : bool bEPSG3857Adjust = false;
5995 8 : if (nEPSGCode == 3857 && poSrcDS->GetGeoTransform(srcGT) == CE_None &&
5996 30 : srcGT[2] == 0 && srcGT[4] == 0 && srcGT[5] < 0)
5997 : {
5998 8 : const auto poSrcSRS = poSrcDS->GetSpatialRef();
5999 8 : if (poSrcSRS && poSrcSRS->IsGeographic())
6000 : {
6001 2 : double maxLat = srcGT[3];
6002 2 : double minLat = srcGT[3] + poSrcDS->GetRasterYSize() * srcGT[5];
6003 : // Corresponds to the latitude of below MAX_GM
6004 2 : constexpr double MAX_LAT = 85.0511287798066;
6005 2 : bool bModified = false;
6006 2 : if (maxLat > MAX_LAT)
6007 : {
6008 2 : maxLat = MAX_LAT;
6009 2 : bModified = true;
6010 : }
6011 2 : if (minLat < -MAX_LAT)
6012 : {
6013 2 : minLat = -MAX_LAT;
6014 2 : bModified = true;
6015 : }
6016 2 : if (bModified)
6017 : {
6018 4 : CPLStringList aosOptions;
6019 2 : aosOptions.AddString("-of");
6020 2 : aosOptions.AddString("VRT");
6021 2 : aosOptions.AddString("-projwin");
6022 2 : aosOptions.AddString(CPLSPrintf("%.17g", srcGT[0]));
6023 2 : aosOptions.AddString(CPLSPrintf("%.17g", maxLat));
6024 : aosOptions.AddString(CPLSPrintf(
6025 2 : "%.17g", srcGT[0] + poSrcDS->GetRasterXSize() * srcGT[1]));
6026 2 : aosOptions.AddString(CPLSPrintf("%.17g", minLat));
6027 : auto psOptions =
6028 2 : GDALTranslateOptionsNew(aosOptions.List(), nullptr);
6029 2 : poTmpDS.reset(GDALDataset::FromHandle(GDALTranslate(
6030 : "", GDALDataset::ToHandle(poSrcDS), psOptions, nullptr)));
6031 2 : GDALTranslateOptionsFree(psOptions);
6032 2 : if (poTmpDS)
6033 : {
6034 2 : bEPSG3857Adjust = true;
6035 2 : hTransformArg = GDALCreateGenImgProjTransformer2(
6036 2 : GDALDataset::FromHandle(poTmpDS.get()), nullptr,
6037 : papszTO);
6038 : }
6039 : }
6040 : }
6041 : }
6042 22 : if (hTransformArg == nullptr)
6043 : {
6044 : hTransformArg =
6045 20 : GDALCreateGenImgProjTransformer2(poSrcDS, nullptr, papszTO);
6046 : }
6047 :
6048 22 : if (hTransformArg == nullptr)
6049 : {
6050 1 : CPLFree(pszWKT);
6051 1 : CSLDestroy(papszTO);
6052 1 : return nullptr;
6053 : }
6054 :
6055 21 : GDALTransformerInfo *psInfo =
6056 : static_cast<GDALTransformerInfo *>(hTransformArg);
6057 21 : GDALGeoTransform gt;
6058 : double adfExtent[4];
6059 : int nXSize, nYSize;
6060 :
6061 21 : if (GDALSuggestedWarpOutput2(poSrcDS, psInfo->pfnTransform, hTransformArg,
6062 : gt.data(), &nXSize, &nYSize, adfExtent,
6063 21 : 0) != CE_None)
6064 : {
6065 0 : CPLFree(pszWKT);
6066 0 : CSLDestroy(papszTO);
6067 0 : GDALDestroyGenImgProjTransformer(hTransformArg);
6068 0 : return nullptr;
6069 : }
6070 :
6071 21 : GDALDestroyGenImgProjTransformer(hTransformArg);
6072 21 : hTransformArg = nullptr;
6073 21 : poTmpDS.reset();
6074 :
6075 21 : if (bEPSG3857Adjust)
6076 : {
6077 2 : constexpr double SPHERICAL_RADIUS = 6378137.0;
6078 2 : constexpr double MAX_GM =
6079 : SPHERICAL_RADIUS * M_PI; // 20037508.342789244
6080 2 : double maxNorthing = gt[3];
6081 2 : double minNorthing = gt[3] + gt[5] * nYSize;
6082 2 : bool bChanged = false;
6083 2 : if (maxNorthing > MAX_GM)
6084 : {
6085 2 : bChanged = true;
6086 2 : maxNorthing = MAX_GM;
6087 : }
6088 2 : if (minNorthing < -MAX_GM)
6089 : {
6090 2 : bChanged = true;
6091 2 : minNorthing = -MAX_GM;
6092 : }
6093 2 : if (bChanged)
6094 : {
6095 2 : gt[3] = maxNorthing;
6096 2 : nYSize = int((maxNorthing - minNorthing) / (-gt[5]) + 0.5);
6097 2 : adfExtent[1] = maxNorthing + nYSize * gt[5];
6098 2 : adfExtent[3] = maxNorthing;
6099 : }
6100 : }
6101 :
6102 21 : double dfComputedRes = gt[1];
6103 21 : double dfPrevRes = 0.0;
6104 21 : double dfRes = 0.0;
6105 21 : int nZoomLevel = 0; // Used after for.
6106 21 : const char *pszZoomLevel = CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
6107 21 : if (pszZoomLevel)
6108 : {
6109 2 : nZoomLevel = atoi(pszZoomLevel);
6110 :
6111 2 : int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
6112 2 : while ((1 << nMaxZoomLevelForThisTM) >
6113 4 : INT_MAX / poTS->nTileXCountZoomLevel0 ||
6114 2 : (1 << nMaxZoomLevelForThisTM) >
6115 2 : INT_MAX / poTS->nTileYCountZoomLevel0)
6116 : {
6117 0 : --nMaxZoomLevelForThisTM;
6118 : }
6119 :
6120 2 : if (nZoomLevel < 0 || nZoomLevel > nMaxZoomLevelForThisTM)
6121 : {
6122 1 : CPLError(CE_Failure, CPLE_AppDefined,
6123 : "ZOOM_LEVEL = %s is invalid. It should be in [0,%d] range",
6124 : pszZoomLevel, nMaxZoomLevelForThisTM);
6125 1 : CPLFree(pszWKT);
6126 1 : CSLDestroy(papszTO);
6127 1 : return nullptr;
6128 : }
6129 : }
6130 : else
6131 : {
6132 171 : for (; nZoomLevel < MAX_ZOOM_LEVEL; nZoomLevel++)
6133 : {
6134 171 : dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
6135 171 : if (dfComputedRes > dfRes ||
6136 152 : fabs(dfComputedRes - dfRes) / dfRes <= 1e-8)
6137 : break;
6138 152 : dfPrevRes = dfRes;
6139 : }
6140 38 : if (nZoomLevel == MAX_ZOOM_LEVEL ||
6141 38 : (1 << nZoomLevel) > INT_MAX / poTS->nTileXCountZoomLevel0 ||
6142 19 : (1 << nZoomLevel) > INT_MAX / poTS->nTileYCountZoomLevel0)
6143 : {
6144 0 : CPLError(CE_Failure, CPLE_AppDefined,
6145 : "Could not find an appropriate zoom level");
6146 0 : CPLFree(pszWKT);
6147 0 : CSLDestroy(papszTO);
6148 0 : return nullptr;
6149 : }
6150 :
6151 19 : if (nZoomLevel > 0 && fabs(dfComputedRes - dfRes) / dfRes > 1e-8)
6152 : {
6153 17 : const char *pszZoomLevelStrategy = CSLFetchNameValueDef(
6154 : papszOptions, "ZOOM_LEVEL_STRATEGY", "AUTO");
6155 17 : if (EQUAL(pszZoomLevelStrategy, "LOWER"))
6156 : {
6157 1 : nZoomLevel--;
6158 : }
6159 16 : else if (EQUAL(pszZoomLevelStrategy, "UPPER"))
6160 : {
6161 : /* do nothing */
6162 : }
6163 : else
6164 : {
6165 15 : if (dfPrevRes / dfComputedRes < dfComputedRes / dfRes)
6166 13 : nZoomLevel--;
6167 : }
6168 : }
6169 : }
6170 :
6171 20 : dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
6172 :
6173 20 : double dfMinX = adfExtent[0];
6174 20 : double dfMinY = adfExtent[1];
6175 20 : double dfMaxX = adfExtent[2];
6176 20 : double dfMaxY = adfExtent[3];
6177 :
6178 20 : nXSize = static_cast<int>(0.5 + (dfMaxX - dfMinX) / dfRes);
6179 20 : nYSize = static_cast<int>(0.5 + (dfMaxY - dfMinY) / dfRes);
6180 20 : gt[1] = dfRes;
6181 20 : gt[5] = -dfRes;
6182 :
6183 20 : const GDALDataType eDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
6184 20 : int nTargetBands = nBands;
6185 : /* For grey level or RGB, if there's reprojection involved, add an alpha */
6186 : /* channel */
6187 37 : if (eDT == GDT_UInt8 &&
6188 13 : ((nBands == 1 &&
6189 17 : poSrcDS->GetRasterBand(1)->GetColorTable() == nullptr) ||
6190 : nBands == 3))
6191 : {
6192 30 : OGRSpatialReference oSrcSRS;
6193 15 : oSrcSRS.SetFromUserInput(poSrcDS->GetProjectionRef());
6194 15 : oSrcSRS.AutoIdentifyEPSG();
6195 30 : if (oSrcSRS.GetAuthorityCode() == nullptr ||
6196 15 : atoi(oSrcSRS.GetAuthorityCode()) != nEPSGCode)
6197 : {
6198 13 : nTargetBands++;
6199 : }
6200 : }
6201 :
6202 20 : GDALResampleAlg eResampleAlg = GRA_Bilinear;
6203 20 : const char *pszResampling = CSLFetchNameValue(papszOptions, "RESAMPLING");
6204 20 : if (pszResampling)
6205 : {
6206 6 : for (size_t iAlg = 0;
6207 6 : iAlg < sizeof(asResamplingAlg) / sizeof(asResamplingAlg[0]);
6208 : iAlg++)
6209 : {
6210 6 : if (EQUAL(pszResampling, asResamplingAlg[iAlg].pszName))
6211 : {
6212 3 : eResampleAlg = asResamplingAlg[iAlg].eResampleAlg;
6213 3 : break;
6214 : }
6215 : }
6216 : }
6217 :
6218 16 : if (nBands == 1 && poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr &&
6219 36 : eResampleAlg != GRA_NearestNeighbour && eResampleAlg != GRA_Mode)
6220 : {
6221 0 : CPLError(
6222 : CE_Warning, CPLE_AppDefined,
6223 : "Input dataset has a color table, which will likely lead to "
6224 : "bad results when using a resampling method other than "
6225 : "nearest neighbour or mode. Converting the dataset to 24/32 bit "
6226 : "(e.g. with gdal_translate -expand rgb/rgba) is advised.");
6227 : }
6228 :
6229 40 : auto poDS = std::make_unique<GDALGeoPackageDataset>();
6230 40 : if (!(poDS->Create(pszFilename, nXSize, nYSize, nTargetBands, eDT,
6231 20 : apszUpdatedOptions)))
6232 : {
6233 1 : CPLFree(pszWKT);
6234 1 : CSLDestroy(papszTO);
6235 1 : return nullptr;
6236 : }
6237 :
6238 : // Assign nodata values before the SetGeoTransform call.
6239 : // SetGeoTransform will trigger creation of the overview datasets for each
6240 : // zoom level and at that point the nodata value needs to be known.
6241 19 : int bHasNoData = FALSE;
6242 : double dfNoDataValue =
6243 19 : poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
6244 19 : if (eDT != GDT_UInt8 && bHasNoData)
6245 : {
6246 3 : poDS->GetRasterBand(1)->SetNoDataValue(dfNoDataValue);
6247 : }
6248 :
6249 19 : poDS->SetGeoTransform(gt);
6250 19 : poDS->SetProjection(pszWKT);
6251 19 : CPLFree(pszWKT);
6252 19 : pszWKT = nullptr;
6253 24 : if (nTargetBands == 1 && nBands == 1 &&
6254 5 : poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr)
6255 : {
6256 2 : poDS->GetRasterBand(1)->SetColorTable(
6257 1 : poSrcDS->GetRasterBand(1)->GetColorTable());
6258 : }
6259 :
6260 : hTransformArg =
6261 19 : GDALCreateGenImgProjTransformer2(poSrcDS, poDS.get(), papszTO);
6262 19 : CSLDestroy(papszTO);
6263 19 : if (hTransformArg == nullptr)
6264 : {
6265 0 : return nullptr;
6266 : }
6267 :
6268 19 : poDS->SetMetadata(poSrcDS->GetMetadata());
6269 :
6270 : /* -------------------------------------------------------------------- */
6271 : /* Warp the transformer with a linear approximator */
6272 : /* -------------------------------------------------------------------- */
6273 19 : hTransformArg = GDALCreateApproxTransformer(GDALGenImgProjTransform,
6274 : hTransformArg, 0.125);
6275 19 : GDALApproxTransformerOwnsSubtransformer(hTransformArg, TRUE);
6276 :
6277 : /* -------------------------------------------------------------------- */
6278 : /* Setup warp options. */
6279 : /* -------------------------------------------------------------------- */
6280 19 : GDALWarpOptions *psWO = GDALCreateWarpOptions();
6281 :
6282 19 : psWO->papszWarpOptions = CSLSetNameValue(nullptr, "OPTIMIZE_SIZE", "YES");
6283 19 : psWO->papszWarpOptions =
6284 19 : CSLSetNameValue(psWO->papszWarpOptions, "SAMPLE_GRID", "YES");
6285 19 : if (bHasNoData)
6286 : {
6287 3 : if (dfNoDataValue == 0.0)
6288 : {
6289 : // Do not initialize in the case where nodata != 0, since we
6290 : // want the GeoPackage driver to return empty tiles at the nodata
6291 : // value instead of 0 as GDAL core would
6292 0 : psWO->papszWarpOptions =
6293 0 : CSLSetNameValue(psWO->papszWarpOptions, "INIT_DEST", "0");
6294 : }
6295 :
6296 3 : psWO->padfSrcNoDataReal =
6297 3 : static_cast<double *>(CPLMalloc(sizeof(double)));
6298 3 : psWO->padfSrcNoDataReal[0] = dfNoDataValue;
6299 :
6300 3 : psWO->padfDstNoDataReal =
6301 3 : static_cast<double *>(CPLMalloc(sizeof(double)));
6302 3 : psWO->padfDstNoDataReal[0] = dfNoDataValue;
6303 : }
6304 19 : psWO->eWorkingDataType = eDT;
6305 19 : psWO->eResampleAlg = eResampleAlg;
6306 :
6307 19 : psWO->hSrcDS = poSrcDS;
6308 19 : psWO->hDstDS = poDS.get();
6309 :
6310 19 : psWO->pfnTransformer = GDALApproxTransform;
6311 19 : psWO->pTransformerArg = hTransformArg;
6312 :
6313 19 : psWO->pfnProgress = pfnProgress;
6314 19 : psWO->pProgressArg = pProgressData;
6315 :
6316 : /* -------------------------------------------------------------------- */
6317 : /* Setup band mapping. */
6318 : /* -------------------------------------------------------------------- */
6319 :
6320 19 : if (nBands == 2 || nBands == 4)
6321 1 : psWO->nBandCount = nBands - 1;
6322 : else
6323 18 : psWO->nBandCount = nBands;
6324 :
6325 19 : psWO->panSrcBands =
6326 19 : static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
6327 19 : psWO->panDstBands =
6328 19 : static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
6329 :
6330 46 : for (int i = 0; i < psWO->nBandCount; i++)
6331 : {
6332 27 : psWO->panSrcBands[i] = i + 1;
6333 27 : psWO->panDstBands[i] = i + 1;
6334 : }
6335 :
6336 19 : if (nBands == 2 || nBands == 4)
6337 : {
6338 1 : psWO->nSrcAlphaBand = nBands;
6339 : }
6340 19 : if (nTargetBands == 2 || nTargetBands == 4)
6341 : {
6342 13 : psWO->nDstAlphaBand = nTargetBands;
6343 : }
6344 :
6345 : /* -------------------------------------------------------------------- */
6346 : /* Initialize and execute the warp. */
6347 : /* -------------------------------------------------------------------- */
6348 38 : GDALWarpOperation oWO;
6349 :
6350 19 : CPLErr eErr = oWO.Initialize(psWO);
6351 19 : if (eErr == CE_None)
6352 : {
6353 : /*if( bMulti )
6354 : eErr = oWO.ChunkAndWarpMulti( 0, 0, nXSize, nYSize );
6355 : else*/
6356 19 : eErr = oWO.ChunkAndWarpImage(0, 0, nXSize, nYSize);
6357 : }
6358 19 : if (eErr != CE_None)
6359 : {
6360 0 : poDS.reset();
6361 : }
6362 :
6363 19 : GDALDestroyTransformer(hTransformArg);
6364 19 : GDALDestroyWarpOptions(psWO);
6365 :
6366 19 : if (poDS)
6367 19 : poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
6368 :
6369 19 : return poDS.release();
6370 : }
6371 :
6372 : /************************************************************************/
6373 : /* ParseCompressionOptions() */
6374 : /************************************************************************/
6375 :
6376 476 : void GDALGeoPackageDataset::ParseCompressionOptions(CSLConstList papszOptions)
6377 : {
6378 476 : const char *pszZLevel = CSLFetchNameValue(papszOptions, "ZLEVEL");
6379 476 : if (pszZLevel)
6380 0 : m_nZLevel = atoi(pszZLevel);
6381 :
6382 476 : const char *pszQuality = CSLFetchNameValue(papszOptions, "QUALITY");
6383 476 : if (pszQuality)
6384 0 : m_nQuality = atoi(pszQuality);
6385 :
6386 476 : const char *pszDither = CSLFetchNameValue(papszOptions, "DITHER");
6387 476 : if (pszDither)
6388 0 : m_bDither = CPLTestBool(pszDither);
6389 476 : }
6390 :
6391 : /************************************************************************/
6392 : /* RegisterWebPExtension() */
6393 : /************************************************************************/
6394 :
6395 11 : bool GDALGeoPackageDataset::RegisterWebPExtension()
6396 : {
6397 11 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
6398 0 : return false;
6399 :
6400 11 : char *pszSQL = sqlite3_mprintf(
6401 : "INSERT INTO gpkg_extensions "
6402 : "(table_name, column_name, extension_name, definition, scope) "
6403 : "VALUES "
6404 : "('%q', 'tile_data', 'gpkg_webp', "
6405 : "'http://www.geopackage.org/spec120/#extension_tiles_webp', "
6406 : "'read-write')",
6407 : m_osRasterTable.c_str());
6408 11 : const OGRErr eErr = SQLCommand(hDB, pszSQL);
6409 11 : sqlite3_free(pszSQL);
6410 :
6411 11 : return OGRERR_NONE == eErr;
6412 : }
6413 :
6414 : /************************************************************************/
6415 : /* RegisterZoomOtherExtension() */
6416 : /************************************************************************/
6417 :
6418 1 : bool GDALGeoPackageDataset::RegisterZoomOtherExtension()
6419 : {
6420 1 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
6421 0 : return false;
6422 :
6423 1 : char *pszSQL = sqlite3_mprintf(
6424 : "INSERT INTO gpkg_extensions "
6425 : "(table_name, column_name, extension_name, definition, scope) "
6426 : "VALUES "
6427 : "('%q', 'tile_data', 'gpkg_zoom_other', "
6428 : "'http://www.geopackage.org/spec120/#extension_zoom_other_intervals', "
6429 : "'read-write')",
6430 : m_osRasterTable.c_str());
6431 1 : const OGRErr eErr = SQLCommand(hDB, pszSQL);
6432 1 : sqlite3_free(pszSQL);
6433 1 : return OGRERR_NONE == eErr;
6434 : }
6435 :
6436 : /************************************************************************/
6437 : /* GetLayer() */
6438 : /************************************************************************/
6439 :
6440 17210 : const OGRLayer *GDALGeoPackageDataset::GetLayer(int iLayer) const
6441 :
6442 : {
6443 17210 : if (iLayer < 0 || iLayer >= static_cast<int>(m_apoLayers.size()))
6444 7 : return nullptr;
6445 : else
6446 17203 : return m_apoLayers[iLayer].get();
6447 : }
6448 :
6449 : /************************************************************************/
6450 : /* LaunderName() */
6451 : /************************************************************************/
6452 :
6453 : /** Launder identifiers (table, column names) according to guidance at
6454 : * https://www.geopackage.org/guidance/getting-started.html:
6455 : * "For maximum interoperability, start your database identifiers (table names,
6456 : * column names, etc.) with a lowercase character and only use lowercase
6457 : * characters, numbers 0-9, and underscores (_)."
6458 : */
6459 :
6460 : /* static */
6461 5 : std::string GDALGeoPackageDataset::LaunderName(const std::string &osStr)
6462 : {
6463 5 : char *pszASCII = CPLUTF8ForceToASCII(osStr.c_str(), '_');
6464 10 : const std::string osStrASCII(pszASCII);
6465 5 : CPLFree(pszASCII);
6466 :
6467 10 : std::string osRet;
6468 5 : osRet.reserve(osStrASCII.size());
6469 :
6470 29 : for (size_t i = 0; i < osStrASCII.size(); ++i)
6471 : {
6472 24 : if (osRet.empty())
6473 : {
6474 5 : if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
6475 : {
6476 2 : osRet += (osStrASCII[i] - 'A' + 'a');
6477 : }
6478 3 : else if (osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z')
6479 : {
6480 2 : osRet += osStrASCII[i];
6481 : }
6482 : else
6483 : {
6484 1 : continue;
6485 : }
6486 : }
6487 19 : else if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
6488 : {
6489 11 : osRet += (osStrASCII[i] - 'A' + 'a');
6490 : }
6491 9 : else if ((osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z') ||
6492 14 : (osStrASCII[i] >= '0' && osStrASCII[i] <= '9') ||
6493 5 : osStrASCII[i] == '_')
6494 : {
6495 7 : osRet += osStrASCII[i];
6496 : }
6497 : else
6498 : {
6499 1 : osRet += '_';
6500 : }
6501 : }
6502 :
6503 5 : if (osRet.empty() && !osStrASCII.empty())
6504 2 : return LaunderName(std::string("x").append(osStrASCII));
6505 :
6506 4 : if (osRet != osStr)
6507 : {
6508 3 : CPLDebug("PG", "LaunderName('%s') -> '%s'", osStr.c_str(),
6509 : osRet.c_str());
6510 : }
6511 :
6512 4 : return osRet;
6513 : }
6514 :
6515 : /************************************************************************/
6516 : /* ICreateLayer() */
6517 : /************************************************************************/
6518 :
6519 : OGRLayer *
6520 1030 : GDALGeoPackageDataset::ICreateLayer(const char *pszLayerName,
6521 : const OGRGeomFieldDefn *poSrcGeomFieldDefn,
6522 : CSLConstList papszOptions)
6523 : {
6524 : /* -------------------------------------------------------------------- */
6525 : /* Verify we are in update mode. */
6526 : /* -------------------------------------------------------------------- */
6527 1030 : if (!GetUpdate())
6528 : {
6529 0 : CPLError(CE_Failure, CPLE_NoWriteAccess,
6530 : "Data source %s opened read-only.\n"
6531 : "New layer %s cannot be created.\n",
6532 : m_pszFilename, pszLayerName);
6533 :
6534 0 : return nullptr;
6535 : }
6536 :
6537 : const bool bLaunder =
6538 1030 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "LAUNDER", "NO"));
6539 : const std::string osTableName(bLaunder ? LaunderName(pszLayerName)
6540 3090 : : std::string(pszLayerName));
6541 :
6542 : const auto eGType =
6543 1030 : poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone;
6544 : const auto poSpatialRef =
6545 1030 : poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr;
6546 :
6547 1030 : if (!m_bHasGPKGGeometryColumns)
6548 : {
6549 1 : if (SQLCommand(hDB, pszCREATE_GPKG_GEOMETRY_COLUMNS) != OGRERR_NONE)
6550 : {
6551 0 : return nullptr;
6552 : }
6553 1 : m_bHasGPKGGeometryColumns = true;
6554 : }
6555 :
6556 : // Check identifier unicity
6557 1030 : const char *pszIdentifier = CSLFetchNameValue(papszOptions, "IDENTIFIER");
6558 1030 : if (pszIdentifier != nullptr && pszIdentifier[0] == '\0')
6559 0 : pszIdentifier = nullptr;
6560 1030 : if (pszIdentifier != nullptr)
6561 : {
6562 13 : for (auto &poLayer : m_apoLayers)
6563 : {
6564 : const char *pszOtherIdentifier =
6565 9 : poLayer->GetMetadataItem("IDENTIFIER");
6566 9 : if (pszOtherIdentifier == nullptr)
6567 6 : pszOtherIdentifier = poLayer->GetName();
6568 18 : if (pszOtherIdentifier != nullptr &&
6569 12 : EQUAL(pszOtherIdentifier, pszIdentifier) &&
6570 3 : !EQUAL(poLayer->GetName(), osTableName.c_str()))
6571 : {
6572 2 : CPLError(CE_Failure, CPLE_AppDefined,
6573 : "Identifier %s is already used by table %s",
6574 : pszIdentifier, poLayer->GetName());
6575 2 : return nullptr;
6576 : }
6577 : }
6578 :
6579 : // In case there would be table in gpkg_contents not listed as a
6580 : // vector layer
6581 4 : char *pszSQL = sqlite3_mprintf(
6582 : "SELECT table_name FROM gpkg_contents WHERE identifier = '%q' "
6583 : "LIMIT 2",
6584 : pszIdentifier);
6585 4 : auto oResult = SQLQuery(hDB, pszSQL);
6586 4 : sqlite3_free(pszSQL);
6587 8 : if (oResult && oResult->RowCount() > 0 &&
6588 9 : oResult->GetValue(0, 0) != nullptr &&
6589 1 : !EQUAL(oResult->GetValue(0, 0), osTableName.c_str()))
6590 : {
6591 1 : CPLError(CE_Failure, CPLE_AppDefined,
6592 : "Identifier %s is already used by table %s", pszIdentifier,
6593 : oResult->GetValue(0, 0));
6594 1 : return nullptr;
6595 : }
6596 : }
6597 :
6598 : /* Read GEOMETRY_NAME option */
6599 : const char *pszGeomColumnName =
6600 1027 : CSLFetchNameValue(papszOptions, "GEOMETRY_NAME");
6601 1027 : if (pszGeomColumnName == nullptr) /* deprecated name */
6602 933 : pszGeomColumnName = CSLFetchNameValue(papszOptions, "GEOMETRY_COLUMN");
6603 1027 : if (pszGeomColumnName == nullptr && poSrcGeomFieldDefn)
6604 : {
6605 851 : pszGeomColumnName = poSrcGeomFieldDefn->GetNameRef();
6606 851 : if (pszGeomColumnName && pszGeomColumnName[0] == 0)
6607 816 : pszGeomColumnName = nullptr;
6608 : }
6609 1027 : if (pszGeomColumnName == nullptr)
6610 898 : pszGeomColumnName = "geom";
6611 : const bool bGeomNullable =
6612 1027 : CPLFetchBool(papszOptions, "GEOMETRY_NULLABLE", true);
6613 :
6614 : /* Read FID option */
6615 1027 : const char *pszFIDColumnName = CSLFetchNameValue(papszOptions, "FID");
6616 1027 : if (pszFIDColumnName == nullptr)
6617 929 : pszFIDColumnName = "fid";
6618 :
6619 1027 : if (CPLTestBool(CPLGetConfigOption("GPKG_NAME_CHECK", "YES")))
6620 : {
6621 1027 : if (strspn(pszFIDColumnName, "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") > 0)
6622 : {
6623 2 : CPLError(CE_Failure, CPLE_AppDefined,
6624 : "The primary key (%s) name may not contain special "
6625 : "characters or spaces",
6626 : pszFIDColumnName);
6627 2 : return nullptr;
6628 : }
6629 :
6630 : /* Avoiding gpkg prefixes is not an official requirement, but seems wise
6631 : */
6632 1025 : if (STARTS_WITH(osTableName.c_str(), "gpkg"))
6633 : {
6634 0 : CPLError(CE_Failure, CPLE_AppDefined,
6635 : "The layer name may not begin with 'gpkg' as it is a "
6636 : "reserved geopackage prefix");
6637 0 : return nullptr;
6638 : }
6639 :
6640 : /* Preemptively try and avoid sqlite3 syntax errors due to */
6641 : /* illegal characters. */
6642 1025 : if (strspn(osTableName.c_str(), "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") >
6643 : 0)
6644 : {
6645 0 : CPLError(
6646 : CE_Failure, CPLE_AppDefined,
6647 : "The layer name may not contain special characters or spaces");
6648 0 : return nullptr;
6649 : }
6650 : }
6651 :
6652 : /* Check for any existing layers that already use this name */
6653 1268 : for (int iLayer = 0; iLayer < static_cast<int>(m_apoLayers.size());
6654 : iLayer++)
6655 : {
6656 244 : if (EQUAL(osTableName.c_str(), m_apoLayers[iLayer]->GetName()))
6657 : {
6658 : const char *pszOverwrite =
6659 2 : CSLFetchNameValue(papszOptions, "OVERWRITE");
6660 2 : if (pszOverwrite != nullptr && CPLTestBool(pszOverwrite))
6661 : {
6662 1 : DeleteLayer(iLayer);
6663 : }
6664 : else
6665 : {
6666 1 : CPLError(CE_Failure, CPLE_AppDefined,
6667 : "Layer %s already exists, CreateLayer failed.\n"
6668 : "Use the layer creation option OVERWRITE=YES to "
6669 : "replace it.",
6670 : osTableName.c_str());
6671 1 : return nullptr;
6672 : }
6673 : }
6674 : }
6675 :
6676 1024 : if (m_apoLayers.size() == 1)
6677 : {
6678 : // Async RTree building doesn't play well with multiple layer:
6679 : // SQLite3 locks being hold for a long time, random failed commits,
6680 : // etc.
6681 95 : m_apoLayers[0]->FinishOrDisableThreadedRTree();
6682 : }
6683 :
6684 : /* Create a blank layer. */
6685 : auto poLayer =
6686 2048 : std::make_unique<OGRGeoPackageTableLayer>(this, osTableName.c_str());
6687 :
6688 2048 : OGRSpatialReferenceRefCountedPtr poSRS;
6689 1024 : if (poSpatialRef)
6690 : {
6691 343 : poSRS = OGRSpatialReferenceRefCountedPtr::makeClone(poSpatialRef);
6692 343 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
6693 : }
6694 3073 : poLayer->SetCreationParameters(
6695 : eGType,
6696 1025 : bLaunder ? LaunderName(pszGeomColumnName).c_str() : pszGeomColumnName,
6697 1024 : bGeomNullable, poSRS.get(), CSLFetchNameValue(papszOptions, "SRID"),
6698 2048 : poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetCoordinatePrecision()
6699 : : OGRGeomCoordinatePrecision(),
6700 1024 : CPLTestBool(
6701 : CSLFetchNameValueDef(papszOptions, "DISCARD_COORD_LSB", "NO")),
6702 1024 : CPLTestBool(CSLFetchNameValueDef(
6703 : papszOptions, "UNDO_DISCARD_COORD_LSB_ON_READING", "NO")),
6704 1025 : bLaunder ? LaunderName(pszFIDColumnName).c_str() : pszFIDColumnName,
6705 : pszIdentifier, CSLFetchNameValue(papszOptions, "DESCRIPTION"));
6706 :
6707 1024 : poLayer->SetLaunder(bLaunder);
6708 :
6709 : /* Should we create a spatial index ? */
6710 1024 : const char *pszSI = CSLFetchNameValue(papszOptions, "SPATIAL_INDEX");
6711 1024 : int bCreateSpatialIndex = (pszSI == nullptr || CPLTestBool(pszSI));
6712 1024 : if (eGType != wkbNone && bCreateSpatialIndex)
6713 : {
6714 920 : poLayer->SetDeferredSpatialIndexCreation(true);
6715 : }
6716 :
6717 1024 : poLayer->SetPrecisionFlag(CPLFetchBool(papszOptions, "PRECISION", true));
6718 1024 : poLayer->SetTruncateFieldsFlag(
6719 1024 : CPLFetchBool(papszOptions, "TRUNCATE_FIELDS", false));
6720 1024 : if (eGType == wkbNone)
6721 : {
6722 82 : const char *pszASpatialVariant = CSLFetchNameValueDef(
6723 : papszOptions, "ASPATIAL_VARIANT",
6724 82 : m_bNonSpatialTablesNonRegisteredInGpkgContentsFound
6725 : ? "NOT_REGISTERED"
6726 : : "GPKG_ATTRIBUTES");
6727 82 : GPKGASpatialVariant eASpatialVariant = GPKG_ATTRIBUTES;
6728 82 : if (EQUAL(pszASpatialVariant, "GPKG_ATTRIBUTES"))
6729 70 : eASpatialVariant = GPKG_ATTRIBUTES;
6730 12 : else if (EQUAL(pszASpatialVariant, "OGR_ASPATIAL"))
6731 : {
6732 0 : CPLError(CE_Failure, CPLE_NotSupported,
6733 : "ASPATIAL_VARIANT=OGR_ASPATIAL is no longer supported");
6734 0 : return nullptr;
6735 : }
6736 12 : else if (EQUAL(pszASpatialVariant, "NOT_REGISTERED"))
6737 12 : eASpatialVariant = NOT_REGISTERED;
6738 : else
6739 : {
6740 0 : CPLError(CE_Failure, CPLE_NotSupported,
6741 : "Unsupported value for ASPATIAL_VARIANT: %s",
6742 : pszASpatialVariant);
6743 0 : return nullptr;
6744 : }
6745 82 : poLayer->SetASpatialVariant(eASpatialVariant);
6746 : }
6747 :
6748 : const char *pszDateTimePrecision =
6749 1024 : CSLFetchNameValueDef(papszOptions, "DATETIME_PRECISION", "AUTO");
6750 1024 : if (EQUAL(pszDateTimePrecision, "MILLISECOND"))
6751 : {
6752 2 : poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
6753 : }
6754 1022 : else if (EQUAL(pszDateTimePrecision, "SECOND"))
6755 : {
6756 1 : if (m_nUserVersion < GPKG_1_4_VERSION)
6757 0 : CPLError(
6758 : CE_Warning, CPLE_AppDefined,
6759 : "DATETIME_PRECISION=SECOND is only valid since GeoPackage 1.4");
6760 1 : poLayer->SetDateTimePrecision(OGRISO8601Precision::SECOND);
6761 : }
6762 1021 : else if (EQUAL(pszDateTimePrecision, "MINUTE"))
6763 : {
6764 1 : if (m_nUserVersion < GPKG_1_4_VERSION)
6765 0 : CPLError(
6766 : CE_Warning, CPLE_AppDefined,
6767 : "DATETIME_PRECISION=MINUTE is only valid since GeoPackage 1.4");
6768 1 : poLayer->SetDateTimePrecision(OGRISO8601Precision::MINUTE);
6769 : }
6770 1020 : else if (EQUAL(pszDateTimePrecision, "AUTO"))
6771 : {
6772 1019 : if (m_nUserVersion < GPKG_1_4_VERSION)
6773 13 : poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
6774 : }
6775 : else
6776 : {
6777 1 : CPLError(CE_Failure, CPLE_NotSupported,
6778 : "Unsupported value for DATETIME_PRECISION: %s",
6779 : pszDateTimePrecision);
6780 1 : return nullptr;
6781 : }
6782 :
6783 : // If there was an ogr_empty_table table, we can remove it
6784 : // But do it at dataset closing, otherwise locking performance issues
6785 : // can arise (probably when transactions are used).
6786 1023 : m_bRemoveOGREmptyTable = true;
6787 :
6788 1023 : m_apoLayers.emplace_back(std::move(poLayer));
6789 1023 : return m_apoLayers.back().get();
6790 : }
6791 :
6792 : /************************************************************************/
6793 : /* FindLayerIndex() */
6794 : /************************************************************************/
6795 :
6796 29 : int GDALGeoPackageDataset::FindLayerIndex(const char *pszLayerName)
6797 :
6798 : {
6799 51 : for (int iLayer = 0; iLayer < static_cast<int>(m_apoLayers.size());
6800 : iLayer++)
6801 : {
6802 35 : if (EQUAL(pszLayerName, m_apoLayers[iLayer]->GetName()))
6803 13 : return iLayer;
6804 : }
6805 16 : return -1;
6806 : }
6807 :
6808 : /************************************************************************/
6809 : /* DeleteLayerCommon() */
6810 : /************************************************************************/
6811 :
6812 43 : OGRErr GDALGeoPackageDataset::DeleteLayerCommon(const char *pszLayerName)
6813 : {
6814 : // Temporary remove foreign key checks
6815 : const GPKGTemporaryForeignKeyCheckDisabler
6816 43 : oGPKGTemporaryForeignKeyCheckDisabler(this);
6817 :
6818 43 : char *pszSQL = sqlite3_mprintf(
6819 : "DELETE FROM gpkg_contents WHERE lower(table_name) = lower('%q')",
6820 : pszLayerName);
6821 43 : OGRErr eErr = SQLCommand(hDB, pszSQL);
6822 43 : sqlite3_free(pszSQL);
6823 :
6824 43 : if (eErr == OGRERR_NONE && HasExtensionsTable())
6825 : {
6826 41 : pszSQL = sqlite3_mprintf(
6827 : "DELETE FROM gpkg_extensions WHERE lower(table_name) = lower('%q')",
6828 : pszLayerName);
6829 41 : eErr = SQLCommand(hDB, pszSQL);
6830 41 : sqlite3_free(pszSQL);
6831 : }
6832 :
6833 43 : if (eErr == OGRERR_NONE && HasMetadataTables())
6834 : {
6835 : // Delete from gpkg_metadata metadata records that are only referenced
6836 : // by the table we are about to drop
6837 12 : pszSQL = sqlite3_mprintf(
6838 : "DELETE FROM gpkg_metadata WHERE id IN ("
6839 : "SELECT DISTINCT md_file_id FROM "
6840 : "gpkg_metadata_reference WHERE "
6841 : "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
6842 : "AND id NOT IN ("
6843 : "SELECT DISTINCT md_file_id FROM gpkg_metadata_reference WHERE "
6844 : "md_file_id IN (SELECT DISTINCT md_file_id FROM "
6845 : "gpkg_metadata_reference WHERE "
6846 : "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
6847 : "AND lower(table_name) <> lower('%q'))",
6848 : pszLayerName, pszLayerName, pszLayerName);
6849 12 : eErr = SQLCommand(hDB, pszSQL);
6850 12 : sqlite3_free(pszSQL);
6851 :
6852 12 : if (eErr == OGRERR_NONE)
6853 : {
6854 : pszSQL =
6855 12 : sqlite3_mprintf("DELETE FROM gpkg_metadata_reference WHERE "
6856 : "lower(table_name) = lower('%q')",
6857 : pszLayerName);
6858 12 : eErr = SQLCommand(hDB, pszSQL);
6859 12 : sqlite3_free(pszSQL);
6860 : }
6861 : }
6862 :
6863 43 : if (eErr == OGRERR_NONE && HasGpkgextRelationsTable())
6864 : {
6865 : // Remove reference to potential corresponding mapping table in
6866 : // gpkg_extensions
6867 4 : pszSQL = sqlite3_mprintf(
6868 : "DELETE FROM gpkg_extensions WHERE "
6869 : "extension_name IN ('related_tables', "
6870 : "'gpkg_related_tables') AND lower(table_name) = "
6871 : "(SELECT lower(mapping_table_name) FROM gpkgext_relations WHERE "
6872 : "lower(base_table_name) = lower('%q') OR "
6873 : "lower(related_table_name) = lower('%q') OR "
6874 : "lower(mapping_table_name) = lower('%q'))",
6875 : pszLayerName, pszLayerName, pszLayerName);
6876 4 : eErr = SQLCommand(hDB, pszSQL);
6877 4 : sqlite3_free(pszSQL);
6878 :
6879 4 : if (eErr == OGRERR_NONE)
6880 : {
6881 : // Remove reference to potential corresponding mapping table in
6882 : // gpkgext_relations
6883 : pszSQL =
6884 4 : sqlite3_mprintf("DELETE FROM gpkgext_relations WHERE "
6885 : "lower(base_table_name) = lower('%q') OR "
6886 : "lower(related_table_name) = lower('%q') OR "
6887 : "lower(mapping_table_name) = lower('%q')",
6888 : pszLayerName, pszLayerName, pszLayerName);
6889 4 : eErr = SQLCommand(hDB, pszSQL);
6890 4 : sqlite3_free(pszSQL);
6891 : }
6892 :
6893 4 : if (eErr == OGRERR_NONE && HasExtensionsTable())
6894 : {
6895 : // If there is no longer any mapping table, then completely
6896 : // remove any reference to the extension in gpkg_extensions
6897 : // as mandated per the related table specification.
6898 : OGRErr err;
6899 4 : if (SQLGetInteger(hDB,
6900 : "SELECT COUNT(*) FROM gpkg_extensions WHERE "
6901 : "extension_name IN ('related_tables', "
6902 : "'gpkg_related_tables') AND "
6903 : "lower(table_name) != 'gpkgext_relations'",
6904 4 : &err) == 0)
6905 : {
6906 2 : eErr = SQLCommand(hDB, "DELETE FROM gpkg_extensions WHERE "
6907 : "extension_name IN ('related_tables', "
6908 : "'gpkg_related_tables')");
6909 : }
6910 :
6911 4 : ClearCachedRelationships();
6912 : }
6913 : }
6914 :
6915 43 : if (eErr == OGRERR_NONE)
6916 : {
6917 43 : pszSQL = sqlite3_mprintf("DROP TABLE \"%w\"", pszLayerName);
6918 43 : eErr = SQLCommand(hDB, pszSQL);
6919 43 : sqlite3_free(pszSQL);
6920 : }
6921 :
6922 : // Check foreign key integrity
6923 43 : if (eErr == OGRERR_NONE)
6924 : {
6925 43 : eErr = PragmaCheck("foreign_key_check", "", 0);
6926 : }
6927 :
6928 86 : return eErr;
6929 : }
6930 :
6931 : /************************************************************************/
6932 : /* DeleteLayer() */
6933 : /************************************************************************/
6934 :
6935 40 : OGRErr GDALGeoPackageDataset::DeleteLayer(int iLayer)
6936 : {
6937 79 : if (!GetUpdate() || iLayer < 0 ||
6938 39 : iLayer >= static_cast<int>(m_apoLayers.size()))
6939 2 : return OGRERR_FAILURE;
6940 :
6941 38 : m_apoLayers[iLayer]->ResetReading();
6942 38 : m_apoLayers[iLayer]->SyncToDisk();
6943 :
6944 76 : CPLString osLayerName = m_apoLayers[iLayer]->GetName();
6945 :
6946 38 : CPLDebug("GPKG", "DeleteLayer(%s)", osLayerName.c_str());
6947 :
6948 : // Temporary remove foreign key checks
6949 : const GPKGTemporaryForeignKeyCheckDisabler
6950 38 : oGPKGTemporaryForeignKeyCheckDisabler(this);
6951 :
6952 38 : OGRErr eErr = SoftStartTransaction();
6953 :
6954 38 : if (eErr == OGRERR_NONE)
6955 : {
6956 38 : if (m_apoLayers[iLayer]->HasSpatialIndex())
6957 35 : m_apoLayers[iLayer]->DropSpatialIndex();
6958 :
6959 : char *pszSQL =
6960 38 : sqlite3_mprintf("DELETE FROM gpkg_geometry_columns WHERE "
6961 : "lower(table_name) = lower('%q')",
6962 : osLayerName.c_str());
6963 38 : eErr = SQLCommand(hDB, pszSQL);
6964 38 : sqlite3_free(pszSQL);
6965 : }
6966 :
6967 38 : if (eErr == OGRERR_NONE && HasDataColumnsTable())
6968 : {
6969 1 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_data_columns WHERE "
6970 : "lower(table_name) = lower('%q')",
6971 : osLayerName.c_str());
6972 1 : eErr = SQLCommand(hDB, pszSQL);
6973 1 : sqlite3_free(pszSQL);
6974 : }
6975 :
6976 : #ifdef ENABLE_GPKG_OGR_CONTENTS
6977 38 : if (eErr == OGRERR_NONE && m_bHasGPKGOGRContents)
6978 : {
6979 38 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_ogr_contents WHERE "
6980 : "lower(table_name) = lower('%q')",
6981 : osLayerName.c_str());
6982 38 : eErr = SQLCommand(hDB, pszSQL);
6983 38 : sqlite3_free(pszSQL);
6984 : }
6985 : #endif
6986 :
6987 38 : if (eErr == OGRERR_NONE)
6988 : {
6989 38 : eErr = DeleteLayerCommon(osLayerName.c_str());
6990 : }
6991 :
6992 38 : if (eErr == OGRERR_NONE)
6993 : {
6994 38 : eErr = SoftCommitTransaction();
6995 38 : if (eErr == OGRERR_NONE)
6996 : {
6997 : /* Delete the layer object */
6998 38 : m_apoLayers.erase(m_apoLayers.begin() + iLayer);
6999 : }
7000 : }
7001 : else
7002 : {
7003 0 : SoftRollbackTransaction();
7004 : }
7005 :
7006 38 : return eErr;
7007 : }
7008 :
7009 : /************************************************************************/
7010 : /* DeleteRasterLayer() */
7011 : /************************************************************************/
7012 :
7013 2 : OGRErr GDALGeoPackageDataset::DeleteRasterLayer(const char *pszLayerName)
7014 : {
7015 : // Temporary remove foreign key checks
7016 : const GPKGTemporaryForeignKeyCheckDisabler
7017 2 : oGPKGTemporaryForeignKeyCheckDisabler(this);
7018 :
7019 2 : OGRErr eErr = SoftStartTransaction();
7020 :
7021 2 : if (eErr == OGRERR_NONE)
7022 : {
7023 2 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix WHERE "
7024 : "lower(table_name) = lower('%q')",
7025 : pszLayerName);
7026 2 : eErr = SQLCommand(hDB, pszSQL);
7027 2 : sqlite3_free(pszSQL);
7028 : }
7029 :
7030 2 : if (eErr == OGRERR_NONE)
7031 : {
7032 2 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix_set WHERE "
7033 : "lower(table_name) = lower('%q')",
7034 : pszLayerName);
7035 2 : eErr = SQLCommand(hDB, pszSQL);
7036 2 : sqlite3_free(pszSQL);
7037 : }
7038 :
7039 2 : if (eErr == OGRERR_NONE && HasGriddedCoverageAncillaryTable())
7040 : {
7041 : char *pszSQL =
7042 1 : sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_coverage_ancillary "
7043 : "WHERE lower(tile_matrix_set_name) = lower('%q')",
7044 : pszLayerName);
7045 1 : eErr = SQLCommand(hDB, pszSQL);
7046 1 : sqlite3_free(pszSQL);
7047 :
7048 1 : if (eErr == OGRERR_NONE)
7049 : {
7050 : pszSQL =
7051 1 : sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_tile_ancillary "
7052 : "WHERE lower(tpudt_name) = lower('%q')",
7053 : pszLayerName);
7054 1 : eErr = SQLCommand(hDB, pszSQL);
7055 1 : sqlite3_free(pszSQL);
7056 : }
7057 : }
7058 :
7059 2 : if (eErr == OGRERR_NONE)
7060 : {
7061 2 : eErr = DeleteLayerCommon(pszLayerName);
7062 : }
7063 :
7064 2 : if (eErr == OGRERR_NONE)
7065 : {
7066 2 : eErr = SoftCommitTransaction();
7067 : }
7068 : else
7069 : {
7070 0 : SoftRollbackTransaction();
7071 : }
7072 :
7073 4 : return eErr;
7074 : }
7075 :
7076 : /************************************************************************/
7077 : /* DeleteVectorOrRasterLayer() */
7078 : /************************************************************************/
7079 :
7080 13 : bool GDALGeoPackageDataset::DeleteVectorOrRasterLayer(const char *pszLayerName)
7081 : {
7082 :
7083 13 : int idx = FindLayerIndex(pszLayerName);
7084 13 : if (idx >= 0)
7085 : {
7086 5 : DeleteLayer(idx);
7087 5 : return true;
7088 : }
7089 :
7090 : char *pszSQL =
7091 8 : sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
7092 : "lower(table_name) = lower('%q') "
7093 : "AND data_type IN ('tiles', '2d-gridded-coverage')",
7094 : pszLayerName);
7095 8 : bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
7096 8 : sqlite3_free(pszSQL);
7097 8 : if (bIsRasterTable)
7098 : {
7099 2 : DeleteRasterLayer(pszLayerName);
7100 2 : return true;
7101 : }
7102 6 : return false;
7103 : }
7104 :
7105 7 : bool GDALGeoPackageDataset::RenameVectorOrRasterLayer(
7106 : const char *pszLayerName, const char *pszNewLayerName)
7107 : {
7108 7 : int idx = FindLayerIndex(pszLayerName);
7109 7 : if (idx >= 0)
7110 : {
7111 4 : m_apoLayers[idx]->Rename(pszNewLayerName);
7112 4 : return true;
7113 : }
7114 :
7115 : char *pszSQL =
7116 3 : sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
7117 : "lower(table_name) = lower('%q') "
7118 : "AND data_type IN ('tiles', '2d-gridded-coverage')",
7119 : pszLayerName);
7120 3 : const bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
7121 3 : sqlite3_free(pszSQL);
7122 :
7123 3 : if (bIsRasterTable)
7124 : {
7125 2 : return RenameRasterLayer(pszLayerName, pszNewLayerName);
7126 : }
7127 :
7128 1 : return false;
7129 : }
7130 :
7131 2 : bool GDALGeoPackageDataset::RenameRasterLayer(const char *pszLayerName,
7132 : const char *pszNewLayerName)
7133 : {
7134 4 : std::string osSQL;
7135 :
7136 2 : char *pszSQL = sqlite3_mprintf(
7137 : "SELECT 1 FROM sqlite_master WHERE lower(name) = lower('%q') "
7138 : "AND type IN ('table', 'view')",
7139 : pszNewLayerName);
7140 2 : const bool bAlreadyExists = SQLGetInteger(GetDB(), pszSQL, nullptr) == 1;
7141 2 : sqlite3_free(pszSQL);
7142 2 : if (bAlreadyExists)
7143 : {
7144 0 : CPLError(CE_Failure, CPLE_AppDefined, "Table %s already exists",
7145 : pszNewLayerName);
7146 0 : return false;
7147 : }
7148 :
7149 : // Temporary remove foreign key checks
7150 : const GPKGTemporaryForeignKeyCheckDisabler
7151 4 : oGPKGTemporaryForeignKeyCheckDisabler(this);
7152 :
7153 2 : if (SoftStartTransaction() != OGRERR_NONE)
7154 : {
7155 0 : return false;
7156 : }
7157 :
7158 2 : pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET table_name = '%q' WHERE "
7159 : "lower(table_name) = lower('%q');",
7160 : pszNewLayerName, pszLayerName);
7161 2 : osSQL = pszSQL;
7162 2 : sqlite3_free(pszSQL);
7163 :
7164 2 : pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' WHERE "
7165 : "lower(identifier) = lower('%q');",
7166 : pszNewLayerName, pszLayerName);
7167 2 : osSQL += pszSQL;
7168 2 : sqlite3_free(pszSQL);
7169 :
7170 : pszSQL =
7171 2 : sqlite3_mprintf("UPDATE gpkg_tile_matrix SET table_name = '%q' WHERE "
7172 : "lower(table_name) = lower('%q');",
7173 : pszNewLayerName, pszLayerName);
7174 2 : osSQL += pszSQL;
7175 2 : sqlite3_free(pszSQL);
7176 :
7177 2 : pszSQL = sqlite3_mprintf(
7178 : "UPDATE gpkg_tile_matrix_set SET table_name = '%q' WHERE "
7179 : "lower(table_name) = lower('%q');",
7180 : pszNewLayerName, pszLayerName);
7181 2 : osSQL += pszSQL;
7182 2 : sqlite3_free(pszSQL);
7183 :
7184 2 : if (HasGriddedCoverageAncillaryTable())
7185 : {
7186 1 : pszSQL = sqlite3_mprintf("UPDATE gpkg_2d_gridded_coverage_ancillary "
7187 : "SET tile_matrix_set_name = '%q' WHERE "
7188 : "lower(tile_matrix_set_name) = lower('%q');",
7189 : pszNewLayerName, pszLayerName);
7190 1 : osSQL += pszSQL;
7191 1 : sqlite3_free(pszSQL);
7192 :
7193 1 : pszSQL = sqlite3_mprintf(
7194 : "UPDATE gpkg_2d_gridded_tile_ancillary SET tpudt_name = '%q' WHERE "
7195 : "lower(tpudt_name) = lower('%q');",
7196 : pszNewLayerName, pszLayerName);
7197 1 : osSQL += pszSQL;
7198 1 : sqlite3_free(pszSQL);
7199 : }
7200 :
7201 2 : if (HasExtensionsTable())
7202 : {
7203 2 : pszSQL = sqlite3_mprintf(
7204 : "UPDATE gpkg_extensions SET table_name = '%q' WHERE "
7205 : "lower(table_name) = lower('%q');",
7206 : pszNewLayerName, pszLayerName);
7207 2 : osSQL += pszSQL;
7208 2 : sqlite3_free(pszSQL);
7209 : }
7210 :
7211 2 : if (HasMetadataTables())
7212 : {
7213 1 : pszSQL = sqlite3_mprintf(
7214 : "UPDATE gpkg_metadata_reference SET table_name = '%q' WHERE "
7215 : "lower(table_name) = lower('%q');",
7216 : pszNewLayerName, pszLayerName);
7217 1 : osSQL += pszSQL;
7218 1 : sqlite3_free(pszSQL);
7219 : }
7220 :
7221 2 : if (HasDataColumnsTable())
7222 : {
7223 0 : pszSQL = sqlite3_mprintf(
7224 : "UPDATE gpkg_data_columns SET table_name = '%q' WHERE "
7225 : "lower(table_name) = lower('%q');",
7226 : pszNewLayerName, pszLayerName);
7227 0 : osSQL += pszSQL;
7228 0 : sqlite3_free(pszSQL);
7229 : }
7230 :
7231 2 : if (HasQGISLayerStyles())
7232 : {
7233 : // Update QGIS styles
7234 : pszSQL =
7235 0 : sqlite3_mprintf("UPDATE layer_styles SET f_table_name = '%q' WHERE "
7236 : "lower(f_table_name) = lower('%q');",
7237 : pszNewLayerName, pszLayerName);
7238 0 : osSQL += pszSQL;
7239 0 : sqlite3_free(pszSQL);
7240 : }
7241 :
7242 : #ifdef ENABLE_GPKG_OGR_CONTENTS
7243 2 : if (m_bHasGPKGOGRContents)
7244 : {
7245 2 : pszSQL = sqlite3_mprintf(
7246 : "UPDATE gpkg_ogr_contents SET table_name = '%q' WHERE "
7247 : "lower(table_name) = lower('%q');",
7248 : pszNewLayerName, pszLayerName);
7249 2 : osSQL += pszSQL;
7250 2 : sqlite3_free(pszSQL);
7251 : }
7252 : #endif
7253 :
7254 2 : if (HasGpkgextRelationsTable())
7255 : {
7256 0 : pszSQL = sqlite3_mprintf(
7257 : "UPDATE gpkgext_relations SET base_table_name = '%q' WHERE "
7258 : "lower(base_table_name) = lower('%q');",
7259 : pszNewLayerName, pszLayerName);
7260 0 : osSQL += pszSQL;
7261 0 : sqlite3_free(pszSQL);
7262 :
7263 0 : pszSQL = sqlite3_mprintf(
7264 : "UPDATE gpkgext_relations SET related_table_name = '%q' WHERE "
7265 : "lower(related_table_name) = lower('%q');",
7266 : pszNewLayerName, pszLayerName);
7267 0 : osSQL += pszSQL;
7268 0 : sqlite3_free(pszSQL);
7269 :
7270 0 : pszSQL = sqlite3_mprintf(
7271 : "UPDATE gpkgext_relations SET mapping_table_name = '%q' WHERE "
7272 : "lower(mapping_table_name) = lower('%q');",
7273 : pszNewLayerName, pszLayerName);
7274 0 : osSQL += pszSQL;
7275 0 : sqlite3_free(pszSQL);
7276 : }
7277 :
7278 : // Drop all triggers for the layer
7279 2 : pszSQL = sqlite3_mprintf("SELECT name FROM sqlite_master WHERE type = "
7280 : "'trigger' AND tbl_name = '%q'",
7281 : pszLayerName);
7282 2 : auto oTriggerResult = SQLQuery(GetDB(), pszSQL);
7283 2 : sqlite3_free(pszSQL);
7284 2 : if (oTriggerResult)
7285 : {
7286 14 : for (int i = 0; i < oTriggerResult->RowCount(); i++)
7287 : {
7288 12 : const char *pszTriggerName = oTriggerResult->GetValue(0, i);
7289 12 : pszSQL = sqlite3_mprintf("DROP TRIGGER IF EXISTS \"%w\";",
7290 : pszTriggerName);
7291 12 : osSQL += pszSQL;
7292 12 : sqlite3_free(pszSQL);
7293 : }
7294 : }
7295 :
7296 2 : pszSQL = sqlite3_mprintf("ALTER TABLE \"%w\" RENAME TO \"%w\";",
7297 : pszLayerName, pszNewLayerName);
7298 2 : osSQL += pszSQL;
7299 2 : sqlite3_free(pszSQL);
7300 :
7301 : // Recreate all zoom/tile triggers
7302 2 : if (oTriggerResult)
7303 : {
7304 2 : osSQL += CreateRasterTriggersSQL(pszNewLayerName);
7305 : }
7306 :
7307 2 : OGRErr eErr = SQLCommand(GetDB(), osSQL.c_str());
7308 :
7309 : // Check foreign key integrity
7310 2 : if (eErr == OGRERR_NONE)
7311 : {
7312 2 : eErr = PragmaCheck("foreign_key_check", "", 0);
7313 : }
7314 :
7315 2 : if (eErr == OGRERR_NONE)
7316 : {
7317 2 : eErr = SoftCommitTransaction();
7318 : }
7319 : else
7320 : {
7321 0 : SoftRollbackTransaction();
7322 : }
7323 :
7324 2 : return eErr == OGRERR_NONE;
7325 : }
7326 :
7327 : /************************************************************************/
7328 : /* TestCapability() */
7329 : /************************************************************************/
7330 :
7331 615 : int GDALGeoPackageDataset::TestCapability(const char *pszCap) const
7332 : {
7333 615 : if (EQUAL(pszCap, ODsCCreateLayer) || EQUAL(pszCap, ODsCDeleteLayer) ||
7334 402 : EQUAL(pszCap, "RenameLayer"))
7335 : {
7336 213 : return GetUpdate();
7337 : }
7338 402 : else if (EQUAL(pszCap, ODsCCurveGeometries))
7339 12 : return TRUE;
7340 390 : else if (EQUAL(pszCap, ODsCMeasuredGeometries))
7341 8 : return TRUE;
7342 382 : else if (EQUAL(pszCap, ODsCZGeometries))
7343 8 : return TRUE;
7344 374 : else if (EQUAL(pszCap, ODsCRandomLayerWrite) ||
7345 374 : EQUAL(pszCap, GDsCAddRelationship) ||
7346 374 : EQUAL(pszCap, GDsCDeleteRelationship) ||
7347 374 : EQUAL(pszCap, GDsCUpdateRelationship) ||
7348 374 : EQUAL(pszCap, ODsCAddFieldDomain) ||
7349 372 : EQUAL(pszCap, ODsCUpdateFieldDomain) ||
7350 370 : EQUAL(pszCap, ODsCDeleteFieldDomain))
7351 : {
7352 6 : return GetUpdate();
7353 : }
7354 :
7355 368 : return OGRSQLiteBaseDataSource::TestCapability(pszCap);
7356 : }
7357 :
7358 : /************************************************************************/
7359 : /* ResetReadingAllLayers() */
7360 : /************************************************************************/
7361 :
7362 205 : void GDALGeoPackageDataset::ResetReadingAllLayers()
7363 : {
7364 415 : for (auto &poLayer : m_apoLayers)
7365 : {
7366 210 : poLayer->ResetReading();
7367 : }
7368 205 : }
7369 :
7370 : /************************************************************************/
7371 : /* ExecuteSQL() */
7372 : /************************************************************************/
7373 :
7374 : static const char *const apszFuncsWithSideEffects[] = {
7375 : "CreateSpatialIndex",
7376 : "DisableSpatialIndex",
7377 : "HasSpatialIndex",
7378 : "RegisterGeometryExtension",
7379 : };
7380 :
7381 5720 : OGRLayer *GDALGeoPackageDataset::ExecuteSQL(const char *pszSQLCommand,
7382 : OGRGeometry *poSpatialFilter,
7383 : const char *pszDialect)
7384 :
7385 : {
7386 5720 : m_bHasReadMetadataFromStorage = false;
7387 :
7388 5720 : FlushMetadata();
7389 :
7390 5738 : while (*pszSQLCommand != '\0' &&
7391 5738 : isspace(static_cast<unsigned char>(*pszSQLCommand)))
7392 18 : pszSQLCommand++;
7393 :
7394 11440 : CPLString osSQLCommand(pszSQLCommand);
7395 5720 : if (!osSQLCommand.empty() && osSQLCommand.back() == ';')
7396 48 : osSQLCommand.pop_back();
7397 :
7398 11439 : if (osSQLCommand.ifind("AsGPB(ST_") != std::string::npos ||
7399 5719 : osSQLCommand.ifind("AsGPB( ST_") != std::string::npos)
7400 : {
7401 1 : CPLError(CE_Warning, CPLE_AppDefined,
7402 : "Use of AsGPB(ST_xxx(...)) found in \"%s\". Since GDAL 3.13, "
7403 : "ST_xxx() functions return a GeoPackage geometry when used "
7404 : "with a GeoPackage connection, and the use of AsGPB() is no "
7405 : "longer needed. It is here automatically removed",
7406 : osSQLCommand.c_str());
7407 1 : osSQLCommand.replaceAll("AsGPB(ST_", "(ST_");
7408 1 : osSQLCommand.replaceAll("AsGPB( ST_", "(ST_");
7409 : }
7410 :
7411 5720 : if (pszDialect == nullptr || !EQUAL(pszDialect, "DEBUG"))
7412 : {
7413 : // Some SQL commands will influence the feature count behind our
7414 : // back, so disable it in that case.
7415 : #ifdef ENABLE_GPKG_OGR_CONTENTS
7416 : const bool bInsertOrDelete =
7417 5651 : osSQLCommand.ifind("insert into ") != std::string::npos ||
7418 2530 : osSQLCommand.ifind("insert or replace into ") !=
7419 8181 : std::string::npos ||
7420 2493 : osSQLCommand.ifind("delete from ") != std::string::npos;
7421 : const bool bRollback =
7422 5651 : osSQLCommand.ifind("rollback ") != std::string::npos;
7423 : #endif
7424 :
7425 7563 : for (auto &poLayer : m_apoLayers)
7426 : {
7427 1912 : if (poLayer->SyncToDisk() != OGRERR_NONE)
7428 0 : return nullptr;
7429 : #ifdef ENABLE_GPKG_OGR_CONTENTS
7430 2117 : if (bRollback ||
7431 205 : (bInsertOrDelete &&
7432 205 : osSQLCommand.ifind(poLayer->GetName()) != std::string::npos))
7433 : {
7434 203 : poLayer->DisableFeatureCount();
7435 : }
7436 : #endif
7437 : }
7438 : }
7439 :
7440 5720 : if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 0") ||
7441 5719 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=0") ||
7442 5719 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =0") ||
7443 5719 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 0"))
7444 : {
7445 1 : OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, false);
7446 : }
7447 5719 : else if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 1") ||
7448 5718 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=1") ||
7449 5718 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =1") ||
7450 5718 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 1"))
7451 : {
7452 1 : OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, true);
7453 : }
7454 :
7455 : /* -------------------------------------------------------------------- */
7456 : /* DEBUG "SELECT nolock" command. */
7457 : /* -------------------------------------------------------------------- */
7458 5789 : if (pszDialect != nullptr && EQUAL(pszDialect, "DEBUG") &&
7459 69 : EQUAL(osSQLCommand, "SELECT nolock"))
7460 : {
7461 3 : return new OGRSQLiteSingleFeatureLayer(osSQLCommand, m_bNoLock ? 1 : 0);
7462 : }
7463 :
7464 : /* -------------------------------------------------------------------- */
7465 : /* Special case DELLAYER: command. */
7466 : /* -------------------------------------------------------------------- */
7467 5717 : if (STARTS_WITH_CI(osSQLCommand, "DELLAYER:"))
7468 : {
7469 4 : const char *pszLayerName = osSQLCommand.c_str() + strlen("DELLAYER:");
7470 :
7471 4 : while (*pszLayerName == ' ')
7472 0 : pszLayerName++;
7473 :
7474 4 : if (!DeleteVectorOrRasterLayer(pszLayerName))
7475 : {
7476 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
7477 : pszLayerName);
7478 : }
7479 4 : return nullptr;
7480 : }
7481 :
7482 : /* -------------------------------------------------------------------- */
7483 : /* Special case RECOMPUTE EXTENT ON command. */
7484 : /* -------------------------------------------------------------------- */
7485 5713 : if (STARTS_WITH_CI(osSQLCommand, "RECOMPUTE EXTENT ON "))
7486 : {
7487 : const char *pszLayerName =
7488 4 : osSQLCommand.c_str() + strlen("RECOMPUTE EXTENT ON ");
7489 :
7490 4 : while (*pszLayerName == ' ')
7491 0 : pszLayerName++;
7492 :
7493 4 : int idx = FindLayerIndex(pszLayerName);
7494 4 : if (idx >= 0)
7495 : {
7496 4 : m_apoLayers[idx]->RecomputeExtent();
7497 : }
7498 : else
7499 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
7500 : pszLayerName);
7501 4 : return nullptr;
7502 : }
7503 :
7504 : /* -------------------------------------------------------------------- */
7505 : /* Intercept DROP TABLE */
7506 : /* -------------------------------------------------------------------- */
7507 5709 : if (STARTS_WITH_CI(osSQLCommand, "DROP TABLE "))
7508 : {
7509 9 : const char *pszLayerName = osSQLCommand.c_str() + strlen("DROP TABLE ");
7510 :
7511 9 : while (*pszLayerName == ' ')
7512 0 : pszLayerName++;
7513 :
7514 9 : if (DeleteVectorOrRasterLayer(SQLUnescape(pszLayerName)))
7515 4 : return nullptr;
7516 : }
7517 :
7518 : /* -------------------------------------------------------------------- */
7519 : /* Intercept ALTER TABLE src_table RENAME TO dst_table */
7520 : /* and ALTER TABLE table RENAME COLUMN src_name TO dst_name */
7521 : /* and ALTER TABLE table DROP COLUMN col_name */
7522 : /* */
7523 : /* We do this because SQLite mechanisms can't deal with updating */
7524 : /* literal values in gpkg_ tables that refer to table and column */
7525 : /* names. */
7526 : /* -------------------------------------------------------------------- */
7527 5705 : if (STARTS_WITH_CI(osSQLCommand, "ALTER TABLE "))
7528 : {
7529 9 : char **papszTokens = SQLTokenize(osSQLCommand);
7530 : /* ALTER TABLE src_table RENAME TO dst_table */
7531 16 : if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "RENAME") &&
7532 7 : EQUAL(papszTokens[4], "TO"))
7533 : {
7534 7 : const char *pszSrcTableName = papszTokens[2];
7535 7 : const char *pszDstTableName = papszTokens[5];
7536 7 : if (RenameVectorOrRasterLayer(SQLUnescape(pszSrcTableName),
7537 14 : SQLUnescape(pszDstTableName)))
7538 : {
7539 6 : CSLDestroy(papszTokens);
7540 6 : return nullptr;
7541 : }
7542 : }
7543 : /* ALTER TABLE table RENAME COLUMN src_name TO dst_name */
7544 2 : else if (CSLCount(papszTokens) == 8 &&
7545 1 : EQUAL(papszTokens[3], "RENAME") &&
7546 3 : EQUAL(papszTokens[4], "COLUMN") && EQUAL(papszTokens[6], "TO"))
7547 : {
7548 1 : const char *pszTableName = papszTokens[2];
7549 1 : const char *pszSrcColumn = papszTokens[5];
7550 1 : const char *pszDstColumn = papszTokens[7];
7551 : OGRGeoPackageTableLayer *poLayer =
7552 0 : dynamic_cast<OGRGeoPackageTableLayer *>(
7553 1 : GetLayerByName(SQLUnescape(pszTableName)));
7554 1 : if (poLayer)
7555 : {
7556 2 : int nSrcFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
7557 2 : SQLUnescape(pszSrcColumn));
7558 1 : if (nSrcFieldIdx >= 0)
7559 : {
7560 : // OFTString or any type will do as we just alter the name
7561 : // so it will be ignored.
7562 1 : OGRFieldDefn oFieldDefn(SQLUnescape(pszDstColumn),
7563 1 : OFTString);
7564 1 : poLayer->AlterFieldDefn(nSrcFieldIdx, &oFieldDefn,
7565 : ALTER_NAME_FLAG);
7566 1 : CSLDestroy(papszTokens);
7567 1 : return nullptr;
7568 : }
7569 : }
7570 : }
7571 : /* ALTER TABLE table DROP COLUMN col_name */
7572 2 : else if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "DROP") &&
7573 1 : EQUAL(papszTokens[4], "COLUMN"))
7574 : {
7575 1 : const char *pszTableName = papszTokens[2];
7576 1 : const char *pszColumnName = papszTokens[5];
7577 : OGRGeoPackageTableLayer *poLayer =
7578 0 : dynamic_cast<OGRGeoPackageTableLayer *>(
7579 1 : GetLayerByName(SQLUnescape(pszTableName)));
7580 1 : if (poLayer)
7581 : {
7582 2 : int nFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
7583 2 : SQLUnescape(pszColumnName));
7584 1 : if (nFieldIdx >= 0)
7585 : {
7586 1 : poLayer->DeleteField(nFieldIdx);
7587 1 : CSLDestroy(papszTokens);
7588 1 : return nullptr;
7589 : }
7590 : }
7591 : }
7592 1 : CSLDestroy(papszTokens);
7593 : }
7594 :
7595 5697 : if (ProcessTransactionSQL(osSQLCommand))
7596 : {
7597 253 : return nullptr;
7598 : }
7599 :
7600 5444 : if (EQUAL(osSQLCommand, "VACUUM"))
7601 : {
7602 13 : ResetReadingAllLayers();
7603 : }
7604 5431 : else if (STARTS_WITH_CI(osSQLCommand, "DELETE FROM "))
7605 : {
7606 : // Optimize truncation of a table, especially if it has a spatial
7607 : // index.
7608 23 : const CPLStringList aosTokens(SQLTokenize(osSQLCommand));
7609 23 : if (aosTokens.size() == 3)
7610 : {
7611 16 : const char *pszTableName = aosTokens[2];
7612 : OGRGeoPackageTableLayer *poLayer =
7613 8 : dynamic_cast<OGRGeoPackageTableLayer *>(
7614 24 : GetLayerByName(SQLUnescape(pszTableName)));
7615 16 : if (poLayer)
7616 : {
7617 8 : poLayer->Truncate();
7618 8 : return nullptr;
7619 : }
7620 : }
7621 : }
7622 5408 : else if (pszDialect != nullptr && EQUAL(pszDialect, "INDIRECT_SQLITE"))
7623 1 : return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter, "SQLITE");
7624 5407 : else if (pszDialect != nullptr && !EQUAL(pszDialect, "") &&
7625 67 : !EQUAL(pszDialect, "NATIVE") && !EQUAL(pszDialect, "SQLITE") &&
7626 67 : !EQUAL(pszDialect, "DEBUG"))
7627 1 : return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter,
7628 1 : pszDialect);
7629 :
7630 : /* -------------------------------------------------------------------- */
7631 : /* Prepare statement. */
7632 : /* -------------------------------------------------------------------- */
7633 : /* This will speed-up layer creation */
7634 : /* ORDER BY are costly to evaluate and are not necessary to establish */
7635 : /* the layer definition. */
7636 5434 : bool bUseStatementForGetNextFeature = true;
7637 5434 : bool bEmptyLayer = false;
7638 10868 : CPLString osSQLCommandTruncated(osSQLCommand);
7639 :
7640 18050 : if (osSQLCommand.ifind("SELECT ") == 0 &&
7641 6308 : CPLString(osSQLCommand.substr(1)).ifind("SELECT ") ==
7642 833 : std::string::npos &&
7643 833 : osSQLCommand.ifind(" UNION ") == std::string::npos &&
7644 7141 : osSQLCommand.ifind(" INTERSECT ") == std::string::npos &&
7645 833 : osSQLCommand.ifind(" EXCEPT ") == std::string::npos)
7646 : {
7647 833 : size_t nOrderByPos = osSQLCommand.ifind(" ORDER BY ");
7648 833 : if (nOrderByPos != std::string::npos)
7649 : {
7650 9 : osSQLCommandTruncated.resize(nOrderByPos);
7651 9 : bUseStatementForGetNextFeature = false;
7652 : }
7653 : }
7654 :
7655 5434 : const auto nErrorCount = CPLGetErrorCounter();
7656 : sqlite3_stmt *hSQLStmt =
7657 5434 : prepareSql(hDB, osSQLCommandTruncated.c_str(),
7658 5434 : static_cast<int>(osSQLCommandTruncated.size()));
7659 5434 : if (!hSQLStmt)
7660 : {
7661 12 : if (nErrorCount == CPLGetErrorCounter())
7662 : {
7663 9 : CPLError(CE_Failure, CPLE_AppDefined, "%s",
7664 18 : SQLFormatErrorMsgFailedPrepare(
7665 : GetDB(), "In ExecuteSQL(): sqlite3_prepare_v2(): ",
7666 : osSQLCommand.c_str())
7667 : .c_str());
7668 : }
7669 12 : return nullptr;
7670 : }
7671 :
7672 : /* -------------------------------------------------------------------- */
7673 : /* Do we get a resultset? */
7674 : /* -------------------------------------------------------------------- */
7675 5422 : int rc = sqlite3_step(hSQLStmt);
7676 :
7677 7096 : for (auto &poLayer : m_apoLayers)
7678 : {
7679 1674 : if (!poLayer->RunDeferredDropRTreeTableIfNecessary())
7680 0 : return nullptr;
7681 : }
7682 :
7683 5422 : if (rc != SQLITE_ROW)
7684 : {
7685 4635 : if (rc != SQLITE_DONE)
7686 : {
7687 7 : CPLError(CE_Failure, CPLE_AppDefined,
7688 : "In ExecuteSQL(): sqlite3_step(%s):\n %s",
7689 : osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
7690 :
7691 7 : sqlite3_finalize(hSQLStmt);
7692 7 : return nullptr;
7693 : }
7694 :
7695 4628 : if (EQUAL(osSQLCommand, "VACUUM"))
7696 : {
7697 13 : sqlite3_finalize(hSQLStmt);
7698 : /* VACUUM rewrites the DB, so we need to reset the application id */
7699 13 : SetApplicationAndUserVersionId();
7700 13 : return nullptr;
7701 : }
7702 :
7703 4615 : if (!STARTS_WITH_CI(osSQLCommand, "SELECT "))
7704 : {
7705 4487 : sqlite3_finalize(hSQLStmt);
7706 4487 : return nullptr;
7707 : }
7708 :
7709 128 : bUseStatementForGetNextFeature = false;
7710 128 : bEmptyLayer = true;
7711 : }
7712 :
7713 : /* -------------------------------------------------------------------- */
7714 : /* Special case for some functions which must be run */
7715 : /* only once */
7716 : /* -------------------------------------------------------------------- */
7717 915 : if (STARTS_WITH_CI(osSQLCommand, "SELECT "))
7718 : {
7719 4199 : for (unsigned int i = 0; i < sizeof(apszFuncsWithSideEffects) /
7720 : sizeof(apszFuncsWithSideEffects[0]);
7721 : i++)
7722 : {
7723 3385 : if (EQUALN(apszFuncsWithSideEffects[i], osSQLCommand.c_str() + 7,
7724 : strlen(apszFuncsWithSideEffects[i])))
7725 : {
7726 112 : if (sqlite3_column_count(hSQLStmt) == 1 &&
7727 56 : sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
7728 : {
7729 56 : int ret = sqlite3_column_int(hSQLStmt, 0);
7730 :
7731 56 : sqlite3_finalize(hSQLStmt);
7732 :
7733 : return new OGRSQLiteSingleFeatureLayer(
7734 56 : apszFuncsWithSideEffects[i], ret);
7735 : }
7736 : }
7737 : }
7738 : }
7739 45 : else if (STARTS_WITH_CI(osSQLCommand, "PRAGMA "))
7740 : {
7741 63 : if (sqlite3_column_count(hSQLStmt) == 1 &&
7742 18 : sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
7743 : {
7744 15 : int ret = sqlite3_column_int(hSQLStmt, 0);
7745 :
7746 15 : sqlite3_finalize(hSQLStmt);
7747 :
7748 15 : return new OGRSQLiteSingleFeatureLayer(osSQLCommand.c_str() + 7,
7749 15 : ret);
7750 : }
7751 33 : else if (sqlite3_column_count(hSQLStmt) == 1 &&
7752 3 : sqlite3_column_type(hSQLStmt, 0) == SQLITE_TEXT)
7753 : {
7754 : const char *pszRet = reinterpret_cast<const char *>(
7755 3 : sqlite3_column_text(hSQLStmt, 0));
7756 :
7757 : OGRLayer *poRet = new OGRSQLiteSingleFeatureLayer(
7758 3 : osSQLCommand.c_str() + 7, pszRet);
7759 :
7760 3 : sqlite3_finalize(hSQLStmt);
7761 :
7762 3 : return poRet;
7763 : }
7764 : }
7765 :
7766 : /* -------------------------------------------------------------------- */
7767 : /* Create layer. */
7768 : /* -------------------------------------------------------------------- */
7769 :
7770 : auto poLayer = std::make_unique<OGRGeoPackageSelectLayer>(
7771 : this, osSQLCommand, hSQLStmt, bUseStatementForGetNextFeature,
7772 1682 : bEmptyLayer);
7773 :
7774 844 : if (poSpatialFilter != nullptr &&
7775 3 : poLayer->GetLayerDefn()->GetGeomFieldCount() > 0)
7776 3 : poLayer->SetSpatialFilter(0, poSpatialFilter);
7777 :
7778 841 : return poLayer.release();
7779 : }
7780 :
7781 : /************************************************************************/
7782 : /* ReleaseResultSet() */
7783 : /************************************************************************/
7784 :
7785 874 : void GDALGeoPackageDataset::ReleaseResultSet(OGRLayer *poLayer)
7786 :
7787 : {
7788 874 : delete poLayer;
7789 874 : }
7790 :
7791 : /************************************************************************/
7792 : /* HasExtensionsTable() */
7793 : /************************************************************************/
7794 :
7795 8124 : bool GDALGeoPackageDataset::HasExtensionsTable()
7796 : {
7797 8124 : return SQLGetInteger(
7798 : hDB,
7799 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_extensions' "
7800 : "AND type IN ('table', 'view')",
7801 8124 : nullptr) == 1;
7802 : }
7803 :
7804 : /************************************************************************/
7805 : /* CheckUnknownExtensions() */
7806 : /************************************************************************/
7807 :
7808 1754 : void GDALGeoPackageDataset::CheckUnknownExtensions(bool bCheckRasterTable)
7809 : {
7810 1754 : if (!HasExtensionsTable())
7811 217 : return;
7812 :
7813 1537 : char *pszSQL = nullptr;
7814 1537 : if (!bCheckRasterTable)
7815 1320 : pszSQL = sqlite3_mprintf(
7816 : "SELECT extension_name, definition, scope FROM gpkg_extensions "
7817 : "WHERE (table_name IS NULL "
7818 : "AND extension_name IS NOT NULL "
7819 : "AND definition IS NOT NULL "
7820 : "AND scope IS NOT NULL "
7821 : "AND extension_name NOT IN ("
7822 : "'gdal_aspatial', "
7823 : "'gpkg_elevation_tiles', " // Old name before GPKG 1.2 approval
7824 : "'2d_gridded_coverage', " // Old name after GPKG 1.2 and before OGC
7825 : // 17-066r1 finalization
7826 : "'gpkg_2d_gridded_coverage', " // Name in OGC 17-066r1 final
7827 : "'gpkg_metadata', "
7828 : "'gpkg_schema', "
7829 : "'gpkg_crs_wkt', "
7830 : "'gpkg_crs_wkt_1_1', "
7831 : "'related_tables', 'gpkg_related_tables')) "
7832 : #ifdef WORKAROUND_SQLITE3_BUGS
7833 : "OR 0 "
7834 : #endif
7835 : "LIMIT 1000");
7836 : else
7837 217 : pszSQL = sqlite3_mprintf(
7838 : "SELECT extension_name, definition, scope FROM gpkg_extensions "
7839 : "WHERE (lower(table_name) = lower('%q') "
7840 : "AND extension_name IS NOT NULL "
7841 : "AND definition IS NOT NULL "
7842 : "AND scope IS NOT NULL "
7843 : "AND extension_name NOT IN ("
7844 : "'gpkg_elevation_tiles', " // Old name before GPKG 1.2 approval
7845 : "'2d_gridded_coverage', " // Old name after GPKG 1.2 and before OGC
7846 : // 17-066r1 finalization
7847 : "'gpkg_2d_gridded_coverage', " // Name in OGC 17-066r1 final
7848 : "'gpkg_metadata', "
7849 : "'gpkg_schema', "
7850 : "'gpkg_crs_wkt', "
7851 : "'gpkg_crs_wkt_1_1', "
7852 : "'related_tables', 'gpkg_related_tables')) "
7853 : #ifdef WORKAROUND_SQLITE3_BUGS
7854 : "OR 0 "
7855 : #endif
7856 : "LIMIT 1000",
7857 : m_osRasterTable.c_str());
7858 :
7859 3074 : auto oResultTable = SQLQuery(GetDB(), pszSQL);
7860 1537 : sqlite3_free(pszSQL);
7861 1537 : if (oResultTable && oResultTable->RowCount() > 0)
7862 : {
7863 42 : for (int i = 0; i < oResultTable->RowCount(); i++)
7864 : {
7865 21 : const char *pszExtName = oResultTable->GetValue(0, i);
7866 21 : const char *pszDefinition = oResultTable->GetValue(1, i);
7867 21 : const char *pszScope = oResultTable->GetValue(2, i);
7868 21 : if (pszExtName == nullptr || pszDefinition == nullptr ||
7869 : pszScope == nullptr)
7870 : {
7871 0 : continue;
7872 : }
7873 :
7874 21 : if (EQUAL(pszExtName, "gpkg_webp"))
7875 : {
7876 15 : if (GDALGetDriverByName("WEBP") == nullptr)
7877 : {
7878 1 : CPLError(
7879 : CE_Warning, CPLE_AppDefined,
7880 : "Table %s contains WEBP tiles, but GDAL configured "
7881 : "without WEBP support. Data will be missing",
7882 : m_osRasterTable.c_str());
7883 : }
7884 15 : m_eTF = GPKG_TF_WEBP;
7885 15 : continue;
7886 : }
7887 6 : if (EQUAL(pszExtName, "gpkg_zoom_other"))
7888 : {
7889 2 : m_bZoomOther = true;
7890 2 : continue;
7891 : }
7892 :
7893 4 : if (GetUpdate() && EQUAL(pszScope, "write-only"))
7894 : {
7895 1 : CPLError(
7896 : CE_Warning, CPLE_AppDefined,
7897 : "Database relies on the '%s' (%s) extension that should "
7898 : "be implemented for safe write-support, but is not "
7899 : "currently. "
7900 : "Update of that database are strongly discouraged to avoid "
7901 : "corruption.",
7902 : pszExtName, pszDefinition);
7903 : }
7904 3 : else if (GetUpdate() && EQUAL(pszScope, "read-write"))
7905 : {
7906 1 : CPLError(
7907 : CE_Warning, CPLE_AppDefined,
7908 : "Database relies on the '%s' (%s) extension that should "
7909 : "be implemented in order to read/write it safely, but is "
7910 : "not currently. "
7911 : "Some data may be missing while reading that database, and "
7912 : "updates are strongly discouraged.",
7913 : pszExtName, pszDefinition);
7914 : }
7915 2 : else if (EQUAL(pszScope, "read-write") &&
7916 : // None of the NGA extensions at
7917 : // http://ngageoint.github.io/GeoPackage/docs/extensions/
7918 : // affect read-only scenarios
7919 1 : !STARTS_WITH(pszExtName, "nga_"))
7920 : {
7921 1 : CPLError(
7922 : CE_Warning, CPLE_AppDefined,
7923 : "Database relies on the '%s' (%s) extension that should "
7924 : "be implemented in order to read it safely, but is not "
7925 : "currently. "
7926 : "Some data may be missing while reading that database.",
7927 : pszExtName, pszDefinition);
7928 : }
7929 : }
7930 : }
7931 : }
7932 :
7933 : /************************************************************************/
7934 : /* HasGDALAspatialExtension() */
7935 : /************************************************************************/
7936 :
7937 1279 : bool GDALGeoPackageDataset::HasGDALAspatialExtension()
7938 : {
7939 1279 : if (!HasExtensionsTable())
7940 103 : return false;
7941 :
7942 : auto oResultTable = SQLQuery(hDB, "SELECT * FROM gpkg_extensions "
7943 : "WHERE (extension_name = 'gdal_aspatial' "
7944 : "AND table_name IS NULL "
7945 : "AND column_name IS NULL)"
7946 : #ifdef WORKAROUND_SQLITE3_BUGS
7947 : " OR 0"
7948 : #endif
7949 1176 : );
7950 1176 : bool bHasExtension = (oResultTable && oResultTable->RowCount() == 1);
7951 1176 : return bHasExtension;
7952 : }
7953 :
7954 : std::string
7955 198 : GDALGeoPackageDataset::CreateRasterTriggersSQL(const std::string &osTableName)
7956 : {
7957 : char *pszSQL;
7958 198 : std::string osSQL;
7959 : /* From D.5. sample_tile_pyramid Table 43. tiles table Trigger
7960 : * Definition SQL */
7961 198 : pszSQL = sqlite3_mprintf(
7962 : "CREATE TRIGGER \"%w_zoom_insert\" "
7963 : "BEFORE INSERT ON \"%w\" "
7964 : "FOR EACH ROW BEGIN "
7965 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
7966 : "constraint: zoom_level not specified for table in "
7967 : "gpkg_tile_matrix') "
7968 : "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
7969 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
7970 : "END; "
7971 : "CREATE TRIGGER \"%w_zoom_update\" "
7972 : "BEFORE UPDATE OF zoom_level ON \"%w\" "
7973 : "FOR EACH ROW BEGIN "
7974 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
7975 : "constraint: zoom_level not specified for table in "
7976 : "gpkg_tile_matrix') "
7977 : "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
7978 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
7979 : "END; "
7980 : "CREATE TRIGGER \"%w_tile_column_insert\" "
7981 : "BEFORE INSERT ON \"%w\" "
7982 : "FOR EACH ROW BEGIN "
7983 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
7984 : "constraint: tile_column cannot be < 0') "
7985 : "WHERE (NEW.tile_column < 0) ; "
7986 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
7987 : "constraint: tile_column must by < matrix_width specified for "
7988 : "table and zoom level in gpkg_tile_matrix') "
7989 : "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
7990 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
7991 : "zoom_level = NEW.zoom_level)); "
7992 : "END; "
7993 : "CREATE TRIGGER \"%w_tile_column_update\" "
7994 : "BEFORE UPDATE OF tile_column ON \"%w\" "
7995 : "FOR EACH ROW BEGIN "
7996 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
7997 : "constraint: tile_column cannot be < 0') "
7998 : "WHERE (NEW.tile_column < 0) ; "
7999 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8000 : "constraint: tile_column must by < matrix_width specified for "
8001 : "table and zoom level in gpkg_tile_matrix') "
8002 : "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
8003 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8004 : "zoom_level = NEW.zoom_level)); "
8005 : "END; "
8006 : "CREATE TRIGGER \"%w_tile_row_insert\" "
8007 : "BEFORE INSERT ON \"%w\" "
8008 : "FOR EACH ROW BEGIN "
8009 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
8010 : "constraint: tile_row cannot be < 0') "
8011 : "WHERE (NEW.tile_row < 0) ; "
8012 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
8013 : "constraint: tile_row must by < matrix_height specified for "
8014 : "table and zoom level in gpkg_tile_matrix') "
8015 : "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
8016 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8017 : "zoom_level = NEW.zoom_level)); "
8018 : "END; "
8019 : "CREATE TRIGGER \"%w_tile_row_update\" "
8020 : "BEFORE UPDATE OF tile_row ON \"%w\" "
8021 : "FOR EACH ROW BEGIN "
8022 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8023 : "constraint: tile_row cannot be < 0') "
8024 : "WHERE (NEW.tile_row < 0) ; "
8025 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8026 : "constraint: tile_row must by < matrix_height specified for "
8027 : "table and zoom level in gpkg_tile_matrix') "
8028 : "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
8029 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8030 : "zoom_level = NEW.zoom_level)); "
8031 : "END; ",
8032 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8033 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8034 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8035 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8036 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8037 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8038 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8039 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8040 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8041 : osTableName.c_str());
8042 198 : osSQL = pszSQL;
8043 198 : sqlite3_free(pszSQL);
8044 198 : return osSQL;
8045 : }
8046 :
8047 : /************************************************************************/
8048 : /* CreateExtensionsTableIfNecessary() */
8049 : /************************************************************************/
8050 :
8051 1488 : OGRErr GDALGeoPackageDataset::CreateExtensionsTableIfNecessary()
8052 : {
8053 : /* Check if the table gpkg_extensions exists */
8054 1488 : if (HasExtensionsTable())
8055 514 : return OGRERR_NONE;
8056 :
8057 : /* Requirement 79 : Every extension of a GeoPackage SHALL be registered */
8058 : /* in a corresponding row in the gpkg_extensions table. The absence of a */
8059 : /* gpkg_extensions table or the absence of rows in gpkg_extensions table */
8060 : /* SHALL both indicate the absence of extensions to a GeoPackage. */
8061 974 : const char *pszCreateGpkgExtensions =
8062 : "CREATE TABLE gpkg_extensions ("
8063 : "table_name TEXT,"
8064 : "column_name TEXT,"
8065 : "extension_name TEXT NOT NULL,"
8066 : "definition TEXT NOT NULL,"
8067 : "scope TEXT NOT NULL,"
8068 : "CONSTRAINT ge_tce UNIQUE (table_name, column_name, extension_name)"
8069 : ")";
8070 :
8071 974 : return SQLCommand(hDB, pszCreateGpkgExtensions);
8072 : }
8073 :
8074 : /************************************************************************/
8075 : /* OGR_GPKG_Intersects_Spatial_Filter() */
8076 : /************************************************************************/
8077 :
8078 23236 : void OGR_GPKG_Intersects_Spatial_Filter(sqlite3_context *pContext, int argc,
8079 : sqlite3_value **argv)
8080 : {
8081 23236 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8082 : {
8083 0 : sqlite3_result_int(pContext, 0);
8084 23226 : return;
8085 : }
8086 :
8087 : auto poLayer =
8088 23236 : static_cast<OGRGeoPackageTableLayer *>(sqlite3_user_data(pContext));
8089 :
8090 23236 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8091 : const GByte *pabyBLOB =
8092 23236 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8093 :
8094 : GPkgHeader sHeader;
8095 46472 : if (poLayer->m_bFilterIsEnvelope &&
8096 23236 : OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false, 0))
8097 : {
8098 23236 : if (sHeader.bExtentHasXY)
8099 : {
8100 95 : OGREnvelope sEnvelope;
8101 95 : sEnvelope.MinX = sHeader.MinX;
8102 95 : sEnvelope.MinY = sHeader.MinY;
8103 95 : sEnvelope.MaxX = sHeader.MaxX;
8104 95 : sEnvelope.MaxY = sHeader.MaxY;
8105 95 : if (poLayer->m_sFilterEnvelope.Contains(sEnvelope))
8106 : {
8107 31 : sqlite3_result_int(pContext, 1);
8108 31 : return;
8109 : }
8110 : }
8111 :
8112 : // Check if at least one point falls into the layer filter envelope
8113 : // nHeaderLen is > 0 for GeoPackage geometries
8114 46410 : if (sHeader.nHeaderLen > 0 &&
8115 23205 : OGRWKBIntersectsPessimistic(pabyBLOB + sHeader.nHeaderLen,
8116 23205 : nBLOBLen - sHeader.nHeaderLen,
8117 23205 : poLayer->m_sFilterEnvelope))
8118 : {
8119 23195 : sqlite3_result_int(pContext, 1);
8120 23195 : return;
8121 : }
8122 : }
8123 :
8124 : auto poGeom = std::unique_ptr<OGRGeometry>(
8125 10 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8126 10 : if (poGeom == nullptr)
8127 : {
8128 : // Try also spatialite geometry blobs
8129 0 : OGRGeometry *poGeomSpatialite = nullptr;
8130 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8131 0 : &poGeomSpatialite) != OGRERR_NONE)
8132 : {
8133 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8134 0 : sqlite3_result_int(pContext, 0);
8135 0 : return;
8136 : }
8137 0 : poGeom.reset(poGeomSpatialite);
8138 : }
8139 :
8140 10 : sqlite3_result_int(pContext, poLayer->FilterGeometry(poGeom.get()));
8141 : }
8142 :
8143 : /************************************************************************/
8144 : /* OGRGeoPackageSTMinX() */
8145 : /************************************************************************/
8146 :
8147 253740 : static void OGRGeoPackageSTMinX(sqlite3_context *pContext, int argc,
8148 : sqlite3_value **argv)
8149 : {
8150 : GPkgHeader sHeader;
8151 253740 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8152 : {
8153 17 : sqlite3_result_null(pContext);
8154 17 : return;
8155 : }
8156 253723 : sqlite3_result_double(pContext, sHeader.MinX);
8157 : }
8158 :
8159 : /************************************************************************/
8160 : /* OGRGeoPackageSTMinY() */
8161 : /************************************************************************/
8162 :
8163 253724 : static void OGRGeoPackageSTMinY(sqlite3_context *pContext, int argc,
8164 : sqlite3_value **argv)
8165 : {
8166 : GPkgHeader sHeader;
8167 253724 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8168 : {
8169 1 : sqlite3_result_null(pContext);
8170 1 : return;
8171 : }
8172 253723 : sqlite3_result_double(pContext, sHeader.MinY);
8173 : }
8174 :
8175 : /************************************************************************/
8176 : /* OGRGeoPackageSTMaxX() */
8177 : /************************************************************************/
8178 :
8179 253724 : static void OGRGeoPackageSTMaxX(sqlite3_context *pContext, int argc,
8180 : sqlite3_value **argv)
8181 : {
8182 : GPkgHeader sHeader;
8183 253724 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8184 : {
8185 1 : sqlite3_result_null(pContext);
8186 1 : return;
8187 : }
8188 253723 : sqlite3_result_double(pContext, sHeader.MaxX);
8189 : }
8190 :
8191 : /************************************************************************/
8192 : /* OGRGeoPackageSTMaxY() */
8193 : /************************************************************************/
8194 :
8195 253724 : static void OGRGeoPackageSTMaxY(sqlite3_context *pContext, int argc,
8196 : sqlite3_value **argv)
8197 : {
8198 : GPkgHeader sHeader;
8199 253724 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8200 : {
8201 1 : sqlite3_result_null(pContext);
8202 1 : return;
8203 : }
8204 253723 : sqlite3_result_double(pContext, sHeader.MaxY);
8205 : }
8206 :
8207 : /************************************************************************/
8208 : /* OGRGeoPackageSTIsEmpty() */
8209 : /************************************************************************/
8210 :
8211 255190 : static void OGRGeoPackageSTIsEmpty(sqlite3_context *pContext, int argc,
8212 : sqlite3_value **argv)
8213 : {
8214 : GPkgHeader sHeader;
8215 255190 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8216 : {
8217 2 : sqlite3_result_null(pContext);
8218 2 : return;
8219 : }
8220 255188 : sqlite3_result_int(pContext, sHeader.bEmpty);
8221 : }
8222 :
8223 : /************************************************************************/
8224 : /* OGRGeoPackageSTGeometryType() */
8225 : /************************************************************************/
8226 :
8227 7 : static void OGRGeoPackageSTGeometryType(sqlite3_context *pContext, int /*argc*/,
8228 : sqlite3_value **argv)
8229 : {
8230 : GPkgHeader sHeader;
8231 :
8232 7 : int nBLOBLen = sqlite3_value_bytes(argv[0]);
8233 : const GByte *pabyBLOB =
8234 7 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8235 : OGRwkbGeometryType eGeometryType;
8236 :
8237 13 : if (nBLOBLen < 8 ||
8238 6 : GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
8239 : {
8240 2 : if (OGRSQLiteGetSpatialiteGeometryHeader(
8241 : pabyBLOB, nBLOBLen, nullptr, &eGeometryType, nullptr, nullptr,
8242 2 : nullptr, nullptr, nullptr) == OGRERR_NONE)
8243 : {
8244 1 : sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
8245 : SQLITE_TRANSIENT);
8246 4 : return;
8247 : }
8248 : else
8249 : {
8250 1 : sqlite3_result_null(pContext);
8251 1 : return;
8252 : }
8253 : }
8254 :
8255 5 : if (static_cast<size_t>(nBLOBLen) < sHeader.nHeaderLen + 5)
8256 : {
8257 2 : sqlite3_result_null(pContext);
8258 2 : return;
8259 : }
8260 :
8261 3 : OGRErr err = OGRReadWKBGeometryType(pabyBLOB + sHeader.nHeaderLen,
8262 : wkbVariantIso, &eGeometryType);
8263 3 : if (err != OGRERR_NONE)
8264 1 : sqlite3_result_null(pContext);
8265 : else
8266 2 : sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
8267 : SQLITE_TRANSIENT);
8268 : }
8269 :
8270 : /************************************************************************/
8271 : /* OGRGeoPackageSTEnvelopesIntersects() */
8272 : /************************************************************************/
8273 :
8274 118 : static void OGRGeoPackageSTEnvelopesIntersects(sqlite3_context *pContext,
8275 : int argc, sqlite3_value **argv)
8276 : {
8277 : GPkgHeader sHeader;
8278 118 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8279 : {
8280 2 : sqlite3_result_int(pContext, FALSE);
8281 107 : return;
8282 : }
8283 116 : const double dfMinX = sqlite3_value_double(argv[1]);
8284 116 : if (sHeader.MaxX < dfMinX)
8285 : {
8286 93 : sqlite3_result_int(pContext, FALSE);
8287 93 : return;
8288 : }
8289 23 : const double dfMinY = sqlite3_value_double(argv[2]);
8290 23 : if (sHeader.MaxY < dfMinY)
8291 : {
8292 11 : sqlite3_result_int(pContext, FALSE);
8293 11 : return;
8294 : }
8295 12 : const double dfMaxX = sqlite3_value_double(argv[3]);
8296 12 : if (sHeader.MinX > dfMaxX)
8297 : {
8298 1 : sqlite3_result_int(pContext, FALSE);
8299 1 : return;
8300 : }
8301 11 : const double dfMaxY = sqlite3_value_double(argv[4]);
8302 11 : sqlite3_result_int(pContext, sHeader.MinY <= dfMaxY);
8303 : }
8304 :
8305 : /************************************************************************/
8306 : /* OGRGeoPackageSTEnvelopesIntersectsTwoParams() */
8307 : /************************************************************************/
8308 :
8309 : static void
8310 3 : OGRGeoPackageSTEnvelopesIntersectsTwoParams(sqlite3_context *pContext, int argc,
8311 : sqlite3_value **argv)
8312 : {
8313 : GPkgHeader sHeader;
8314 3 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false, 0))
8315 : {
8316 0 : sqlite3_result_int(pContext, FALSE);
8317 2 : return;
8318 : }
8319 : GPkgHeader sHeader2;
8320 3 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader2, true, false,
8321 : 1))
8322 : {
8323 0 : sqlite3_result_int(pContext, FALSE);
8324 0 : return;
8325 : }
8326 3 : if (sHeader.MaxX < sHeader2.MinX)
8327 : {
8328 1 : sqlite3_result_int(pContext, FALSE);
8329 1 : return;
8330 : }
8331 2 : if (sHeader.MaxY < sHeader2.MinY)
8332 : {
8333 0 : sqlite3_result_int(pContext, FALSE);
8334 0 : return;
8335 : }
8336 2 : if (sHeader.MinX > sHeader2.MaxX)
8337 : {
8338 1 : sqlite3_result_int(pContext, FALSE);
8339 1 : return;
8340 : }
8341 1 : sqlite3_result_int(pContext, sHeader.MinY <= sHeader2.MaxY);
8342 : }
8343 :
8344 : /************************************************************************/
8345 : /* OGRGeoPackageGPKGIsAssignable() */
8346 : /************************************************************************/
8347 :
8348 8 : static void OGRGeoPackageGPKGIsAssignable(sqlite3_context *pContext,
8349 : int /*argc*/, sqlite3_value **argv)
8350 : {
8351 15 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8352 7 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
8353 : {
8354 2 : sqlite3_result_int(pContext, 0);
8355 2 : return;
8356 : }
8357 :
8358 : const char *pszExpected =
8359 6 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8360 : const char *pszActual =
8361 6 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8362 6 : int bIsAssignable = OGR_GT_IsSubClassOf(OGRFromOGCGeomType(pszActual),
8363 : OGRFromOGCGeomType(pszExpected));
8364 6 : sqlite3_result_int(pContext, bIsAssignable);
8365 : }
8366 :
8367 : /************************************************************************/
8368 : /* OGRGeoPackageSTSRID() */
8369 : /************************************************************************/
8370 :
8371 12 : static void OGRGeoPackageSTSRID(sqlite3_context *pContext, int argc,
8372 : sqlite3_value **argv)
8373 : {
8374 : GPkgHeader sHeader;
8375 12 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8376 : {
8377 2 : sqlite3_result_null(pContext);
8378 2 : return;
8379 : }
8380 10 : sqlite3_result_int(pContext, sHeader.iSrsId);
8381 : }
8382 :
8383 : /************************************************************************/
8384 : /* OGRGeoPackageSetSRID() */
8385 : /************************************************************************/
8386 :
8387 28 : static void OGRGeoPackageSetSRID(sqlite3_context *pContext, int /* argc */,
8388 : sqlite3_value **argv)
8389 : {
8390 28 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8391 : {
8392 1 : sqlite3_result_null(pContext);
8393 1 : return;
8394 : }
8395 27 : const int nDestSRID = sqlite3_value_int(argv[1]);
8396 : GPkgHeader sHeader;
8397 27 : int nBLOBLen = sqlite3_value_bytes(argv[0]);
8398 : const GByte *pabyBLOB =
8399 27 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8400 :
8401 54 : if (nBLOBLen < 8 ||
8402 27 : GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
8403 : {
8404 : // Try also spatialite geometry blobs
8405 0 : OGRGeometry *poGeom = nullptr;
8406 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeom) !=
8407 : OGRERR_NONE)
8408 : {
8409 0 : sqlite3_result_null(pContext);
8410 0 : return;
8411 : }
8412 0 : size_t nBLOBDestLen = 0;
8413 : GByte *pabyDestBLOB =
8414 0 : GPkgGeometryFromOGR(poGeom, nDestSRID, nullptr, &nBLOBDestLen);
8415 0 : if (!pabyDestBLOB)
8416 : {
8417 0 : sqlite3_result_null(pContext);
8418 0 : return;
8419 : }
8420 0 : sqlite3_result_blob(pContext, pabyDestBLOB,
8421 : static_cast<int>(nBLOBDestLen), VSIFree);
8422 0 : return;
8423 : }
8424 :
8425 27 : GByte *pabyDestBLOB = static_cast<GByte *>(CPLMalloc(nBLOBLen));
8426 27 : memcpy(pabyDestBLOB, pabyBLOB, nBLOBLen);
8427 27 : int32_t nSRIDToSerialize = nDestSRID;
8428 27 : if (OGR_SWAP(sHeader.eByteOrder))
8429 0 : nSRIDToSerialize = CPL_SWAP32(nSRIDToSerialize);
8430 27 : memcpy(pabyDestBLOB + 4, &nSRIDToSerialize, 4);
8431 27 : sqlite3_result_blob(pContext, pabyDestBLOB, nBLOBLen, VSIFree);
8432 : }
8433 :
8434 : /************************************************************************/
8435 : /* OGRGeoPackageSTMakeValid() */
8436 : /************************************************************************/
8437 :
8438 3 : static void OGRGeoPackageSTMakeValid(sqlite3_context *pContext, int argc,
8439 : sqlite3_value **argv)
8440 : {
8441 3 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8442 : {
8443 2 : sqlite3_result_null(pContext);
8444 2 : return;
8445 : }
8446 1 : int nBLOBLen = sqlite3_value_bytes(argv[0]);
8447 : const GByte *pabyBLOB =
8448 1 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8449 :
8450 : GPkgHeader sHeader;
8451 1 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8452 : {
8453 0 : sqlite3_result_null(pContext);
8454 0 : return;
8455 : }
8456 :
8457 : auto poGeom = std::unique_ptr<OGRGeometry>(
8458 1 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8459 1 : if (poGeom == nullptr)
8460 : {
8461 : // Try also spatialite geometry blobs
8462 0 : OGRGeometry *poGeomPtr = nullptr;
8463 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
8464 : OGRERR_NONE)
8465 : {
8466 0 : sqlite3_result_null(pContext);
8467 0 : return;
8468 : }
8469 0 : poGeom.reset(poGeomPtr);
8470 : }
8471 1 : auto poValid = std::unique_ptr<OGRGeometry>(poGeom->MakeValid());
8472 1 : if (poValid == nullptr)
8473 : {
8474 0 : sqlite3_result_null(pContext);
8475 0 : return;
8476 : }
8477 :
8478 1 : size_t nBLOBDestLen = 0;
8479 1 : GByte *pabyDestBLOB = GPkgGeometryFromOGR(poValid.get(), sHeader.iSrsId,
8480 : nullptr, &nBLOBDestLen);
8481 1 : if (!pabyDestBLOB)
8482 : {
8483 0 : sqlite3_result_null(pContext);
8484 0 : return;
8485 : }
8486 1 : sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
8487 : VSIFree);
8488 : }
8489 :
8490 : /************************************************************************/
8491 : /* OGRGeoPackageSTArea() */
8492 : /************************************************************************/
8493 :
8494 19 : static void OGRGeoPackageSTArea(sqlite3_context *pContext, int /*argc*/,
8495 : sqlite3_value **argv)
8496 : {
8497 19 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8498 : {
8499 1 : sqlite3_result_null(pContext);
8500 15 : return;
8501 : }
8502 18 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8503 : const GByte *pabyBLOB =
8504 18 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8505 :
8506 : GPkgHeader sHeader;
8507 0 : std::unique_ptr<OGRGeometry> poGeom;
8508 18 : if (GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) == OGRERR_NONE)
8509 : {
8510 16 : if (sHeader.bEmpty)
8511 : {
8512 3 : sqlite3_result_double(pContext, 0);
8513 13 : return;
8514 : }
8515 13 : const GByte *pabyWkb = pabyBLOB + sHeader.nHeaderLen;
8516 13 : size_t nWKBSize = nBLOBLen - sHeader.nHeaderLen;
8517 : bool bNeedSwap;
8518 : uint32_t nType;
8519 13 : if (OGRWKBGetGeomType(pabyWkb, nWKBSize, bNeedSwap, nType))
8520 : {
8521 13 : if (nType == wkbPolygon || nType == wkbPolygon25D ||
8522 11 : nType == wkbPolygon + 1000 || // wkbPolygonZ
8523 10 : nType == wkbPolygonM || nType == wkbPolygonZM)
8524 : {
8525 : double dfArea;
8526 5 : if (OGRWKBPolygonGetArea(pabyWkb, nWKBSize, dfArea))
8527 : {
8528 5 : sqlite3_result_double(pContext, dfArea);
8529 5 : return;
8530 0 : }
8531 : }
8532 8 : else if (nType == wkbMultiPolygon || nType == wkbMultiPolygon25D ||
8533 6 : nType == wkbMultiPolygon + 1000 || // wkbMultiPolygonZ
8534 5 : nType == wkbMultiPolygonM || nType == wkbMultiPolygonZM)
8535 : {
8536 : double dfArea;
8537 5 : if (OGRWKBMultiPolygonGetArea(pabyWkb, nWKBSize, dfArea))
8538 : {
8539 5 : sqlite3_result_double(pContext, dfArea);
8540 5 : return;
8541 : }
8542 : }
8543 : }
8544 :
8545 : // For curve geometries, fallback to OGRGeometry methods
8546 3 : poGeom.reset(GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8547 : }
8548 : else
8549 : {
8550 : // Try also spatialite geometry blobs
8551 2 : OGRGeometry *poGeomPtr = nullptr;
8552 2 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
8553 : OGRERR_NONE)
8554 : {
8555 1 : sqlite3_result_null(pContext);
8556 1 : return;
8557 : }
8558 1 : poGeom.reset(poGeomPtr);
8559 : }
8560 4 : auto poSurface = dynamic_cast<OGRSurface *>(poGeom.get());
8561 4 : if (poSurface == nullptr)
8562 : {
8563 2 : auto poMultiSurface = dynamic_cast<OGRMultiSurface *>(poGeom.get());
8564 2 : if (poMultiSurface == nullptr)
8565 : {
8566 1 : sqlite3_result_double(pContext, 0);
8567 : }
8568 : else
8569 : {
8570 1 : sqlite3_result_double(pContext, poMultiSurface->get_Area());
8571 : }
8572 : }
8573 : else
8574 : {
8575 2 : sqlite3_result_double(pContext, poSurface->get_Area());
8576 : }
8577 : }
8578 :
8579 : /************************************************************************/
8580 : /* OGRGeoPackageGeodesicArea() */
8581 : /************************************************************************/
8582 :
8583 5 : static void OGRGeoPackageGeodesicArea(sqlite3_context *pContext, int argc,
8584 : sqlite3_value **argv)
8585 : {
8586 5 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8587 : {
8588 1 : sqlite3_result_null(pContext);
8589 3 : return;
8590 : }
8591 4 : if (sqlite3_value_int(argv[1]) != 1)
8592 : {
8593 2 : CPLError(CE_Warning, CPLE_NotSupported,
8594 : "ST_Area(geom, use_ellipsoid) is only supported for "
8595 : "use_ellipsoid = 1");
8596 : }
8597 :
8598 4 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8599 : const GByte *pabyBLOB =
8600 4 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8601 : GPkgHeader sHeader;
8602 4 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8603 : {
8604 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8605 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8606 1 : return;
8607 : }
8608 :
8609 : GDALGeoPackageDataset *poDS =
8610 3 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8611 :
8612 3 : auto poSrcSRS = poDS->GetSpatialRef(sHeader.iSrsId, true);
8613 3 : if (poSrcSRS == nullptr)
8614 : {
8615 1 : CPLError(CE_Failure, CPLE_AppDefined,
8616 : "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8617 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8618 1 : return;
8619 : }
8620 :
8621 : auto poGeom = std::unique_ptr<OGRGeometry>(
8622 2 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8623 2 : if (poGeom == nullptr)
8624 : {
8625 : // Try also spatialite geometry blobs
8626 0 : OGRGeometry *poGeomSpatialite = nullptr;
8627 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8628 0 : &poGeomSpatialite) != OGRERR_NONE)
8629 : {
8630 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8631 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8632 0 : return;
8633 : }
8634 0 : poGeom.reset(poGeomSpatialite);
8635 : }
8636 :
8637 2 : poGeom->assignSpatialReference(poSrcSRS.get());
8638 2 : sqlite3_result_double(
8639 : pContext, OGR_G_GeodesicArea(OGRGeometry::ToHandle(poGeom.get())));
8640 : }
8641 :
8642 : /************************************************************************/
8643 : /* OGRGeoPackageLengthOrGeodesicLength() */
8644 : /************************************************************************/
8645 :
8646 8 : static void OGRGeoPackageLengthOrGeodesicLength(sqlite3_context *pContext,
8647 : int argc, sqlite3_value **argv)
8648 : {
8649 8 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8650 : {
8651 2 : sqlite3_result_null(pContext);
8652 5 : return;
8653 : }
8654 6 : if (argc == 2 && sqlite3_value_int(argv[1]) != 1)
8655 : {
8656 2 : CPLError(CE_Warning, CPLE_NotSupported,
8657 : "ST_Length(geom, use_ellipsoid) is only supported for "
8658 : "use_ellipsoid = 1");
8659 : }
8660 :
8661 6 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8662 : const GByte *pabyBLOB =
8663 6 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8664 : GPkgHeader sHeader;
8665 6 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8666 : {
8667 2 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8668 2 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8669 2 : return;
8670 : }
8671 :
8672 : GDALGeoPackageDataset *poDS =
8673 4 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8674 :
8675 4 : OGRSpatialReferenceRefCountedPtr poSrcSRS;
8676 4 : if (argc == 2)
8677 : {
8678 3 : poSrcSRS = poDS->GetSpatialRef(sHeader.iSrsId, true);
8679 3 : if (!poSrcSRS)
8680 : {
8681 1 : CPLError(CE_Failure, CPLE_AppDefined,
8682 : "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8683 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8684 1 : return;
8685 : }
8686 : }
8687 :
8688 : auto poGeom = std::unique_ptr<OGRGeometry>(
8689 3 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8690 3 : if (poGeom == nullptr)
8691 : {
8692 : // Try also spatialite geometry blobs
8693 0 : OGRGeometry *poGeomSpatialite = nullptr;
8694 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8695 0 : &poGeomSpatialite) != OGRERR_NONE)
8696 : {
8697 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8698 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8699 0 : return;
8700 : }
8701 0 : poGeom.reset(poGeomSpatialite);
8702 : }
8703 :
8704 3 : if (argc == 2)
8705 2 : poGeom->assignSpatialReference(poSrcSRS.get());
8706 :
8707 6 : sqlite3_result_double(
8708 : pContext,
8709 1 : argc == 1 ? OGR_G_Length(OGRGeometry::ToHandle(poGeom.get()))
8710 2 : : OGR_G_GeodesicLength(OGRGeometry::ToHandle(poGeom.get())));
8711 : }
8712 :
8713 : /************************************************************************/
8714 : /* OGRGeoPackageTransform() */
8715 : /************************************************************************/
8716 :
8717 : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
8718 : sqlite3_value **argv);
8719 :
8720 32 : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
8721 : sqlite3_value **argv)
8722 : {
8723 63 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB ||
8724 31 : sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
8725 : {
8726 2 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8727 32 : return;
8728 : }
8729 :
8730 30 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8731 : const GByte *pabyBLOB =
8732 30 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8733 : GPkgHeader sHeader;
8734 30 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8735 : {
8736 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8737 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8738 1 : return;
8739 : }
8740 :
8741 29 : const int nDestSRID = sqlite3_value_int(argv[1]);
8742 29 : if (sHeader.iSrsId == nDestSRID)
8743 : {
8744 : // Return blob unmodified
8745 3 : sqlite3_result_blob(pContext, pabyBLOB, nBLOBLen, SQLITE_TRANSIENT);
8746 3 : return;
8747 : }
8748 :
8749 : GDALGeoPackageDataset *poDS =
8750 26 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8751 :
8752 : // Try to get the cached coordinate transformation
8753 : OGRCoordinateTransformation *poCT;
8754 26 : if (poDS->m_nLastCachedCTSrcSRId == sHeader.iSrsId &&
8755 20 : poDS->m_nLastCachedCTDstSRId == nDestSRID)
8756 : {
8757 20 : poCT = poDS->m_poLastCachedCT.get();
8758 : }
8759 : else
8760 : {
8761 6 : auto poSrcSRS = poDS->GetSpatialRef(sHeader.iSrsId, true);
8762 6 : if (poSrcSRS == nullptr)
8763 : {
8764 0 : CPLError(CE_Failure, CPLE_AppDefined,
8765 : "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8766 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8767 0 : return;
8768 : }
8769 :
8770 6 : auto poDstSRS = poDS->GetSpatialRef(nDestSRID, true);
8771 6 : if (poDstSRS == nullptr)
8772 : {
8773 0 : CPLError(CE_Failure, CPLE_AppDefined, "Target SRID (%d) is invalid",
8774 : nDestSRID);
8775 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8776 0 : return;
8777 : }
8778 : poCT =
8779 6 : OGRCreateCoordinateTransformation(poSrcSRS.get(), poDstSRS.get());
8780 6 : if (poCT == nullptr)
8781 : {
8782 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8783 0 : return;
8784 : }
8785 :
8786 : // Cache coordinate transformation for potential later reuse
8787 6 : poDS->m_nLastCachedCTSrcSRId = sHeader.iSrsId;
8788 6 : poDS->m_nLastCachedCTDstSRId = nDestSRID;
8789 6 : poDS->m_poLastCachedCT.reset(poCT);
8790 6 : poCT = poDS->m_poLastCachedCT.get();
8791 : }
8792 :
8793 26 : if (sHeader.nHeaderLen >= 8)
8794 : {
8795 26 : std::vector<GByte> &abyNewBLOB = poDS->m_abyWKBTransformCache;
8796 26 : abyNewBLOB.resize(nBLOBLen);
8797 26 : memcpy(abyNewBLOB.data(), pabyBLOB, nBLOBLen);
8798 :
8799 26 : OGREnvelope3D oEnv3d;
8800 26 : if (!OGRWKBTransform(abyNewBLOB.data() + sHeader.nHeaderLen,
8801 26 : nBLOBLen - sHeader.nHeaderLen, poCT,
8802 78 : poDS->m_oWKBTransformCache, oEnv3d) ||
8803 26 : !GPkgUpdateHeader(abyNewBLOB.data(), nBLOBLen, nDestSRID,
8804 : oEnv3d.MinX, oEnv3d.MaxX, oEnv3d.MinY,
8805 : oEnv3d.MaxY, oEnv3d.MinZ, oEnv3d.MaxZ))
8806 : {
8807 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8808 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8809 0 : return;
8810 : }
8811 :
8812 26 : sqlite3_result_blob(pContext, abyNewBLOB.data(), nBLOBLen,
8813 : SQLITE_TRANSIENT);
8814 26 : return;
8815 : }
8816 :
8817 : // Try also spatialite geometry blobs
8818 0 : OGRGeometry *poGeomSpatialite = nullptr;
8819 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8820 0 : &poGeomSpatialite) != OGRERR_NONE)
8821 : {
8822 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8823 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8824 0 : return;
8825 : }
8826 0 : auto poGeom = std::unique_ptr<OGRGeometry>(poGeomSpatialite);
8827 :
8828 0 : if (poGeom->transform(poCT) != OGRERR_NONE)
8829 : {
8830 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8831 0 : return;
8832 : }
8833 :
8834 0 : size_t nBLOBDestLen = 0;
8835 : GByte *pabyDestBLOB =
8836 0 : GPkgGeometryFromOGR(poGeom.get(), nDestSRID, nullptr, &nBLOBDestLen);
8837 0 : if (!pabyDestBLOB)
8838 : {
8839 0 : sqlite3_result_null(pContext);
8840 0 : return;
8841 : }
8842 0 : sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
8843 : VSIFree);
8844 : }
8845 :
8846 : /************************************************************************/
8847 : /* OGRGeoPackageSridFromAuthCRS() */
8848 : /************************************************************************/
8849 :
8850 4 : static void OGRGeoPackageSridFromAuthCRS(sqlite3_context *pContext,
8851 : int /*argc*/, sqlite3_value **argv)
8852 : {
8853 7 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8854 3 : sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
8855 : {
8856 2 : sqlite3_result_int(pContext, -1);
8857 2 : return;
8858 : }
8859 :
8860 : GDALGeoPackageDataset *poDS =
8861 2 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8862 :
8863 2 : char *pszSQL = sqlite3_mprintf(
8864 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
8865 : "lower(organization) = lower('%q') AND organization_coordsys_id = %d",
8866 2 : sqlite3_value_text(argv[0]), sqlite3_value_int(argv[1]));
8867 2 : OGRErr err = OGRERR_NONE;
8868 2 : int nSRSId = SQLGetInteger(poDS->GetDB(), pszSQL, &err);
8869 2 : sqlite3_free(pszSQL);
8870 2 : if (err != OGRERR_NONE)
8871 1 : nSRSId = -1;
8872 2 : sqlite3_result_int(pContext, nSRSId);
8873 : }
8874 :
8875 : /************************************************************************/
8876 : /* OGRGeoPackageImportFromEPSG() */
8877 : /************************************************************************/
8878 :
8879 4 : static void OGRGeoPackageImportFromEPSG(sqlite3_context *pContext, int /*argc*/,
8880 : sqlite3_value **argv)
8881 : {
8882 4 : if (sqlite3_value_type(argv[0]) != SQLITE_INTEGER)
8883 : {
8884 1 : sqlite3_result_int(pContext, -1);
8885 2 : return;
8886 : }
8887 :
8888 : GDALGeoPackageDataset *poDS =
8889 3 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8890 3 : OGRSpatialReference oSRS;
8891 3 : if (oSRS.importFromEPSG(sqlite3_value_int(argv[0])) != OGRERR_NONE)
8892 : {
8893 1 : sqlite3_result_int(pContext, -1);
8894 1 : return;
8895 : }
8896 :
8897 2 : sqlite3_result_int(pContext, poDS->GetSrsId(&oSRS));
8898 : }
8899 :
8900 : /************************************************************************/
8901 : /* OGRGeoPackageRegisterGeometryExtension() */
8902 : /************************************************************************/
8903 :
8904 1 : static void OGRGeoPackageRegisterGeometryExtension(sqlite3_context *pContext,
8905 : int /*argc*/,
8906 : sqlite3_value **argv)
8907 : {
8908 1 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8909 2 : sqlite3_value_type(argv[1]) != SQLITE_TEXT ||
8910 1 : sqlite3_value_type(argv[2]) != SQLITE_TEXT)
8911 : {
8912 0 : sqlite3_result_int(pContext, 0);
8913 0 : return;
8914 : }
8915 :
8916 : const char *pszTableName =
8917 1 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8918 : const char *pszGeomName =
8919 1 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8920 : const char *pszGeomType =
8921 1 : reinterpret_cast<const char *>(sqlite3_value_text(argv[2]));
8922 :
8923 : GDALGeoPackageDataset *poDS =
8924 1 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8925 :
8926 1 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
8927 1 : poDS->GetLayerByName(pszTableName));
8928 1 : if (poLyr == nullptr)
8929 : {
8930 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
8931 0 : sqlite3_result_int(pContext, 0);
8932 0 : return;
8933 : }
8934 1 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
8935 : {
8936 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
8937 0 : sqlite3_result_int(pContext, 0);
8938 0 : return;
8939 : }
8940 1 : const OGRwkbGeometryType eGeomType = OGRFromOGCGeomType(pszGeomType);
8941 1 : if (eGeomType == wkbUnknown)
8942 : {
8943 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry type name");
8944 0 : sqlite3_result_int(pContext, 0);
8945 0 : return;
8946 : }
8947 :
8948 1 : sqlite3_result_int(
8949 : pContext,
8950 1 : static_cast<int>(poLyr->CreateGeometryExtensionIfNecessary(eGeomType)));
8951 : }
8952 :
8953 : /************************************************************************/
8954 : /* OGRGeoPackageCreateSpatialIndex() */
8955 : /************************************************************************/
8956 :
8957 14 : static void OGRGeoPackageCreateSpatialIndex(sqlite3_context *pContext,
8958 : int /*argc*/, sqlite3_value **argv)
8959 : {
8960 27 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8961 13 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
8962 : {
8963 2 : sqlite3_result_int(pContext, 0);
8964 2 : return;
8965 : }
8966 :
8967 : const char *pszTableName =
8968 12 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8969 : const char *pszGeomName =
8970 12 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8971 : GDALGeoPackageDataset *poDS =
8972 12 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8973 :
8974 12 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
8975 12 : poDS->GetLayerByName(pszTableName));
8976 12 : if (poLyr == nullptr)
8977 : {
8978 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
8979 1 : sqlite3_result_int(pContext, 0);
8980 1 : return;
8981 : }
8982 11 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
8983 : {
8984 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
8985 1 : sqlite3_result_int(pContext, 0);
8986 1 : return;
8987 : }
8988 :
8989 10 : sqlite3_result_int(pContext, poLyr->CreateSpatialIndex());
8990 : }
8991 :
8992 : /************************************************************************/
8993 : /* OGRGeoPackageDisableSpatialIndex() */
8994 : /************************************************************************/
8995 :
8996 12 : static void OGRGeoPackageDisableSpatialIndex(sqlite3_context *pContext,
8997 : int /*argc*/, sqlite3_value **argv)
8998 : {
8999 23 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9000 11 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9001 : {
9002 2 : sqlite3_result_int(pContext, 0);
9003 2 : return;
9004 : }
9005 :
9006 : const char *pszTableName =
9007 10 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9008 : const char *pszGeomName =
9009 10 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9010 : GDALGeoPackageDataset *poDS =
9011 10 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9012 :
9013 10 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
9014 10 : poDS->GetLayerByName(pszTableName));
9015 10 : if (poLyr == nullptr)
9016 : {
9017 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
9018 1 : sqlite3_result_int(pContext, 0);
9019 1 : return;
9020 : }
9021 9 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
9022 : {
9023 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
9024 1 : sqlite3_result_int(pContext, 0);
9025 1 : return;
9026 : }
9027 :
9028 8 : sqlite3_result_int(pContext, poLyr->DropSpatialIndex(true));
9029 : }
9030 :
9031 : /************************************************************************/
9032 : /* OGRGeoPackageHasSpatialIndex() */
9033 : /************************************************************************/
9034 :
9035 29 : static void OGRGeoPackageHasSpatialIndex(sqlite3_context *pContext,
9036 : int /*argc*/, sqlite3_value **argv)
9037 : {
9038 57 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9039 28 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9040 : {
9041 2 : sqlite3_result_int(pContext, 0);
9042 2 : return;
9043 : }
9044 :
9045 : const char *pszTableName =
9046 27 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9047 : const char *pszGeomName =
9048 27 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9049 : GDALGeoPackageDataset *poDS =
9050 27 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9051 :
9052 27 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
9053 27 : poDS->GetLayerByName(pszTableName));
9054 27 : if (poLyr == nullptr)
9055 : {
9056 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
9057 1 : sqlite3_result_int(pContext, 0);
9058 1 : return;
9059 : }
9060 26 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
9061 : {
9062 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
9063 1 : sqlite3_result_int(pContext, 0);
9064 1 : return;
9065 : }
9066 :
9067 25 : poLyr->RunDeferredCreationIfNecessary();
9068 25 : poLyr->CreateSpatialIndexIfNecessary();
9069 :
9070 25 : sqlite3_result_int(pContext, poLyr->HasSpatialIndex());
9071 : }
9072 :
9073 : /************************************************************************/
9074 : /* GPKG_hstore_get_value() */
9075 : /************************************************************************/
9076 :
9077 4 : static void GPKG_hstore_get_value(sqlite3_context *pContext, int /*argc*/,
9078 : sqlite3_value **argv)
9079 : {
9080 7 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9081 3 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9082 : {
9083 2 : sqlite3_result_null(pContext);
9084 2 : return;
9085 : }
9086 :
9087 : const char *pszHStore =
9088 2 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9089 : const char *pszSearchedKey =
9090 2 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9091 2 : char *pszValue = OGRHStoreGetValue(pszHStore, pszSearchedKey);
9092 2 : if (pszValue != nullptr)
9093 1 : sqlite3_result_text(pContext, pszValue, -1, CPLFree);
9094 : else
9095 1 : sqlite3_result_null(pContext);
9096 : }
9097 :
9098 : /************************************************************************/
9099 : /* GPKG_GDAL_GetMemFileFromBlob() */
9100 : /************************************************************************/
9101 :
9102 105 : static CPLString GPKG_GDAL_GetMemFileFromBlob(sqlite3_value **argv)
9103 : {
9104 105 : int nBytes = sqlite3_value_bytes(argv[0]);
9105 : const GByte *pabyBLOB =
9106 105 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
9107 : CPLString osMemFileName(
9108 105 : VSIMemGenerateHiddenFilename("GPKG_GDAL_GetMemFileFromBlob"));
9109 105 : VSILFILE *fp = VSIFileFromMemBuffer(
9110 : osMemFileName.c_str(), const_cast<GByte *>(pabyBLOB), nBytes, FALSE);
9111 105 : VSIFCloseL(fp);
9112 105 : return osMemFileName;
9113 : }
9114 :
9115 : /************************************************************************/
9116 : /* GPKG_GDAL_GetMimeType() */
9117 : /************************************************************************/
9118 :
9119 35 : static void GPKG_GDAL_GetMimeType(sqlite3_context *pContext, int /*argc*/,
9120 : sqlite3_value **argv)
9121 : {
9122 35 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
9123 : {
9124 0 : sqlite3_result_null(pContext);
9125 0 : return;
9126 : }
9127 :
9128 70 : CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
9129 : GDALDriver *poDriver =
9130 35 : GDALDriver::FromHandle(GDALIdentifyDriver(osMemFileName, nullptr));
9131 35 : if (poDriver != nullptr)
9132 : {
9133 35 : const char *pszRes = nullptr;
9134 35 : if (EQUAL(poDriver->GetDescription(), "PNG"))
9135 23 : pszRes = "image/png";
9136 12 : else if (EQUAL(poDriver->GetDescription(), "JPEG"))
9137 6 : pszRes = "image/jpeg";
9138 6 : else if (EQUAL(poDriver->GetDescription(), "WEBP"))
9139 6 : pszRes = "image/x-webp";
9140 0 : else if (EQUAL(poDriver->GetDescription(), "GTIFF"))
9141 0 : pszRes = "image/tiff";
9142 : else
9143 0 : pszRes = CPLSPrintf("gdal/%s", poDriver->GetDescription());
9144 35 : sqlite3_result_text(pContext, pszRes, -1, SQLITE_TRANSIENT);
9145 : }
9146 : else
9147 0 : sqlite3_result_null(pContext);
9148 35 : VSIUnlink(osMemFileName);
9149 : }
9150 :
9151 : /************************************************************************/
9152 : /* GPKG_GDAL_GetBandCount() */
9153 : /************************************************************************/
9154 :
9155 35 : static void GPKG_GDAL_GetBandCount(sqlite3_context *pContext, int /*argc*/,
9156 : sqlite3_value **argv)
9157 : {
9158 35 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
9159 : {
9160 0 : sqlite3_result_null(pContext);
9161 0 : return;
9162 : }
9163 :
9164 70 : CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
9165 : auto poDS = std::unique_ptr<GDALDataset>(
9166 : GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
9167 70 : nullptr, nullptr, nullptr));
9168 35 : if (poDS != nullptr)
9169 : {
9170 35 : sqlite3_result_int(pContext, poDS->GetRasterCount());
9171 : }
9172 : else
9173 0 : sqlite3_result_null(pContext);
9174 35 : VSIUnlink(osMemFileName);
9175 : }
9176 :
9177 : /************************************************************************/
9178 : /* GPKG_GDAL_HasColorTable() */
9179 : /************************************************************************/
9180 :
9181 35 : static void GPKG_GDAL_HasColorTable(sqlite3_context *pContext, int /*argc*/,
9182 : sqlite3_value **argv)
9183 : {
9184 35 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
9185 : {
9186 0 : sqlite3_result_null(pContext);
9187 0 : return;
9188 : }
9189 :
9190 70 : CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
9191 : auto poDS = std::unique_ptr<GDALDataset>(
9192 : GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
9193 70 : nullptr, nullptr, nullptr));
9194 35 : if (poDS != nullptr)
9195 : {
9196 35 : sqlite3_result_int(
9197 46 : pContext, poDS->GetRasterCount() == 1 &&
9198 11 : poDS->GetRasterBand(1)->GetColorTable() != nullptr);
9199 : }
9200 : else
9201 0 : sqlite3_result_null(pContext);
9202 35 : VSIUnlink(osMemFileName);
9203 : }
9204 :
9205 : /************************************************************************/
9206 : /* GetRasterLayerDataset() */
9207 : /************************************************************************/
9208 :
9209 : GDALDataset *
9210 12 : GDALGeoPackageDataset::GetRasterLayerDataset(const char *pszLayerName)
9211 : {
9212 12 : const auto oIter = m_oCachedRasterDS.find(pszLayerName);
9213 12 : if (oIter != m_oCachedRasterDS.end())
9214 10 : return oIter->second.get();
9215 :
9216 : auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
9217 4 : (std::string("GPKG:\"") + m_pszFilename + "\":" + pszLayerName).c_str(),
9218 4 : GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
9219 2 : if (!poDS)
9220 : {
9221 0 : return nullptr;
9222 : }
9223 2 : m_oCachedRasterDS[pszLayerName] = std::move(poDS);
9224 2 : return m_oCachedRasterDS[pszLayerName].get();
9225 : }
9226 :
9227 : /************************************************************************/
9228 : /* GPKG_gdal_get_layer_pixel_value() */
9229 : /************************************************************************/
9230 :
9231 : // NOTE: keep in sync implementations in ogrsqlitesqlfunctionscommon.cpp
9232 : // and ogrgeopackagedatasource.cpp
9233 13 : static void GPKG_gdal_get_layer_pixel_value(sqlite3_context *pContext, int argc,
9234 : sqlite3_value **argv)
9235 : {
9236 13 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
9237 : {
9238 1 : CPLError(CE_Failure, CPLE_AppDefined,
9239 : "Invalid arguments to gdal_get_layer_pixel_value()");
9240 1 : sqlite3_result_null(pContext);
9241 1 : return;
9242 : }
9243 :
9244 : const char *pszLayerName =
9245 12 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9246 :
9247 : GDALGeoPackageDataset *poGlobalDS =
9248 12 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9249 12 : auto poDS = poGlobalDS->GetRasterLayerDataset(pszLayerName);
9250 12 : if (!poDS)
9251 : {
9252 0 : sqlite3_result_null(pContext);
9253 0 : return;
9254 : }
9255 :
9256 12 : OGRSQLite_gdal_get_pixel_value_common("gdal_get_layer_pixel_value",
9257 : pContext, argc, argv, poDS);
9258 : }
9259 :
9260 : /************************************************************************/
9261 : /* GPKG_ogr_layer_Extent() */
9262 : /************************************************************************/
9263 :
9264 3 : static void GPKG_ogr_layer_Extent(sqlite3_context *pContext, int /*argc*/,
9265 : sqlite3_value **argv)
9266 : {
9267 3 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
9268 : {
9269 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s: Invalid argument type",
9270 : "ogr_layer_Extent");
9271 1 : sqlite3_result_null(pContext);
9272 2 : return;
9273 : }
9274 :
9275 : const char *pszLayerName =
9276 2 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9277 : GDALGeoPackageDataset *poDS =
9278 2 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9279 2 : OGRLayer *poLayer = poDS->GetLayerByName(pszLayerName);
9280 2 : if (!poLayer)
9281 : {
9282 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s: unknown layer",
9283 : "ogr_layer_Extent");
9284 1 : sqlite3_result_null(pContext);
9285 1 : return;
9286 : }
9287 :
9288 1 : if (poLayer->GetGeomType() == wkbNone)
9289 : {
9290 0 : sqlite3_result_null(pContext);
9291 0 : return;
9292 : }
9293 :
9294 1 : OGREnvelope sExtent;
9295 1 : if (poLayer->GetExtent(&sExtent, true) != OGRERR_NONE)
9296 : {
9297 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s: Cannot fetch layer extent",
9298 : "ogr_layer_Extent");
9299 0 : sqlite3_result_null(pContext);
9300 0 : return;
9301 : }
9302 :
9303 1 : OGRPolygon oPoly;
9304 1 : auto poRing = std::make_unique<OGRLinearRing>();
9305 1 : poRing->addPoint(sExtent.MinX, sExtent.MinY);
9306 1 : poRing->addPoint(sExtent.MaxX, sExtent.MinY);
9307 1 : poRing->addPoint(sExtent.MaxX, sExtent.MaxY);
9308 1 : poRing->addPoint(sExtent.MinX, sExtent.MaxY);
9309 1 : poRing->addPoint(sExtent.MinX, sExtent.MinY);
9310 1 : oPoly.addRing(std::move(poRing));
9311 :
9312 1 : const auto poSRS = poLayer->GetSpatialRef();
9313 1 : const int nSRID = poDS->GetSrsId(poSRS);
9314 1 : size_t nBLOBDestLen = 0;
9315 : GByte *pabyDestBLOB =
9316 1 : GPkgGeometryFromOGR(&oPoly, nSRID, nullptr, &nBLOBDestLen);
9317 1 : if (!pabyDestBLOB)
9318 : {
9319 0 : sqlite3_result_null(pContext);
9320 0 : return;
9321 : }
9322 1 : sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
9323 : VSIFree);
9324 : }
9325 :
9326 : /************************************************************************/
9327 : /* GPKG_ST_Hilbert_X_Y_TableName() */
9328 : /************************************************************************/
9329 :
9330 8 : static void GPKG_ST_Hilbert_X_Y_TableName(sqlite3_context *pContext,
9331 : [[maybe_unused]] int argc,
9332 : sqlite3_value **argv)
9333 : {
9334 8 : CPLAssert(argc == 3);
9335 8 : const double dfX = sqlite3_value_double(argv[0]);
9336 8 : const double dfY = sqlite3_value_double(argv[1]);
9337 :
9338 8 : if (sqlite3_value_type(argv[2]) != SQLITE_TEXT)
9339 : {
9340 1 : CPLError(CE_Failure, CPLE_AppDefined,
9341 : "%s: Invalid argument type for 3rd argument. Text expected",
9342 : "ST_Hilbert()");
9343 1 : sqlite3_result_null(pContext);
9344 6 : return;
9345 : }
9346 :
9347 : const char *pszLayerName =
9348 7 : reinterpret_cast<const char *>(sqlite3_value_text(argv[2]));
9349 : GDALGeoPackageDataset *poDS =
9350 7 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9351 7 : OGRLayer *poLayer = poDS->GetLayerByName(pszLayerName);
9352 7 : if (!poLayer)
9353 : {
9354 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s: unknown layer '%s'",
9355 : "ST_Hilbert()", pszLayerName);
9356 1 : sqlite3_result_null(pContext);
9357 1 : return;
9358 : }
9359 :
9360 6 : OGREnvelope sExtent;
9361 6 : if (poLayer->GetExtent(&sExtent, true) != OGRERR_NONE)
9362 : {
9363 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s: Cannot fetch layer extent",
9364 : "ST_Hilbert()");
9365 0 : sqlite3_result_null(pContext);
9366 0 : return;
9367 : }
9368 6 : if (!(dfX >= sExtent.MinX && dfY >= sExtent.MinY && dfX <= sExtent.MaxX &&
9369 3 : dfY <= sExtent.MaxY))
9370 : {
9371 4 : CPLError(CE_Warning, CPLE_AppDefined,
9372 : "ST_Hilbert(): (%g, %g) is not within passed bounding box",
9373 : dfX, dfY);
9374 4 : sqlite3_result_null(pContext);
9375 4 : return;
9376 : }
9377 2 : sqlite3_result_int64(pContext, GDALHilbertCode(&sExtent, dfX, dfY));
9378 : }
9379 :
9380 : /************************************************************************/
9381 : /* GPKG_ST_Hilbert_Geom_BBOX() */
9382 : /************************************************************************/
9383 :
9384 6 : static void GPKG_ST_Hilbert_Geom_BBOX(sqlite3_context *pContext, int argc,
9385 : sqlite3_value **argv)
9386 : {
9387 6 : CPLAssert(argc == 5);
9388 : GPkgHeader sHeader;
9389 6 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
9390 : {
9391 1 : sqlite3_result_null(pContext);
9392 5 : return;
9393 : }
9394 5 : const double dfX = (sHeader.MinX + sHeader.MaxX) / 2;
9395 5 : const double dfY = (sHeader.MinY + sHeader.MaxY) / 2;
9396 :
9397 5 : OGREnvelope sExtent;
9398 5 : sExtent.MinX = sqlite3_value_double(argv[1]);
9399 5 : sExtent.MinY = sqlite3_value_double(argv[2]);
9400 5 : sExtent.MaxX = sqlite3_value_double(argv[3]);
9401 5 : sExtent.MaxY = sqlite3_value_double(argv[4]);
9402 5 : if (!(dfX >= sExtent.MinX && dfY >= sExtent.MinY && dfX <= sExtent.MaxX &&
9403 2 : dfY <= sExtent.MaxY))
9404 : {
9405 4 : CPLError(CE_Warning, CPLE_AppDefined,
9406 : "ST_Hilbert(): (%g, %g) is not within passed bounding box",
9407 : dfX, dfY);
9408 4 : sqlite3_result_null(pContext);
9409 4 : return;
9410 : }
9411 1 : sqlite3_result_int64(pContext, GDALHilbertCode(&sExtent, dfX, dfY));
9412 : }
9413 :
9414 : /************************************************************************/
9415 : /* GPKG_ST_Hilbert_Geom_TableName() */
9416 : /************************************************************************/
9417 :
9418 4 : static void GPKG_ST_Hilbert_Geom_TableName(sqlite3_context *pContext, int argc,
9419 : sqlite3_value **argv)
9420 : {
9421 4 : CPLAssert(argc == 2);
9422 : GPkgHeader sHeader;
9423 4 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
9424 : {
9425 1 : sqlite3_result_null(pContext);
9426 3 : return;
9427 : }
9428 3 : const double dfX = (sHeader.MinX + sHeader.MaxX) / 2;
9429 3 : const double dfY = (sHeader.MinY + sHeader.MaxY) / 2;
9430 :
9431 3 : if (sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9432 : {
9433 1 : CPLError(CE_Failure, CPLE_AppDefined,
9434 : "%s: Invalid argument type for 2nd argument. Text expected",
9435 : "ST_Hilbert()");
9436 1 : sqlite3_result_null(pContext);
9437 1 : return;
9438 : }
9439 :
9440 : const char *pszLayerName =
9441 2 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9442 : GDALGeoPackageDataset *poDS =
9443 2 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9444 2 : OGRLayer *poLayer = poDS->GetLayerByName(pszLayerName);
9445 2 : if (!poLayer)
9446 : {
9447 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s: unknown layer '%s'",
9448 : "ST_Hilbert()", pszLayerName);
9449 1 : sqlite3_result_null(pContext);
9450 1 : return;
9451 : }
9452 :
9453 1 : OGREnvelope sExtent;
9454 1 : if (poLayer->GetExtent(&sExtent, true) != OGRERR_NONE)
9455 : {
9456 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s: Cannot fetch layer extent",
9457 : "ST_Hilbert()");
9458 0 : sqlite3_result_null(pContext);
9459 0 : return;
9460 : }
9461 1 : if (!(dfX >= sExtent.MinX && dfY >= sExtent.MinY && dfX <= sExtent.MaxX &&
9462 1 : dfY <= sExtent.MaxY))
9463 : {
9464 0 : CPLError(CE_Warning, CPLE_AppDefined,
9465 : "ST_Hilbert(): (%g, %g) is not within passed bounding box",
9466 : dfX, dfY);
9467 0 : sqlite3_result_null(pContext);
9468 0 : return;
9469 : }
9470 1 : sqlite3_result_int64(pContext, GDALHilbertCode(&sExtent, dfX, dfY));
9471 : }
9472 :
9473 : /************************************************************************/
9474 : /* InstallSQLFunctions() */
9475 : /************************************************************************/
9476 :
9477 : #ifndef SQLITE_DETERMINISTIC
9478 : #define SQLITE_DETERMINISTIC 0
9479 : #endif
9480 :
9481 : #ifndef SQLITE_INNOCUOUS
9482 : #define SQLITE_INNOCUOUS 0
9483 : #endif
9484 :
9485 : #ifndef UTF8_INNOCUOUS
9486 : #define UTF8_INNOCUOUS (SQLITE_UTF8 | SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS)
9487 : #endif
9488 :
9489 2597 : void GDALGeoPackageDataset::InstallSQLFunctions()
9490 : {
9491 2597 : InitSpatialite();
9492 :
9493 : // Enable SpatiaLite 4.3 GPKG mode, i.e. that SpatiaLite functions
9494 : // that take geometries will accept and return GPKG encoded geometries without
9495 : // explicit conversion.
9496 : // Use sqlite3_exec() instead of SQLCommand() since we don't want verbose
9497 : // error.
9498 2597 : sqlite3_exec(hDB, "SELECT EnableGpkgMode()", nullptr, nullptr, nullptr);
9499 :
9500 : /* Used by RTree Spatial Index Extension */
9501 2597 : sqlite3_create_function(hDB, "ST_MinX", 1, UTF8_INNOCUOUS, nullptr,
9502 : OGRGeoPackageSTMinX, nullptr, nullptr);
9503 2597 : sqlite3_create_function(hDB, "ST_MinY", 1, UTF8_INNOCUOUS, nullptr,
9504 : OGRGeoPackageSTMinY, nullptr, nullptr);
9505 2597 : sqlite3_create_function(hDB, "ST_MaxX", 1, UTF8_INNOCUOUS, nullptr,
9506 : OGRGeoPackageSTMaxX, nullptr, nullptr);
9507 2597 : sqlite3_create_function(hDB, "ST_MaxY", 1, UTF8_INNOCUOUS, nullptr,
9508 : OGRGeoPackageSTMaxY, nullptr, nullptr);
9509 2597 : sqlite3_create_function(hDB, "ST_IsEmpty", 1, UTF8_INNOCUOUS, nullptr,
9510 : OGRGeoPackageSTIsEmpty, nullptr, nullptr);
9511 :
9512 : /* Used by Geometry Type Triggers Extension */
9513 2597 : sqlite3_create_function(hDB, "ST_GeometryType", 1, UTF8_INNOCUOUS, nullptr,
9514 : OGRGeoPackageSTGeometryType, nullptr, nullptr);
9515 2597 : sqlite3_create_function(hDB, "GPKG_IsAssignable", 2, UTF8_INNOCUOUS,
9516 : nullptr, OGRGeoPackageGPKGIsAssignable, nullptr,
9517 : nullptr);
9518 :
9519 : /* Used by Geometry SRS ID Triggers Extension */
9520 2597 : sqlite3_create_function(hDB, "ST_SRID", 1, UTF8_INNOCUOUS, nullptr,
9521 : OGRGeoPackageSTSRID, nullptr, nullptr);
9522 :
9523 : /* Spatialite-like functions */
9524 2597 : sqlite3_create_function(hDB, "CreateSpatialIndex", 2, SQLITE_UTF8, this,
9525 : OGRGeoPackageCreateSpatialIndex, nullptr, nullptr);
9526 2597 : sqlite3_create_function(hDB, "DisableSpatialIndex", 2, SQLITE_UTF8, this,
9527 : OGRGeoPackageDisableSpatialIndex, nullptr, nullptr);
9528 2597 : sqlite3_create_function(hDB, "HasSpatialIndex", 2, SQLITE_UTF8, this,
9529 : OGRGeoPackageHasSpatialIndex, nullptr, nullptr);
9530 :
9531 : // HSTORE functions
9532 2597 : sqlite3_create_function(hDB, "hstore_get_value", 2, UTF8_INNOCUOUS, nullptr,
9533 : GPKG_hstore_get_value, nullptr, nullptr);
9534 :
9535 : // Override a few Spatialite functions to work with gpkg_spatial_ref_sys
9536 2597 : sqlite3_create_function(hDB, "ST_Transform", 2, UTF8_INNOCUOUS, this,
9537 : OGRGeoPackageTransform, nullptr, nullptr);
9538 2597 : sqlite3_create_function(hDB, "Transform", 2, UTF8_INNOCUOUS, this,
9539 : OGRGeoPackageTransform, nullptr, nullptr);
9540 2597 : sqlite3_create_function(hDB, "SridFromAuthCRS", 2, SQLITE_UTF8, this,
9541 : OGRGeoPackageSridFromAuthCRS, nullptr, nullptr);
9542 :
9543 2597 : sqlite3_create_function(hDB, "ST_EnvIntersects", 2, UTF8_INNOCUOUS, nullptr,
9544 : OGRGeoPackageSTEnvelopesIntersectsTwoParams,
9545 : nullptr, nullptr);
9546 2597 : sqlite3_create_function(
9547 : hDB, "ST_EnvelopesIntersects", 2, UTF8_INNOCUOUS, nullptr,
9548 : OGRGeoPackageSTEnvelopesIntersectsTwoParams, nullptr, nullptr);
9549 :
9550 2597 : sqlite3_create_function(hDB, "ST_EnvIntersects", 5, UTF8_INNOCUOUS, nullptr,
9551 : OGRGeoPackageSTEnvelopesIntersects, nullptr,
9552 : nullptr);
9553 2597 : sqlite3_create_function(hDB, "ST_EnvelopesIntersects", 5, UTF8_INNOCUOUS,
9554 : nullptr, OGRGeoPackageSTEnvelopesIntersects,
9555 : nullptr, nullptr);
9556 :
9557 : // Implementation that directly hacks the GeoPackage geometry blob header
9558 2597 : sqlite3_create_function(hDB, "SetSRID", 2, UTF8_INNOCUOUS, nullptr,
9559 : OGRGeoPackageSetSRID, nullptr, nullptr);
9560 :
9561 : // GDAL specific function
9562 2597 : sqlite3_create_function(hDB, "ImportFromEPSG", 1, SQLITE_UTF8, this,
9563 : OGRGeoPackageImportFromEPSG, nullptr, nullptr);
9564 :
9565 : // May be used by ogrmerge.py
9566 2597 : sqlite3_create_function(hDB, "RegisterGeometryExtension", 3, SQLITE_UTF8,
9567 : this, OGRGeoPackageRegisterGeometryExtension,
9568 : nullptr, nullptr);
9569 :
9570 2597 : if (OGRGeometryFactory::haveGEOS())
9571 : {
9572 2597 : sqlite3_create_function(hDB, "ST_MakeValid", 1, UTF8_INNOCUOUS, nullptr,
9573 : OGRGeoPackageSTMakeValid, nullptr, nullptr);
9574 : }
9575 :
9576 2597 : sqlite3_create_function(hDB, "ST_Length", 1, UTF8_INNOCUOUS, nullptr,
9577 : OGRGeoPackageLengthOrGeodesicLength, nullptr,
9578 : nullptr);
9579 2597 : sqlite3_create_function(hDB, "ST_Length", 2, UTF8_INNOCUOUS, this,
9580 : OGRGeoPackageLengthOrGeodesicLength, nullptr,
9581 : nullptr);
9582 :
9583 2597 : sqlite3_create_function(hDB, "ST_Area", 1, UTF8_INNOCUOUS, nullptr,
9584 : OGRGeoPackageSTArea, nullptr, nullptr);
9585 2597 : sqlite3_create_function(hDB, "ST_Area", 2, UTF8_INNOCUOUS, this,
9586 : OGRGeoPackageGeodesicArea, nullptr, nullptr);
9587 :
9588 : // Debug functions
9589 2597 : if (CPLTestBool(CPLGetConfigOption("GPKG_DEBUG", "FALSE")))
9590 : {
9591 422 : sqlite3_create_function(hDB, "GDAL_GetMimeType", 1,
9592 : SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9593 : GPKG_GDAL_GetMimeType, nullptr, nullptr);
9594 422 : sqlite3_create_function(hDB, "GDAL_GetBandCount", 1,
9595 : SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9596 : GPKG_GDAL_GetBandCount, nullptr, nullptr);
9597 422 : sqlite3_create_function(hDB, "GDAL_HasColorTable", 1,
9598 : SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9599 : GPKG_GDAL_HasColorTable, nullptr, nullptr);
9600 : }
9601 :
9602 2597 : sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 5, SQLITE_UTF8,
9603 : this, GPKG_gdal_get_layer_pixel_value, nullptr,
9604 : nullptr);
9605 2597 : sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 6, SQLITE_UTF8,
9606 : this, GPKG_gdal_get_layer_pixel_value, nullptr,
9607 : nullptr);
9608 :
9609 : // Function from VirtualOGR
9610 2597 : sqlite3_create_function(hDB, "ogr_layer_Extent", 1, SQLITE_UTF8, this,
9611 : GPKG_ogr_layer_Extent, nullptr, nullptr);
9612 :
9613 2597 : m_pSQLFunctionData = OGRSQLiteRegisterSQLFunctionsCommon(hDB);
9614 :
9615 : // ST_Hilbert() inspired from https://duckdb.org/docs/stable/core_extensions/spatial/functions#st_hilbert
9616 : // Override the generic version of OGRSQLiteRegisterSQLFunctionsCommon()
9617 :
9618 : // X,Y,table_name
9619 2597 : sqlite3_create_function(hDB, "ST_Hilbert", 2 + 1, UTF8_INNOCUOUS, this,
9620 : GPKG_ST_Hilbert_X_Y_TableName, nullptr, nullptr);
9621 :
9622 : // geometry,minX,minY,maxX,maxY
9623 2597 : sqlite3_create_function(hDB, "ST_Hilbert", 1 + 4, UTF8_INNOCUOUS, nullptr,
9624 : GPKG_ST_Hilbert_Geom_BBOX, nullptr, nullptr);
9625 :
9626 : // geometry,table_name
9627 2597 : sqlite3_create_function(hDB, "ST_Hilbert", 1 + 1, UTF8_INNOCUOUS, this,
9628 : GPKG_ST_Hilbert_Geom_TableName, nullptr, nullptr);
9629 2597 : }
9630 :
9631 : /************************************************************************/
9632 : /* OpenOrCreateDB() */
9633 : /************************************************************************/
9634 :
9635 2603 : bool GDALGeoPackageDataset::OpenOrCreateDB(int flags)
9636 : {
9637 2603 : const bool bSuccess = OGRSQLiteBaseDataSource::OpenOrCreateDB(
9638 : flags, /*bRegisterOGR2SQLiteExtensions=*/false,
9639 : /*bLoadExtensions=*/true);
9640 2603 : if (!bSuccess)
9641 11 : return false;
9642 :
9643 : // Turning on recursive_triggers is needed so that DELETE triggers fire
9644 : // in a INSERT OR REPLACE statement. In particular this is needed to
9645 : // make sure gpkg_ogr_contents.feature_count is properly updated.
9646 2592 : SQLCommand(hDB, "PRAGMA recursive_triggers = 1");
9647 :
9648 2592 : InstallSQLFunctions();
9649 :
9650 : const char *pszSqlitePragma =
9651 2592 : CPLGetConfigOption("OGR_SQLITE_PRAGMA", nullptr);
9652 2592 : OGRErr eErr = OGRERR_NONE;
9653 6 : if ((!pszSqlitePragma || !strstr(pszSqlitePragma, "trusted_schema")) &&
9654 : // Older sqlite versions don't have this pragma
9655 5190 : SQLGetInteger(hDB, "PRAGMA trusted_schema", &eErr) == 0 &&
9656 2592 : eErr == OGRERR_NONE)
9657 : {
9658 2592 : bool bNeedsTrustedSchema = false;
9659 :
9660 : // Current SQLite versions require PRAGMA trusted_schema = 1 to be
9661 : // able to use the RTree from triggers, which is only needed when
9662 : // modifying the RTree.
9663 6369 : if (((flags & SQLITE_OPEN_READWRITE) != 0 ||
9664 3999 : (flags & SQLITE_OPEN_CREATE) != 0) &&
9665 1407 : OGRSQLiteRTreeRequiresTrustedSchemaOn())
9666 : {
9667 1407 : bNeedsTrustedSchema = true;
9668 : }
9669 :
9670 : #ifdef HAVE_SPATIALITE
9671 : // Spatialite <= 5.1.0 doesn't declare its functions as SQLITE_INNOCUOUS
9672 1185 : if (!bNeedsTrustedSchema && HasExtensionsTable() &&
9673 1090 : SQLGetInteger(
9674 : hDB,
9675 : "SELECT 1 FROM gpkg_extensions WHERE "
9676 : "extension_name ='gdal_spatialite_computed_geom_column'",
9677 1 : nullptr) == 1 &&
9678 3777 : SpatialiteRequiresTrustedSchemaOn() && AreSpatialiteTriggersSafe())
9679 : {
9680 1 : bNeedsTrustedSchema = true;
9681 : }
9682 : #endif
9683 :
9684 2592 : if (bNeedsTrustedSchema)
9685 : {
9686 1408 : CPLDebug("GPKG", "Setting PRAGMA trusted_schema = 1");
9687 1408 : SQLCommand(hDB, "PRAGMA trusted_schema = 1");
9688 : }
9689 : }
9690 :
9691 : const char *pszPreludeStatements =
9692 2592 : CSLFetchNameValue(papszOpenOptions, "PRELUDE_STATEMENTS");
9693 2592 : if (pszPreludeStatements)
9694 : {
9695 2 : if (SQLCommand(hDB, pszPreludeStatements) != OGRERR_NONE)
9696 0 : return false;
9697 : }
9698 :
9699 2592 : return true;
9700 : }
9701 :
9702 : /************************************************************************/
9703 : /* GetLayerWithGetSpatialWhereByName() */
9704 : /************************************************************************/
9705 :
9706 : std::pair<OGRLayer *, IOGRSQLiteGetSpatialWhere *>
9707 90 : GDALGeoPackageDataset::GetLayerWithGetSpatialWhereByName(const char *pszName)
9708 : {
9709 : OGRGeoPackageLayer *poRet =
9710 90 : cpl::down_cast<OGRGeoPackageLayer *>(GetLayerByName(pszName));
9711 90 : return std::pair(poRet, poRet);
9712 : }
9713 :
9714 : /************************************************************************/
9715 : /* CommitTransaction() */
9716 : /************************************************************************/
9717 :
9718 418 : OGRErr GDALGeoPackageDataset::CommitTransaction()
9719 :
9720 : {
9721 418 : if (m_nSoftTransactionLevel == 1)
9722 : {
9723 412 : FlushMetadata();
9724 879 : for (auto &poLayer : m_apoLayers)
9725 : {
9726 467 : poLayer->DoJobAtTransactionCommit();
9727 : }
9728 : }
9729 :
9730 418 : return OGRSQLiteBaseDataSource::CommitTransaction();
9731 : }
9732 :
9733 : /************************************************************************/
9734 : /* RollbackTransaction() */
9735 : /************************************************************************/
9736 :
9737 37 : OGRErr GDALGeoPackageDataset::RollbackTransaction()
9738 :
9739 : {
9740 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9741 74 : std::vector<bool> abAddTriggers;
9742 37 : std::vector<bool> abTriggersDeletedInTransaction;
9743 : #endif
9744 37 : if (m_nSoftTransactionLevel == 1)
9745 : {
9746 36 : FlushMetadata();
9747 74 : for (auto &poLayer : m_apoLayers)
9748 : {
9749 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9750 38 : abAddTriggers.push_back(poLayer->GetAddOGRFeatureCountTriggers());
9751 38 : abTriggersDeletedInTransaction.push_back(
9752 38 : poLayer->GetOGRFeatureCountTriggersDeletedInTransaction());
9753 38 : poLayer->SetAddOGRFeatureCountTriggers(false);
9754 : #endif
9755 38 : poLayer->DoJobAtTransactionRollback();
9756 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9757 38 : poLayer->DisableFeatureCount();
9758 : #endif
9759 : }
9760 : }
9761 :
9762 37 : const OGRErr eErr = OGRSQLiteBaseDataSource::RollbackTransaction();
9763 :
9764 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9765 37 : if (!abAddTriggers.empty())
9766 : {
9767 72 : for (size_t i = 0; i < m_apoLayers.size(); ++i)
9768 : {
9769 38 : auto &poLayer = m_apoLayers[i];
9770 38 : if (abTriggersDeletedInTransaction[i])
9771 : {
9772 7 : poLayer->SetOGRFeatureCountTriggersEnabled(true);
9773 : }
9774 : else
9775 : {
9776 31 : poLayer->SetAddOGRFeatureCountTriggers(abAddTriggers[i]);
9777 : }
9778 : }
9779 : }
9780 : #endif
9781 74 : return eErr;
9782 : }
9783 :
9784 : /************************************************************************/
9785 : /* GetGeometryTypeString() */
9786 : /************************************************************************/
9787 :
9788 : const char *
9789 1931 : GDALGeoPackageDataset::GetGeometryTypeString(OGRwkbGeometryType eType)
9790 : {
9791 1931 : const char *pszGPKGGeomType = OGRToOGCGeomType(eType);
9792 1943 : if (EQUAL(pszGPKGGeomType, "GEOMETRYCOLLECTION") &&
9793 12 : CPLTestBool(CPLGetConfigOption("OGR_GPKG_GEOMCOLLECTION", "NO")))
9794 : {
9795 0 : pszGPKGGeomType = "GEOMCOLLECTION";
9796 : }
9797 1931 : return pszGPKGGeomType;
9798 : }
9799 :
9800 : /************************************************************************/
9801 : /* GetFieldDomainNames() */
9802 : /************************************************************************/
9803 :
9804 : std::vector<std::string>
9805 18 : GDALGeoPackageDataset::GetFieldDomainNames(CSLConstList) const
9806 : {
9807 18 : if (!HasDataColumnConstraintsTable())
9808 3 : return std::vector<std::string>();
9809 :
9810 30 : std::vector<std::string> oDomainNamesList;
9811 :
9812 15 : std::unique_ptr<SQLResult> oResultTable;
9813 : {
9814 : std::string osSQL =
9815 : "SELECT DISTINCT constraint_name "
9816 : "FROM gpkg_data_column_constraints "
9817 : "WHERE constraint_name NOT LIKE '_%_domain_description' "
9818 : "ORDER BY constraint_name "
9819 15 : "LIMIT 10000" // to avoid denial of service
9820 : ;
9821 15 : oResultTable = SQLQuery(hDB, osSQL.c_str());
9822 15 : if (!oResultTable)
9823 0 : return oDomainNamesList;
9824 : }
9825 :
9826 15 : if (oResultTable->RowCount() == 10000)
9827 : {
9828 0 : CPLError(CE_Warning, CPLE_AppDefined,
9829 : "Number of rows returned for field domain names has been "
9830 : "truncated.");
9831 : }
9832 15 : else if (oResultTable->RowCount() > 0)
9833 : {
9834 14 : oDomainNamesList.reserve(oResultTable->RowCount());
9835 147 : for (int i = 0; i < oResultTable->RowCount(); i++)
9836 : {
9837 133 : const char *pszConstraintName = oResultTable->GetValue(0, i);
9838 133 : if (!pszConstraintName)
9839 0 : continue;
9840 :
9841 133 : oDomainNamesList.emplace_back(pszConstraintName);
9842 : }
9843 : }
9844 :
9845 15 : return oDomainNamesList;
9846 : }
9847 :
9848 : /************************************************************************/
9849 : /* GetFieldDomain() */
9850 : /************************************************************************/
9851 :
9852 : const OGRFieldDomain *
9853 140 : GDALGeoPackageDataset::GetFieldDomain(const std::string &name) const
9854 : {
9855 140 : const auto baseRet = GDALDataset::GetFieldDomain(name);
9856 140 : if (baseRet)
9857 43 : return baseRet;
9858 :
9859 97 : if (!HasDataColumnConstraintsTable())
9860 4 : return nullptr;
9861 :
9862 93 : const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
9863 93 : const char *min_is_inclusive =
9864 93 : bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
9865 93 : const char *max_is_inclusive =
9866 93 : bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
9867 :
9868 93 : std::unique_ptr<SQLResult> oResultTable;
9869 : // Note: for coded domains, we use a little trick by using a dummy
9870 : // _{domainname}_domain_description enum that has a single entry whose
9871 : // description is the description of the main domain.
9872 : {
9873 93 : char *pszSQL = sqlite3_mprintf(
9874 : "SELECT constraint_type, value, min, %s, "
9875 : "max, %s, description, constraint_name "
9876 : "FROM gpkg_data_column_constraints "
9877 : "WHERE constraint_name IN ('%q', "
9878 : "'_%q_domain_description') "
9879 : "AND length(constraint_type) < 100 " // to
9880 : // avoid
9881 : // denial
9882 : // of
9883 : // service
9884 : "AND (value IS NULL OR length(value) < "
9885 : "10000) " // to avoid denial
9886 : // of service
9887 : "AND (description IS NULL OR "
9888 : "length(description) < 10000) " // to
9889 : // avoid
9890 : // denial
9891 : // of
9892 : // service
9893 : "ORDER BY value "
9894 : "LIMIT 10000", // to avoid denial of
9895 : // service
9896 : min_is_inclusive, max_is_inclusive, name.c_str(), name.c_str());
9897 93 : oResultTable = SQLQuery(hDB, pszSQL);
9898 93 : sqlite3_free(pszSQL);
9899 93 : if (!oResultTable)
9900 0 : return nullptr;
9901 : }
9902 93 : if (oResultTable->RowCount() == 0)
9903 : {
9904 33 : return nullptr;
9905 : }
9906 60 : if (oResultTable->RowCount() == 10000)
9907 : {
9908 0 : CPLError(CE_Warning, CPLE_AppDefined,
9909 : "Number of rows returned for field domain %s has been "
9910 : "truncated.",
9911 : name.c_str());
9912 : }
9913 :
9914 : // Try to find the field domain data type from fields that implement it
9915 60 : int nFieldType = -1;
9916 60 : OGRFieldSubType eSubType = OFSTNone;
9917 60 : if (HasDataColumnsTable())
9918 : {
9919 55 : char *pszSQL = sqlite3_mprintf(
9920 : "SELECT table_name, column_name FROM gpkg_data_columns WHERE "
9921 : "constraint_name = '%q' LIMIT 10",
9922 : name.c_str());
9923 110 : auto oResultTable2 = SQLQuery(hDB, pszSQL);
9924 55 : sqlite3_free(pszSQL);
9925 55 : if (oResultTable2 && oResultTable2->RowCount() >= 1)
9926 : {
9927 62 : for (int iRecord = 0; iRecord < oResultTable2->RowCount();
9928 : iRecord++)
9929 : {
9930 31 : const char *pszTableName = oResultTable2->GetValue(0, iRecord);
9931 31 : const char *pszColumnName = oResultTable2->GetValue(1, iRecord);
9932 31 : if (pszTableName == nullptr || pszColumnName == nullptr)
9933 0 : continue;
9934 : OGRLayer *poLayer =
9935 62 : const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
9936 31 : pszTableName);
9937 31 : if (poLayer)
9938 : {
9939 31 : const auto poFDefn = poLayer->GetLayerDefn();
9940 31 : int nIdx = poFDefn->GetFieldIndex(pszColumnName);
9941 31 : if (nIdx >= 0)
9942 : {
9943 31 : const auto poFieldDefn = poFDefn->GetFieldDefn(nIdx);
9944 31 : const auto eType = poFieldDefn->GetType();
9945 31 : if (nFieldType < 0)
9946 : {
9947 31 : nFieldType = eType;
9948 31 : eSubType = poFieldDefn->GetSubType();
9949 : }
9950 0 : else if ((eType == OFTInteger64 || eType == OFTReal) &&
9951 : nFieldType == OFTInteger)
9952 : {
9953 : // ok
9954 : }
9955 0 : else if (eType == OFTInteger &&
9956 0 : (nFieldType == OFTInteger64 ||
9957 : nFieldType == OFTReal))
9958 : {
9959 0 : nFieldType = OFTInteger;
9960 0 : eSubType = OFSTNone;
9961 : }
9962 0 : else if (nFieldType != eType)
9963 : {
9964 0 : nFieldType = -1;
9965 0 : eSubType = OFSTNone;
9966 0 : break;
9967 : }
9968 : }
9969 : }
9970 : }
9971 : }
9972 : }
9973 :
9974 60 : std::unique_ptr<OGRFieldDomain> poDomain;
9975 120 : std::vector<OGRCodedValue> asValues;
9976 60 : bool error = false;
9977 120 : CPLString osLastConstraintType;
9978 60 : int nFieldTypeFromEnumCode = -1;
9979 120 : std::string osConstraintDescription;
9980 120 : std::string osDescrConstraintName("_");
9981 60 : osDescrConstraintName += name;
9982 60 : osDescrConstraintName += "_domain_description";
9983 151 : for (int iRecord = 0; iRecord < oResultTable->RowCount(); iRecord++)
9984 : {
9985 95 : const char *pszConstraintType = oResultTable->GetValue(0, iRecord);
9986 95 : if (pszConstraintType == nullptr)
9987 2 : continue;
9988 95 : const char *pszValue = oResultTable->GetValue(1, iRecord);
9989 95 : const char *pszMin = oResultTable->GetValue(2, iRecord);
9990 : const bool bIsMinIncluded =
9991 95 : oResultTable->GetValueAsInteger(3, iRecord) == 1;
9992 95 : const char *pszMax = oResultTable->GetValue(4, iRecord);
9993 : const bool bIsMaxIncluded =
9994 95 : oResultTable->GetValueAsInteger(5, iRecord) == 1;
9995 95 : const char *pszDescription = oResultTable->GetValue(6, iRecord);
9996 95 : const char *pszConstraintName = oResultTable->GetValue(7, iRecord);
9997 :
9998 95 : if (!osLastConstraintType.empty() && osLastConstraintType != "enum")
9999 : {
10000 1 : CPLError(CE_Failure, CPLE_AppDefined,
10001 : "Only constraint of type 'enum' can have multiple rows");
10002 1 : error = true;
10003 4 : break;
10004 : }
10005 :
10006 94 : if (strcmp(pszConstraintType, "enum") == 0)
10007 : {
10008 67 : if (pszValue == nullptr)
10009 : {
10010 1 : CPLError(CE_Failure, CPLE_AppDefined,
10011 : "NULL in 'value' column of enumeration");
10012 1 : error = true;
10013 1 : break;
10014 : }
10015 66 : if (osDescrConstraintName == pszConstraintName)
10016 : {
10017 2 : if (pszDescription)
10018 : {
10019 2 : osConstraintDescription = pszDescription;
10020 : }
10021 2 : continue;
10022 : }
10023 64 : if (asValues.empty())
10024 : {
10025 32 : asValues.reserve(oResultTable->RowCount() + 1);
10026 : }
10027 : OGRCodedValue cv;
10028 : // intended: the 'value' column in GPKG is actually the code
10029 64 : cv.pszCode = VSI_STRDUP_VERBOSE(pszValue);
10030 64 : if (cv.pszCode == nullptr)
10031 : {
10032 0 : error = true;
10033 0 : break;
10034 : }
10035 64 : if (pszDescription)
10036 : {
10037 50 : cv.pszValue = VSI_STRDUP_VERBOSE(pszDescription);
10038 50 : if (cv.pszValue == nullptr)
10039 : {
10040 0 : VSIFree(cv.pszCode);
10041 0 : error = true;
10042 0 : break;
10043 : }
10044 : }
10045 : else
10046 : {
10047 14 : cv.pszValue = nullptr;
10048 : }
10049 :
10050 : // If we can't get the data type from field definition, guess it
10051 : // from code.
10052 64 : if (nFieldType < 0 && nFieldTypeFromEnumCode != OFTString)
10053 : {
10054 36 : switch (CPLGetValueType(cv.pszCode))
10055 : {
10056 26 : case CPL_VALUE_INTEGER:
10057 : {
10058 26 : if (nFieldTypeFromEnumCode != OFTReal &&
10059 : nFieldTypeFromEnumCode != OFTInteger64)
10060 : {
10061 18 : const auto nVal = CPLAtoGIntBig(cv.pszCode);
10062 34 : if (nVal < std::numeric_limits<int>::min() ||
10063 16 : nVal > std::numeric_limits<int>::max())
10064 : {
10065 6 : nFieldTypeFromEnumCode = OFTInteger64;
10066 : }
10067 : else
10068 : {
10069 12 : nFieldTypeFromEnumCode = OFTInteger;
10070 : }
10071 : }
10072 26 : break;
10073 : }
10074 :
10075 6 : case CPL_VALUE_REAL:
10076 6 : nFieldTypeFromEnumCode = OFTReal;
10077 6 : break;
10078 :
10079 4 : case CPL_VALUE_STRING:
10080 4 : nFieldTypeFromEnumCode = OFTString;
10081 4 : break;
10082 : }
10083 : }
10084 :
10085 64 : asValues.emplace_back(cv);
10086 : }
10087 27 : else if (strcmp(pszConstraintType, "range") == 0)
10088 : {
10089 : OGRField sMin;
10090 : OGRField sMax;
10091 20 : OGR_RawField_SetUnset(&sMin);
10092 20 : OGR_RawField_SetUnset(&sMax);
10093 20 : if (nFieldType != OFTInteger && nFieldType != OFTInteger64)
10094 11 : nFieldType = OFTReal;
10095 39 : if (pszMin != nullptr &&
10096 19 : CPLAtof(pszMin) != -std::numeric_limits<double>::infinity())
10097 : {
10098 15 : if (nFieldType == OFTInteger)
10099 6 : sMin.Integer = atoi(pszMin);
10100 9 : else if (nFieldType == OFTInteger64)
10101 3 : sMin.Integer64 = CPLAtoGIntBig(pszMin);
10102 : else /* if( nFieldType == OFTReal ) */
10103 6 : sMin.Real = CPLAtof(pszMin);
10104 : }
10105 39 : if (pszMax != nullptr &&
10106 19 : CPLAtof(pszMax) != std::numeric_limits<double>::infinity())
10107 : {
10108 15 : if (nFieldType == OFTInteger)
10109 6 : sMax.Integer = atoi(pszMax);
10110 9 : else if (nFieldType == OFTInteger64)
10111 3 : sMax.Integer64 = CPLAtoGIntBig(pszMax);
10112 : else /* if( nFieldType == OFTReal ) */
10113 6 : sMax.Real = CPLAtof(pszMax);
10114 : }
10115 20 : poDomain = std::make_unique<OGRRangeFieldDomain>(
10116 20 : name, pszDescription ? pszDescription : "",
10117 40 : static_cast<OGRFieldType>(nFieldType), eSubType, sMin,
10118 20 : bIsMinIncluded, sMax, bIsMaxIncluded);
10119 : }
10120 7 : else if (strcmp(pszConstraintType, "glob") == 0)
10121 : {
10122 6 : if (pszValue == nullptr)
10123 : {
10124 1 : CPLError(CE_Failure, CPLE_AppDefined,
10125 : "NULL in 'value' column of glob");
10126 1 : error = true;
10127 1 : break;
10128 : }
10129 5 : if (nFieldType < 0)
10130 1 : nFieldType = OFTString;
10131 5 : poDomain = std::make_unique<OGRGlobFieldDomain>(
10132 5 : name, pszDescription ? pszDescription : "",
10133 15 : static_cast<OGRFieldType>(nFieldType), eSubType, pszValue);
10134 : }
10135 : else
10136 : {
10137 1 : CPLError(CE_Failure, CPLE_AppDefined,
10138 : "Unhandled constraint_type: %s", pszConstraintType);
10139 1 : error = true;
10140 1 : break;
10141 : }
10142 :
10143 89 : osLastConstraintType = pszConstraintType;
10144 : }
10145 :
10146 60 : if (!asValues.empty())
10147 : {
10148 32 : if (nFieldType < 0)
10149 18 : nFieldType = nFieldTypeFromEnumCode;
10150 32 : poDomain = std::make_unique<OGRCodedFieldDomain>(
10151 : name, osConstraintDescription,
10152 64 : static_cast<OGRFieldType>(nFieldType), eSubType,
10153 64 : std::move(asValues));
10154 : }
10155 :
10156 60 : if (error)
10157 : {
10158 4 : return nullptr;
10159 : }
10160 :
10161 56 : m_oMapFieldDomains[name] = std::move(poDomain);
10162 56 : return GDALDataset::GetFieldDomain(name);
10163 : }
10164 :
10165 : /************************************************************************/
10166 : /* AddFieldDomain() */
10167 : /************************************************************************/
10168 :
10169 19 : bool GDALGeoPackageDataset::AddFieldDomain(
10170 : std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
10171 : {
10172 38 : const std::string domainName(domain->GetName());
10173 19 : if (!GetUpdate())
10174 : {
10175 0 : CPLError(CE_Failure, CPLE_NotSupported,
10176 : "AddFieldDomain() not supported on read-only dataset");
10177 0 : return false;
10178 : }
10179 19 : if (GetFieldDomain(domainName) != nullptr)
10180 : {
10181 1 : failureReason = "A domain of identical name already exists";
10182 1 : return false;
10183 : }
10184 18 : if (!CreateColumnsTableAndColumnConstraintsTablesIfNecessary())
10185 0 : return false;
10186 :
10187 18 : const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
10188 18 : const char *min_is_inclusive =
10189 18 : bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
10190 18 : const char *max_is_inclusive =
10191 18 : bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
10192 :
10193 18 : const auto &osDescription = domain->GetDescription();
10194 18 : switch (domain->GetDomainType())
10195 : {
10196 11 : case OFDT_CODED:
10197 : {
10198 : const auto poCodedDomain =
10199 11 : cpl::down_cast<const OGRCodedFieldDomain *>(domain.get());
10200 11 : if (!osDescription.empty())
10201 : {
10202 : // We use a little trick by using a dummy
10203 : // _{domainname}_domain_description enum that has a single
10204 : // entry whose description is the description of the main
10205 : // domain.
10206 1 : char *pszSQL = sqlite3_mprintf(
10207 : "INSERT INTO gpkg_data_column_constraints ("
10208 : "constraint_name, constraint_type, value, "
10209 : "min, %s, max, %s, "
10210 : "description) VALUES ("
10211 : "'_%q_domain_description', 'enum', '', NULL, NULL, NULL, "
10212 : "NULL, %Q)",
10213 : min_is_inclusive, max_is_inclusive, domainName.c_str(),
10214 : osDescription.c_str());
10215 1 : CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
10216 1 : sqlite3_free(pszSQL);
10217 : }
10218 11 : const auto &enumeration = poCodedDomain->GetEnumeration();
10219 33 : for (int i = 0; enumeration[i].pszCode != nullptr; ++i)
10220 : {
10221 22 : char *pszSQL = sqlite3_mprintf(
10222 : "INSERT INTO gpkg_data_column_constraints ("
10223 : "constraint_name, constraint_type, value, "
10224 : "min, %s, max, %s, "
10225 : "description) VALUES ("
10226 : "'%q', 'enum', '%q', NULL, NULL, NULL, NULL, %Q)",
10227 : min_is_inclusive, max_is_inclusive, domainName.c_str(),
10228 22 : enumeration[i].pszCode, enumeration[i].pszValue);
10229 22 : bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
10230 22 : sqlite3_free(pszSQL);
10231 22 : if (!ok)
10232 0 : return false;
10233 : }
10234 11 : break;
10235 : }
10236 :
10237 6 : case OFDT_RANGE:
10238 : {
10239 : const auto poRangeDomain =
10240 6 : cpl::down_cast<const OGRRangeFieldDomain *>(domain.get());
10241 6 : const auto eFieldType = poRangeDomain->GetFieldType();
10242 6 : if (eFieldType != OFTInteger && eFieldType != OFTInteger64 &&
10243 : eFieldType != OFTReal)
10244 : {
10245 : failureReason = "Only range domains of numeric type are "
10246 0 : "supported in GeoPackage";
10247 0 : return false;
10248 : }
10249 :
10250 6 : double dfMin = -std::numeric_limits<double>::infinity();
10251 6 : double dfMax = std::numeric_limits<double>::infinity();
10252 6 : bool bMinIsInclusive = true;
10253 6 : const auto &sMin = poRangeDomain->GetMin(bMinIsInclusive);
10254 6 : bool bMaxIsInclusive = true;
10255 6 : const auto &sMax = poRangeDomain->GetMax(bMaxIsInclusive);
10256 6 : if (eFieldType == OFTInteger)
10257 : {
10258 2 : if (!OGR_RawField_IsUnset(&sMin))
10259 2 : dfMin = sMin.Integer;
10260 2 : if (!OGR_RawField_IsUnset(&sMax))
10261 2 : dfMax = sMax.Integer;
10262 : }
10263 4 : else if (eFieldType == OFTInteger64)
10264 : {
10265 1 : if (!OGR_RawField_IsUnset(&sMin))
10266 1 : dfMin = static_cast<double>(sMin.Integer64);
10267 1 : if (!OGR_RawField_IsUnset(&sMax))
10268 1 : dfMax = static_cast<double>(sMax.Integer64);
10269 : }
10270 : else /* if( eFieldType == OFTReal ) */
10271 : {
10272 3 : if (!OGR_RawField_IsUnset(&sMin))
10273 3 : dfMin = sMin.Real;
10274 3 : if (!OGR_RawField_IsUnset(&sMax))
10275 3 : dfMax = sMax.Real;
10276 : }
10277 :
10278 6 : sqlite3_stmt *hInsertStmt = nullptr;
10279 : const char *pszSQL =
10280 6 : CPLSPrintf("INSERT INTO gpkg_data_column_constraints ("
10281 : "constraint_name, constraint_type, value, "
10282 : "min, %s, max, %s, "
10283 : "description) VALUES ("
10284 : "?, 'range', NULL, ?, ?, ?, ?, ?)",
10285 : min_is_inclusive, max_is_inclusive);
10286 6 : if (SQLPrepareWithError(hDB, pszSQL, -1, &hInsertStmt, nullptr) !=
10287 : SQLITE_OK)
10288 : {
10289 0 : return false;
10290 : }
10291 6 : sqlite3_bind_text(hInsertStmt, 1, domainName.c_str(),
10292 6 : static_cast<int>(domainName.size()),
10293 : SQLITE_TRANSIENT);
10294 6 : sqlite3_bind_double(hInsertStmt, 2, dfMin);
10295 6 : sqlite3_bind_int(hInsertStmt, 3, bMinIsInclusive ? 1 : 0);
10296 6 : sqlite3_bind_double(hInsertStmt, 4, dfMax);
10297 6 : sqlite3_bind_int(hInsertStmt, 5, bMaxIsInclusive ? 1 : 0);
10298 6 : if (osDescription.empty())
10299 : {
10300 3 : sqlite3_bind_null(hInsertStmt, 6);
10301 : }
10302 : else
10303 : {
10304 3 : sqlite3_bind_text(hInsertStmt, 6, osDescription.c_str(),
10305 3 : static_cast<int>(osDescription.size()),
10306 : SQLITE_TRANSIENT);
10307 : }
10308 6 : const int sqlite_err = sqlite3_step(hInsertStmt);
10309 6 : sqlite3_finalize(hInsertStmt);
10310 6 : if (sqlite_err != SQLITE_OK && sqlite_err != SQLITE_DONE)
10311 : {
10312 0 : CPLError(CE_Failure, CPLE_AppDefined,
10313 : "failed to execute insertion '%s': %s", pszSQL,
10314 : sqlite3_errmsg(hDB));
10315 0 : return false;
10316 : }
10317 :
10318 6 : break;
10319 : }
10320 :
10321 1 : case OFDT_GLOB:
10322 : {
10323 : const auto poGlobDomain =
10324 1 : cpl::down_cast<const OGRGlobFieldDomain *>(domain.get());
10325 2 : char *pszSQL = sqlite3_mprintf(
10326 : "INSERT INTO gpkg_data_column_constraints ("
10327 : "constraint_name, constraint_type, value, "
10328 : "min, %s, max, %s, "
10329 : "description) VALUES ("
10330 : "'%q', 'glob', '%q', NULL, NULL, NULL, NULL, %Q)",
10331 : min_is_inclusive, max_is_inclusive, domainName.c_str(),
10332 1 : poGlobDomain->GetGlob().c_str(),
10333 2 : osDescription.empty() ? nullptr : osDescription.c_str());
10334 1 : bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
10335 1 : sqlite3_free(pszSQL);
10336 1 : if (!ok)
10337 0 : return false;
10338 :
10339 1 : break;
10340 : }
10341 : }
10342 :
10343 18 : m_oMapFieldDomains[domainName] = std::move(domain);
10344 18 : return true;
10345 : }
10346 :
10347 : /************************************************************************/
10348 : /* UpdateFieldDomain() */
10349 : /************************************************************************/
10350 :
10351 3 : bool GDALGeoPackageDataset::UpdateFieldDomain(
10352 : std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
10353 : {
10354 6 : const std::string domainName(domain->GetName());
10355 3 : if (eAccess != GA_Update)
10356 : {
10357 1 : CPLError(CE_Failure, CPLE_NotSupported,
10358 : "UpdateFieldDomain() not supported on read-only dataset");
10359 1 : return false;
10360 : }
10361 :
10362 2 : if (GetFieldDomain(domainName) == nullptr)
10363 : {
10364 1 : failureReason = "The domain should already exist to be updated";
10365 1 : return false;
10366 : }
10367 :
10368 1 : bool bRet = SoftStartTransaction() == OGRERR_NONE;
10369 1 : if (bRet)
10370 : {
10371 2 : bRet = DeleteFieldDomain(domainName, failureReason) &&
10372 1 : AddFieldDomain(std::move(domain), failureReason);
10373 1 : if (bRet)
10374 1 : bRet = SoftCommitTransaction() == OGRERR_NONE;
10375 : else
10376 0 : SoftRollbackTransaction();
10377 : }
10378 1 : return bRet;
10379 : }
10380 :
10381 : /************************************************************************/
10382 : /* DeleteFieldDomain() */
10383 : /************************************************************************/
10384 :
10385 18 : bool GDALGeoPackageDataset::DeleteFieldDomain(const std::string &name,
10386 : std::string &failureReason)
10387 : {
10388 18 : if (eAccess != GA_Update)
10389 : {
10390 1 : CPLError(CE_Failure, CPLE_NotSupported,
10391 : "DeleteFieldDomain() not supported on read-only dataset");
10392 1 : return false;
10393 : }
10394 17 : if (GetFieldDomain(name) == nullptr)
10395 : {
10396 1 : failureReason = "Domain does not exist";
10397 1 : return false;
10398 : }
10399 :
10400 : char *pszSQL =
10401 16 : sqlite3_mprintf("DELETE FROM gpkg_data_column_constraints WHERE "
10402 : "constraint_name IN ('%q', '_%q_domain_description')",
10403 : name.c_str(), name.c_str());
10404 16 : const bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
10405 16 : sqlite3_free(pszSQL);
10406 16 : if (ok)
10407 16 : m_oMapFieldDomains.erase(name);
10408 16 : return ok;
10409 : }
10410 :
10411 : /************************************************************************/
10412 : /* AddRelationship() */
10413 : /************************************************************************/
10414 :
10415 26 : bool GDALGeoPackageDataset::AddRelationship(
10416 : std::unique_ptr<GDALRelationship> &&relationship,
10417 : std::string &failureReason)
10418 : {
10419 26 : if (!GetUpdate())
10420 : {
10421 0 : CPLError(CE_Failure, CPLE_NotSupported,
10422 : "AddRelationship() not supported on read-only dataset");
10423 0 : return false;
10424 : }
10425 :
10426 : const std::string osRelationshipName = GenerateNameForRelationship(
10427 26 : relationship->GetLeftTableName().c_str(),
10428 26 : relationship->GetRightTableName().c_str(),
10429 104 : relationship->GetRelatedTableType().c_str());
10430 : // sanity checks
10431 26 : if (GetRelationship(osRelationshipName) != nullptr)
10432 : {
10433 1 : failureReason = "A relationship of identical name already exists";
10434 1 : return false;
10435 : }
10436 :
10437 25 : if (!ValidateRelationship(relationship.get(), failureReason))
10438 : {
10439 14 : return false;
10440 : }
10441 :
10442 75 : for (auto &poLayer : m_apoLayers)
10443 : {
10444 64 : if (poLayer->SyncToDisk() != OGRERR_NONE)
10445 0 : return false;
10446 : }
10447 :
10448 11 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
10449 : {
10450 0 : return false;
10451 : }
10452 11 : if (!CreateRelationsTableIfNecessary())
10453 : {
10454 0 : failureReason = "Could not create gpkgext_relations table";
10455 0 : return false;
10456 : }
10457 11 : if (SQLGetInteger(GetDB(),
10458 : "SELECT 1 FROM gpkg_extensions WHERE "
10459 : "table_name = 'gpkgext_relations'",
10460 11 : nullptr) != 1)
10461 : {
10462 5 : if (OGRERR_NONE !=
10463 5 : SQLCommand(
10464 : GetDB(),
10465 : "INSERT INTO gpkg_extensions "
10466 : "(table_name,column_name,extension_name,definition,scope) "
10467 : "VALUES ('gpkgext_relations', NULL, 'gpkg_related_tables', "
10468 : "'http://www.geopackage.org/18-000.html', "
10469 : "'read-write')"))
10470 : {
10471 : failureReason =
10472 0 : "Could not create gpkg_extensions entry for gpkgext_relations";
10473 0 : return false;
10474 : }
10475 : }
10476 :
10477 11 : const std::string &osLeftTableName = relationship->GetLeftTableName();
10478 11 : const std::string &osRightTableName = relationship->GetRightTableName();
10479 11 : const auto &aosLeftTableFields = relationship->GetLeftTableFields();
10480 11 : const auto &aosRightTableFields = relationship->GetRightTableFields();
10481 :
10482 22 : std::string osRelatedTableType = relationship->GetRelatedTableType();
10483 11 : if (osRelatedTableType.empty())
10484 : {
10485 5 : osRelatedTableType = "features";
10486 : }
10487 :
10488 : // generate mapping table if not set
10489 22 : CPLString osMappingTableName = relationship->GetMappingTableName();
10490 11 : if (osMappingTableName.empty())
10491 : {
10492 5 : int nIndex = 1;
10493 5 : osMappingTableName = osLeftTableName + "_" + osRightTableName;
10494 5 : while (FindLayerIndex(osMappingTableName.c_str()) >= 0)
10495 : {
10496 0 : nIndex += 1;
10497 : osMappingTableName.Printf("%s_%s_%d", osLeftTableName.c_str(),
10498 0 : osRightTableName.c_str(), nIndex);
10499 : }
10500 :
10501 : // determine whether base/related keys are unique
10502 5 : bool bBaseKeyIsUnique = false;
10503 : {
10504 : const std::set<std::string> uniqueBaseFieldsUC =
10505 : SQLGetUniqueFieldUCConstraints(GetDB(),
10506 10 : osLeftTableName.c_str());
10507 10 : if (uniqueBaseFieldsUC.find(
10508 5 : CPLString(aosLeftTableFields[0]).toupper()) !=
10509 10 : uniqueBaseFieldsUC.end())
10510 : {
10511 2 : bBaseKeyIsUnique = true;
10512 : }
10513 : }
10514 5 : bool bRelatedKeyIsUnique = false;
10515 : {
10516 : const std::set<std::string> uniqueRelatedFieldsUC =
10517 : SQLGetUniqueFieldUCConstraints(GetDB(),
10518 10 : osRightTableName.c_str());
10519 10 : if (uniqueRelatedFieldsUC.find(
10520 5 : CPLString(aosRightTableFields[0]).toupper()) !=
10521 10 : uniqueRelatedFieldsUC.end())
10522 : {
10523 2 : bRelatedKeyIsUnique = true;
10524 : }
10525 : }
10526 :
10527 : // create mapping table
10528 :
10529 5 : std::string osBaseIdDefinition = "base_id INTEGER";
10530 5 : if (bBaseKeyIsUnique)
10531 : {
10532 2 : char *pszSQL = sqlite3_mprintf(
10533 : " CONSTRAINT 'fk_base_id_%q' REFERENCES \"%w\"(\"%w\") ON "
10534 : "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
10535 : "DEFERRED",
10536 : osMappingTableName.c_str(), osLeftTableName.c_str(),
10537 2 : aosLeftTableFields[0].c_str());
10538 2 : osBaseIdDefinition += pszSQL;
10539 2 : sqlite3_free(pszSQL);
10540 : }
10541 :
10542 5 : std::string osRelatedIdDefinition = "related_id INTEGER";
10543 5 : if (bRelatedKeyIsUnique)
10544 : {
10545 2 : char *pszSQL = sqlite3_mprintf(
10546 : " CONSTRAINT 'fk_related_id_%q' REFERENCES \"%w\"(\"%w\") ON "
10547 : "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
10548 : "DEFERRED",
10549 : osMappingTableName.c_str(), osRightTableName.c_str(),
10550 2 : aosRightTableFields[0].c_str());
10551 2 : osRelatedIdDefinition += pszSQL;
10552 2 : sqlite3_free(pszSQL);
10553 : }
10554 :
10555 5 : char *pszSQL = sqlite3_mprintf("CREATE TABLE \"%w\" ("
10556 : "id INTEGER PRIMARY KEY AUTOINCREMENT, "
10557 : "%s, %s);",
10558 : osMappingTableName.c_str(),
10559 : osBaseIdDefinition.c_str(),
10560 : osRelatedIdDefinition.c_str());
10561 5 : OGRErr eErr = SQLCommand(hDB, pszSQL);
10562 5 : sqlite3_free(pszSQL);
10563 5 : if (eErr != OGRERR_NONE)
10564 : {
10565 : failureReason =
10566 0 : ("Could not create mapping table " + osMappingTableName)
10567 0 : .c_str();
10568 0 : return false;
10569 : }
10570 :
10571 : /*
10572 : * Strictly speaking we should NOT be inserting the mapping table into gpkg_contents.
10573 : * The related tables extension explicitly states that the mapping table should only be
10574 : * in the gpkgext_relations table and not in gpkg_contents. (See also discussion at
10575 : * https://github.com/opengeospatial/geopackage/issues/679).
10576 : *
10577 : * However, if we don't insert the mapping table into gpkg_contents then it is no longer
10578 : * visible to some clients (eg ESRI software only allows opening tables that are present
10579 : * in gpkg_contents). So we'll do this anyway, for maximum compatibility and flexibility.
10580 : *
10581 : * More related discussion is at https://github.com/OSGeo/gdal/pull/9258
10582 : */
10583 5 : pszSQL = sqlite3_mprintf(
10584 : "INSERT INTO gpkg_contents "
10585 : "(table_name,data_type,identifier,description,last_change,srs_id) "
10586 : "VALUES "
10587 : "('%q','attributes','%q','Mapping table for relationship between "
10588 : "%q and %q',%s,0)",
10589 : osMappingTableName.c_str(), /*table_name*/
10590 : osMappingTableName.c_str(), /*identifier*/
10591 : osLeftTableName.c_str(), /*description left table name*/
10592 : osRightTableName.c_str(), /*description right table name*/
10593 10 : GDALGeoPackageDataset::GetCurrentDateEscapedSQL().c_str());
10594 :
10595 : // Note -- we explicitly ignore failures here, because hey, we aren't really
10596 : // supposed to be adding this table to gpkg_contents anyway!
10597 5 : (void)SQLCommand(hDB, pszSQL);
10598 5 : sqlite3_free(pszSQL);
10599 :
10600 5 : pszSQL = sqlite3_mprintf(
10601 : "CREATE INDEX \"idx_%w_base_id\" ON \"%w\" (base_id);",
10602 : osMappingTableName.c_str(), osMappingTableName.c_str());
10603 5 : eErr = SQLCommand(hDB, pszSQL);
10604 5 : sqlite3_free(pszSQL);
10605 5 : if (eErr != OGRERR_NONE)
10606 : {
10607 0 : failureReason = ("Could not create index for " +
10608 0 : osMappingTableName + " (base_id)")
10609 0 : .c_str();
10610 0 : return false;
10611 : }
10612 :
10613 5 : pszSQL = sqlite3_mprintf(
10614 : "CREATE INDEX \"idx_%qw_related_id\" ON \"%w\" (related_id);",
10615 : osMappingTableName.c_str(), osMappingTableName.c_str());
10616 5 : eErr = SQLCommand(hDB, pszSQL);
10617 5 : sqlite3_free(pszSQL);
10618 5 : if (eErr != OGRERR_NONE)
10619 : {
10620 0 : failureReason = ("Could not create index for " +
10621 0 : osMappingTableName + " (related_id)")
10622 0 : .c_str();
10623 0 : return false;
10624 : }
10625 :
10626 : auto poLayer = std::make_unique<OGRGeoPackageTableLayer>(
10627 10 : this, osMappingTableName.c_str());
10628 5 : poLayer->SetOpeningParameters(osMappingTableName.c_str(), "table",
10629 : /* bIsInGpkgContents = */ true,
10630 : /* bIsSpatial = */ false,
10631 : /* pszGeomColName =*/nullptr,
10632 : /* pszGeomType =*/nullptr,
10633 : /* bHasZ = */ false, /* bHasM = */ false);
10634 5 : m_apoLayers.push_back(std::move(poLayer));
10635 : }
10636 : else
10637 : {
10638 : // validate mapping table structure
10639 6 : if (OGRGeoPackageTableLayer *poLayer =
10640 6 : cpl::down_cast<OGRGeoPackageTableLayer *>(
10641 6 : GetLayerByName(osMappingTableName)))
10642 : {
10643 4 : if (poLayer->GetLayerDefn()->GetFieldIndex("base_id") < 0)
10644 : {
10645 : failureReason =
10646 2 : ("Field base_id must exist in " + osMappingTableName)
10647 1 : .c_str();
10648 1 : return false;
10649 : }
10650 3 : if (poLayer->GetLayerDefn()->GetFieldIndex("related_id") < 0)
10651 : {
10652 : failureReason =
10653 2 : ("Field related_id must exist in " + osMappingTableName)
10654 1 : .c_str();
10655 1 : return false;
10656 : }
10657 : }
10658 : else
10659 : {
10660 : failureReason =
10661 2 : ("Could not retrieve table " + osMappingTableName).c_str();
10662 2 : return false;
10663 : }
10664 : }
10665 :
10666 7 : char *pszSQL = sqlite3_mprintf(
10667 : "INSERT INTO gpkg_extensions "
10668 : "(table_name,column_name,extension_name,definition,scope) "
10669 : "VALUES ('%q', NULL, 'gpkg_related_tables', "
10670 : "'http://www.geopackage.org/18-000.html', "
10671 : "'read-write')",
10672 : osMappingTableName.c_str());
10673 7 : OGRErr eErr = SQLCommand(hDB, pszSQL);
10674 7 : sqlite3_free(pszSQL);
10675 7 : if (eErr != OGRERR_NONE)
10676 : {
10677 0 : failureReason = ("Could not insert mapping table " +
10678 0 : osMappingTableName + " into gpkg_extensions")
10679 0 : .c_str();
10680 0 : return false;
10681 : }
10682 :
10683 21 : pszSQL = sqlite3_mprintf(
10684 : "INSERT INTO gpkgext_relations "
10685 : "(base_table_name,base_primary_column,related_table_name,related_"
10686 : "primary_column,relation_name,mapping_table_name) "
10687 : "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
10688 7 : osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
10689 7 : osRightTableName.c_str(), aosRightTableFields[0].c_str(),
10690 : osRelatedTableType.c_str(), osMappingTableName.c_str());
10691 7 : eErr = SQLCommand(hDB, pszSQL);
10692 7 : sqlite3_free(pszSQL);
10693 7 : if (eErr != OGRERR_NONE)
10694 : {
10695 0 : failureReason = "Could not insert relationship into gpkgext_relations";
10696 0 : return false;
10697 : }
10698 :
10699 7 : ClearCachedRelationships();
10700 7 : LoadRelationships();
10701 7 : return true;
10702 : }
10703 :
10704 : /************************************************************************/
10705 : /* DeleteRelationship() */
10706 : /************************************************************************/
10707 :
10708 4 : bool GDALGeoPackageDataset::DeleteRelationship(const std::string &name,
10709 : std::string &failureReason)
10710 : {
10711 4 : if (eAccess != GA_Update)
10712 : {
10713 0 : CPLError(CE_Failure, CPLE_NotSupported,
10714 : "DeleteRelationship() not supported on read-only dataset");
10715 0 : return false;
10716 : }
10717 :
10718 : // ensure relationships are up to date before we try to remove one
10719 4 : ClearCachedRelationships();
10720 4 : LoadRelationships();
10721 :
10722 8 : std::string osMappingTableName;
10723 : {
10724 4 : const GDALRelationship *poRelationship = GetRelationship(name);
10725 4 : if (poRelationship == nullptr)
10726 : {
10727 1 : failureReason = "Could not find relationship with name " + name;
10728 1 : return false;
10729 : }
10730 :
10731 3 : osMappingTableName = poRelationship->GetMappingTableName();
10732 : }
10733 :
10734 : // DeleteLayerCommon will delete existing relationship objects, so we can't
10735 : // refer to poRelationship or any of its members previously obtained here
10736 3 : if (DeleteLayerCommon(osMappingTableName.c_str()) != OGRERR_NONE)
10737 : {
10738 : failureReason =
10739 0 : "Could not remove mapping layer name " + osMappingTableName;
10740 :
10741 : // relationships may have been left in an inconsistent state -- reload
10742 : // them now
10743 0 : ClearCachedRelationships();
10744 0 : LoadRelationships();
10745 0 : return false;
10746 : }
10747 :
10748 3 : ClearCachedRelationships();
10749 3 : LoadRelationships();
10750 3 : return true;
10751 : }
10752 :
10753 : /************************************************************************/
10754 : /* UpdateRelationship() */
10755 : /************************************************************************/
10756 :
10757 6 : bool GDALGeoPackageDataset::UpdateRelationship(
10758 : std::unique_ptr<GDALRelationship> &&relationship,
10759 : std::string &failureReason)
10760 : {
10761 6 : if (eAccess != GA_Update)
10762 : {
10763 0 : CPLError(CE_Failure, CPLE_NotSupported,
10764 : "UpdateRelationship() not supported on read-only dataset");
10765 0 : return false;
10766 : }
10767 :
10768 : // ensure relationships are up to date before we try to update one
10769 6 : ClearCachedRelationships();
10770 6 : LoadRelationships();
10771 :
10772 6 : const std::string &osRelationshipName = relationship->GetName();
10773 6 : const std::string &osLeftTableName = relationship->GetLeftTableName();
10774 6 : const std::string &osRightTableName = relationship->GetRightTableName();
10775 6 : const std::string &osMappingTableName = relationship->GetMappingTableName();
10776 6 : const auto &aosLeftTableFields = relationship->GetLeftTableFields();
10777 6 : const auto &aosRightTableFields = relationship->GetRightTableFields();
10778 :
10779 : // sanity checks
10780 : {
10781 : const GDALRelationship *poExistingRelationship =
10782 6 : GetRelationship(osRelationshipName);
10783 6 : if (poExistingRelationship == nullptr)
10784 : {
10785 : failureReason =
10786 1 : "The relationship should already exist to be updated";
10787 1 : return false;
10788 : }
10789 :
10790 5 : if (!ValidateRelationship(relationship.get(), failureReason))
10791 : {
10792 2 : return false;
10793 : }
10794 :
10795 : // we don't permit changes to the participating tables
10796 3 : if (osLeftTableName != poExistingRelationship->GetLeftTableName())
10797 : {
10798 0 : failureReason = ("Cannot change base table from " +
10799 0 : poExistingRelationship->GetLeftTableName() +
10800 0 : " to " + osLeftTableName)
10801 0 : .c_str();
10802 0 : return false;
10803 : }
10804 3 : if (osRightTableName != poExistingRelationship->GetRightTableName())
10805 : {
10806 0 : failureReason = ("Cannot change related table from " +
10807 0 : poExistingRelationship->GetRightTableName() +
10808 0 : " to " + osRightTableName)
10809 0 : .c_str();
10810 0 : return false;
10811 : }
10812 3 : if (osMappingTableName != poExistingRelationship->GetMappingTableName())
10813 : {
10814 0 : failureReason = ("Cannot change mapping table from " +
10815 0 : poExistingRelationship->GetMappingTableName() +
10816 0 : " to " + osMappingTableName)
10817 0 : .c_str();
10818 0 : return false;
10819 : }
10820 : }
10821 :
10822 6 : std::string osRelatedTableType = relationship->GetRelatedTableType();
10823 3 : if (osRelatedTableType.empty())
10824 : {
10825 0 : osRelatedTableType = "features";
10826 : }
10827 :
10828 3 : char *pszSQL = sqlite3_mprintf(
10829 : "DELETE FROM gpkgext_relations WHERE mapping_table_name='%q'",
10830 : osMappingTableName.c_str());
10831 3 : OGRErr eErr = SQLCommand(hDB, pszSQL);
10832 3 : sqlite3_free(pszSQL);
10833 3 : if (eErr != OGRERR_NONE)
10834 : {
10835 : failureReason =
10836 0 : "Could not delete old relationship from gpkgext_relations";
10837 0 : return false;
10838 : }
10839 :
10840 9 : pszSQL = sqlite3_mprintf(
10841 : "INSERT INTO gpkgext_relations "
10842 : "(base_table_name,base_primary_column,related_table_name,related_"
10843 : "primary_column,relation_name,mapping_table_name) "
10844 : "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
10845 3 : osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
10846 3 : osRightTableName.c_str(), aosRightTableFields[0].c_str(),
10847 : osRelatedTableType.c_str(), osMappingTableName.c_str());
10848 3 : eErr = SQLCommand(hDB, pszSQL);
10849 3 : sqlite3_free(pszSQL);
10850 3 : if (eErr != OGRERR_NONE)
10851 : {
10852 : failureReason =
10853 0 : "Could not insert updated relationship into gpkgext_relations";
10854 0 : return false;
10855 : }
10856 :
10857 3 : ClearCachedRelationships();
10858 3 : LoadRelationships();
10859 3 : return true;
10860 : }
10861 :
10862 : /************************************************************************/
10863 : /* GetSqliteMasterContent() */
10864 : /************************************************************************/
10865 :
10866 : const std::vector<SQLSqliteMasterContent> &
10867 2 : GDALGeoPackageDataset::GetSqliteMasterContent()
10868 : {
10869 2 : if (m_aoSqliteMasterContent.empty())
10870 : {
10871 : auto oResultTable =
10872 2 : SQLQuery(hDB, "SELECT sql, type, tbl_name FROM sqlite_master");
10873 1 : if (oResultTable)
10874 : {
10875 58 : for (int rowCnt = 0; rowCnt < oResultTable->RowCount(); ++rowCnt)
10876 : {
10877 114 : SQLSqliteMasterContent row;
10878 57 : const char *pszSQL = oResultTable->GetValue(0, rowCnt);
10879 57 : row.osSQL = pszSQL ? pszSQL : "";
10880 57 : const char *pszType = oResultTable->GetValue(1, rowCnt);
10881 57 : row.osType = pszType ? pszType : "";
10882 57 : const char *pszTableName = oResultTable->GetValue(2, rowCnt);
10883 57 : row.osTableName = pszTableName ? pszTableName : "";
10884 57 : m_aoSqliteMasterContent.emplace_back(std::move(row));
10885 : }
10886 : }
10887 : }
10888 2 : return m_aoSqliteMasterContent;
10889 : }
|