Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GeoPackage Translator
4 : * Purpose: Implements GDALGeoPackageDataset class
5 : * Author: Paul Ramsey <pramsey@boundlessgeo.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2013, Paul Ramsey <pramsey@boundlessgeo.com>
9 : * Copyright (c) 2014, Even Rouault <even dot rouault at spatialys.com>
10 : *
11 : * SPDX-License-Identifier: MIT
12 : ****************************************************************************/
13 :
14 : #include "ogr_geopackage.h"
15 : #include "ogr_p.h"
16 : #include "ogr_swq.h"
17 : #include "gdalwarper.h"
18 : #include "gdal_utils.h"
19 : #include "ogrgeopackageutility.h"
20 : #include "ogrsqliteutility.h"
21 : #include "ogr_wkb.h"
22 : #include "vrt/vrtdataset.h"
23 :
24 : #include "tilematrixset.hpp"
25 :
26 : #include <cstdlib>
27 :
28 : #include <algorithm>
29 : #include <limits>
30 : #include <sstream>
31 :
32 : #define COMPILATION_ALLOWED
33 : #define DEFINE_OGRSQLiteSQLFunctionsSetCaseSensitiveLike
34 : #include "ogrsqlitesqlfunctionscommon.cpp"
35 :
36 : // Keep in sync prototype of those 2 functions between gdalopeninfo.cpp,
37 : // ogrsqlitedatasource.cpp and ogrgeopackagedatasource.cpp
38 : void GDALOpenInfoDeclareFileNotToOpen(const char *pszFilename,
39 : const GByte *pabyHeader,
40 : int nHeaderBytes);
41 : void GDALOpenInfoUnDeclareFileNotToOpen(const char *pszFilename);
42 :
43 : /************************************************************************/
44 : /* Tiling schemes */
45 : /************************************************************************/
46 :
47 : typedef struct
48 : {
49 : const char *pszName;
50 : int nEPSGCode;
51 : double dfMinX;
52 : double dfMaxY;
53 : int nTileXCountZoomLevel0;
54 : int nTileYCountZoomLevel0;
55 : int nTileWidth;
56 : int nTileHeight;
57 : double dfPixelXSizeZoomLevel0;
58 : double dfPixelYSizeZoomLevel0;
59 : } TilingSchemeDefinition;
60 :
61 : static const TilingSchemeDefinition asTilingSchemes[] = {
62 : /* See http://portal.opengeospatial.org/files/?artifact_id=35326 (WMTS 1.0),
63 : Annex E.3 */
64 : {"GoogleCRS84Quad", 4326, -180.0, 180.0, 1, 1, 256, 256, 360.0 / 256,
65 : 360.0 / 256},
66 :
67 : /* See global-mercator at
68 : http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
69 : {"PseudoTMS_GlobalMercator", 3857, -20037508.34, 20037508.34, 2, 2, 256,
70 : 256, 78271.516, 78271.516},
71 : };
72 :
73 : // Setting it above 30 would lead to integer overflow ((1 << 31) > INT_MAX)
74 : constexpr int MAX_ZOOM_LEVEL = 30;
75 :
76 : /************************************************************************/
77 : /* GetTilingScheme() */
78 : /************************************************************************/
79 :
80 : static std::unique_ptr<TilingSchemeDefinition>
81 562 : GetTilingScheme(const char *pszName)
82 : {
83 562 : if (EQUAL(pszName, "CUSTOM"))
84 434 : return nullptr;
85 :
86 256 : for (const auto &tilingScheme : asTilingSchemes)
87 : {
88 195 : if (EQUAL(pszName, tilingScheme.pszName))
89 : {
90 67 : return std::make_unique<TilingSchemeDefinition>(tilingScheme);
91 : }
92 : }
93 :
94 61 : if (EQUAL(pszName, "PseudoTMS_GlobalGeodetic"))
95 6 : pszName = "InspireCRS84Quad";
96 :
97 122 : auto poTM = gdal::TileMatrixSet::parse(pszName);
98 61 : if (poTM == nullptr)
99 1 : return nullptr;
100 60 : if (!poTM->haveAllLevelsSameTopLeft())
101 : {
102 0 : CPLError(CE_Failure, CPLE_NotSupported,
103 : "Unsupported tiling scheme: not all zoom levels have same top "
104 : "left corner");
105 0 : return nullptr;
106 : }
107 60 : if (!poTM->haveAllLevelsSameTileSize())
108 : {
109 0 : CPLError(CE_Failure, CPLE_NotSupported,
110 : "Unsupported tiling scheme: not all zoom levels have same "
111 : "tile size");
112 0 : return nullptr;
113 : }
114 60 : if (!poTM->hasOnlyPowerOfTwoVaryingScales())
115 : {
116 1 : CPLError(CE_Failure, CPLE_NotSupported,
117 : "Unsupported tiling scheme: resolution of consecutive zoom "
118 : "levels is not always 2");
119 1 : return nullptr;
120 : }
121 59 : if (poTM->hasVariableMatrixWidth())
122 : {
123 0 : CPLError(CE_Failure, CPLE_NotSupported,
124 : "Unsupported tiling scheme: some levels have variable matrix "
125 : "width");
126 0 : return nullptr;
127 : }
128 118 : auto poTilingScheme = std::make_unique<TilingSchemeDefinition>();
129 59 : poTilingScheme->pszName = pszName;
130 :
131 118 : OGRSpatialReference oSRS;
132 59 : if (oSRS.SetFromUserInput(poTM->crs().c_str()) != OGRERR_NONE)
133 : {
134 0 : return nullptr;
135 : }
136 59 : if (poTM->crs() == "http://www.opengis.net/def/crs/OGC/1.3/CRS84")
137 : {
138 6 : poTilingScheme->nEPSGCode = 4326;
139 : }
140 : else
141 : {
142 53 : const char *pszAuthName = oSRS.GetAuthorityName(nullptr);
143 53 : const char *pszAuthCode = oSRS.GetAuthorityCode(nullptr);
144 53 : if (pszAuthName == nullptr || !EQUAL(pszAuthName, "EPSG") ||
145 : pszAuthCode == nullptr)
146 : {
147 0 : CPLError(CE_Failure, CPLE_NotSupported,
148 : "Unsupported tiling scheme: only EPSG CRS supported");
149 0 : return nullptr;
150 : }
151 53 : poTilingScheme->nEPSGCode = atoi(pszAuthCode);
152 : }
153 59 : const auto &zoomLevel0 = poTM->tileMatrixList()[0];
154 59 : poTilingScheme->dfMinX = zoomLevel0.mTopLeftX;
155 59 : poTilingScheme->dfMaxY = zoomLevel0.mTopLeftY;
156 59 : poTilingScheme->nTileXCountZoomLevel0 = zoomLevel0.mMatrixWidth;
157 59 : poTilingScheme->nTileYCountZoomLevel0 = zoomLevel0.mMatrixHeight;
158 59 : poTilingScheme->nTileWidth = zoomLevel0.mTileWidth;
159 59 : poTilingScheme->nTileHeight = zoomLevel0.mTileHeight;
160 59 : poTilingScheme->dfPixelXSizeZoomLevel0 = zoomLevel0.mResX;
161 59 : poTilingScheme->dfPixelYSizeZoomLevel0 = zoomLevel0.mResY;
162 :
163 118 : const bool bInvertAxis = oSRS.EPSGTreatsAsLatLong() != FALSE ||
164 59 : oSRS.EPSGTreatsAsNorthingEasting() != FALSE;
165 59 : if (bInvertAxis)
166 : {
167 6 : std::swap(poTilingScheme->dfMinX, poTilingScheme->dfMaxY);
168 6 : std::swap(poTilingScheme->dfPixelXSizeZoomLevel0,
169 6 : poTilingScheme->dfPixelYSizeZoomLevel0);
170 : }
171 59 : return poTilingScheme;
172 : }
173 :
174 : static const char *pszCREATE_GPKG_GEOMETRY_COLUMNS =
175 : "CREATE TABLE gpkg_geometry_columns ("
176 : "table_name TEXT NOT NULL,"
177 : "column_name TEXT NOT NULL,"
178 : "geometry_type_name TEXT NOT NULL,"
179 : "srs_id INTEGER NOT NULL,"
180 : "z TINYINT NOT NULL,"
181 : "m TINYINT NOT NULL,"
182 : "CONSTRAINT pk_geom_cols PRIMARY KEY (table_name, column_name),"
183 : "CONSTRAINT uk_gc_table_name UNIQUE (table_name),"
184 : "CONSTRAINT fk_gc_tn FOREIGN KEY (table_name) REFERENCES "
185 : "gpkg_contents(table_name),"
186 : "CONSTRAINT fk_gc_srs FOREIGN KEY (srs_id) REFERENCES gpkg_spatial_ref_sys "
187 : "(srs_id)"
188 : ")";
189 :
190 846 : OGRErr GDALGeoPackageDataset::SetApplicationAndUserVersionId()
191 : {
192 846 : CPLAssert(hDB != nullptr);
193 :
194 846 : const CPLString osPragma(CPLString().Printf("PRAGMA application_id = %u;"
195 : "PRAGMA user_version = %u",
196 : m_nApplicationId,
197 1692 : m_nUserVersion));
198 1692 : return SQLCommand(hDB, osPragma.c_str());
199 : }
200 :
201 2384 : bool GDALGeoPackageDataset::CloseDB()
202 : {
203 2384 : OGRSQLiteUnregisterSQLFunctions(m_pSQLFunctionData);
204 2384 : m_pSQLFunctionData = nullptr;
205 2384 : return OGRSQLiteBaseDataSource::CloseDB();
206 : }
207 :
208 11 : bool GDALGeoPackageDataset::ReOpenDB()
209 : {
210 11 : CPLAssert(hDB != nullptr);
211 11 : CPLAssert(m_pszFilename != nullptr);
212 :
213 11 : FinishSpatialite();
214 :
215 11 : CloseDB();
216 :
217 : /* And re-open the file */
218 11 : return OpenOrCreateDB(SQLITE_OPEN_READWRITE);
219 : }
220 :
221 775 : static OGRErr GDALGPKGImportFromEPSG(OGRSpatialReference *poSpatialRef,
222 : int nEPSGCode)
223 : {
224 775 : CPLPushErrorHandler(CPLQuietErrorHandler);
225 775 : const OGRErr eErr = poSpatialRef->importFromEPSG(nEPSGCode);
226 775 : CPLPopErrorHandler();
227 775 : CPLErrorReset();
228 775 : return eErr;
229 : }
230 :
231 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
232 1183 : GDALGeoPackageDataset::GetSpatialRef(int iSrsId, bool bFallbackToEPSG,
233 : bool bEmitErrorIfNotFound)
234 : {
235 1183 : const auto oIter = m_oMapSrsIdToSrs.find(iSrsId);
236 1183 : if (oIter != m_oMapSrsIdToSrs.end())
237 : {
238 82 : if (oIter->second == nullptr)
239 31 : return nullptr;
240 51 : oIter->second->Reference();
241 : return std::unique_ptr<OGRSpatialReference,
242 51 : OGRSpatialReferenceReleaser>(oIter->second);
243 : }
244 :
245 1101 : if (iSrsId == 0 || iSrsId == -1)
246 : {
247 120 : OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
248 120 : poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
249 :
250 : // See corresponding tests in GDALGeoPackageDataset::GetSrsId
251 120 : if (iSrsId == 0)
252 : {
253 29 : poSpatialRef->SetGeogCS("Undefined geographic SRS", "unknown",
254 : "unknown", SRS_WGS84_SEMIMAJOR,
255 : SRS_WGS84_INVFLATTENING);
256 : }
257 91 : else if (iSrsId == -1)
258 : {
259 91 : poSpatialRef->SetLocalCS("Undefined Cartesian SRS");
260 91 : poSpatialRef->SetLinearUnits(SRS_UL_METER, 1.0);
261 : }
262 :
263 120 : m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
264 120 : poSpatialRef->Reference();
265 : return std::unique_ptr<OGRSpatialReference,
266 120 : OGRSpatialReferenceReleaser>(poSpatialRef);
267 : }
268 :
269 1962 : CPLString oSQL;
270 981 : oSQL.Printf("SELECT srs_name, definition, organization, "
271 : "organization_coordsys_id%s%s "
272 : "FROM gpkg_spatial_ref_sys WHERE "
273 : "srs_id = %d LIMIT 2",
274 981 : m_bHasDefinition12_063 ? ", definition_12_063" : "",
275 981 : m_bHasEpochColumn ? ", epoch" : "", iSrsId);
276 :
277 1962 : auto oResult = SQLQuery(hDB, oSQL.c_str());
278 :
279 981 : if (!oResult || oResult->RowCount() != 1)
280 : {
281 12 : if (bFallbackToEPSG)
282 : {
283 7 : CPLDebug("GPKG",
284 : "unable to read srs_id '%d' from gpkg_spatial_ref_sys",
285 : iSrsId);
286 7 : OGRSpatialReference *poSRS = new OGRSpatialReference();
287 7 : if (poSRS->importFromEPSG(iSrsId) == OGRERR_NONE)
288 : {
289 5 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
290 : return std::unique_ptr<OGRSpatialReference,
291 5 : OGRSpatialReferenceReleaser>(poSRS);
292 : }
293 2 : poSRS->Release();
294 : }
295 5 : else if (bEmitErrorIfNotFound)
296 : {
297 2 : CPLError(CE_Warning, CPLE_AppDefined,
298 : "unable to read srs_id '%d' from gpkg_spatial_ref_sys",
299 : iSrsId);
300 2 : m_oMapSrsIdToSrs[iSrsId] = nullptr;
301 : }
302 7 : return nullptr;
303 : }
304 :
305 969 : const char *pszName = oResult->GetValue(0, 0);
306 969 : if (pszName && EQUAL(pszName, "Undefined SRS"))
307 : {
308 404 : m_oMapSrsIdToSrs[iSrsId] = nullptr;
309 404 : return nullptr;
310 : }
311 565 : const char *pszWkt = oResult->GetValue(1, 0);
312 565 : if (pszWkt == nullptr)
313 0 : return nullptr;
314 565 : const char *pszOrganization = oResult->GetValue(2, 0);
315 565 : const char *pszOrganizationCoordsysID = oResult->GetValue(3, 0);
316 : const char *pszWkt2 =
317 565 : m_bHasDefinition12_063 ? oResult->GetValue(4, 0) : nullptr;
318 565 : if (pszWkt2 && !EQUAL(pszWkt2, "undefined"))
319 76 : pszWkt = pszWkt2;
320 : const char *pszCoordinateEpoch =
321 565 : m_bHasEpochColumn ? oResult->GetValue(5, 0) : nullptr;
322 : const double dfCoordinateEpoch =
323 565 : pszCoordinateEpoch ? CPLAtof(pszCoordinateEpoch) : 0.0;
324 :
325 565 : OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
326 565 : poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
327 : // Try to import first from EPSG code, and then from WKT
328 565 : if (!(pszOrganization && pszOrganizationCoordsysID &&
329 565 : EQUAL(pszOrganization, "EPSG") &&
330 545 : (atoi(pszOrganizationCoordsysID) == iSrsId ||
331 4 : (dfCoordinateEpoch > 0 && strstr(pszWkt, "DYNAMIC[") == nullptr)) &&
332 545 : GDALGPKGImportFromEPSG(
333 1130 : poSpatialRef, atoi(pszOrganizationCoordsysID)) == OGRERR_NONE) &&
334 20 : poSpatialRef->importFromWkt(pszWkt) != OGRERR_NONE)
335 : {
336 0 : CPLError(CE_Warning, CPLE_AppDefined,
337 : "Unable to parse srs_id '%d' well-known text '%s'", iSrsId,
338 : pszWkt);
339 0 : delete poSpatialRef;
340 0 : m_oMapSrsIdToSrs[iSrsId] = nullptr;
341 0 : return nullptr;
342 : }
343 :
344 565 : poSpatialRef->StripTOWGS84IfKnownDatumAndAllowed();
345 565 : poSpatialRef->SetCoordinateEpoch(dfCoordinateEpoch);
346 565 : m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
347 565 : poSpatialRef->Reference();
348 : return std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>(
349 565 : poSpatialRef);
350 : }
351 :
352 259 : const char *GDALGeoPackageDataset::GetSrsName(const OGRSpatialReference &oSRS)
353 : {
354 259 : const char *pszName = oSRS.GetName();
355 259 : if (pszName)
356 259 : return pszName;
357 :
358 : // Something odd. Return empty.
359 0 : return "Unnamed SRS";
360 : }
361 :
362 : /* Add the definition_12_063 column to an existing gpkg_spatial_ref_sys table */
363 7 : bool GDALGeoPackageDataset::ConvertGpkgSpatialRefSysToExtensionWkt2(
364 : bool bForceEpoch)
365 : {
366 7 : const bool bAddEpoch = (m_nUserVersion >= GPKG_1_4_VERSION || bForceEpoch);
367 : auto oResultTable = SQLQuery(
368 : hDB, "SELECT srs_name, srs_id, organization, organization_coordsys_id, "
369 14 : "definition, description FROM gpkg_spatial_ref_sys LIMIT 100000");
370 7 : if (!oResultTable)
371 0 : return false;
372 :
373 : // Temporary remove foreign key checks
374 : const GPKGTemporaryForeignKeyCheckDisabler
375 7 : oGPKGTemporaryForeignKeyCheckDisabler(this);
376 :
377 7 : bool bRet = SoftStartTransaction() == OGRERR_NONE;
378 :
379 7 : if (bRet)
380 : {
381 : std::string osSQL("CREATE TABLE gpkg_spatial_ref_sys_temp ("
382 : "srs_name TEXT NOT NULL,"
383 : "srs_id INTEGER NOT NULL PRIMARY KEY,"
384 : "organization TEXT NOT NULL,"
385 : "organization_coordsys_id INTEGER NOT NULL,"
386 : "definition TEXT NOT NULL,"
387 : "description TEXT, "
388 7 : "definition_12_063 TEXT NOT NULL");
389 7 : if (bAddEpoch)
390 6 : osSQL += ", epoch DOUBLE";
391 7 : osSQL += ")";
392 7 : bRet = SQLCommand(hDB, osSQL.c_str()) == OGRERR_NONE;
393 : }
394 :
395 7 : if (bRet)
396 : {
397 32 : for (int i = 0; bRet && i < oResultTable->RowCount(); i++)
398 : {
399 25 : const char *pszSrsName = oResultTable->GetValue(0, i);
400 25 : const char *pszSrsId = oResultTable->GetValue(1, i);
401 25 : const char *pszOrganization = oResultTable->GetValue(2, i);
402 : const char *pszOrganizationCoordsysID =
403 25 : oResultTable->GetValue(3, i);
404 25 : const char *pszDefinition = oResultTable->GetValue(4, i);
405 : if (pszSrsName == nullptr || pszSrsId == nullptr ||
406 : pszOrganization == nullptr ||
407 : pszOrganizationCoordsysID == nullptr)
408 : {
409 : // should not happen as there are NOT NULL constraints
410 : // But a database could lack such NOT NULL constraints or have
411 : // large values that would cause a memory allocation failure.
412 : }
413 25 : const char *pszDescription = oResultTable->GetValue(5, i);
414 : char *pszSQL;
415 :
416 50 : OGRSpatialReference oSRS;
417 25 : if (pszOrganization && pszOrganizationCoordsysID &&
418 25 : EQUAL(pszOrganization, "EPSG"))
419 : {
420 9 : oSRS.importFromEPSG(atoi(pszOrganizationCoordsysID));
421 : }
422 34 : if (!oSRS.IsEmpty() && pszDefinition &&
423 9 : !EQUAL(pszDefinition, "undefined"))
424 : {
425 9 : oSRS.SetFromUserInput(pszDefinition);
426 : }
427 25 : char *pszWKT2 = nullptr;
428 25 : if (!oSRS.IsEmpty())
429 : {
430 9 : const char *const apszOptionsWkt2[] = {"FORMAT=WKT2_2015",
431 : nullptr};
432 9 : oSRS.exportToWkt(&pszWKT2, apszOptionsWkt2);
433 9 : if (pszWKT2 && pszWKT2[0] == '\0')
434 : {
435 0 : CPLFree(pszWKT2);
436 0 : pszWKT2 = nullptr;
437 : }
438 : }
439 25 : if (pszWKT2 == nullptr)
440 : {
441 16 : pszWKT2 = CPLStrdup("undefined");
442 : }
443 :
444 25 : if (pszDescription)
445 : {
446 22 : pszSQL = sqlite3_mprintf(
447 : "INSERT INTO gpkg_spatial_ref_sys_temp(srs_name, srs_id, "
448 : "organization, organization_coordsys_id, definition, "
449 : "description, definition_12_063) VALUES ('%q', '%q', '%q', "
450 : "'%q', '%q', '%q', '%q')",
451 : pszSrsName, pszSrsId, pszOrganization,
452 : pszOrganizationCoordsysID, pszDefinition, pszDescription,
453 : pszWKT2);
454 : }
455 : else
456 : {
457 3 : pszSQL = sqlite3_mprintf(
458 : "INSERT INTO gpkg_spatial_ref_sys_temp(srs_name, srs_id, "
459 : "organization, organization_coordsys_id, definition, "
460 : "description, definition_12_063) VALUES ('%q', '%q', '%q', "
461 : "'%q', '%q', NULL, '%q')",
462 : pszSrsName, pszSrsId, pszOrganization,
463 : pszOrganizationCoordsysID, pszDefinition, pszWKT2);
464 : }
465 :
466 25 : CPLFree(pszWKT2);
467 25 : bRet &= SQLCommand(hDB, pszSQL) == OGRERR_NONE;
468 25 : sqlite3_free(pszSQL);
469 : }
470 : }
471 :
472 7 : if (bRet)
473 : {
474 7 : bRet =
475 7 : SQLCommand(hDB, "DROP TABLE gpkg_spatial_ref_sys") == OGRERR_NONE;
476 : }
477 7 : if (bRet)
478 : {
479 7 : bRet = SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys_temp RENAME "
480 : "TO gpkg_spatial_ref_sys") == OGRERR_NONE;
481 : }
482 7 : if (bRet)
483 : {
484 14 : bRet = OGRERR_NONE == CreateExtensionsTableIfNecessary() &&
485 7 : OGRERR_NONE == SQLCommand(hDB,
486 : "INSERT INTO gpkg_extensions "
487 : "(table_name, column_name, "
488 : "extension_name, definition, scope) "
489 : "VALUES "
490 : "('gpkg_spatial_ref_sys', "
491 : "'definition_12_063', 'gpkg_crs_wkt', "
492 : "'http://www.geopackage.org/spec120/"
493 : "#extension_crs_wkt', 'read-write')");
494 : }
495 7 : if (bRet && bAddEpoch)
496 : {
497 6 : bRet =
498 : OGRERR_NONE ==
499 6 : SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
500 : "'gpkg_crs_wkt_1_1' "
501 12 : "WHERE extension_name = 'gpkg_crs_wkt'") &&
502 : OGRERR_NONE ==
503 6 : SQLCommand(
504 : hDB,
505 : "INSERT INTO gpkg_extensions "
506 : "(table_name, column_name, extension_name, definition, "
507 : "scope) "
508 : "VALUES "
509 : "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
510 : "'http://www.geopackage.org/spec/#extension_crs_wkt', "
511 : "'read-write')");
512 : }
513 7 : if (bRet)
514 : {
515 7 : SoftCommitTransaction();
516 7 : m_bHasDefinition12_063 = true;
517 7 : if (bAddEpoch)
518 6 : m_bHasEpochColumn = true;
519 : }
520 : else
521 : {
522 0 : SoftRollbackTransaction();
523 : }
524 :
525 7 : return bRet;
526 : }
527 :
528 830 : int GDALGeoPackageDataset::GetSrsId(const OGRSpatialReference *poSRSIn)
529 : {
530 830 : const char *pszName = poSRSIn ? poSRSIn->GetName() : nullptr;
531 1210 : if (!poSRSIn || poSRSIn->IsEmpty() ||
532 380 : (pszName && EQUAL(pszName, "Undefined SRS")))
533 : {
534 452 : OGRErr err = OGRERR_NONE;
535 452 : const int nSRSId = SQLGetInteger(
536 : hDB,
537 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE srs_name = "
538 : "'Undefined SRS' AND organization = 'GDAL'",
539 : &err);
540 452 : if (err == OGRERR_NONE)
541 54 : return nSRSId;
542 :
543 : // The below WKT definitions are somehow questionable (using a unknown
544 : // unit). For GDAL >= 3.9, they won't be used. They will only be used
545 : // for earlier versions.
546 : const char *pszSQL;
547 : #define UNDEFINED_CRS_SRS_ID 99999
548 : static_assert(UNDEFINED_CRS_SRS_ID == FIRST_CUSTOM_SRSID - 1);
549 : #define STRINGIFY(x) #x
550 : #define XSTRINGIFY(x) STRINGIFY(x)
551 398 : if (m_bHasDefinition12_063)
552 : {
553 : /* clang-format off */
554 1 : pszSQL =
555 : "INSERT INTO gpkg_spatial_ref_sys "
556 : "(srs_name,srs_id,organization,organization_coordsys_id,"
557 : "definition, definition_12_063, description) VALUES "
558 : "('Undefined SRS'," XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ",'GDAL',"
559 : XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ","
560 : "'LOCAL_CS[\"Undefined SRS\",LOCAL_DATUM[\"unknown\",32767],"
561 : "UNIT[\"unknown\",0],AXIS[\"Easting\",EAST],"
562 : "AXIS[\"Northing\",NORTH]]',"
563 : "'ENGCRS[\"Undefined SRS\",EDATUM[\"unknown\"],CS[Cartesian,2],"
564 : "AXIS[\"easting\",east,ORDER[1],LENGTHUNIT[\"unknown\",0]],"
565 : "AXIS[\"northing\",north,ORDER[2],LENGTHUNIT[\"unknown\",0]]]',"
566 : "'Custom undefined coordinate reference system')";
567 : /* clang-format on */
568 : }
569 : else
570 : {
571 : /* clang-format off */
572 397 : pszSQL =
573 : "INSERT INTO gpkg_spatial_ref_sys "
574 : "(srs_name,srs_id,organization,organization_coordsys_id,"
575 : "definition, description) VALUES "
576 : "('Undefined SRS'," XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ",'GDAL',"
577 : XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ","
578 : "'LOCAL_CS[\"Undefined SRS\",LOCAL_DATUM[\"unknown\",32767],"
579 : "UNIT[\"unknown\",0],AXIS[\"Easting\",EAST],"
580 : "AXIS[\"Northing\",NORTH]]',"
581 : "'Custom undefined coordinate reference system')";
582 : /* clang-format on */
583 : }
584 398 : if (SQLCommand(hDB, pszSQL) == OGRERR_NONE)
585 398 : return UNDEFINED_CRS_SRS_ID;
586 : #undef UNDEFINED_CRS_SRS_ID
587 : #undef XSTRINGIFY
588 : #undef STRINGIFY
589 0 : return -1;
590 : }
591 :
592 756 : std::unique_ptr<OGRSpatialReference> poSRS(poSRSIn->Clone());
593 :
594 378 : if (poSRS->IsGeographic() || poSRS->IsLocal())
595 : {
596 : // See corresponding tests in GDALGeoPackageDataset::GetSpatialRef
597 138 : if (pszName != nullptr && strlen(pszName) > 0)
598 : {
599 138 : if (EQUAL(pszName, "Undefined geographic SRS"))
600 2 : return 0;
601 :
602 136 : if (EQUAL(pszName, "Undefined Cartesian SRS"))
603 1 : return -1;
604 : }
605 : }
606 :
607 375 : const char *pszAuthorityName = poSRS->GetAuthorityName(nullptr);
608 :
609 375 : if (pszAuthorityName == nullptr || strlen(pszAuthorityName) == 0)
610 : {
611 : // Try to force identify an EPSG code.
612 26 : poSRS->AutoIdentifyEPSG();
613 :
614 26 : pszAuthorityName = poSRS->GetAuthorityName(nullptr);
615 26 : if (pszAuthorityName != nullptr && EQUAL(pszAuthorityName, "EPSG"))
616 : {
617 0 : const char *pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
618 0 : if (pszAuthorityCode != nullptr && strlen(pszAuthorityCode) > 0)
619 : {
620 : /* Import 'clean' SRS */
621 0 : poSRS->importFromEPSG(atoi(pszAuthorityCode));
622 :
623 0 : pszAuthorityName = poSRS->GetAuthorityName(nullptr);
624 : }
625 : }
626 :
627 26 : poSRS->SetCoordinateEpoch(poSRSIn->GetCoordinateEpoch());
628 : }
629 :
630 : // Check whether the EPSG authority code is already mapped to a
631 : // SRS ID.
632 375 : char *pszSQL = nullptr;
633 375 : int nSRSId = DEFAULT_SRID;
634 375 : int nAuthorityCode = 0;
635 375 : OGRErr err = OGRERR_NONE;
636 375 : bool bCanUseAuthorityCode = false;
637 375 : const char *const apszIsSameOptions[] = {
638 : "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES",
639 : "IGNORE_COORDINATE_EPOCH=YES", nullptr};
640 375 : if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0)
641 : {
642 349 : const char *pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
643 349 : if (pszAuthorityCode)
644 : {
645 349 : if (CPLGetValueType(pszAuthorityCode) == CPL_VALUE_INTEGER)
646 : {
647 349 : nAuthorityCode = atoi(pszAuthorityCode);
648 : }
649 : else
650 : {
651 0 : CPLDebug("GPKG",
652 : "SRS has %s:%s identification, but the code not "
653 : "being an integer value cannot be stored as such "
654 : "in the database.",
655 : pszAuthorityName, pszAuthorityCode);
656 0 : pszAuthorityName = nullptr;
657 : }
658 : }
659 : }
660 :
661 724 : if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
662 349 : poSRSIn->GetCoordinateEpoch() == 0)
663 : {
664 : pszSQL =
665 344 : sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
666 : "upper(organization) = upper('%q') AND "
667 : "organization_coordsys_id = %d",
668 : pszAuthorityName, nAuthorityCode);
669 :
670 344 : nSRSId = SQLGetInteger(hDB, pszSQL, &err);
671 344 : sqlite3_free(pszSQL);
672 :
673 : // Got a match? Return it!
674 344 : if (OGRERR_NONE == err)
675 : {
676 112 : auto poRefSRS = GetSpatialRef(nSRSId);
677 : bool bOK =
678 112 : (poRefSRS == nullptr ||
679 113 : poSRS->IsSame(poRefSRS.get(), apszIsSameOptions) ||
680 1 : !CPLTestBool(CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")));
681 112 : if (bOK)
682 : {
683 111 : return nSRSId;
684 : }
685 : else
686 : {
687 1 : CPLError(CE_Warning, CPLE_AppDefined,
688 : "Passed SRS uses %s:%d identification, but its "
689 : "definition is not compatible with the "
690 : "definition of that object already in the database. "
691 : "Registering it as a new entry into the database.",
692 : pszAuthorityName, nAuthorityCode);
693 1 : pszAuthorityName = nullptr;
694 1 : nAuthorityCode = 0;
695 : }
696 : }
697 : }
698 :
699 : // Translate SRS to WKT.
700 264 : CPLCharUniquePtr pszWKT1;
701 264 : CPLCharUniquePtr pszWKT2_2015;
702 264 : CPLCharUniquePtr pszWKT2_2019;
703 264 : const char *const apszOptionsWkt1[] = {"FORMAT=WKT1_GDAL", nullptr};
704 264 : const char *const apszOptionsWkt2_2015[] = {"FORMAT=WKT2_2015", nullptr};
705 264 : const char *const apszOptionsWkt2_2019[] = {"FORMAT=WKT2_2019", nullptr};
706 :
707 528 : std::string osEpochTest;
708 264 : if (poSRSIn->GetCoordinateEpoch() > 0 && m_bHasEpochColumn)
709 : {
710 : osEpochTest =
711 3 : CPLSPrintf(" AND epoch = %.17g", poSRSIn->GetCoordinateEpoch());
712 : }
713 :
714 264 : if (!(poSRS->IsGeographic() && poSRS->GetAxesCount() == 3))
715 : {
716 255 : char *pszTmp = nullptr;
717 255 : poSRS->exportToWkt(&pszTmp, apszOptionsWkt1);
718 255 : pszWKT1.reset(pszTmp);
719 255 : if (pszWKT1 && pszWKT1.get()[0] == '\0')
720 : {
721 0 : pszWKT1.reset();
722 : }
723 : }
724 : {
725 264 : char *pszTmp = nullptr;
726 264 : poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2015);
727 264 : pszWKT2_2015.reset(pszTmp);
728 264 : if (pszWKT2_2015 && pszWKT2_2015.get()[0] == '\0')
729 : {
730 0 : pszWKT2_2015.reset();
731 : }
732 : }
733 : {
734 264 : char *pszTmp = nullptr;
735 264 : poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2019);
736 264 : pszWKT2_2019.reset(pszTmp);
737 264 : if (pszWKT2_2019 && pszWKT2_2019.get()[0] == '\0')
738 : {
739 0 : pszWKT2_2019.reset();
740 : }
741 : }
742 :
743 264 : if (!pszWKT1 && !pszWKT2_2015 && !pszWKT2_2019)
744 : {
745 0 : return DEFAULT_SRID;
746 : }
747 :
748 264 : if (poSRSIn->GetCoordinateEpoch() == 0 || m_bHasEpochColumn)
749 : {
750 : // Search if there is already an existing entry with this WKT
751 261 : if (m_bHasDefinition12_063 && (pszWKT2_2015 || pszWKT2_2019))
752 : {
753 42 : if (pszWKT1)
754 : {
755 144 : pszSQL = sqlite3_mprintf(
756 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
757 : "(definition = '%q' OR definition_12_063 IN ('%q','%q'))%s",
758 : pszWKT1.get(),
759 72 : pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
760 72 : pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
761 : osEpochTest.c_str());
762 : }
763 : else
764 : {
765 24 : pszSQL = sqlite3_mprintf(
766 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
767 : "definition_12_063 IN ('%q', '%q')%s",
768 12 : pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
769 12 : pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
770 : osEpochTest.c_str());
771 : }
772 : }
773 219 : else if (pszWKT1)
774 : {
775 : pszSQL =
776 216 : sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
777 : "definition = '%q'%s",
778 : pszWKT1.get(), osEpochTest.c_str());
779 : }
780 : else
781 : {
782 3 : pszSQL = nullptr;
783 : }
784 261 : if (pszSQL)
785 : {
786 258 : nSRSId = SQLGetInteger(hDB, pszSQL, &err);
787 258 : sqlite3_free(pszSQL);
788 258 : if (OGRERR_NONE == err)
789 : {
790 5 : return nSRSId;
791 : }
792 : }
793 : }
794 :
795 494 : if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
796 235 : poSRSIn->GetCoordinateEpoch() == 0)
797 : {
798 231 : bool bTryToReuseSRSId = true;
799 231 : if (EQUAL(pszAuthorityName, "EPSG"))
800 : {
801 460 : OGRSpatialReference oSRS_EPSG;
802 230 : if (GDALGPKGImportFromEPSG(&oSRS_EPSG, nAuthorityCode) ==
803 : OGRERR_NONE)
804 : {
805 231 : if (!poSRS->IsSame(&oSRS_EPSG, apszIsSameOptions) &&
806 1 : CPLTestBool(
807 : CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")))
808 : {
809 1 : bTryToReuseSRSId = false;
810 1 : CPLError(
811 : CE_Warning, CPLE_AppDefined,
812 : "Passed SRS uses %s:%d identification, but its "
813 : "definition is not compatible with the "
814 : "official definition of the object. "
815 : "Registering it as a non-%s entry into the database.",
816 : pszAuthorityName, nAuthorityCode, pszAuthorityName);
817 1 : pszAuthorityName = nullptr;
818 1 : nAuthorityCode = 0;
819 : }
820 : }
821 : }
822 231 : if (bTryToReuseSRSId)
823 : {
824 : // No match, but maybe we can use the nAuthorityCode as the nSRSId?
825 230 : pszSQL = sqlite3_mprintf(
826 : "SELECT Count(*) FROM gpkg_spatial_ref_sys WHERE "
827 : "srs_id = %d",
828 : nAuthorityCode);
829 :
830 : // Yep, we can!
831 230 : if (SQLGetInteger(hDB, pszSQL, nullptr) == 0)
832 229 : bCanUseAuthorityCode = true;
833 230 : sqlite3_free(pszSQL);
834 : }
835 : }
836 :
837 259 : bool bConvertGpkgSpatialRefSysToExtensionWkt2 = false;
838 259 : bool bForceEpoch = false;
839 262 : if (!m_bHasDefinition12_063 && pszWKT1 == nullptr &&
840 3 : (pszWKT2_2015 != nullptr || pszWKT2_2019 != nullptr))
841 : {
842 3 : bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
843 : }
844 :
845 : // Add epoch column if needed
846 259 : if (poSRSIn->GetCoordinateEpoch() > 0 && !m_bHasEpochColumn)
847 : {
848 3 : if (m_bHasDefinition12_063)
849 : {
850 0 : if (SoftStartTransaction() != OGRERR_NONE)
851 0 : return DEFAULT_SRID;
852 0 : if (SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys "
853 0 : "ADD COLUMN epoch DOUBLE") != OGRERR_NONE ||
854 0 : SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
855 : "'gpkg_crs_wkt_1_1' "
856 : "WHERE extension_name = 'gpkg_crs_wkt'") !=
857 0 : OGRERR_NONE ||
858 0 : SQLCommand(
859 : hDB,
860 : "INSERT INTO gpkg_extensions "
861 : "(table_name, column_name, extension_name, definition, "
862 : "scope) "
863 : "VALUES "
864 : "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
865 : "'http://www.geopackage.org/spec/#extension_crs_wkt', "
866 : "'read-write')") != OGRERR_NONE)
867 : {
868 0 : SoftRollbackTransaction();
869 0 : return DEFAULT_SRID;
870 : }
871 :
872 0 : if (SoftCommitTransaction() != OGRERR_NONE)
873 0 : return DEFAULT_SRID;
874 :
875 0 : m_bHasEpochColumn = true;
876 : }
877 : else
878 : {
879 3 : bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
880 3 : bForceEpoch = true;
881 : }
882 : }
883 :
884 265 : if (bConvertGpkgSpatialRefSysToExtensionWkt2 &&
885 6 : !ConvertGpkgSpatialRefSysToExtensionWkt2(bForceEpoch))
886 : {
887 0 : return DEFAULT_SRID;
888 : }
889 :
890 : // Reuse the authority code number as SRS_ID if we can
891 259 : if (bCanUseAuthorityCode)
892 : {
893 229 : nSRSId = nAuthorityCode;
894 : }
895 : // Otherwise, generate a new SRS_ID number (max + 1)
896 : else
897 : {
898 : // Get the current maximum srid in the srs table.
899 30 : const int nMaxSRSId = SQLGetInteger(
900 : hDB, "SELECT MAX(srs_id) FROM gpkg_spatial_ref_sys", nullptr);
901 30 : nSRSId = std::max(FIRST_CUSTOM_SRSID, nMaxSRSId + 1);
902 : }
903 :
904 518 : std::string osEpochColumn;
905 259 : std::string osEpochVal;
906 259 : if (poSRSIn->GetCoordinateEpoch() > 0)
907 : {
908 5 : osEpochColumn = ", epoch";
909 5 : osEpochVal = CPLSPrintf(", %.17g", poSRSIn->GetCoordinateEpoch());
910 : }
911 :
912 : // Add new SRS row to gpkg_spatial_ref_sys.
913 259 : if (m_bHasDefinition12_063)
914 : {
915 : // Force WKT2_2019 when we have a dynamic CRS and coordinate epoch
916 45 : const char *pszWKT2 = poSRSIn->IsDynamic() &&
917 10 : poSRSIn->GetCoordinateEpoch() > 0 &&
918 1 : pszWKT2_2019
919 1 : ? pszWKT2_2019.get()
920 44 : : pszWKT2_2015 ? pszWKT2_2015.get()
921 97 : : pszWKT2_2019.get();
922 :
923 45 : if (pszAuthorityName != nullptr && nAuthorityCode > 0)
924 : {
925 99 : pszSQL = sqlite3_mprintf(
926 : "INSERT INTO gpkg_spatial_ref_sys "
927 : "(srs_name,srs_id,organization,organization_coordsys_id,"
928 : "definition, definition_12_063%s) VALUES "
929 : "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
930 33 : osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId,
931 : pszAuthorityName, nAuthorityCode,
932 62 : pszWKT1 ? pszWKT1.get() : "undefined",
933 : pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
934 : }
935 : else
936 : {
937 36 : pszSQL = sqlite3_mprintf(
938 : "INSERT INTO gpkg_spatial_ref_sys "
939 : "(srs_name,srs_id,organization,organization_coordsys_id,"
940 : "definition, definition_12_063%s) VALUES "
941 : "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
942 12 : osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId, "NONE",
943 21 : nSRSId, pszWKT1 ? pszWKT1.get() : "undefined",
944 : pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
945 : }
946 : }
947 : else
948 : {
949 214 : if (pszAuthorityName != nullptr && nAuthorityCode > 0)
950 : {
951 402 : pszSQL = sqlite3_mprintf(
952 : "INSERT INTO gpkg_spatial_ref_sys "
953 : "(srs_name,srs_id,organization,organization_coordsys_id,"
954 : "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
955 201 : GetSrsName(*poSRS), nSRSId, pszAuthorityName, nAuthorityCode,
956 402 : pszWKT1 ? pszWKT1.get() : "undefined");
957 : }
958 : else
959 : {
960 26 : pszSQL = sqlite3_mprintf(
961 : "INSERT INTO gpkg_spatial_ref_sys "
962 : "(srs_name,srs_id,organization,organization_coordsys_id,"
963 : "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
964 13 : GetSrsName(*poSRS), nSRSId, "NONE", nSRSId,
965 26 : pszWKT1 ? pszWKT1.get() : "undefined");
966 : }
967 : }
968 :
969 : // Add new row to gpkg_spatial_ref_sys.
970 259 : CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
971 :
972 : // Free everything that was allocated.
973 259 : sqlite3_free(pszSQL);
974 :
975 259 : return nSRSId;
976 : }
977 :
978 : /************************************************************************/
979 : /* ~GDALGeoPackageDataset() */
980 : /************************************************************************/
981 :
982 4746 : GDALGeoPackageDataset::~GDALGeoPackageDataset()
983 : {
984 2373 : GDALGeoPackageDataset::Close();
985 4746 : }
986 :
987 : /************************************************************************/
988 : /* Close() */
989 : /************************************************************************/
990 :
991 3993 : CPLErr GDALGeoPackageDataset::Close()
992 : {
993 3993 : CPLErr eErr = CE_None;
994 3993 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
995 : {
996 1380 : if (eAccess == GA_Update && m_poParentDS == nullptr &&
997 3753 : !m_osRasterTable.empty() && !m_bGeoTransformValid)
998 : {
999 3 : CPLError(CE_Failure, CPLE_AppDefined,
1000 : "Raster table %s not correctly initialized due to missing "
1001 : "call to SetGeoTransform()",
1002 : m_osRasterTable.c_str());
1003 : }
1004 :
1005 2373 : if (GDALGeoPackageDataset::FlushCache(true) != CE_None)
1006 7 : eErr = CE_Failure;
1007 :
1008 : // Destroy bands now since we don't want
1009 : // GDALGPKGMBTilesLikeRasterBand::FlushCache() to run after dataset
1010 : // destruction
1011 4190 : for (int i = 0; i < nBands; i++)
1012 1817 : delete papoBands[i];
1013 2373 : nBands = 0;
1014 2373 : CPLFree(papoBands);
1015 2373 : papoBands = nullptr;
1016 :
1017 : // Destroy overviews before cleaning m_hTempDB as they could still
1018 : // need it
1019 2373 : m_apoOverviewDS.clear();
1020 :
1021 2373 : if (m_poParentDS)
1022 : {
1023 325 : hDB = nullptr;
1024 : }
1025 :
1026 2373 : m_apoLayers.clear();
1027 :
1028 : std::map<int, OGRSpatialReference *>::iterator oIter =
1029 2373 : m_oMapSrsIdToSrs.begin();
1030 3464 : for (; oIter != m_oMapSrsIdToSrs.end(); ++oIter)
1031 : {
1032 1091 : OGRSpatialReference *poSRS = oIter->second;
1033 1091 : if (poSRS)
1034 685 : poSRS->Release();
1035 : }
1036 :
1037 2373 : if (!CloseDB())
1038 0 : eErr = CE_Failure;
1039 :
1040 2373 : if (OGRSQLiteBaseDataSource::Close() != CE_None)
1041 0 : eErr = CE_Failure;
1042 : }
1043 3993 : return eErr;
1044 : }
1045 :
1046 : /************************************************************************/
1047 : /* ICanIWriteBlock() */
1048 : /************************************************************************/
1049 :
1050 5694 : bool GDALGeoPackageDataset::ICanIWriteBlock()
1051 : {
1052 5694 : if (!GetUpdate())
1053 : {
1054 0 : CPLError(
1055 : CE_Failure, CPLE_NotSupported,
1056 : "IWriteBlock() not supported on dataset opened in read-only mode");
1057 0 : return false;
1058 : }
1059 :
1060 5694 : if (m_pabyCachedTiles == nullptr)
1061 : {
1062 0 : return false;
1063 : }
1064 :
1065 5694 : if (!m_bGeoTransformValid || m_nSRID == UNKNOWN_SRID)
1066 : {
1067 0 : CPLError(CE_Failure, CPLE_NotSupported,
1068 : "IWriteBlock() not supported if georeferencing not set");
1069 0 : return false;
1070 : }
1071 5694 : return true;
1072 : }
1073 :
1074 : /************************************************************************/
1075 : /* IRasterIO() */
1076 : /************************************************************************/
1077 :
1078 130 : CPLErr GDALGeoPackageDataset::IRasterIO(
1079 : GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
1080 : void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
1081 : int nBandCount, BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
1082 : GSpacing nLineSpace, GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
1083 :
1084 : {
1085 130 : CPLErr eErr = OGRSQLiteBaseDataSource::IRasterIO(
1086 : eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
1087 : eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace, nBandSpace,
1088 : psExtraArg);
1089 :
1090 : // If writing all bands, in non-shifted mode, flush all entirely written
1091 : // tiles This can avoid "stressing" the block cache with too many dirty
1092 : // blocks. Note: this logic would be useless with a per-dataset block cache.
1093 130 : if (eErr == CE_None && eRWFlag == GF_Write && nXSize == nBufXSize &&
1094 121 : nYSize == nBufYSize && nBandCount == nBands &&
1095 118 : m_nShiftXPixelsMod == 0 && m_nShiftYPixelsMod == 0)
1096 : {
1097 : auto poBand =
1098 114 : cpl::down_cast<GDALGPKGMBTilesLikeRasterBand *>(GetRasterBand(1));
1099 : int nBlockXSize, nBlockYSize;
1100 114 : poBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
1101 114 : const int nBlockXStart = DIV_ROUND_UP(nXOff, nBlockXSize);
1102 114 : const int nBlockYStart = DIV_ROUND_UP(nYOff, nBlockYSize);
1103 114 : const int nBlockXEnd = (nXOff + nXSize) / nBlockXSize;
1104 114 : const int nBlockYEnd = (nYOff + nYSize) / nBlockYSize;
1105 268 : for (int nBlockY = nBlockXStart; nBlockY < nBlockYEnd; nBlockY++)
1106 : {
1107 4371 : for (int nBlockX = nBlockYStart; nBlockX < nBlockXEnd; nBlockX++)
1108 : {
1109 : GDALRasterBlock *poBlock =
1110 4217 : poBand->AccessibleTryGetLockedBlockRef(nBlockX, nBlockY);
1111 4217 : if (poBlock)
1112 : {
1113 : // GetDirty() should be true in most situation (otherwise
1114 : // it means the block cache is under extreme pressure!)
1115 4215 : if (poBlock->GetDirty())
1116 : {
1117 : // IWriteBlock() on one band will check the dirty state
1118 : // of the corresponding blocks in other bands, to decide
1119 : // if it can call WriteTile(), so we have only to do
1120 : // that on one of the bands
1121 4215 : if (poBlock->Write() != CE_None)
1122 250 : eErr = CE_Failure;
1123 : }
1124 4215 : poBlock->DropLock();
1125 : }
1126 : }
1127 : }
1128 : }
1129 :
1130 130 : return eErr;
1131 : }
1132 :
1133 : /************************************************************************/
1134 : /* GetOGRTableLimit() */
1135 : /************************************************************************/
1136 :
1137 3853 : static int GetOGRTableLimit()
1138 : {
1139 3853 : return atoi(CPLGetConfigOption("OGR_TABLE_LIMIT", "10000"));
1140 : }
1141 :
1142 : /************************************************************************/
1143 : /* GetNameTypeMapFromSQliteMaster() */
1144 : /************************************************************************/
1145 :
1146 : const std::map<CPLString, CPLString> &
1147 1193 : GDALGeoPackageDataset::GetNameTypeMapFromSQliteMaster()
1148 : {
1149 1193 : if (!m_oMapNameToType.empty())
1150 327 : return m_oMapNameToType;
1151 :
1152 : CPLString osSQL(
1153 : "SELECT name, type FROM sqlite_master WHERE "
1154 : "type IN ('view', 'table') OR "
1155 1732 : "(name LIKE 'trigger_%_feature_count_%' AND type = 'trigger')");
1156 866 : const int nTableLimit = GetOGRTableLimit();
1157 866 : if (nTableLimit > 0)
1158 : {
1159 866 : osSQL += " LIMIT ";
1160 866 : osSQL += CPLSPrintf("%d", 1 + 3 * nTableLimit);
1161 : }
1162 :
1163 866 : auto oResult = SQLQuery(hDB, osSQL);
1164 866 : if (oResult)
1165 : {
1166 14482 : for (int i = 0; i < oResult->RowCount(); i++)
1167 : {
1168 13616 : const char *pszName = oResult->GetValue(0, i);
1169 13616 : const char *pszType = oResult->GetValue(1, i);
1170 13616 : m_oMapNameToType[CPLString(pszName).toupper()] = pszType;
1171 : }
1172 : }
1173 :
1174 866 : return m_oMapNameToType;
1175 : }
1176 :
1177 : /************************************************************************/
1178 : /* RemoveTableFromSQLiteMasterCache() */
1179 : /************************************************************************/
1180 :
1181 54 : void GDALGeoPackageDataset::RemoveTableFromSQLiteMasterCache(
1182 : const char *pszTableName)
1183 : {
1184 54 : m_oMapNameToType.erase(CPLString(pszTableName).toupper());
1185 54 : }
1186 :
1187 : /************************************************************************/
1188 : /* GetUnknownExtensionsTableSpecific() */
1189 : /************************************************************************/
1190 :
1191 : const std::map<CPLString, std::vector<GPKGExtensionDesc>> &
1192 825 : GDALGeoPackageDataset::GetUnknownExtensionsTableSpecific()
1193 : {
1194 825 : if (m_bMapTableToExtensionsBuilt)
1195 84 : return m_oMapTableToExtensions;
1196 741 : m_bMapTableToExtensionsBuilt = true;
1197 :
1198 741 : if (!HasExtensionsTable())
1199 38 : return m_oMapTableToExtensions;
1200 :
1201 : CPLString osSQL(
1202 : "SELECT table_name, extension_name, definition, scope "
1203 : "FROM gpkg_extensions WHERE "
1204 : "table_name IS NOT NULL "
1205 : "AND extension_name IS NOT NULL "
1206 : "AND definition IS NOT NULL "
1207 : "AND scope IS NOT NULL "
1208 : "AND extension_name NOT IN ('gpkg_geom_CIRCULARSTRING', "
1209 : "'gpkg_geom_COMPOUNDCURVE', 'gpkg_geom_CURVEPOLYGON', "
1210 : "'gpkg_geom_MULTICURVE', "
1211 : "'gpkg_geom_MULTISURFACE', 'gpkg_geom_CURVE', 'gpkg_geom_SURFACE', "
1212 : "'gpkg_geom_POLYHEDRALSURFACE', 'gpkg_geom_TIN', 'gpkg_geom_TRIANGLE', "
1213 : "'gpkg_rtree_index', 'gpkg_geometry_type_trigger', "
1214 : "'gpkg_srs_id_trigger', "
1215 : "'gpkg_crs_wkt', 'gpkg_crs_wkt_1_1', 'gpkg_schema', "
1216 : "'gpkg_related_tables', 'related_tables'"
1217 : #ifdef HAVE_SPATIALITE
1218 : ", 'gdal_spatialite_computed_geom_column'"
1219 : #endif
1220 1406 : ")");
1221 703 : const int nTableLimit = GetOGRTableLimit();
1222 703 : if (nTableLimit > 0)
1223 : {
1224 703 : osSQL += " LIMIT ";
1225 703 : osSQL += CPLSPrintf("%d", 1 + 10 * nTableLimit);
1226 : }
1227 :
1228 703 : auto oResult = SQLQuery(hDB, osSQL);
1229 703 : if (oResult)
1230 : {
1231 1352 : for (int i = 0; i < oResult->RowCount(); i++)
1232 : {
1233 649 : const char *pszTableName = oResult->GetValue(0, i);
1234 649 : const char *pszExtensionName = oResult->GetValue(1, i);
1235 649 : const char *pszDefinition = oResult->GetValue(2, i);
1236 649 : const char *pszScope = oResult->GetValue(3, i);
1237 649 : if (pszTableName && pszExtensionName && pszDefinition && pszScope)
1238 : {
1239 649 : GPKGExtensionDesc oDesc;
1240 649 : oDesc.osExtensionName = pszExtensionName;
1241 649 : oDesc.osDefinition = pszDefinition;
1242 649 : oDesc.osScope = pszScope;
1243 1298 : m_oMapTableToExtensions[CPLString(pszTableName).toupper()]
1244 649 : .push_back(oDesc);
1245 : }
1246 : }
1247 : }
1248 :
1249 703 : return m_oMapTableToExtensions;
1250 : }
1251 :
1252 : /************************************************************************/
1253 : /* GetContents() */
1254 : /************************************************************************/
1255 :
1256 : const std::map<CPLString, GPKGContentsDesc> &
1257 807 : GDALGeoPackageDataset::GetContents()
1258 : {
1259 807 : if (m_bMapTableToContentsBuilt)
1260 68 : return m_oMapTableToContents;
1261 739 : m_bMapTableToContentsBuilt = true;
1262 :
1263 : CPLString osSQL("SELECT table_name, data_type, identifier, "
1264 : "description, min_x, min_y, max_x, max_y "
1265 1478 : "FROM gpkg_contents");
1266 739 : const int nTableLimit = GetOGRTableLimit();
1267 739 : if (nTableLimit > 0)
1268 : {
1269 739 : osSQL += " LIMIT ";
1270 739 : osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1271 : }
1272 :
1273 739 : auto oResult = SQLQuery(hDB, osSQL);
1274 739 : if (oResult)
1275 : {
1276 1589 : for (int i = 0; i < oResult->RowCount(); i++)
1277 : {
1278 850 : const char *pszTableName = oResult->GetValue(0, i);
1279 850 : if (pszTableName == nullptr)
1280 0 : continue;
1281 850 : const char *pszDataType = oResult->GetValue(1, i);
1282 850 : const char *pszIdentifier = oResult->GetValue(2, i);
1283 850 : const char *pszDescription = oResult->GetValue(3, i);
1284 850 : const char *pszMinX = oResult->GetValue(4, i);
1285 850 : const char *pszMinY = oResult->GetValue(5, i);
1286 850 : const char *pszMaxX = oResult->GetValue(6, i);
1287 850 : const char *pszMaxY = oResult->GetValue(7, i);
1288 850 : GPKGContentsDesc oDesc;
1289 850 : if (pszDataType)
1290 850 : oDesc.osDataType = pszDataType;
1291 850 : if (pszIdentifier)
1292 850 : oDesc.osIdentifier = pszIdentifier;
1293 850 : if (pszDescription)
1294 849 : oDesc.osDescription = pszDescription;
1295 850 : if (pszMinX)
1296 578 : oDesc.osMinX = pszMinX;
1297 850 : if (pszMinY)
1298 578 : oDesc.osMinY = pszMinY;
1299 850 : if (pszMaxX)
1300 578 : oDesc.osMaxX = pszMaxX;
1301 850 : if (pszMaxY)
1302 578 : oDesc.osMaxY = pszMaxY;
1303 1700 : m_oMapTableToContents[CPLString(pszTableName).toupper()] =
1304 1700 : std::move(oDesc);
1305 : }
1306 : }
1307 :
1308 739 : return m_oMapTableToContents;
1309 : }
1310 :
1311 : /************************************************************************/
1312 : /* Open() */
1313 : /************************************************************************/
1314 :
1315 1176 : int GDALGeoPackageDataset::Open(GDALOpenInfo *poOpenInfo,
1316 : const std::string &osFilenameInZip)
1317 : {
1318 1176 : m_osFilenameInZip = osFilenameInZip;
1319 1176 : CPLAssert(m_apoLayers.empty());
1320 1176 : CPLAssert(hDB == nullptr);
1321 :
1322 1176 : SetDescription(poOpenInfo->pszFilename);
1323 2352 : CPLString osFilename(poOpenInfo->pszFilename);
1324 2352 : CPLString osSubdatasetTableName;
1325 : GByte abyHeaderLetMeHerePlease[100];
1326 1176 : const GByte *pabyHeader = poOpenInfo->pabyHeader;
1327 1176 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GPKG:"))
1328 : {
1329 242 : char **papszTokens = CSLTokenizeString2(poOpenInfo->pszFilename, ":",
1330 : CSLT_HONOURSTRINGS);
1331 242 : int nCount = CSLCount(papszTokens);
1332 242 : if (nCount < 2)
1333 : {
1334 0 : CSLDestroy(papszTokens);
1335 0 : return FALSE;
1336 : }
1337 :
1338 242 : if (nCount <= 3)
1339 : {
1340 240 : osFilename = papszTokens[1];
1341 : }
1342 : /* GPKG:C:\BLA.GPKG:foo */
1343 2 : else if (nCount == 4 && strlen(papszTokens[1]) == 1 &&
1344 2 : (papszTokens[2][0] == '/' || papszTokens[2][0] == '\\'))
1345 : {
1346 2 : osFilename = CPLString(papszTokens[1]) + ":" + papszTokens[2];
1347 : }
1348 : // GPKG:/vsicurl/http[s]://[user:passwd@]example.com[:8080]/foo.gpkg:bar
1349 0 : else if (/*nCount >= 4 && */
1350 0 : (EQUAL(papszTokens[1], "/vsicurl/http") ||
1351 0 : EQUAL(papszTokens[1], "/vsicurl/https")))
1352 : {
1353 0 : osFilename = CPLString(papszTokens[1]);
1354 0 : for (int i = 2; i < nCount - 1; i++)
1355 : {
1356 0 : osFilename += ':';
1357 0 : osFilename += papszTokens[i];
1358 : }
1359 : }
1360 242 : if (nCount >= 3)
1361 14 : osSubdatasetTableName = papszTokens[nCount - 1];
1362 :
1363 242 : CSLDestroy(papszTokens);
1364 242 : VSILFILE *fp = VSIFOpenL(osFilename, "rb");
1365 242 : if (fp != nullptr)
1366 : {
1367 242 : VSIFReadL(abyHeaderLetMeHerePlease, 1, 100, fp);
1368 242 : VSIFCloseL(fp);
1369 : }
1370 242 : pabyHeader = abyHeaderLetMeHerePlease;
1371 : }
1372 934 : else if (poOpenInfo->pabyHeader &&
1373 934 : STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
1374 : "SQLite format 3"))
1375 : {
1376 928 : m_bCallUndeclareFileNotToOpen = true;
1377 928 : GDALOpenInfoDeclareFileNotToOpen(osFilename, poOpenInfo->pabyHeader,
1378 : poOpenInfo->nHeaderBytes);
1379 : }
1380 :
1381 1176 : eAccess = poOpenInfo->eAccess;
1382 1176 : if (!m_osFilenameInZip.empty())
1383 : {
1384 1 : m_pszFilename = CPLStrdup(CPLSPrintf(
1385 : "/vsizip/{%s}/%s", osFilename.c_str(), m_osFilenameInZip.c_str()));
1386 : }
1387 : else
1388 : {
1389 1175 : m_pszFilename = CPLStrdup(osFilename);
1390 : }
1391 :
1392 1176 : if (poOpenInfo->papszOpenOptions)
1393 : {
1394 100 : CSLDestroy(papszOpenOptions);
1395 100 : papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
1396 : }
1397 :
1398 : #ifdef ENABLE_SQL_GPKG_FORMAT
1399 1176 : if (poOpenInfo->pabyHeader &&
1400 934 : STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
1401 5 : "-- SQL GPKG") &&
1402 5 : poOpenInfo->fpL != nullptr)
1403 : {
1404 5 : if (sqlite3_open_v2(":memory:", &hDB, SQLITE_OPEN_READWRITE, nullptr) !=
1405 : SQLITE_OK)
1406 : {
1407 0 : return FALSE;
1408 : }
1409 :
1410 5 : InstallSQLFunctions();
1411 :
1412 : // Ingest the lines of the dump
1413 5 : VSIFSeekL(poOpenInfo->fpL, 0, SEEK_SET);
1414 : const char *pszLine;
1415 76 : while ((pszLine = CPLReadLineL(poOpenInfo->fpL)) != nullptr)
1416 : {
1417 71 : if (STARTS_WITH(pszLine, "--"))
1418 5 : continue;
1419 :
1420 66 : if (!SQLCheckLineIsSafe(pszLine))
1421 0 : return false;
1422 :
1423 66 : char *pszErrMsg = nullptr;
1424 66 : if (sqlite3_exec(hDB, pszLine, nullptr, nullptr, &pszErrMsg) !=
1425 : SQLITE_OK)
1426 : {
1427 0 : if (pszErrMsg)
1428 0 : CPLDebug("SQLITE", "Error %s", pszErrMsg);
1429 : }
1430 66 : sqlite3_free(pszErrMsg);
1431 5 : }
1432 : }
1433 :
1434 1171 : else if (pabyHeader != nullptr)
1435 : #endif
1436 : {
1437 1171 : if (poOpenInfo->fpL)
1438 : {
1439 : // See above comment about -wal locking for the importance of
1440 : // closing that file, prior to calling sqlite3_open()
1441 829 : VSIFCloseL(poOpenInfo->fpL);
1442 829 : poOpenInfo->fpL = nullptr;
1443 : }
1444 :
1445 : /* See if we can open the SQLite database */
1446 1171 : if (!OpenOrCreateDB(GetUpdate() ? SQLITE_OPEN_READWRITE
1447 : : SQLITE_OPEN_READONLY))
1448 2 : return FALSE;
1449 :
1450 1169 : memcpy(&m_nApplicationId, pabyHeader + knApplicationIdPos, 4);
1451 1169 : m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
1452 1169 : memcpy(&m_nUserVersion, pabyHeader + knUserVersionPos, 4);
1453 1169 : m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
1454 1169 : if (m_nApplicationId == GP10_APPLICATION_ID)
1455 : {
1456 7 : CPLDebug("GPKG", "GeoPackage v1.0");
1457 : }
1458 1162 : else if (m_nApplicationId == GP11_APPLICATION_ID)
1459 : {
1460 2 : CPLDebug("GPKG", "GeoPackage v1.1");
1461 : }
1462 1160 : else if (m_nApplicationId == GPKG_APPLICATION_ID &&
1463 1157 : m_nUserVersion >= GPKG_1_2_VERSION)
1464 : {
1465 1155 : CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
1466 1155 : (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
1467 : }
1468 : }
1469 :
1470 : /* Requirement 6: The SQLite PRAGMA integrity_check SQL command SHALL return
1471 : * “ok” */
1472 : /* http://opengis.github.io/geopackage/#_file_integrity */
1473 : /* Disable integrity check by default, since it is expensive on big files */
1474 1174 : if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_INTEGRITY_CHECK", "NO")) &&
1475 0 : OGRERR_NONE != PragmaCheck("integrity_check", "ok", 1))
1476 : {
1477 0 : CPLError(CE_Failure, CPLE_AppDefined,
1478 : "pragma integrity_check on '%s' failed", m_pszFilename);
1479 0 : return FALSE;
1480 : }
1481 :
1482 : /* Requirement 7: The SQLite PRAGMA foreign_key_check() SQL with no */
1483 : /* parameter value SHALL return an empty result set */
1484 : /* http://opengis.github.io/geopackage/#_file_integrity */
1485 : /* Disable the check by default, since it is to corrupt databases, and */
1486 : /* that causes issues to downstream software that can't open them. */
1487 1174 : if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_FOREIGN_KEY_CHECK", "NO")) &&
1488 0 : OGRERR_NONE != PragmaCheck("foreign_key_check", "", 0))
1489 : {
1490 0 : CPLError(CE_Failure, CPLE_AppDefined,
1491 : "pragma foreign_key_check on '%s' failed.", m_pszFilename);
1492 0 : return FALSE;
1493 : }
1494 :
1495 : /* Check for requirement metadata tables */
1496 : /* Requirement 10: gpkg_spatial_ref_sys must exist */
1497 : /* Requirement 13: gpkg_contents must exist */
1498 1174 : if (SQLGetInteger(hDB,
1499 : "SELECT COUNT(*) FROM sqlite_master WHERE "
1500 : "name IN ('gpkg_spatial_ref_sys', 'gpkg_contents') AND "
1501 : "type IN ('table', 'view')",
1502 1174 : nullptr) != 2)
1503 : {
1504 0 : CPLError(CE_Failure, CPLE_AppDefined,
1505 : "At least one of the required GeoPackage tables, "
1506 : "gpkg_spatial_ref_sys or gpkg_contents, is missing");
1507 0 : return FALSE;
1508 : }
1509 :
1510 1174 : DetectSpatialRefSysColumns();
1511 :
1512 : #ifdef ENABLE_GPKG_OGR_CONTENTS
1513 1174 : if (SQLGetInteger(hDB,
1514 : "SELECT 1 FROM sqlite_master WHERE "
1515 : "name = 'gpkg_ogr_contents' AND type = 'table'",
1516 1174 : nullptr) == 1)
1517 : {
1518 1166 : m_bHasGPKGOGRContents = true;
1519 : }
1520 : #endif
1521 :
1522 1174 : CheckUnknownExtensions();
1523 :
1524 1174 : int bRet = FALSE;
1525 1174 : bool bHasGPKGExtRelations = false;
1526 1174 : if (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR)
1527 : {
1528 987 : m_bHasGPKGGeometryColumns =
1529 987 : SQLGetInteger(hDB,
1530 : "SELECT 1 FROM sqlite_master WHERE "
1531 : "name = 'gpkg_geometry_columns' AND "
1532 : "type IN ('table', 'view')",
1533 987 : nullptr) == 1;
1534 987 : bHasGPKGExtRelations = HasGpkgextRelationsTable();
1535 : }
1536 1174 : if (m_bHasGPKGGeometryColumns)
1537 : {
1538 : /* Load layer definitions for all tables in gpkg_contents &
1539 : * gpkg_geometry_columns */
1540 : /* and non-spatial tables as well */
1541 : std::string osSQL =
1542 : "SELECT c.table_name, c.identifier, 1 as is_spatial, "
1543 : "g.column_name, g.geometry_type_name, g.z, g.m, c.min_x, c.min_y, "
1544 : "c.max_x, c.max_y, 1 AS is_in_gpkg_contents, "
1545 : "(SELECT type FROM sqlite_master WHERE lower(name) = "
1546 : "lower(c.table_name) AND type IN ('table', 'view')) AS object_type "
1547 : " FROM gpkg_geometry_columns g "
1548 : " JOIN gpkg_contents c ON (g.table_name = c.table_name)"
1549 : " WHERE "
1550 : " c.table_name <> 'ogr_empty_table' AND"
1551 : " c.data_type = 'features' "
1552 : // aspatial: Was the only method available in OGR 2.0 and 2.1
1553 : // attributes: GPKG 1.2 or later
1554 : "UNION ALL "
1555 : "SELECT table_name, identifier, 0 as is_spatial, NULL, NULL, 0, 0, "
1556 : "0 AS xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 1 AS "
1557 : "is_in_gpkg_contents, "
1558 : "(SELECT type FROM sqlite_master WHERE lower(name) = "
1559 : "lower(table_name) AND type IN ('table', 'view')) AS object_type "
1560 : " FROM gpkg_contents"
1561 986 : " WHERE data_type IN ('aspatial', 'attributes') ";
1562 :
1563 1972 : const char *pszListAllTables = CSLFetchNameValueDef(
1564 986 : poOpenInfo->papszOpenOptions, "LIST_ALL_TABLES", "AUTO");
1565 986 : bool bHasASpatialOrAttributes = HasGDALAspatialExtension();
1566 986 : if (!bHasASpatialOrAttributes)
1567 : {
1568 : auto oResultTable =
1569 : SQLQuery(hDB, "SELECT * FROM gpkg_contents WHERE "
1570 985 : "data_type = 'attributes' LIMIT 1");
1571 985 : bHasASpatialOrAttributes =
1572 985 : (oResultTable && oResultTable->RowCount() == 1);
1573 : }
1574 986 : if (bHasGPKGExtRelations)
1575 : {
1576 : osSQL += "UNION ALL "
1577 : "SELECT mapping_table_name, mapping_table_name, 0 as "
1578 : "is_spatial, NULL, NULL, 0, 0, 0 AS "
1579 : "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
1580 : "is_in_gpkg_contents, 'table' AS object_type "
1581 : "FROM gpkgext_relations WHERE "
1582 : "lower(mapping_table_name) NOT IN (SELECT "
1583 : "lower(table_name) FROM gpkg_contents) AND "
1584 : "EXISTS (SELECT 1 FROM sqlite_master WHERE "
1585 : "type IN ('table', 'view') AND "
1586 18 : "lower(name) = lower(mapping_table_name))";
1587 : }
1588 986 : if (EQUAL(pszListAllTables, "YES") ||
1589 985 : (!bHasASpatialOrAttributes && EQUAL(pszListAllTables, "AUTO")))
1590 : {
1591 : // vgpkg_ is Spatialite virtual table
1592 : osSQL +=
1593 : "UNION ALL "
1594 : "SELECT name, name, 0 as is_spatial, NULL, NULL, 0, 0, 0 AS "
1595 : "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
1596 : "is_in_gpkg_contents, type AS object_type "
1597 : "FROM sqlite_master WHERE type IN ('table', 'view') "
1598 : "AND name NOT LIKE 'gpkg_%' "
1599 : "AND name NOT LIKE 'vgpkg_%' "
1600 : "AND name NOT LIKE 'rtree_%' AND name NOT LIKE 'sqlite_%' "
1601 : // Avoid reading those views from simple_sewer_features.gpkg
1602 : "AND name NOT IN ('st_spatial_ref_sys', 'spatial_ref_sys', "
1603 : "'st_geometry_columns', 'geometry_columns') "
1604 : "AND lower(name) NOT IN (SELECT lower(table_name) FROM "
1605 927 : "gpkg_contents)";
1606 927 : if (bHasGPKGExtRelations)
1607 : {
1608 : osSQL += " AND lower(name) NOT IN (SELECT "
1609 : "lower(mapping_table_name) FROM "
1610 13 : "gpkgext_relations)";
1611 : }
1612 : }
1613 986 : const int nTableLimit = GetOGRTableLimit();
1614 986 : if (nTableLimit > 0)
1615 : {
1616 986 : osSQL += " LIMIT ";
1617 986 : osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1618 : }
1619 :
1620 986 : auto oResult = SQLQuery(hDB, osSQL.c_str());
1621 986 : if (!oResult)
1622 : {
1623 0 : return FALSE;
1624 : }
1625 :
1626 986 : if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
1627 : {
1628 1 : CPLError(CE_Warning, CPLE_AppDefined,
1629 : "File has more than %d vector tables. "
1630 : "Limiting to first %d (can be overridden with "
1631 : "OGR_TABLE_LIMIT config option)",
1632 : nTableLimit, nTableLimit);
1633 1 : oResult->LimitRowCount(nTableLimit);
1634 : }
1635 :
1636 986 : if (oResult->RowCount() > 0)
1637 : {
1638 870 : bRet = TRUE;
1639 :
1640 870 : m_apoLayers.reserve(oResult->RowCount());
1641 :
1642 1740 : std::map<std::string, int> oMapTableRefCount;
1643 3922 : for (int i = 0; i < oResult->RowCount(); i++)
1644 : {
1645 3052 : const char *pszTableName = oResult->GetValue(0, i);
1646 3052 : if (pszTableName == nullptr)
1647 0 : continue;
1648 3052 : if (++oMapTableRefCount[pszTableName] == 2)
1649 : {
1650 : // This should normally not happen if all constraints are
1651 : // properly set
1652 2 : CPLError(CE_Warning, CPLE_AppDefined,
1653 : "Table %s appearing several times in "
1654 : "gpkg_contents and/or gpkg_geometry_columns",
1655 : pszTableName);
1656 : }
1657 : }
1658 :
1659 1740 : std::set<std::string> oExistingLayers;
1660 3922 : for (int i = 0; i < oResult->RowCount(); i++)
1661 : {
1662 3052 : const char *pszTableName = oResult->GetValue(0, i);
1663 3052 : if (pszTableName == nullptr)
1664 2 : continue;
1665 : const bool bTableHasSeveralGeomColumns =
1666 3052 : oMapTableRefCount[pszTableName] > 1;
1667 3052 : bool bIsSpatial = CPL_TO_BOOL(oResult->GetValueAsInteger(2, i));
1668 3052 : const char *pszGeomColName = oResult->GetValue(3, i);
1669 3052 : const char *pszGeomType = oResult->GetValue(4, i);
1670 3052 : const char *pszZ = oResult->GetValue(5, i);
1671 3052 : const char *pszM = oResult->GetValue(6, i);
1672 : bool bIsInGpkgContents =
1673 3052 : CPL_TO_BOOL(oResult->GetValueAsInteger(11, i));
1674 3052 : if (!bIsInGpkgContents)
1675 44 : m_bNonSpatialTablesNonRegisteredInGpkgContentsFound = true;
1676 3052 : const char *pszObjectType = oResult->GetValue(12, i);
1677 3052 : if (pszObjectType == nullptr ||
1678 3051 : !(EQUAL(pszObjectType, "table") ||
1679 21 : EQUAL(pszObjectType, "view")))
1680 : {
1681 1 : CPLError(CE_Warning, CPLE_AppDefined,
1682 : "Table/view %s is referenced in gpkg_contents, "
1683 : "but does not exist",
1684 : pszTableName);
1685 1 : continue;
1686 : }
1687 : // Non-standard and undocumented behavior:
1688 : // if the same table appears to have several geometry columns,
1689 : // handle it for now as multiple layers named
1690 : // "table_name (geom_col_name)"
1691 : // The way we handle that might change in the future (e.g
1692 : // could be a single layer with multiple geometry columns)
1693 : const std::string osLayerNameWithGeomColName =
1694 5886 : pszGeomColName ? std::string(pszTableName) + " (" +
1695 : pszGeomColName + ')'
1696 6102 : : std::string(pszTableName);
1697 3051 : if (cpl::contains(oExistingLayers, osLayerNameWithGeomColName))
1698 1 : continue;
1699 3050 : oExistingLayers.insert(osLayerNameWithGeomColName);
1700 : const std::string osLayerName = bTableHasSeveralGeomColumns
1701 : ? osLayerNameWithGeomColName
1702 6100 : : std::string(pszTableName);
1703 : auto poLayer = std::make_unique<OGRGeoPackageTableLayer>(
1704 6100 : this, osLayerName.c_str());
1705 3050 : bool bHasZ = pszZ && atoi(pszZ) > 0;
1706 3050 : bool bHasM = pszM && atoi(pszM) > 0;
1707 3050 : if (pszGeomType && EQUAL(pszGeomType, "GEOMETRY"))
1708 : {
1709 604 : if (pszZ && atoi(pszZ) == 2)
1710 7 : bHasZ = false;
1711 604 : if (pszM && atoi(pszM) == 2)
1712 6 : bHasM = false;
1713 : }
1714 3050 : poLayer->SetOpeningParameters(
1715 : pszTableName, pszObjectType, bIsInGpkgContents, bIsSpatial,
1716 : pszGeomColName, pszGeomType, bHasZ, bHasM);
1717 3050 : m_apoLayers.push_back(std::move(poLayer));
1718 : }
1719 : }
1720 : }
1721 :
1722 1174 : bool bHasTileMatrixSet = false;
1723 1174 : if (poOpenInfo->nOpenFlags & GDAL_OF_RASTER)
1724 : {
1725 560 : bHasTileMatrixSet = SQLGetInteger(hDB,
1726 : "SELECT 1 FROM sqlite_master WHERE "
1727 : "name = 'gpkg_tile_matrix_set' AND "
1728 : "type IN ('table', 'view')",
1729 : nullptr) == 1;
1730 : }
1731 1174 : if (bHasTileMatrixSet)
1732 : {
1733 : std::string osSQL =
1734 : "SELECT c.table_name, c.identifier, c.description, c.srs_id, "
1735 : "c.min_x, c.min_y, c.max_x, c.max_y, "
1736 : "tms.min_x, tms.min_y, tms.max_x, tms.max_y, c.data_type "
1737 : "FROM gpkg_contents c JOIN gpkg_tile_matrix_set tms ON "
1738 : "c.table_name = tms.table_name WHERE "
1739 559 : "data_type IN ('tiles', '2d-gridded-coverage')";
1740 559 : if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE"))
1741 : osSubdatasetTableName =
1742 2 : CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE");
1743 559 : if (!osSubdatasetTableName.empty())
1744 : {
1745 16 : char *pszTmp = sqlite3_mprintf(" AND c.table_name='%q'",
1746 : osSubdatasetTableName.c_str());
1747 16 : osSQL += pszTmp;
1748 16 : sqlite3_free(pszTmp);
1749 16 : SetPhysicalFilename(osFilename.c_str());
1750 : }
1751 559 : const int nTableLimit = GetOGRTableLimit();
1752 559 : if (nTableLimit > 0)
1753 : {
1754 559 : osSQL += " LIMIT ";
1755 559 : osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1756 : }
1757 :
1758 559 : auto oResult = SQLQuery(hDB, osSQL.c_str());
1759 559 : if (!oResult)
1760 : {
1761 0 : return FALSE;
1762 : }
1763 :
1764 559 : if (oResult->RowCount() == 0 && !osSubdatasetTableName.empty())
1765 : {
1766 1 : CPLError(CE_Failure, CPLE_AppDefined,
1767 : "Cannot find table '%s' in GeoPackage dataset",
1768 : osSubdatasetTableName.c_str());
1769 : }
1770 558 : else if (oResult->RowCount() == 1)
1771 : {
1772 274 : const char *pszTableName = oResult->GetValue(0, 0);
1773 274 : const char *pszIdentifier = oResult->GetValue(1, 0);
1774 274 : const char *pszDescription = oResult->GetValue(2, 0);
1775 274 : const char *pszSRSId = oResult->GetValue(3, 0);
1776 274 : const char *pszMinX = oResult->GetValue(4, 0);
1777 274 : const char *pszMinY = oResult->GetValue(5, 0);
1778 274 : const char *pszMaxX = oResult->GetValue(6, 0);
1779 274 : const char *pszMaxY = oResult->GetValue(7, 0);
1780 274 : const char *pszTMSMinX = oResult->GetValue(8, 0);
1781 274 : const char *pszTMSMinY = oResult->GetValue(9, 0);
1782 274 : const char *pszTMSMaxX = oResult->GetValue(10, 0);
1783 274 : const char *pszTMSMaxY = oResult->GetValue(11, 0);
1784 274 : const char *pszDataType = oResult->GetValue(12, 0);
1785 274 : if (pszTableName && pszTMSMinX && pszTMSMinY && pszTMSMaxX &&
1786 : pszTMSMaxY)
1787 : {
1788 548 : bRet = OpenRaster(
1789 : pszTableName, pszIdentifier, pszDescription,
1790 274 : pszSRSId ? atoi(pszSRSId) : 0, CPLAtof(pszTMSMinX),
1791 : CPLAtof(pszTMSMinY), CPLAtof(pszTMSMaxX),
1792 : CPLAtof(pszTMSMaxY), pszMinX, pszMinY, pszMaxX, pszMaxY,
1793 274 : EQUAL(pszDataType, "tiles"), poOpenInfo->papszOpenOptions);
1794 : }
1795 : }
1796 284 : else if (oResult->RowCount() >= 1)
1797 : {
1798 5 : bRet = TRUE;
1799 :
1800 5 : if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
1801 : {
1802 1 : CPLError(CE_Warning, CPLE_AppDefined,
1803 : "File has more than %d raster tables. "
1804 : "Limiting to first %d (can be overridden with "
1805 : "OGR_TABLE_LIMIT config option)",
1806 : nTableLimit, nTableLimit);
1807 1 : oResult->LimitRowCount(nTableLimit);
1808 : }
1809 :
1810 5 : int nSDSCount = 0;
1811 2013 : for (int i = 0; i < oResult->RowCount(); i++)
1812 : {
1813 2008 : const char *pszTableName = oResult->GetValue(0, i);
1814 2008 : const char *pszIdentifier = oResult->GetValue(1, i);
1815 2008 : if (pszTableName == nullptr)
1816 0 : continue;
1817 : m_aosSubDatasets.AddNameValue(
1818 : CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
1819 2008 : CPLSPrintf("GPKG:%s:%s", m_pszFilename, pszTableName));
1820 : m_aosSubDatasets.AddNameValue(
1821 : CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
1822 : pszIdentifier
1823 2008 : ? CPLSPrintf("%s - %s", pszTableName, pszIdentifier)
1824 4016 : : pszTableName);
1825 2008 : nSDSCount++;
1826 : }
1827 : }
1828 : }
1829 :
1830 1174 : if (!bRet && (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR))
1831 : {
1832 32 : if ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE))
1833 : {
1834 21 : bRet = TRUE;
1835 : }
1836 : else
1837 : {
1838 11 : CPLDebug("GPKG",
1839 : "This GeoPackage has no vector content and is opened "
1840 : "in read-only mode. If you open it in update mode, "
1841 : "opening will be successful.");
1842 : }
1843 : }
1844 :
1845 1174 : if (eAccess == GA_Update)
1846 : {
1847 225 : FixupWrongRTreeTrigger();
1848 225 : FixupWrongMedataReferenceColumnNameUpdate();
1849 : }
1850 :
1851 1174 : SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
1852 :
1853 1174 : return bRet;
1854 : }
1855 :
1856 : /************************************************************************/
1857 : /* DetectSpatialRefSysColumns() */
1858 : /************************************************************************/
1859 :
1860 1184 : void GDALGeoPackageDataset::DetectSpatialRefSysColumns()
1861 : {
1862 : // Detect definition_12_063 column
1863 : {
1864 1184 : sqlite3_stmt *hSQLStmt = nullptr;
1865 1184 : int rc = sqlite3_prepare_v2(
1866 : hDB, "SELECT definition_12_063 FROM gpkg_spatial_ref_sys ", -1,
1867 : &hSQLStmt, nullptr);
1868 1184 : if (rc == SQLITE_OK)
1869 : {
1870 85 : m_bHasDefinition12_063 = true;
1871 85 : sqlite3_finalize(hSQLStmt);
1872 : }
1873 : }
1874 :
1875 : // Detect epoch column
1876 1184 : if (m_bHasDefinition12_063)
1877 : {
1878 85 : sqlite3_stmt *hSQLStmt = nullptr;
1879 : int rc =
1880 85 : sqlite3_prepare_v2(hDB, "SELECT epoch FROM gpkg_spatial_ref_sys ",
1881 : -1, &hSQLStmt, nullptr);
1882 85 : if (rc == SQLITE_OK)
1883 : {
1884 76 : m_bHasEpochColumn = true;
1885 76 : sqlite3_finalize(hSQLStmt);
1886 : }
1887 : }
1888 1184 : }
1889 :
1890 : /************************************************************************/
1891 : /* FixupWrongRTreeTrigger() */
1892 : /************************************************************************/
1893 :
1894 225 : void GDALGeoPackageDataset::FixupWrongRTreeTrigger()
1895 : {
1896 : auto oResult = SQLQuery(
1897 : hDB,
1898 : "SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND "
1899 225 : "NAME LIKE 'rtree_%_update3' AND sql LIKE '% AFTER UPDATE OF % ON %'");
1900 225 : if (oResult == nullptr)
1901 0 : return;
1902 225 : if (oResult->RowCount() > 0)
1903 : {
1904 1 : CPLDebug("GPKG", "Fixing incorrect trigger(s) related to RTree");
1905 : }
1906 227 : for (int i = 0; i < oResult->RowCount(); i++)
1907 : {
1908 2 : const char *pszName = oResult->GetValue(0, i);
1909 2 : const char *pszSQL = oResult->GetValue(1, i);
1910 2 : const char *pszPtr1 = strstr(pszSQL, " AFTER UPDATE OF ");
1911 2 : if (pszPtr1)
1912 : {
1913 2 : const char *pszPtr = pszPtr1 + strlen(" AFTER UPDATE OF ");
1914 : // Skipping over geometry column name
1915 4 : while (*pszPtr == ' ')
1916 2 : pszPtr++;
1917 2 : if (pszPtr[0] == '"' || pszPtr[0] == '\'')
1918 : {
1919 1 : char chStringDelim = pszPtr[0];
1920 1 : pszPtr++;
1921 9 : while (*pszPtr != '\0' && *pszPtr != chStringDelim)
1922 : {
1923 8 : if (*pszPtr == '\\' && pszPtr[1] == chStringDelim)
1924 0 : pszPtr += 2;
1925 : else
1926 8 : pszPtr += 1;
1927 : }
1928 1 : if (*pszPtr == chStringDelim)
1929 1 : pszPtr++;
1930 : }
1931 : else
1932 : {
1933 1 : pszPtr++;
1934 8 : while (*pszPtr != ' ')
1935 7 : pszPtr++;
1936 : }
1937 2 : if (*pszPtr == ' ')
1938 : {
1939 2 : SQLCommand(hDB,
1940 4 : ("DROP TRIGGER \"" + SQLEscapeName(pszName) + "\"")
1941 : .c_str());
1942 4 : CPLString newSQL;
1943 2 : newSQL.assign(pszSQL, pszPtr1 - pszSQL);
1944 2 : newSQL += " AFTER UPDATE";
1945 2 : newSQL += pszPtr;
1946 2 : SQLCommand(hDB, newSQL);
1947 : }
1948 : }
1949 : }
1950 : }
1951 :
1952 : /************************************************************************/
1953 : /* FixupWrongMedataReferenceColumnNameUpdate() */
1954 : /************************************************************************/
1955 :
1956 225 : void GDALGeoPackageDataset::FixupWrongMedataReferenceColumnNameUpdate()
1957 : {
1958 : // Fix wrong trigger that was generated by GDAL < 2.4.0
1959 : // See https://github.com/qgis/QGIS/issues/42768
1960 : auto oResult = SQLQuery(
1961 : hDB, "SELECT sql FROM sqlite_master WHERE type = 'trigger' AND "
1962 : "NAME ='gpkg_metadata_reference_column_name_update' AND "
1963 225 : "sql LIKE '%column_nameIS%'");
1964 225 : if (oResult == nullptr)
1965 0 : return;
1966 225 : if (oResult->RowCount() == 1)
1967 : {
1968 1 : CPLDebug("GPKG", "Fixing incorrect trigger "
1969 : "gpkg_metadata_reference_column_name_update");
1970 1 : const char *pszSQL = oResult->GetValue(0, 0);
1971 : std::string osNewSQL(
1972 3 : CPLString(pszSQL).replaceAll("column_nameIS", "column_name IS"));
1973 :
1974 1 : SQLCommand(hDB,
1975 : "DROP TRIGGER gpkg_metadata_reference_column_name_update");
1976 1 : SQLCommand(hDB, osNewSQL.c_str());
1977 : }
1978 : }
1979 :
1980 : /************************************************************************/
1981 : /* ClearCachedRelationships() */
1982 : /************************************************************************/
1983 :
1984 36 : void GDALGeoPackageDataset::ClearCachedRelationships()
1985 : {
1986 36 : m_bHasPopulatedRelationships = false;
1987 36 : m_osMapRelationships.clear();
1988 36 : }
1989 :
1990 : /************************************************************************/
1991 : /* LoadRelationships() */
1992 : /************************************************************************/
1993 :
1994 82 : void GDALGeoPackageDataset::LoadRelationships() const
1995 : {
1996 82 : m_osMapRelationships.clear();
1997 :
1998 82 : std::vector<std::string> oExcludedTables;
1999 82 : if (HasGpkgextRelationsTable())
2000 : {
2001 37 : LoadRelationshipsUsingRelatedTablesExtension();
2002 :
2003 89 : for (const auto &oRelationship : m_osMapRelationships)
2004 : {
2005 : oExcludedTables.emplace_back(
2006 52 : oRelationship.second->GetMappingTableName());
2007 : }
2008 : }
2009 :
2010 : // Also load relationships defined using foreign keys (i.e. one-to-many
2011 : // relationships). Here we must exclude any relationships defined from the
2012 : // related tables extension, we don't want them included twice.
2013 82 : LoadRelationshipsFromForeignKeys(oExcludedTables);
2014 82 : m_bHasPopulatedRelationships = true;
2015 82 : }
2016 :
2017 : /************************************************************************/
2018 : /* LoadRelationshipsUsingRelatedTablesExtension() */
2019 : /************************************************************************/
2020 :
2021 37 : void GDALGeoPackageDataset::LoadRelationshipsUsingRelatedTablesExtension() const
2022 : {
2023 37 : m_osMapRelationships.clear();
2024 :
2025 : auto oResultTable = SQLQuery(
2026 37 : hDB, "SELECT base_table_name, base_primary_column, "
2027 : "related_table_name, related_primary_column, relation_name, "
2028 74 : "mapping_table_name FROM gpkgext_relations");
2029 37 : if (oResultTable && oResultTable->RowCount() > 0)
2030 : {
2031 86 : for (int i = 0; i < oResultTable->RowCount(); i++)
2032 : {
2033 53 : const char *pszBaseTableName = oResultTable->GetValue(0, i);
2034 53 : if (!pszBaseTableName)
2035 : {
2036 0 : CPLError(CE_Warning, CPLE_AppDefined,
2037 : "Could not retrieve base_table_name from "
2038 : "gpkgext_relations");
2039 1 : continue;
2040 : }
2041 53 : const char *pszBasePrimaryColumn = oResultTable->GetValue(1, i);
2042 53 : if (!pszBasePrimaryColumn)
2043 : {
2044 0 : CPLError(CE_Warning, CPLE_AppDefined,
2045 : "Could not retrieve base_primary_column from "
2046 : "gpkgext_relations");
2047 0 : continue;
2048 : }
2049 53 : const char *pszRelatedTableName = oResultTable->GetValue(2, i);
2050 53 : if (!pszRelatedTableName)
2051 : {
2052 0 : CPLError(CE_Warning, CPLE_AppDefined,
2053 : "Could not retrieve related_table_name from "
2054 : "gpkgext_relations");
2055 0 : continue;
2056 : }
2057 53 : const char *pszRelatedPrimaryColumn = oResultTable->GetValue(3, i);
2058 53 : if (!pszRelatedPrimaryColumn)
2059 : {
2060 0 : CPLError(CE_Warning, CPLE_AppDefined,
2061 : "Could not retrieve related_primary_column from "
2062 : "gpkgext_relations");
2063 0 : continue;
2064 : }
2065 53 : const char *pszRelationName = oResultTable->GetValue(4, i);
2066 53 : if (!pszRelationName)
2067 : {
2068 0 : CPLError(
2069 : CE_Warning, CPLE_AppDefined,
2070 : "Could not retrieve relation_name from gpkgext_relations");
2071 0 : continue;
2072 : }
2073 53 : const char *pszMappingTableName = oResultTable->GetValue(5, i);
2074 53 : if (!pszMappingTableName)
2075 : {
2076 0 : CPLError(CE_Warning, CPLE_AppDefined,
2077 : "Could not retrieve mapping_table_name from "
2078 : "gpkgext_relations");
2079 0 : continue;
2080 : }
2081 :
2082 : // confirm that mapping table exists
2083 : char *pszSQL =
2084 53 : sqlite3_mprintf("SELECT 1 FROM sqlite_master WHERE "
2085 : "name='%q' AND type IN ('table', 'view')",
2086 : pszMappingTableName);
2087 53 : const int nMappingTableCount = SQLGetInteger(hDB, pszSQL, nullptr);
2088 53 : sqlite3_free(pszSQL);
2089 :
2090 55 : if (nMappingTableCount < 1 &&
2091 2 : !const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
2092 2 : pszMappingTableName))
2093 : {
2094 1 : CPLError(CE_Warning, CPLE_AppDefined,
2095 : "Relationship mapping table %s does not exist",
2096 : pszMappingTableName);
2097 1 : continue;
2098 : }
2099 :
2100 : const std::string osRelationName = GenerateNameForRelationship(
2101 104 : pszBaseTableName, pszRelatedTableName, pszRelationName);
2102 :
2103 104 : std::string osType{};
2104 : // defined requirement classes -- for these types the relation name
2105 : // will be specific string value from the related tables extension.
2106 : // In this case we need to construct a unique relationship name
2107 : // based on the related tables
2108 52 : if (EQUAL(pszRelationName, "media") ||
2109 40 : EQUAL(pszRelationName, "simple_attributes") ||
2110 40 : EQUAL(pszRelationName, "features") ||
2111 18 : EQUAL(pszRelationName, "attributes") ||
2112 2 : EQUAL(pszRelationName, "tiles"))
2113 : {
2114 50 : osType = pszRelationName;
2115 : }
2116 : else
2117 : {
2118 : // user defined types default to features
2119 2 : osType = "features";
2120 : }
2121 :
2122 : auto poRelationship = std::make_unique<GDALRelationship>(
2123 : osRelationName, pszBaseTableName, pszRelatedTableName,
2124 104 : GRC_MANY_TO_MANY);
2125 :
2126 104 : poRelationship->SetLeftTableFields({pszBasePrimaryColumn});
2127 104 : poRelationship->SetRightTableFields({pszRelatedPrimaryColumn});
2128 104 : poRelationship->SetLeftMappingTableFields({"base_id"});
2129 104 : poRelationship->SetRightMappingTableFields({"related_id"});
2130 52 : poRelationship->SetMappingTableName(pszMappingTableName);
2131 52 : poRelationship->SetRelatedTableType(osType);
2132 :
2133 52 : m_osMapRelationships[osRelationName] = std::move(poRelationship);
2134 : }
2135 : }
2136 37 : }
2137 :
2138 : /************************************************************************/
2139 : /* GenerateNameForRelationship() */
2140 : /************************************************************************/
2141 :
2142 76 : std::string GDALGeoPackageDataset::GenerateNameForRelationship(
2143 : const char *pszBaseTableName, const char *pszRelatedTableName,
2144 : const char *pszType)
2145 : {
2146 : // defined requirement classes -- for these types the relation name will be
2147 : // specific string value from the related tables extension. In this case we
2148 : // need to construct a unique relationship name based on the related tables
2149 76 : if (EQUAL(pszType, "media") || EQUAL(pszType, "simple_attributes") ||
2150 53 : EQUAL(pszType, "features") || EQUAL(pszType, "attributes") ||
2151 8 : EQUAL(pszType, "tiles"))
2152 : {
2153 136 : std::ostringstream stream;
2154 : stream << pszBaseTableName << '_' << pszRelatedTableName << '_'
2155 68 : << pszType;
2156 68 : return stream.str();
2157 : }
2158 : else
2159 : {
2160 : // user defined types default to features
2161 8 : return pszType;
2162 : }
2163 : }
2164 :
2165 : /************************************************************************/
2166 : /* ValidateRelationship() */
2167 : /************************************************************************/
2168 :
2169 28 : bool GDALGeoPackageDataset::ValidateRelationship(
2170 : const GDALRelationship *poRelationship, std::string &failureReason)
2171 : {
2172 :
2173 28 : if (poRelationship->GetCardinality() !=
2174 : GDALRelationshipCardinality::GRC_MANY_TO_MANY)
2175 : {
2176 3 : failureReason = "Only many to many relationships are supported";
2177 3 : return false;
2178 : }
2179 :
2180 50 : std::string osRelatedTableType = poRelationship->GetRelatedTableType();
2181 65 : if (!osRelatedTableType.empty() && osRelatedTableType != "features" &&
2182 30 : osRelatedTableType != "media" &&
2183 20 : osRelatedTableType != "simple_attributes" &&
2184 55 : osRelatedTableType != "attributes" && osRelatedTableType != "tiles")
2185 : {
2186 : failureReason =
2187 4 : ("Related table type " + osRelatedTableType +
2188 : " is not a valid value for the GeoPackage specification. "
2189 : "Valid values are: features, media, simple_attributes, "
2190 : "attributes, tiles.")
2191 2 : .c_str();
2192 2 : return false;
2193 : }
2194 :
2195 23 : const std::string &osLeftTableName = poRelationship->GetLeftTableName();
2196 23 : OGRGeoPackageLayer *poLeftTable = cpl::down_cast<OGRGeoPackageLayer *>(
2197 23 : GetLayerByName(osLeftTableName.c_str()));
2198 23 : if (!poLeftTable)
2199 : {
2200 4 : failureReason = ("Left table " + osLeftTableName +
2201 : " is not an existing layer in the dataset")
2202 2 : .c_str();
2203 2 : return false;
2204 : }
2205 21 : const std::string &osRightTableName = poRelationship->GetRightTableName();
2206 21 : OGRGeoPackageLayer *poRightTable = cpl::down_cast<OGRGeoPackageLayer *>(
2207 21 : GetLayerByName(osRightTableName.c_str()));
2208 21 : if (!poRightTable)
2209 : {
2210 4 : failureReason = ("Right table " + osRightTableName +
2211 : " is not an existing layer in the dataset")
2212 2 : .c_str();
2213 2 : return false;
2214 : }
2215 :
2216 19 : const auto &aosLeftTableFields = poRelationship->GetLeftTableFields();
2217 19 : if (aosLeftTableFields.empty())
2218 : {
2219 1 : failureReason = "No left table fields were specified";
2220 1 : return false;
2221 : }
2222 18 : else if (aosLeftTableFields.size() > 1)
2223 : {
2224 : failureReason = "Only a single left table field is permitted for the "
2225 1 : "GeoPackage specification";
2226 1 : return false;
2227 : }
2228 : else
2229 : {
2230 : // validate left field exists
2231 34 : if (poLeftTable->GetLayerDefn()->GetFieldIndex(
2232 37 : aosLeftTableFields[0].c_str()) < 0 &&
2233 3 : !EQUAL(poLeftTable->GetFIDColumn(), aosLeftTableFields[0].c_str()))
2234 : {
2235 2 : failureReason = ("Left table field " + aosLeftTableFields[0] +
2236 2 : " does not exist in " + osLeftTableName)
2237 1 : .c_str();
2238 1 : return false;
2239 : }
2240 : }
2241 :
2242 16 : const auto &aosRightTableFields = poRelationship->GetRightTableFields();
2243 16 : if (aosRightTableFields.empty())
2244 : {
2245 1 : failureReason = "No right table fields were specified";
2246 1 : return false;
2247 : }
2248 15 : else if (aosRightTableFields.size() > 1)
2249 : {
2250 : failureReason = "Only a single right table field is permitted for the "
2251 1 : "GeoPackage specification";
2252 1 : return false;
2253 : }
2254 : else
2255 : {
2256 : // validate right field exists
2257 28 : if (poRightTable->GetLayerDefn()->GetFieldIndex(
2258 32 : aosRightTableFields[0].c_str()) < 0 &&
2259 4 : !EQUAL(poRightTable->GetFIDColumn(),
2260 : aosRightTableFields[0].c_str()))
2261 : {
2262 4 : failureReason = ("Right table field " + aosRightTableFields[0] +
2263 4 : " does not exist in " + osRightTableName)
2264 2 : .c_str();
2265 2 : return false;
2266 : }
2267 : }
2268 :
2269 12 : return true;
2270 : }
2271 :
2272 : /************************************************************************/
2273 : /* InitRaster() */
2274 : /************************************************************************/
2275 :
2276 358 : bool GDALGeoPackageDataset::InitRaster(
2277 : GDALGeoPackageDataset *poParentDS, const char *pszTableName, double dfMinX,
2278 : double dfMinY, double dfMaxX, double dfMaxY, const char *pszContentsMinX,
2279 : const char *pszContentsMinY, const char *pszContentsMaxX,
2280 : const char *pszContentsMaxY, char **papszOpenOptionsIn,
2281 : const SQLResult &oResult, int nIdxInResult)
2282 : {
2283 358 : m_osRasterTable = pszTableName;
2284 358 : m_dfTMSMinX = dfMinX;
2285 358 : m_dfTMSMaxY = dfMaxY;
2286 :
2287 : // Despite prior checking, the type might be Binary and
2288 : // SQLResultGetValue() not working properly on it
2289 358 : int nZoomLevel = atoi(oResult.GetValue(0, nIdxInResult));
2290 358 : if (nZoomLevel < 0 || nZoomLevel > 65536)
2291 : {
2292 0 : return false;
2293 : }
2294 358 : double dfPixelXSize = CPLAtof(oResult.GetValue(1, nIdxInResult));
2295 358 : double dfPixelYSize = CPLAtof(oResult.GetValue(2, nIdxInResult));
2296 358 : if (dfPixelXSize <= 0 || dfPixelYSize <= 0)
2297 : {
2298 0 : return false;
2299 : }
2300 358 : int nTileWidth = atoi(oResult.GetValue(3, nIdxInResult));
2301 358 : int nTileHeight = atoi(oResult.GetValue(4, nIdxInResult));
2302 358 : if (nTileWidth <= 0 || nTileWidth > 65536 || nTileHeight <= 0 ||
2303 : nTileHeight > 65536)
2304 : {
2305 0 : return false;
2306 : }
2307 : int nTileMatrixWidth = static_cast<int>(
2308 716 : std::min(static_cast<GIntBig>(INT_MAX),
2309 358 : CPLAtoGIntBig(oResult.GetValue(5, nIdxInResult))));
2310 : int nTileMatrixHeight = static_cast<int>(
2311 716 : std::min(static_cast<GIntBig>(INT_MAX),
2312 358 : CPLAtoGIntBig(oResult.GetValue(6, nIdxInResult))));
2313 358 : if (nTileMatrixWidth <= 0 || nTileMatrixHeight <= 0)
2314 : {
2315 0 : return false;
2316 : }
2317 :
2318 : /* Use content bounds in priority over tile_matrix_set bounds */
2319 358 : double dfGDALMinX = dfMinX;
2320 358 : double dfGDALMinY = dfMinY;
2321 358 : double dfGDALMaxX = dfMaxX;
2322 358 : double dfGDALMaxY = dfMaxY;
2323 : pszContentsMinX =
2324 358 : CSLFetchNameValueDef(papszOpenOptionsIn, "MINX", pszContentsMinX);
2325 : pszContentsMinY =
2326 358 : CSLFetchNameValueDef(papszOpenOptionsIn, "MINY", pszContentsMinY);
2327 : pszContentsMaxX =
2328 358 : CSLFetchNameValueDef(papszOpenOptionsIn, "MAXX", pszContentsMaxX);
2329 : pszContentsMaxY =
2330 358 : CSLFetchNameValueDef(papszOpenOptionsIn, "MAXY", pszContentsMaxY);
2331 358 : if (pszContentsMinX != nullptr && pszContentsMinY != nullptr &&
2332 358 : pszContentsMaxX != nullptr && pszContentsMaxY != nullptr)
2333 : {
2334 715 : if (CPLAtof(pszContentsMinX) < CPLAtof(pszContentsMaxX) &&
2335 357 : CPLAtof(pszContentsMinY) < CPLAtof(pszContentsMaxY))
2336 : {
2337 357 : dfGDALMinX = CPLAtof(pszContentsMinX);
2338 357 : dfGDALMinY = CPLAtof(pszContentsMinY);
2339 357 : dfGDALMaxX = CPLAtof(pszContentsMaxX);
2340 357 : dfGDALMaxY = CPLAtof(pszContentsMaxY);
2341 : }
2342 : else
2343 : {
2344 1 : CPLError(CE_Warning, CPLE_AppDefined,
2345 : "Illegal min_x/min_y/max_x/max_y values for %s in open "
2346 : "options and/or gpkg_contents. Using bounds of "
2347 : "gpkg_tile_matrix_set instead",
2348 : pszTableName);
2349 : }
2350 : }
2351 358 : if (dfGDALMinX >= dfGDALMaxX || dfGDALMinY >= dfGDALMaxY)
2352 : {
2353 0 : CPLError(CE_Failure, CPLE_AppDefined,
2354 : "Illegal min_x/min_y/max_x/max_y values for %s", pszTableName);
2355 0 : return false;
2356 : }
2357 :
2358 358 : int nBandCount = 0;
2359 : const char *pszBAND_COUNT =
2360 358 : CSLFetchNameValue(papszOpenOptionsIn, "BAND_COUNT");
2361 358 : if (poParentDS)
2362 : {
2363 86 : nBandCount = poParentDS->GetRasterCount();
2364 : }
2365 272 : else if (m_eDT != GDT_Byte)
2366 : {
2367 65 : if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO") &&
2368 0 : !EQUAL(pszBAND_COUNT, "1"))
2369 : {
2370 0 : CPLError(CE_Warning, CPLE_AppDefined,
2371 : "BAND_COUNT ignored for non-Byte data");
2372 : }
2373 65 : nBandCount = 1;
2374 : }
2375 : else
2376 : {
2377 207 : if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO"))
2378 : {
2379 69 : nBandCount = atoi(pszBAND_COUNT);
2380 69 : if (nBandCount == 1)
2381 5 : GetMetadata("IMAGE_STRUCTURE");
2382 : }
2383 : else
2384 : {
2385 138 : GetMetadata("IMAGE_STRUCTURE");
2386 138 : nBandCount = m_nBandCountFromMetadata;
2387 138 : if (nBandCount == 1)
2388 38 : m_eTF = GPKG_TF_PNG;
2389 : }
2390 207 : if (nBandCount == 1 && !m_osTFFromMetadata.empty())
2391 : {
2392 2 : m_eTF = GDALGPKGMBTilesGetTileFormat(m_osTFFromMetadata.c_str());
2393 : }
2394 207 : if (nBandCount <= 0 || nBandCount > 4)
2395 86 : nBandCount = 4;
2396 : }
2397 :
2398 358 : return InitRaster(poParentDS, pszTableName, nZoomLevel, nBandCount, dfMinX,
2399 : dfMaxY, dfPixelXSize, dfPixelYSize, nTileWidth,
2400 : nTileHeight, nTileMatrixWidth, nTileMatrixHeight,
2401 358 : dfGDALMinX, dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
2402 : }
2403 :
2404 : /************************************************************************/
2405 : /* ComputeTileAndPixelShifts() */
2406 : /************************************************************************/
2407 :
2408 781 : bool GDALGeoPackageDataset::ComputeTileAndPixelShifts()
2409 : {
2410 : int nTileWidth, nTileHeight;
2411 781 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
2412 :
2413 : // Compute shift between GDAL origin and TileMatrixSet origin
2414 : const double dfShiftXPixels =
2415 781 : (m_adfGeoTransform[0] - m_dfTMSMinX) / m_adfGeoTransform[1];
2416 781 : if (dfShiftXPixels / nTileWidth <= INT_MIN ||
2417 779 : dfShiftXPixels / nTileWidth > INT_MAX)
2418 2 : return false;
2419 779 : const int64_t nShiftXPixels =
2420 779 : static_cast<int64_t>(floor(0.5 + dfShiftXPixels));
2421 779 : m_nShiftXTiles = static_cast<int>(nShiftXPixels / nTileWidth);
2422 779 : if (nShiftXPixels < 0 && (nShiftXPixels % nTileWidth) != 0)
2423 11 : m_nShiftXTiles--;
2424 779 : m_nShiftXPixelsMod =
2425 779 : (static_cast<int>(nShiftXPixels % nTileWidth) + nTileWidth) %
2426 : nTileWidth;
2427 :
2428 : const double dfShiftYPixels =
2429 779 : (m_adfGeoTransform[3] - m_dfTMSMaxY) / m_adfGeoTransform[5];
2430 779 : if (dfShiftYPixels / nTileHeight <= INT_MIN ||
2431 779 : dfShiftYPixels / nTileHeight > INT_MAX)
2432 1 : return false;
2433 778 : const int64_t nShiftYPixels =
2434 778 : static_cast<int64_t>(floor(0.5 + dfShiftYPixels));
2435 778 : m_nShiftYTiles = static_cast<int>(nShiftYPixels / nTileHeight);
2436 778 : if (nShiftYPixels < 0 && (nShiftYPixels % nTileHeight) != 0)
2437 11 : m_nShiftYTiles--;
2438 778 : m_nShiftYPixelsMod =
2439 778 : (static_cast<int>(nShiftYPixels % nTileHeight) + nTileHeight) %
2440 : nTileHeight;
2441 778 : return true;
2442 : }
2443 :
2444 : /************************************************************************/
2445 : /* AllocCachedTiles() */
2446 : /************************************************************************/
2447 :
2448 778 : bool GDALGeoPackageDataset::AllocCachedTiles()
2449 : {
2450 : int nTileWidth, nTileHeight;
2451 778 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
2452 :
2453 : // We currently need 4 caches because of
2454 : // GDALGPKGMBTilesLikePseudoDataset::ReadTile(int nRow, int nCol)
2455 778 : const int nCacheCount = 4;
2456 : /*
2457 : (m_nShiftXPixelsMod != 0 || m_nShiftYPixelsMod != 0) ? 4 :
2458 : (GetUpdate() && m_eDT == GDT_Byte) ? 2 : 1;
2459 : */
2460 778 : m_pabyCachedTiles = static_cast<GByte *>(VSI_MALLOC3_VERBOSE(
2461 : cpl::fits_on<int>(nCacheCount * (m_eDT == GDT_Byte ? 4 : 1) *
2462 : m_nDTSize),
2463 : nTileWidth, nTileHeight));
2464 778 : if (m_pabyCachedTiles == nullptr)
2465 : {
2466 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too big tiles: %d x %d",
2467 : nTileWidth, nTileHeight);
2468 0 : return false;
2469 : }
2470 :
2471 778 : return true;
2472 : }
2473 :
2474 : /************************************************************************/
2475 : /* InitRaster() */
2476 : /************************************************************************/
2477 :
2478 597 : bool GDALGeoPackageDataset::InitRaster(
2479 : GDALGeoPackageDataset *poParentDS, const char *pszTableName, int nZoomLevel,
2480 : int nBandCount, double dfTMSMinX, double dfTMSMaxY, double dfPixelXSize,
2481 : double dfPixelYSize, int nTileWidth, int nTileHeight, int nTileMatrixWidth,
2482 : int nTileMatrixHeight, double dfGDALMinX, double dfGDALMinY,
2483 : double dfGDALMaxX, double dfGDALMaxY)
2484 : {
2485 597 : m_osRasterTable = pszTableName;
2486 597 : m_dfTMSMinX = dfTMSMinX;
2487 597 : m_dfTMSMaxY = dfTMSMaxY;
2488 597 : m_nZoomLevel = nZoomLevel;
2489 597 : m_nTileMatrixWidth = nTileMatrixWidth;
2490 597 : m_nTileMatrixHeight = nTileMatrixHeight;
2491 :
2492 597 : m_bGeoTransformValid = true;
2493 597 : m_adfGeoTransform[0] = dfGDALMinX;
2494 597 : m_adfGeoTransform[1] = dfPixelXSize;
2495 597 : m_adfGeoTransform[3] = dfGDALMaxY;
2496 597 : m_adfGeoTransform[5] = -dfPixelYSize;
2497 597 : double dfRasterXSize = 0.5 + (dfGDALMaxX - dfGDALMinX) / dfPixelXSize;
2498 597 : double dfRasterYSize = 0.5 + (dfGDALMaxY - dfGDALMinY) / dfPixelYSize;
2499 597 : if (dfRasterXSize > INT_MAX || dfRasterYSize > INT_MAX)
2500 : {
2501 0 : CPLError(CE_Failure, CPLE_NotSupported, "Too big raster: %f x %f",
2502 : dfRasterXSize, dfRasterYSize);
2503 0 : return false;
2504 : }
2505 597 : nRasterXSize = std::max(1, static_cast<int>(dfRasterXSize));
2506 597 : nRasterYSize = std::max(1, static_cast<int>(dfRasterYSize));
2507 :
2508 597 : if (poParentDS)
2509 : {
2510 325 : m_poParentDS = poParentDS;
2511 325 : eAccess = poParentDS->eAccess;
2512 325 : hDB = poParentDS->hDB;
2513 325 : m_eTF = poParentDS->m_eTF;
2514 325 : m_eDT = poParentDS->m_eDT;
2515 325 : m_nDTSize = poParentDS->m_nDTSize;
2516 325 : m_dfScale = poParentDS->m_dfScale;
2517 325 : m_dfOffset = poParentDS->m_dfOffset;
2518 325 : m_dfPrecision = poParentDS->m_dfPrecision;
2519 325 : m_usGPKGNull = poParentDS->m_usGPKGNull;
2520 325 : m_nQuality = poParentDS->m_nQuality;
2521 325 : m_nZLevel = poParentDS->m_nZLevel;
2522 325 : m_bDither = poParentDS->m_bDither;
2523 : /*m_nSRID = poParentDS->m_nSRID;*/
2524 325 : m_osWHERE = poParentDS->m_osWHERE;
2525 325 : SetDescription(CPLSPrintf("%s - zoom_level=%d",
2526 325 : poParentDS->GetDescription(), m_nZoomLevel));
2527 : }
2528 :
2529 2094 : for (int i = 1; i <= nBandCount; i++)
2530 : {
2531 : auto poNewBand = std::make_unique<GDALGeoPackageRasterBand>(
2532 1497 : this, nTileWidth, nTileHeight);
2533 1497 : if (poParentDS)
2534 : {
2535 761 : int bHasNoData = FALSE;
2536 : double dfNoDataValue =
2537 761 : poParentDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
2538 761 : if (bHasNoData)
2539 24 : poNewBand->SetNoDataValueInternal(dfNoDataValue);
2540 : }
2541 :
2542 1497 : if (nBandCount == 1 && m_poCTFromMetadata)
2543 : {
2544 3 : poNewBand->AssignColorTable(m_poCTFromMetadata.get());
2545 : }
2546 1497 : if (!m_osNodataValueFromMetadata.empty())
2547 : {
2548 8 : poNewBand->SetNoDataValueInternal(
2549 : CPLAtof(m_osNodataValueFromMetadata.c_str()));
2550 : }
2551 :
2552 1497 : SetBand(i, std::move(poNewBand));
2553 : }
2554 :
2555 597 : if (!ComputeTileAndPixelShifts())
2556 : {
2557 3 : CPLError(CE_Failure, CPLE_AppDefined,
2558 : "Overflow occurred in ComputeTileAndPixelShifts()");
2559 3 : return false;
2560 : }
2561 :
2562 594 : GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
2563 594 : GDALPamDataset::SetMetadataItem("ZOOM_LEVEL",
2564 : CPLSPrintf("%d", m_nZoomLevel));
2565 :
2566 594 : return AllocCachedTiles();
2567 : }
2568 :
2569 : /************************************************************************/
2570 : /* GDALGPKGMBTilesGetTileFormat() */
2571 : /************************************************************************/
2572 :
2573 80 : GPKGTileFormat GDALGPKGMBTilesGetTileFormat(const char *pszTF)
2574 : {
2575 80 : GPKGTileFormat eTF = GPKG_TF_PNG_JPEG;
2576 80 : if (pszTF)
2577 : {
2578 80 : if (EQUAL(pszTF, "PNG_JPEG") || EQUAL(pszTF, "AUTO"))
2579 1 : eTF = GPKG_TF_PNG_JPEG;
2580 79 : else if (EQUAL(pszTF, "PNG"))
2581 46 : eTF = GPKG_TF_PNG;
2582 33 : else if (EQUAL(pszTF, "PNG8"))
2583 6 : eTF = GPKG_TF_PNG8;
2584 27 : else if (EQUAL(pszTF, "JPEG"))
2585 14 : eTF = GPKG_TF_JPEG;
2586 13 : else if (EQUAL(pszTF, "WEBP"))
2587 13 : eTF = GPKG_TF_WEBP;
2588 : else
2589 : {
2590 0 : CPLError(CE_Failure, CPLE_NotSupported,
2591 : "Unsuppoted value for TILE_FORMAT: %s", pszTF);
2592 : }
2593 : }
2594 80 : return eTF;
2595 : }
2596 :
2597 28 : const char *GDALMBTilesGetTileFormatName(GPKGTileFormat eTF)
2598 : {
2599 28 : switch (eTF)
2600 : {
2601 26 : case GPKG_TF_PNG:
2602 : case GPKG_TF_PNG8:
2603 26 : return "png";
2604 1 : case GPKG_TF_JPEG:
2605 1 : return "jpg";
2606 1 : case GPKG_TF_WEBP:
2607 1 : return "webp";
2608 0 : default:
2609 0 : break;
2610 : }
2611 0 : CPLError(CE_Failure, CPLE_NotSupported,
2612 : "Unsuppoted value for TILE_FORMAT: %d", static_cast<int>(eTF));
2613 0 : return nullptr;
2614 : }
2615 :
2616 : /************************************************************************/
2617 : /* OpenRaster() */
2618 : /************************************************************************/
2619 :
2620 274 : bool GDALGeoPackageDataset::OpenRaster(
2621 : const char *pszTableName, const char *pszIdentifier,
2622 : const char *pszDescription, int nSRSId, double dfMinX, double dfMinY,
2623 : double dfMaxX, double dfMaxY, const char *pszContentsMinX,
2624 : const char *pszContentsMinY, const char *pszContentsMaxX,
2625 : const char *pszContentsMaxY, bool bIsTiles, char **papszOpenOptionsIn)
2626 : {
2627 274 : if (dfMinX >= dfMaxX || dfMinY >= dfMaxY)
2628 0 : return false;
2629 :
2630 : // Config option just for debug, and for example force set to NaN
2631 : // which is not supported
2632 548 : CPLString osDataNull = CPLGetConfigOption("GPKG_NODATA", "");
2633 548 : CPLString osUom;
2634 548 : CPLString osFieldName;
2635 548 : CPLString osGridCellEncoding;
2636 274 : if (!bIsTiles)
2637 : {
2638 65 : char *pszSQL = sqlite3_mprintf(
2639 : "SELECT datatype, scale, offset, data_null, precision FROM "
2640 : "gpkg_2d_gridded_coverage_ancillary "
2641 : "WHERE tile_matrix_set_name = '%q' "
2642 : "AND datatype IN ('integer', 'float')"
2643 : "AND (scale > 0 OR scale IS NULL)",
2644 : pszTableName);
2645 65 : auto oResult = SQLQuery(hDB, pszSQL);
2646 65 : sqlite3_free(pszSQL);
2647 65 : if (!oResult || oResult->RowCount() == 0)
2648 : {
2649 0 : return false;
2650 : }
2651 65 : const char *pszDataType = oResult->GetValue(0, 0);
2652 65 : const char *pszScale = oResult->GetValue(1, 0);
2653 65 : const char *pszOffset = oResult->GetValue(2, 0);
2654 65 : const char *pszDataNull = oResult->GetValue(3, 0);
2655 65 : const char *pszPrecision = oResult->GetValue(4, 0);
2656 65 : if (pszDataNull)
2657 23 : osDataNull = pszDataNull;
2658 65 : if (EQUAL(pszDataType, "float"))
2659 : {
2660 6 : SetDataType(GDT_Float32);
2661 6 : m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
2662 : }
2663 : else
2664 : {
2665 59 : SetDataType(GDT_Float32);
2666 59 : m_eTF = GPKG_TF_PNG_16BIT;
2667 59 : const double dfScale = pszScale ? CPLAtof(pszScale) : 1.0;
2668 59 : const double dfOffset = pszOffset ? CPLAtof(pszOffset) : 0.0;
2669 59 : if (dfScale == 1.0)
2670 : {
2671 59 : if (dfOffset == 0.0)
2672 : {
2673 24 : SetDataType(GDT_UInt16);
2674 : }
2675 35 : else if (dfOffset == -32768.0)
2676 : {
2677 35 : SetDataType(GDT_Int16);
2678 : }
2679 : // coverity[tainted_data]
2680 0 : else if (dfOffset == -32767.0 && !osDataNull.empty() &&
2681 0 : CPLAtof(osDataNull) == 65535.0)
2682 : // Given that we will map the nodata value to -32768
2683 : {
2684 0 : SetDataType(GDT_Int16);
2685 : }
2686 : }
2687 :
2688 : // Check that the tile offset and scales are compatible of a
2689 : // final integer result.
2690 59 : if (m_eDT != GDT_Float32)
2691 : {
2692 : // coverity[tainted_data]
2693 59 : if (dfScale == 1.0 && dfOffset == -32768.0 &&
2694 118 : !osDataNull.empty() && CPLAtof(osDataNull) == 65535.0)
2695 : {
2696 : // Given that we will map the nodata value to -32768
2697 9 : pszSQL = sqlite3_mprintf(
2698 : "SELECT 1 FROM "
2699 : "gpkg_2d_gridded_tile_ancillary WHERE "
2700 : "tpudt_name = '%q' "
2701 : "AND NOT ((offset = 0.0 or offset = 1.0) "
2702 : "AND scale = 1.0) "
2703 : "LIMIT 1",
2704 : pszTableName);
2705 : }
2706 : else
2707 : {
2708 50 : pszSQL = sqlite3_mprintf(
2709 : "SELECT 1 FROM "
2710 : "gpkg_2d_gridded_tile_ancillary WHERE "
2711 : "tpudt_name = '%q' "
2712 : "AND NOT (offset = 0.0 AND scale = 1.0) LIMIT 1",
2713 : pszTableName);
2714 : }
2715 59 : sqlite3_stmt *hSQLStmt = nullptr;
2716 : int rc =
2717 59 : SQLPrepareWithError(hDB, pszSQL, -1, &hSQLStmt, nullptr);
2718 :
2719 59 : if (rc == SQLITE_OK)
2720 : {
2721 59 : if (sqlite3_step(hSQLStmt) == SQLITE_ROW)
2722 : {
2723 8 : SetDataType(GDT_Float32);
2724 : }
2725 59 : sqlite3_finalize(hSQLStmt);
2726 : }
2727 59 : sqlite3_free(pszSQL);
2728 : }
2729 :
2730 59 : SetGlobalOffsetScale(dfOffset, dfScale);
2731 : }
2732 65 : if (pszPrecision)
2733 65 : m_dfPrecision = CPLAtof(pszPrecision);
2734 :
2735 : // Request those columns in a separate query, so as to keep
2736 : // compatibility with pre OGC 17-066r1 databases
2737 : pszSQL =
2738 65 : sqlite3_mprintf("SELECT uom, field_name, grid_cell_encoding FROM "
2739 : "gpkg_2d_gridded_coverage_ancillary "
2740 : "WHERE tile_matrix_set_name = '%q'",
2741 : pszTableName);
2742 65 : CPLPushErrorHandler(CPLQuietErrorHandler);
2743 65 : oResult = SQLQuery(hDB, pszSQL);
2744 65 : CPLPopErrorHandler();
2745 65 : sqlite3_free(pszSQL);
2746 65 : if (oResult && oResult->RowCount() == 1)
2747 : {
2748 64 : const char *pszUom = oResult->GetValue(0, 0);
2749 64 : if (pszUom)
2750 2 : osUom = pszUom;
2751 64 : const char *pszFieldName = oResult->GetValue(1, 0);
2752 64 : if (pszFieldName)
2753 64 : osFieldName = pszFieldName;
2754 64 : const char *pszGridCellEncoding = oResult->GetValue(2, 0);
2755 64 : if (pszGridCellEncoding)
2756 64 : osGridCellEncoding = pszGridCellEncoding;
2757 : }
2758 : }
2759 :
2760 274 : m_bRecordInsertedInGPKGContent = true;
2761 274 : m_nSRID = nSRSId;
2762 :
2763 547 : if (auto poSRS = GetSpatialRef(nSRSId))
2764 : {
2765 273 : m_oSRS = *(poSRS.get());
2766 : }
2767 :
2768 : /* Various sanity checks added in the SELECT */
2769 274 : char *pszQuotedTableName = sqlite3_mprintf("'%q'", pszTableName);
2770 548 : CPLString osQuotedTableName(pszQuotedTableName);
2771 274 : sqlite3_free(pszQuotedTableName);
2772 274 : char *pszSQL = sqlite3_mprintf(
2773 : "SELECT zoom_level, pixel_x_size, pixel_y_size, tile_width, "
2774 : "tile_height, matrix_width, matrix_height "
2775 : "FROM gpkg_tile_matrix tm "
2776 : "WHERE table_name = %s "
2777 : // INT_MAX would be the theoretical maximum value to avoid
2778 : // overflows, but that's already a insane value.
2779 : "AND zoom_level >= 0 AND zoom_level <= 65536 "
2780 : "AND pixel_x_size > 0 AND pixel_y_size > 0 "
2781 : "AND tile_width >= 1 AND tile_width <= 65536 "
2782 : "AND tile_height >= 1 AND tile_height <= 65536 "
2783 : "AND matrix_width >= 1 AND matrix_height >= 1",
2784 : osQuotedTableName.c_str());
2785 548 : CPLString osSQL(pszSQL);
2786 : const char *pszZoomLevel =
2787 274 : CSLFetchNameValue(papszOpenOptionsIn, "ZOOM_LEVEL");
2788 274 : if (pszZoomLevel)
2789 : {
2790 5 : if (GetUpdate())
2791 1 : osSQL += CPLSPrintf(" AND zoom_level <= %d", atoi(pszZoomLevel));
2792 : else
2793 : {
2794 : osSQL += CPLSPrintf(
2795 : " AND (zoom_level = %d OR (zoom_level < %d AND EXISTS(SELECT 1 "
2796 : "FROM %s WHERE zoom_level = tm.zoom_level LIMIT 1)))",
2797 : atoi(pszZoomLevel), atoi(pszZoomLevel),
2798 4 : osQuotedTableName.c_str());
2799 : }
2800 : }
2801 : // In read-only mode, only lists non empty zoom levels
2802 269 : else if (!GetUpdate())
2803 : {
2804 : osSQL += CPLSPrintf(" AND EXISTS(SELECT 1 FROM %s WHERE zoom_level = "
2805 : "tm.zoom_level LIMIT 1)",
2806 215 : osQuotedTableName.c_str());
2807 : }
2808 : else // if( pszZoomLevel == nullptr )
2809 : {
2810 : osSQL +=
2811 : CPLSPrintf(" AND zoom_level <= (SELECT MAX(zoom_level) FROM %s)",
2812 54 : osQuotedTableName.c_str());
2813 : }
2814 274 : osSQL += " ORDER BY zoom_level DESC";
2815 : // To avoid denial of service.
2816 274 : osSQL += " LIMIT 100";
2817 :
2818 548 : auto oResult = SQLQuery(hDB, osSQL.c_str());
2819 274 : if (!oResult || oResult->RowCount() == 0)
2820 : {
2821 114 : if (oResult && oResult->RowCount() == 0 && pszContentsMinX != nullptr &&
2822 114 : pszContentsMinY != nullptr && pszContentsMaxX != nullptr &&
2823 : pszContentsMaxY != nullptr)
2824 : {
2825 56 : osSQL = pszSQL;
2826 56 : osSQL += " ORDER BY zoom_level DESC";
2827 56 : if (!GetUpdate())
2828 30 : osSQL += " LIMIT 1";
2829 56 : oResult = SQLQuery(hDB, osSQL.c_str());
2830 : }
2831 57 : if (!oResult || oResult->RowCount() == 0)
2832 : {
2833 1 : if (oResult && pszZoomLevel != nullptr)
2834 : {
2835 1 : CPLError(CE_Failure, CPLE_AppDefined,
2836 : "ZOOM_LEVEL is probably not valid w.r.t tile "
2837 : "table content");
2838 : }
2839 1 : sqlite3_free(pszSQL);
2840 1 : return false;
2841 : }
2842 : }
2843 273 : sqlite3_free(pszSQL);
2844 :
2845 : // If USE_TILE_EXTENT=YES, then query the tile table to find which tiles
2846 : // actually exist.
2847 :
2848 : // CAUTION: Do not move those variables inside inner scope !
2849 546 : CPLString osContentsMinX, osContentsMinY, osContentsMaxX, osContentsMaxY;
2850 :
2851 273 : if (CPLTestBool(
2852 : CSLFetchNameValueDef(papszOpenOptionsIn, "USE_TILE_EXTENT", "NO")))
2853 : {
2854 13 : pszSQL = sqlite3_mprintf(
2855 : "SELECT MIN(tile_column), MIN(tile_row), MAX(tile_column), "
2856 : "MAX(tile_row) FROM \"%w\" WHERE zoom_level = %d",
2857 : pszTableName, atoi(oResult->GetValue(0, 0)));
2858 13 : auto oResult2 = SQLQuery(hDB, pszSQL);
2859 13 : sqlite3_free(pszSQL);
2860 26 : if (!oResult2 || oResult2->RowCount() == 0 ||
2861 : // Can happen if table is empty
2862 38 : oResult2->GetValue(0, 0) == nullptr ||
2863 : // Can happen if table has no NOT NULL constraint on tile_row
2864 : // and that all tile_row are NULL
2865 12 : oResult2->GetValue(1, 0) == nullptr)
2866 : {
2867 1 : return false;
2868 : }
2869 12 : const double dfPixelXSize = CPLAtof(oResult->GetValue(1, 0));
2870 12 : const double dfPixelYSize = CPLAtof(oResult->GetValue(2, 0));
2871 12 : const int nTileWidth = atoi(oResult->GetValue(3, 0));
2872 12 : const int nTileHeight = atoi(oResult->GetValue(4, 0));
2873 : osContentsMinX =
2874 24 : CPLSPrintf("%.17g", dfMinX + dfPixelXSize * nTileWidth *
2875 12 : atoi(oResult2->GetValue(0, 0)));
2876 : osContentsMaxY =
2877 24 : CPLSPrintf("%.17g", dfMaxY - dfPixelYSize * nTileHeight *
2878 12 : atoi(oResult2->GetValue(1, 0)));
2879 : osContentsMaxX = CPLSPrintf(
2880 24 : "%.17g", dfMinX + dfPixelXSize * nTileWidth *
2881 12 : (1 + atoi(oResult2->GetValue(2, 0))));
2882 : osContentsMinY = CPLSPrintf(
2883 24 : "%.17g", dfMaxY - dfPixelYSize * nTileHeight *
2884 12 : (1 + atoi(oResult2->GetValue(3, 0))));
2885 12 : pszContentsMinX = osContentsMinX.c_str();
2886 12 : pszContentsMinY = osContentsMinY.c_str();
2887 12 : pszContentsMaxX = osContentsMaxX.c_str();
2888 12 : pszContentsMaxY = osContentsMaxY.c_str();
2889 : }
2890 :
2891 272 : if (!InitRaster(nullptr, pszTableName, dfMinX, dfMinY, dfMaxX, dfMaxY,
2892 : pszContentsMinX, pszContentsMinY, pszContentsMaxX,
2893 272 : pszContentsMaxY, papszOpenOptionsIn, *oResult, 0))
2894 : {
2895 3 : return false;
2896 : }
2897 :
2898 : auto poBand =
2899 269 : reinterpret_cast<GDALGeoPackageRasterBand *>(GetRasterBand(1));
2900 269 : if (!osDataNull.empty())
2901 : {
2902 23 : double dfGPKGNoDataValue = CPLAtof(osDataNull);
2903 23 : if (m_eTF == GPKG_TF_PNG_16BIT)
2904 : {
2905 21 : if (dfGPKGNoDataValue < 0 || dfGPKGNoDataValue > 65535 ||
2906 21 : static_cast<int>(dfGPKGNoDataValue) != dfGPKGNoDataValue)
2907 : {
2908 0 : CPLError(CE_Warning, CPLE_AppDefined,
2909 : "data_null = %.17g is invalid for integer data_type",
2910 : dfGPKGNoDataValue);
2911 : }
2912 : else
2913 : {
2914 21 : m_usGPKGNull = static_cast<GUInt16>(dfGPKGNoDataValue);
2915 21 : if (m_eDT == GDT_Int16 && m_usGPKGNull > 32767)
2916 9 : dfGPKGNoDataValue = -32768.0;
2917 12 : else if (m_eDT == GDT_Float32)
2918 : {
2919 : // Pick a value that is unlikely to be hit with offset &
2920 : // scale
2921 4 : dfGPKGNoDataValue = -std::numeric_limits<float>::max();
2922 : }
2923 21 : poBand->SetNoDataValueInternal(dfGPKGNoDataValue);
2924 : }
2925 : }
2926 : else
2927 : {
2928 2 : poBand->SetNoDataValueInternal(
2929 2 : static_cast<float>(dfGPKGNoDataValue));
2930 : }
2931 : }
2932 269 : if (!osUom.empty())
2933 : {
2934 2 : poBand->SetUnitTypeInternal(osUom);
2935 : }
2936 269 : if (!osFieldName.empty())
2937 : {
2938 64 : GetRasterBand(1)->GDALRasterBand::SetDescription(osFieldName);
2939 : }
2940 269 : if (!osGridCellEncoding.empty())
2941 : {
2942 64 : if (osGridCellEncoding == "grid-value-is-center")
2943 : {
2944 15 : GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
2945 : GDALMD_AOP_POINT);
2946 : }
2947 49 : else if (osGridCellEncoding == "grid-value-is-area")
2948 : {
2949 45 : GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
2950 : GDALMD_AOP_AREA);
2951 : }
2952 : else
2953 : {
2954 4 : GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
2955 : GDALMD_AOP_POINT);
2956 4 : GetRasterBand(1)->GDALRasterBand::SetMetadataItem(
2957 : "GRID_CELL_ENCODING", osGridCellEncoding);
2958 : }
2959 : }
2960 :
2961 269 : CheckUnknownExtensions(true);
2962 :
2963 : // Do this after CheckUnknownExtensions() so that m_eTF is set to
2964 : // GPKG_TF_WEBP if the table already registers the gpkg_webp extension
2965 269 : const char *pszTF = CSLFetchNameValue(papszOpenOptionsIn, "TILE_FORMAT");
2966 269 : if (pszTF)
2967 : {
2968 4 : if (!GetUpdate())
2969 : {
2970 0 : CPLError(CE_Warning, CPLE_AppDefined,
2971 : "TILE_FORMAT open option ignored in read-only mode");
2972 : }
2973 4 : else if (m_eTF == GPKG_TF_PNG_16BIT ||
2974 4 : m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
2975 : {
2976 0 : CPLError(CE_Warning, CPLE_AppDefined,
2977 : "TILE_FORMAT open option ignored on gridded coverages");
2978 : }
2979 : else
2980 : {
2981 4 : GPKGTileFormat eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
2982 4 : if (eTF == GPKG_TF_WEBP && m_eTF != eTF)
2983 : {
2984 1 : if (!RegisterWebPExtension())
2985 0 : return false;
2986 : }
2987 4 : m_eTF = eTF;
2988 : }
2989 : }
2990 :
2991 269 : ParseCompressionOptions(papszOpenOptionsIn);
2992 :
2993 269 : m_osWHERE = CSLFetchNameValueDef(papszOpenOptionsIn, "WHERE", "");
2994 :
2995 : // Set metadata
2996 269 : if (pszIdentifier && pszIdentifier[0])
2997 269 : GDALPamDataset::SetMetadataItem("IDENTIFIER", pszIdentifier);
2998 269 : if (pszDescription && pszDescription[0])
2999 21 : GDALPamDataset::SetMetadataItem("DESCRIPTION", pszDescription);
3000 :
3001 : // Add overviews
3002 354 : for (int i = 1; i < oResult->RowCount(); i++)
3003 : {
3004 86 : auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
3005 86 : poOvrDS->ShareLockWithParentDataset(this);
3006 172 : if (!poOvrDS->InitRaster(this, pszTableName, dfMinX, dfMinY, dfMaxX,
3007 : dfMaxY, pszContentsMinX, pszContentsMinY,
3008 : pszContentsMaxX, pszContentsMaxY,
3009 86 : papszOpenOptionsIn, *oResult, i))
3010 : {
3011 0 : break;
3012 : }
3013 :
3014 : int nTileWidth, nTileHeight;
3015 86 : poOvrDS->GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3016 : const bool bStop =
3017 87 : (eAccess == GA_ReadOnly && poOvrDS->GetRasterXSize() < nTileWidth &&
3018 1 : poOvrDS->GetRasterYSize() < nTileHeight);
3019 :
3020 86 : m_apoOverviewDS.push_back(std::move(poOvrDS));
3021 :
3022 86 : if (bStop)
3023 : {
3024 1 : break;
3025 : }
3026 : }
3027 :
3028 269 : return true;
3029 : }
3030 :
3031 : /************************************************************************/
3032 : /* GetSpatialRef() */
3033 : /************************************************************************/
3034 :
3035 17 : const OGRSpatialReference *GDALGeoPackageDataset::GetSpatialRef() const
3036 : {
3037 17 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
3038 : }
3039 :
3040 : /************************************************************************/
3041 : /* SetSpatialRef() */
3042 : /************************************************************************/
3043 :
3044 150 : CPLErr GDALGeoPackageDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
3045 : {
3046 150 : if (nBands == 0)
3047 : {
3048 1 : CPLError(CE_Failure, CPLE_NotSupported,
3049 : "SetProjection() not supported on a dataset with 0 band");
3050 1 : return CE_Failure;
3051 : }
3052 149 : if (eAccess != GA_Update)
3053 : {
3054 1 : CPLError(CE_Failure, CPLE_NotSupported,
3055 : "SetProjection() not supported on read-only dataset");
3056 1 : return CE_Failure;
3057 : }
3058 :
3059 148 : const int nSRID = GetSrsId(poSRS);
3060 296 : const auto poTS = GetTilingScheme(m_osTilingScheme);
3061 148 : if (poTS && nSRID != poTS->nEPSGCode)
3062 : {
3063 2 : CPLError(CE_Failure, CPLE_NotSupported,
3064 : "Projection should be EPSG:%d for %s tiling scheme",
3065 1 : poTS->nEPSGCode, m_osTilingScheme.c_str());
3066 1 : return CE_Failure;
3067 : }
3068 :
3069 147 : m_nSRID = nSRID;
3070 147 : m_oSRS.Clear();
3071 147 : if (poSRS)
3072 146 : m_oSRS = *poSRS;
3073 :
3074 147 : if (m_bRecordInsertedInGPKGContent)
3075 : {
3076 119 : char *pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET srs_id = %d "
3077 : "WHERE lower(table_name) = lower('%q')",
3078 : m_nSRID, m_osRasterTable.c_str());
3079 119 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3080 119 : sqlite3_free(pszSQL);
3081 119 : if (eErr != OGRERR_NONE)
3082 0 : return CE_Failure;
3083 :
3084 119 : pszSQL = sqlite3_mprintf("UPDATE gpkg_tile_matrix_set SET srs_id = %d "
3085 : "WHERE lower(table_name) = lower('%q')",
3086 : m_nSRID, m_osRasterTable.c_str());
3087 119 : eErr = SQLCommand(hDB, pszSQL);
3088 119 : sqlite3_free(pszSQL);
3089 119 : if (eErr != OGRERR_NONE)
3090 0 : return CE_Failure;
3091 : }
3092 :
3093 147 : return CE_None;
3094 : }
3095 :
3096 : /************************************************************************/
3097 : /* GetGeoTransform() */
3098 : /************************************************************************/
3099 :
3100 33 : CPLErr GDALGeoPackageDataset::GetGeoTransform(double *padfGeoTransform)
3101 : {
3102 33 : memcpy(padfGeoTransform, m_adfGeoTransform.data(), 6 * sizeof(double));
3103 33 : if (!m_bGeoTransformValid)
3104 2 : return CE_Failure;
3105 : else
3106 31 : return CE_None;
3107 : }
3108 :
3109 : /************************************************************************/
3110 : /* SetGeoTransform() */
3111 : /************************************************************************/
3112 :
3113 189 : CPLErr GDALGeoPackageDataset::SetGeoTransform(double *padfGeoTransform)
3114 : {
3115 189 : if (nBands == 0)
3116 : {
3117 2 : CPLError(CE_Failure, CPLE_NotSupported,
3118 : "SetGeoTransform() not supported on a dataset with 0 band");
3119 2 : return CE_Failure;
3120 : }
3121 187 : if (eAccess != GA_Update)
3122 : {
3123 1 : CPLError(CE_Failure, CPLE_NotSupported,
3124 : "SetGeoTransform() not supported on read-only dataset");
3125 1 : return CE_Failure;
3126 : }
3127 186 : if (m_bGeoTransformValid)
3128 : {
3129 1 : CPLError(CE_Failure, CPLE_NotSupported,
3130 : "Cannot modify geotransform once set");
3131 1 : return CE_Failure;
3132 : }
3133 185 : if (padfGeoTransform[2] != 0.0 || padfGeoTransform[4] != 0 ||
3134 185 : padfGeoTransform[5] > 0.0)
3135 : {
3136 0 : CPLError(CE_Failure, CPLE_NotSupported,
3137 : "Only north-up non rotated geotransform supported");
3138 0 : return CE_Failure;
3139 : }
3140 :
3141 185 : if (m_nZoomLevel < 0)
3142 : {
3143 184 : const auto poTS = GetTilingScheme(m_osTilingScheme);
3144 184 : if (poTS)
3145 : {
3146 20 : double dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
3147 20 : double dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
3148 199 : for (m_nZoomLevel = 0; m_nZoomLevel < MAX_ZOOM_LEVEL;
3149 179 : m_nZoomLevel++)
3150 : {
3151 198 : double dfExpectedPixelXSize =
3152 198 : dfPixelXSizeZoomLevel0 / (1 << m_nZoomLevel);
3153 198 : double dfExpectedPixelYSize =
3154 198 : dfPixelYSizeZoomLevel0 / (1 << m_nZoomLevel);
3155 198 : if (fabs(padfGeoTransform[1] - dfExpectedPixelXSize) <
3156 198 : 1e-8 * dfExpectedPixelXSize &&
3157 19 : fabs(fabs(padfGeoTransform[5]) - dfExpectedPixelYSize) <
3158 19 : 1e-8 * dfExpectedPixelYSize)
3159 : {
3160 19 : break;
3161 : }
3162 : }
3163 20 : if (m_nZoomLevel == MAX_ZOOM_LEVEL)
3164 : {
3165 1 : m_nZoomLevel = -1;
3166 1 : CPLError(
3167 : CE_Failure, CPLE_NotSupported,
3168 : "Could not find an appropriate zoom level of %s tiling "
3169 : "scheme that matches raster pixel size",
3170 : m_osTilingScheme.c_str());
3171 1 : return CE_Failure;
3172 : }
3173 : }
3174 : }
3175 :
3176 184 : memcpy(m_adfGeoTransform.data(), padfGeoTransform, 6 * sizeof(double));
3177 184 : m_bGeoTransformValid = true;
3178 :
3179 184 : return FinalizeRasterRegistration();
3180 : }
3181 :
3182 : /************************************************************************/
3183 : /* FinalizeRasterRegistration() */
3184 : /************************************************************************/
3185 :
3186 184 : CPLErr GDALGeoPackageDataset::FinalizeRasterRegistration()
3187 : {
3188 : OGRErr eErr;
3189 :
3190 184 : m_dfTMSMinX = m_adfGeoTransform[0];
3191 184 : m_dfTMSMaxY = m_adfGeoTransform[3];
3192 :
3193 : int nTileWidth, nTileHeight;
3194 184 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3195 :
3196 184 : if (m_nZoomLevel < 0)
3197 : {
3198 164 : m_nZoomLevel = 0;
3199 238 : while ((nRasterXSize >> m_nZoomLevel) > nTileWidth ||
3200 164 : (nRasterYSize >> m_nZoomLevel) > nTileHeight)
3201 74 : m_nZoomLevel++;
3202 : }
3203 :
3204 184 : double dfPixelXSizeZoomLevel0 = m_adfGeoTransform[1] * (1 << m_nZoomLevel);
3205 : double dfPixelYSizeZoomLevel0 =
3206 184 : fabs(m_adfGeoTransform[5]) * (1 << m_nZoomLevel);
3207 : int nTileXCountZoomLevel0 =
3208 184 : std::max(1, DIV_ROUND_UP((nRasterXSize >> m_nZoomLevel), nTileWidth));
3209 : int nTileYCountZoomLevel0 =
3210 184 : std::max(1, DIV_ROUND_UP((nRasterYSize >> m_nZoomLevel), nTileHeight));
3211 :
3212 368 : const auto poTS = GetTilingScheme(m_osTilingScheme);
3213 184 : if (poTS)
3214 : {
3215 20 : CPLAssert(m_nZoomLevel >= 0);
3216 20 : m_dfTMSMinX = poTS->dfMinX;
3217 20 : m_dfTMSMaxY = poTS->dfMaxY;
3218 20 : dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
3219 20 : dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
3220 20 : nTileXCountZoomLevel0 = poTS->nTileXCountZoomLevel0;
3221 20 : nTileYCountZoomLevel0 = poTS->nTileYCountZoomLevel0;
3222 : }
3223 184 : m_nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << m_nZoomLevel);
3224 184 : m_nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << m_nZoomLevel);
3225 :
3226 184 : if (!ComputeTileAndPixelShifts())
3227 : {
3228 0 : CPLError(CE_Failure, CPLE_AppDefined,
3229 : "Overflow occurred in ComputeTileAndPixelShifts()");
3230 0 : return CE_Failure;
3231 : }
3232 :
3233 184 : if (!AllocCachedTiles())
3234 : {
3235 0 : return CE_Failure;
3236 : }
3237 :
3238 184 : double dfGDALMinX = m_adfGeoTransform[0];
3239 : double dfGDALMinY =
3240 184 : m_adfGeoTransform[3] + nRasterYSize * m_adfGeoTransform[5];
3241 : double dfGDALMaxX =
3242 184 : m_adfGeoTransform[0] + nRasterXSize * m_adfGeoTransform[1];
3243 184 : double dfGDALMaxY = m_adfGeoTransform[3];
3244 :
3245 184 : if (SoftStartTransaction() != OGRERR_NONE)
3246 0 : return CE_Failure;
3247 :
3248 : const char *pszCurrentDate =
3249 184 : CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
3250 : CPLString osInsertGpkgContentsFormatting(
3251 : "INSERT INTO gpkg_contents "
3252 : "(table_name,data_type,identifier,description,min_x,min_y,max_x,max_y,"
3253 : "last_change,srs_id) VALUES "
3254 368 : "('%q','%q','%q','%q',%.17g,%.17g,%.17g,%.17g,");
3255 184 : osInsertGpkgContentsFormatting += (pszCurrentDate) ? "'%q'" : "%s";
3256 184 : osInsertGpkgContentsFormatting += ",%d)";
3257 368 : char *pszSQL = sqlite3_mprintf(
3258 : osInsertGpkgContentsFormatting.c_str(), m_osRasterTable.c_str(),
3259 184 : (m_eDT == GDT_Byte) ? "tiles" : "2d-gridded-coverage",
3260 : m_osIdentifier.c_str(), m_osDescription.c_str(), dfGDALMinX, dfGDALMinY,
3261 : dfGDALMaxX, dfGDALMaxY,
3262 : pszCurrentDate ? pszCurrentDate
3263 : : "strftime('%Y-%m-%dT%H:%M:%fZ','now')",
3264 : m_nSRID);
3265 :
3266 184 : eErr = SQLCommand(hDB, pszSQL);
3267 184 : sqlite3_free(pszSQL);
3268 184 : if (eErr != OGRERR_NONE)
3269 : {
3270 8 : SoftRollbackTransaction();
3271 8 : return CE_Failure;
3272 : }
3273 :
3274 176 : double dfTMSMaxX = m_dfTMSMinX + nTileXCountZoomLevel0 * nTileWidth *
3275 : dfPixelXSizeZoomLevel0;
3276 176 : double dfTMSMinY = m_dfTMSMaxY - nTileYCountZoomLevel0 * nTileHeight *
3277 : dfPixelYSizeZoomLevel0;
3278 :
3279 : pszSQL =
3280 176 : sqlite3_mprintf("INSERT INTO gpkg_tile_matrix_set "
3281 : "(table_name,srs_id,min_x,min_y,max_x,max_y) VALUES "
3282 : "('%q',%d,%.17g,%.17g,%.17g,%.17g)",
3283 : m_osRasterTable.c_str(), m_nSRID, m_dfTMSMinX,
3284 : dfTMSMinY, dfTMSMaxX, m_dfTMSMaxY);
3285 176 : eErr = SQLCommand(hDB, pszSQL);
3286 176 : sqlite3_free(pszSQL);
3287 176 : if (eErr != OGRERR_NONE)
3288 : {
3289 0 : SoftRollbackTransaction();
3290 0 : return CE_Failure;
3291 : }
3292 :
3293 176 : m_apoOverviewDS.resize(m_nZoomLevel);
3294 :
3295 587 : for (int i = 0; i <= m_nZoomLevel; i++)
3296 : {
3297 411 : double dfPixelXSizeZoomLevel = 0.0;
3298 411 : double dfPixelYSizeZoomLevel = 0.0;
3299 411 : int nTileMatrixWidth = 0;
3300 411 : int nTileMatrixHeight = 0;
3301 411 : if (EQUAL(m_osTilingScheme, "CUSTOM"))
3302 : {
3303 230 : dfPixelXSizeZoomLevel =
3304 230 : m_adfGeoTransform[1] * (1 << (m_nZoomLevel - i));
3305 230 : dfPixelYSizeZoomLevel =
3306 230 : fabs(m_adfGeoTransform[5]) * (1 << (m_nZoomLevel - i));
3307 : }
3308 : else
3309 : {
3310 181 : dfPixelXSizeZoomLevel = dfPixelXSizeZoomLevel0 / (1 << i);
3311 181 : dfPixelYSizeZoomLevel = dfPixelYSizeZoomLevel0 / (1 << i);
3312 : }
3313 411 : nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << i);
3314 411 : nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << i);
3315 :
3316 411 : pszSQL = sqlite3_mprintf(
3317 : "INSERT INTO gpkg_tile_matrix "
3318 : "(table_name,zoom_level,matrix_width,matrix_height,tile_width,tile_"
3319 : "height,pixel_x_size,pixel_y_size) VALUES "
3320 : "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
3321 : m_osRasterTable.c_str(), i, nTileMatrixWidth, nTileMatrixHeight,
3322 : nTileWidth, nTileHeight, dfPixelXSizeZoomLevel,
3323 : dfPixelYSizeZoomLevel);
3324 411 : eErr = SQLCommand(hDB, pszSQL);
3325 411 : sqlite3_free(pszSQL);
3326 411 : if (eErr != OGRERR_NONE)
3327 : {
3328 0 : SoftRollbackTransaction();
3329 0 : return CE_Failure;
3330 : }
3331 :
3332 411 : if (i < m_nZoomLevel)
3333 : {
3334 470 : auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
3335 235 : poOvrDS->ShareLockWithParentDataset(this);
3336 235 : poOvrDS->InitRaster(this, m_osRasterTable, i, nBands, m_dfTMSMinX,
3337 : m_dfTMSMaxY, dfPixelXSizeZoomLevel,
3338 : dfPixelYSizeZoomLevel, nTileWidth, nTileHeight,
3339 : nTileMatrixWidth, nTileMatrixHeight, dfGDALMinX,
3340 : dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
3341 :
3342 235 : m_apoOverviewDS[m_nZoomLevel - 1 - i] = std::move(poOvrDS);
3343 : }
3344 : }
3345 :
3346 176 : if (!m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.empty())
3347 : {
3348 40 : eErr = SQLCommand(
3349 : hDB, m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.c_str());
3350 40 : m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.clear();
3351 40 : if (eErr != OGRERR_NONE)
3352 : {
3353 0 : SoftRollbackTransaction();
3354 0 : return CE_Failure;
3355 : }
3356 : }
3357 :
3358 176 : SoftCommitTransaction();
3359 :
3360 176 : m_apoOverviewDS.resize(m_nZoomLevel);
3361 176 : m_bRecordInsertedInGPKGContent = true;
3362 :
3363 176 : return CE_None;
3364 : }
3365 :
3366 : /************************************************************************/
3367 : /* FlushCache() */
3368 : /************************************************************************/
3369 :
3370 2550 : CPLErr GDALGeoPackageDataset::FlushCache(bool bAtClosing)
3371 : {
3372 2550 : if (m_bInFlushCache)
3373 0 : return CE_None;
3374 :
3375 2550 : if (eAccess == GA_Update || !m_bMetadataDirty)
3376 : {
3377 2547 : SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
3378 : }
3379 :
3380 2550 : if (m_bRemoveOGREmptyTable)
3381 : {
3382 637 : m_bRemoveOGREmptyTable = false;
3383 637 : RemoveOGREmptyTable();
3384 : }
3385 :
3386 2550 : CPLErr eErr = IFlushCacheWithErrCode(bAtClosing);
3387 :
3388 2550 : FlushMetadata();
3389 :
3390 2550 : if (eAccess == GA_Update || !m_bMetadataDirty)
3391 : {
3392 : // Needed again as above IFlushCacheWithErrCode()
3393 : // may have call GDALGeoPackageRasterBand::InvalidateStatistics()
3394 : // which modifies metadata
3395 2550 : SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
3396 : }
3397 :
3398 2550 : return eErr;
3399 : }
3400 :
3401 4784 : CPLErr GDALGeoPackageDataset::IFlushCacheWithErrCode(bool bAtClosing)
3402 :
3403 : {
3404 4784 : if (m_bInFlushCache)
3405 2167 : return CE_None;
3406 2617 : m_bInFlushCache = true;
3407 2617 : if (hDB && eAccess == GA_ReadOnly && bAtClosing)
3408 : {
3409 : // Clean-up metadata that will go to PAM by removing items that
3410 : // are reconstructed.
3411 1924 : CPLStringList aosMD;
3412 1584 : for (CSLConstList papszIter = GetMetadata(); papszIter && *papszIter;
3413 : ++papszIter)
3414 : {
3415 622 : char *pszKey = nullptr;
3416 622 : CPLParseNameValue(*papszIter, &pszKey);
3417 1244 : if (pszKey &&
3418 622 : (EQUAL(pszKey, "AREA_OR_POINT") ||
3419 477 : EQUAL(pszKey, "IDENTIFIER") || EQUAL(pszKey, "DESCRIPTION") ||
3420 256 : EQUAL(pszKey, "ZOOM_LEVEL") ||
3421 652 : STARTS_WITH(pszKey, "GPKG_METADATA_ITEM_")))
3422 : {
3423 : // remove it
3424 : }
3425 : else
3426 : {
3427 30 : aosMD.AddString(*papszIter);
3428 : }
3429 622 : CPLFree(pszKey);
3430 : }
3431 962 : oMDMD.SetMetadata(aosMD.List());
3432 962 : oMDMD.SetMetadata(nullptr, "IMAGE_STRUCTURE");
3433 :
3434 1924 : GDALPamDataset::FlushCache(bAtClosing);
3435 : }
3436 : else
3437 : {
3438 : // Short circuit GDALPamDataset to avoid serialization to .aux.xml
3439 1655 : GDALDataset::FlushCache(bAtClosing);
3440 : }
3441 :
3442 6482 : for (auto &poLayer : m_apoLayers)
3443 : {
3444 3865 : poLayer->RunDeferredCreationIfNecessary();
3445 3865 : poLayer->CreateSpatialIndexIfNecessary();
3446 : }
3447 :
3448 : // Update raster table last_change column in gpkg_contents if needed
3449 2617 : if (m_bHasModifiedTiles)
3450 : {
3451 536 : for (int i = 1; i <= nBands; ++i)
3452 : {
3453 : auto poBand =
3454 357 : cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
3455 357 : if (!poBand->HaveStatsMetadataBeenSetInThisSession())
3456 : {
3457 344 : poBand->InvalidateStatistics();
3458 344 : if (psPam && psPam->pszPamFilename)
3459 344 : VSIUnlink(psPam->pszPamFilename);
3460 : }
3461 : }
3462 :
3463 179 : UpdateGpkgContentsLastChange(m_osRasterTable);
3464 :
3465 179 : m_bHasModifiedTiles = false;
3466 : }
3467 :
3468 2617 : CPLErr eErr = FlushTiles();
3469 :
3470 2617 : m_bInFlushCache = false;
3471 2617 : return eErr;
3472 : }
3473 :
3474 : /************************************************************************/
3475 : /* GetCurrentDateEscapedSQL() */
3476 : /************************************************************************/
3477 :
3478 1863 : std::string GDALGeoPackageDataset::GetCurrentDateEscapedSQL()
3479 : {
3480 : const char *pszCurrentDate =
3481 1863 : CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
3482 1863 : if (pszCurrentDate)
3483 10 : return '\'' + SQLEscapeLiteral(pszCurrentDate) + '\'';
3484 1858 : return "strftime('%Y-%m-%dT%H:%M:%fZ','now')";
3485 : }
3486 :
3487 : /************************************************************************/
3488 : /* UpdateGpkgContentsLastChange() */
3489 : /************************************************************************/
3490 :
3491 : OGRErr
3492 806 : GDALGeoPackageDataset::UpdateGpkgContentsLastChange(const char *pszTableName)
3493 : {
3494 : char *pszSQL =
3495 806 : sqlite3_mprintf("UPDATE gpkg_contents SET "
3496 : "last_change = %s "
3497 : "WHERE lower(table_name) = lower('%q')",
3498 1612 : GetCurrentDateEscapedSQL().c_str(), pszTableName);
3499 806 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3500 806 : sqlite3_free(pszSQL);
3501 806 : return eErr;
3502 : }
3503 :
3504 : /************************************************************************/
3505 : /* IBuildOverviews() */
3506 : /************************************************************************/
3507 :
3508 20 : CPLErr GDALGeoPackageDataset::IBuildOverviews(
3509 : const char *pszResampling, int nOverviews, const int *panOverviewList,
3510 : int nBandsIn, const int * /*panBandList*/, GDALProgressFunc pfnProgress,
3511 : void *pProgressData, CSLConstList papszOptions)
3512 : {
3513 20 : if (GetAccess() != GA_Update)
3514 : {
3515 1 : CPLError(CE_Failure, CPLE_NotSupported,
3516 : "Overview building not supported on a database opened in "
3517 : "read-only mode");
3518 1 : return CE_Failure;
3519 : }
3520 19 : if (m_poParentDS != nullptr)
3521 : {
3522 1 : CPLError(CE_Failure, CPLE_NotSupported,
3523 : "Overview building not supported on overview dataset");
3524 1 : return CE_Failure;
3525 : }
3526 :
3527 18 : if (nOverviews == 0)
3528 : {
3529 5 : for (auto &poOvrDS : m_apoOverviewDS)
3530 3 : poOvrDS->FlushCache(false);
3531 :
3532 2 : SoftStartTransaction();
3533 :
3534 2 : if (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
3535 : {
3536 1 : char *pszSQL = sqlite3_mprintf(
3537 : "DELETE FROM gpkg_2d_gridded_tile_ancillary WHERE id IN "
3538 : "(SELECT y.id FROM \"%w\" x "
3539 : "JOIN gpkg_2d_gridded_tile_ancillary y "
3540 : "ON x.id = y.tpudt_id AND y.tpudt_name = '%q' AND "
3541 : "x.zoom_level < %d)",
3542 : m_osRasterTable.c_str(), m_osRasterTable.c_str(), m_nZoomLevel);
3543 1 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3544 1 : sqlite3_free(pszSQL);
3545 1 : if (eErr != OGRERR_NONE)
3546 : {
3547 0 : SoftRollbackTransaction();
3548 0 : return CE_Failure;
3549 : }
3550 : }
3551 :
3552 : char *pszSQL =
3553 2 : sqlite3_mprintf("DELETE FROM \"%w\" WHERE zoom_level < %d",
3554 : m_osRasterTable.c_str(), m_nZoomLevel);
3555 2 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3556 2 : sqlite3_free(pszSQL);
3557 2 : if (eErr != OGRERR_NONE)
3558 : {
3559 0 : SoftRollbackTransaction();
3560 0 : return CE_Failure;
3561 : }
3562 :
3563 2 : SoftCommitTransaction();
3564 :
3565 2 : return CE_None;
3566 : }
3567 :
3568 16 : if (nBandsIn != nBands)
3569 : {
3570 0 : CPLError(CE_Failure, CPLE_NotSupported,
3571 : "Generation of overviews in GPKG only"
3572 : "supported when operating on all bands.");
3573 0 : return CE_Failure;
3574 : }
3575 :
3576 16 : if (m_apoOverviewDS.empty())
3577 : {
3578 0 : CPLError(CE_Failure, CPLE_AppDefined,
3579 : "Image too small to support overviews");
3580 0 : return CE_Failure;
3581 : }
3582 :
3583 16 : FlushCache(false);
3584 60 : for (int i = 0; i < nOverviews; i++)
3585 : {
3586 47 : if (panOverviewList[i] < 2)
3587 : {
3588 1 : CPLError(CE_Failure, CPLE_IllegalArg,
3589 : "Overview factor must be >= 2");
3590 1 : return CE_Failure;
3591 : }
3592 :
3593 46 : bool bFound = false;
3594 46 : int jCandidate = -1;
3595 46 : int nMaxOvFactor = 0;
3596 196 : for (int j = 0; j < static_cast<int>(m_apoOverviewDS.size()); j++)
3597 : {
3598 190 : const auto poODS = m_apoOverviewDS[j].get();
3599 : const int nOvFactor = static_cast<int>(
3600 190 : 0.5 + poODS->m_adfGeoTransform[1] / m_adfGeoTransform[1]);
3601 :
3602 190 : nMaxOvFactor = nOvFactor;
3603 :
3604 190 : if (nOvFactor == panOverviewList[i])
3605 : {
3606 40 : bFound = true;
3607 40 : break;
3608 : }
3609 :
3610 150 : if (jCandidate < 0 && nOvFactor > panOverviewList[i])
3611 1 : jCandidate = j;
3612 : }
3613 :
3614 46 : if (!bFound)
3615 : {
3616 : /* Mostly for debug */
3617 6 : if (!CPLTestBool(CPLGetConfigOption(
3618 : "ALLOW_GPKG_ZOOM_OTHER_EXTENSION", "YES")))
3619 : {
3620 2 : CPLString osOvrList;
3621 4 : for (const auto &poODS : m_apoOverviewDS)
3622 : {
3623 : const int nOvFactor =
3624 2 : static_cast<int>(0.5 + poODS->m_adfGeoTransform[1] /
3625 2 : m_adfGeoTransform[1]);
3626 :
3627 2 : if (!osOvrList.empty())
3628 0 : osOvrList += ' ';
3629 2 : osOvrList += CPLSPrintf("%d", nOvFactor);
3630 : }
3631 2 : CPLError(CE_Failure, CPLE_NotSupported,
3632 : "Only overviews %s can be computed",
3633 : osOvrList.c_str());
3634 2 : return CE_Failure;
3635 : }
3636 : else
3637 : {
3638 4 : int nOvFactor = panOverviewList[i];
3639 4 : if (jCandidate < 0)
3640 3 : jCandidate = static_cast<int>(m_apoOverviewDS.size());
3641 :
3642 4 : int nOvXSize = std::max(1, GetRasterXSize() / nOvFactor);
3643 4 : int nOvYSize = std::max(1, GetRasterYSize() / nOvFactor);
3644 4 : if (!(jCandidate == static_cast<int>(m_apoOverviewDS.size()) &&
3645 5 : nOvFactor == 2 * nMaxOvFactor) &&
3646 1 : !m_bZoomOther)
3647 : {
3648 1 : CPLError(CE_Warning, CPLE_AppDefined,
3649 : "Use of overview factor %d causes gpkg_zoom_other "
3650 : "extension to be needed",
3651 : nOvFactor);
3652 1 : RegisterZoomOtherExtension();
3653 1 : m_bZoomOther = true;
3654 : }
3655 :
3656 4 : SoftStartTransaction();
3657 :
3658 4 : CPLAssert(jCandidate > 0);
3659 : const int nNewZoomLevel =
3660 4 : m_apoOverviewDS[jCandidate - 1]->m_nZoomLevel;
3661 :
3662 : char *pszSQL;
3663 : OGRErr eErr;
3664 24 : for (int k = 0; k <= jCandidate; k++)
3665 : {
3666 60 : pszSQL = sqlite3_mprintf(
3667 : "UPDATE gpkg_tile_matrix SET zoom_level = %d "
3668 : "WHERE lower(table_name) = lower('%q') AND zoom_level "
3669 : "= %d",
3670 20 : m_nZoomLevel - k + 1, m_osRasterTable.c_str(),
3671 20 : m_nZoomLevel - k);
3672 20 : eErr = SQLCommand(hDB, pszSQL);
3673 20 : sqlite3_free(pszSQL);
3674 20 : if (eErr != OGRERR_NONE)
3675 : {
3676 0 : SoftRollbackTransaction();
3677 0 : return CE_Failure;
3678 : }
3679 :
3680 : pszSQL =
3681 20 : sqlite3_mprintf("UPDATE \"%w\" SET zoom_level = %d "
3682 : "WHERE zoom_level = %d",
3683 : m_osRasterTable.c_str(),
3684 20 : m_nZoomLevel - k + 1, m_nZoomLevel - k);
3685 20 : eErr = SQLCommand(hDB, pszSQL);
3686 20 : sqlite3_free(pszSQL);
3687 20 : if (eErr != OGRERR_NONE)
3688 : {
3689 0 : SoftRollbackTransaction();
3690 0 : return CE_Failure;
3691 : }
3692 : }
3693 :
3694 4 : double dfGDALMinX = m_adfGeoTransform[0];
3695 : double dfGDALMinY =
3696 4 : m_adfGeoTransform[3] + nRasterYSize * m_adfGeoTransform[5];
3697 : double dfGDALMaxX =
3698 4 : m_adfGeoTransform[0] + nRasterXSize * m_adfGeoTransform[1];
3699 4 : double dfGDALMaxY = m_adfGeoTransform[3];
3700 4 : double dfPixelXSizeZoomLevel = m_adfGeoTransform[1] * nOvFactor;
3701 : double dfPixelYSizeZoomLevel =
3702 4 : fabs(m_adfGeoTransform[5]) * nOvFactor;
3703 : int nTileWidth, nTileHeight;
3704 4 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3705 4 : int nTileMatrixWidth = (nOvXSize + nTileWidth - 1) / nTileWidth;
3706 4 : int nTileMatrixHeight =
3707 4 : (nOvYSize + nTileHeight - 1) / nTileHeight;
3708 4 : pszSQL = sqlite3_mprintf(
3709 : "INSERT INTO gpkg_tile_matrix "
3710 : "(table_name,zoom_level,matrix_width,matrix_height,tile_"
3711 : "width,tile_height,pixel_x_size,pixel_y_size) VALUES "
3712 : "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
3713 : m_osRasterTable.c_str(), nNewZoomLevel, nTileMatrixWidth,
3714 : nTileMatrixHeight, nTileWidth, nTileHeight,
3715 : dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel);
3716 4 : eErr = SQLCommand(hDB, pszSQL);
3717 4 : sqlite3_free(pszSQL);
3718 4 : if (eErr != OGRERR_NONE)
3719 : {
3720 0 : SoftRollbackTransaction();
3721 0 : return CE_Failure;
3722 : }
3723 :
3724 4 : SoftCommitTransaction();
3725 :
3726 4 : m_nZoomLevel++; /* this change our zoom level as well as
3727 : previous overviews */
3728 20 : for (int k = 0; k < jCandidate; k++)
3729 16 : m_apoOverviewDS[k]->m_nZoomLevel++;
3730 :
3731 4 : auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
3732 4 : poOvrDS->ShareLockWithParentDataset(this);
3733 4 : poOvrDS->InitRaster(
3734 : this, m_osRasterTable, nNewZoomLevel, nBands, m_dfTMSMinX,
3735 : m_dfTMSMaxY, dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel,
3736 : nTileWidth, nTileHeight, nTileMatrixWidth,
3737 : nTileMatrixHeight, dfGDALMinX, dfGDALMinY, dfGDALMaxX,
3738 : dfGDALMaxY);
3739 4 : m_apoOverviewDS.insert(m_apoOverviewDS.begin() + jCandidate,
3740 8 : std::move(poOvrDS));
3741 : }
3742 : }
3743 : }
3744 :
3745 : GDALRasterBand ***papapoOverviewBands = static_cast<GDALRasterBand ***>(
3746 13 : CPLCalloc(sizeof(GDALRasterBand **), nBands));
3747 13 : CPLErr eErr = CE_None;
3748 49 : for (int iBand = 0; eErr == CE_None && iBand < nBands; iBand++)
3749 : {
3750 72 : papapoOverviewBands[iBand] = static_cast<GDALRasterBand **>(
3751 36 : CPLCalloc(sizeof(GDALRasterBand *), nOverviews));
3752 36 : int iCurOverview = 0;
3753 185 : for (int i = 0; i < nOverviews; i++)
3754 : {
3755 149 : bool bFound = false;
3756 724 : for (const auto &poODS : m_apoOverviewDS)
3757 : {
3758 : const int nOvFactor = static_cast<int>(
3759 724 : 0.5 + poODS->m_adfGeoTransform[1] / m_adfGeoTransform[1]);
3760 :
3761 724 : if (nOvFactor == panOverviewList[i])
3762 : {
3763 298 : papapoOverviewBands[iBand][iCurOverview] =
3764 149 : poODS->GetRasterBand(iBand + 1);
3765 149 : iCurOverview++;
3766 149 : bFound = true;
3767 149 : break;
3768 : }
3769 : }
3770 149 : if (!bFound)
3771 : {
3772 0 : CPLError(CE_Failure, CPLE_AppDefined,
3773 : "Could not find dataset corresponding to ov factor %d",
3774 0 : panOverviewList[i]);
3775 0 : eErr = CE_Failure;
3776 : }
3777 : }
3778 36 : if (eErr == CE_None)
3779 : {
3780 36 : CPLAssert(iCurOverview == nOverviews);
3781 : }
3782 : }
3783 :
3784 13 : if (eErr == CE_None)
3785 13 : eErr = GDALRegenerateOverviewsMultiBand(
3786 13 : nBands, papoBands, nOverviews, papapoOverviewBands, pszResampling,
3787 : pfnProgress, pProgressData, papszOptions);
3788 :
3789 49 : for (int iBand = 0; iBand < nBands; iBand++)
3790 : {
3791 36 : CPLFree(papapoOverviewBands[iBand]);
3792 : }
3793 13 : CPLFree(papapoOverviewBands);
3794 :
3795 13 : return eErr;
3796 : }
3797 :
3798 : /************************************************************************/
3799 : /* GetFileList() */
3800 : /************************************************************************/
3801 :
3802 36 : char **GDALGeoPackageDataset::GetFileList()
3803 : {
3804 36 : TryLoadXML();
3805 36 : return GDALPamDataset::GetFileList();
3806 : }
3807 :
3808 : /************************************************************************/
3809 : /* GetMetadataDomainList() */
3810 : /************************************************************************/
3811 :
3812 42 : char **GDALGeoPackageDataset::GetMetadataDomainList()
3813 : {
3814 42 : GetMetadata();
3815 42 : if (!m_osRasterTable.empty())
3816 5 : GetMetadata("GEOPACKAGE");
3817 42 : return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
3818 42 : TRUE, "SUBDATASETS", nullptr);
3819 : }
3820 :
3821 : /************************************************************************/
3822 : /* CheckMetadataDomain() */
3823 : /************************************************************************/
3824 :
3825 5163 : const char *GDALGeoPackageDataset::CheckMetadataDomain(const char *pszDomain)
3826 : {
3827 5346 : if (pszDomain != nullptr && EQUAL(pszDomain, "GEOPACKAGE") &&
3828 183 : m_osRasterTable.empty())
3829 : {
3830 4 : CPLError(
3831 : CE_Warning, CPLE_IllegalArg,
3832 : "Using GEOPACKAGE for a non-raster geopackage is not supported. "
3833 : "Using default domain instead");
3834 4 : return nullptr;
3835 : }
3836 5159 : return pszDomain;
3837 : }
3838 :
3839 : /************************************************************************/
3840 : /* HasMetadataTables() */
3841 : /************************************************************************/
3842 :
3843 5234 : bool GDALGeoPackageDataset::HasMetadataTables() const
3844 : {
3845 5234 : if (m_nHasMetadataTables < 0)
3846 : {
3847 : const int nCount =
3848 1937 : SQLGetInteger(hDB,
3849 : "SELECT COUNT(*) FROM sqlite_master WHERE name IN "
3850 : "('gpkg_metadata', 'gpkg_metadata_reference') "
3851 : "AND type IN ('table', 'view')",
3852 : nullptr);
3853 1937 : m_nHasMetadataTables = nCount == 2;
3854 : }
3855 5234 : return CPL_TO_BOOL(m_nHasMetadataTables);
3856 : }
3857 :
3858 : /************************************************************************/
3859 : /* HasDataColumnsTable() */
3860 : /************************************************************************/
3861 :
3862 1156 : bool GDALGeoPackageDataset::HasDataColumnsTable() const
3863 : {
3864 2312 : const int nCount = SQLGetInteger(
3865 1156 : hDB,
3866 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_data_columns'"
3867 : "AND type IN ('table', 'view')",
3868 : nullptr);
3869 1156 : return nCount == 1;
3870 : }
3871 :
3872 : /************************************************************************/
3873 : /* HasDataColumnConstraintsTable() */
3874 : /************************************************************************/
3875 :
3876 119 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTable() const
3877 : {
3878 119 : const int nCount = SQLGetInteger(hDB,
3879 : "SELECT 1 FROM sqlite_master WHERE name = "
3880 : "'gpkg_data_column_constraints'"
3881 : "AND type IN ('table', 'view')",
3882 : nullptr);
3883 119 : return nCount == 1;
3884 : }
3885 :
3886 : /************************************************************************/
3887 : /* HasDataColumnConstraintsTableGPKG_1_0() */
3888 : /************************************************************************/
3889 :
3890 73 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTableGPKG_1_0() const
3891 : {
3892 73 : if (m_nApplicationId != GP10_APPLICATION_ID)
3893 71 : return false;
3894 : // In GPKG 1.0, the columns were named minIsInclusive, maxIsInclusive
3895 : // They were changed in 1.1 to min_is_inclusive, max_is_inclusive
3896 2 : bool bRet = false;
3897 2 : sqlite3_stmt *hSQLStmt = nullptr;
3898 2 : int rc = sqlite3_prepare_v2(hDB,
3899 : "SELECT minIsInclusive, maxIsInclusive FROM "
3900 : "gpkg_data_column_constraints",
3901 : -1, &hSQLStmt, nullptr);
3902 2 : if (rc == SQLITE_OK)
3903 : {
3904 2 : bRet = true;
3905 2 : sqlite3_finalize(hSQLStmt);
3906 : }
3907 2 : return bRet;
3908 : }
3909 :
3910 : /************************************************************************/
3911 : /* CreateColumnsTableAndColumnConstraintsTablesIfNecessary() */
3912 : /************************************************************************/
3913 :
3914 49 : bool GDALGeoPackageDataset::
3915 : CreateColumnsTableAndColumnConstraintsTablesIfNecessary()
3916 : {
3917 49 : if (!HasDataColumnsTable())
3918 : {
3919 : // Geopackage < 1.3 had
3920 : // CONSTRAINT fk_gdc_tn FOREIGN KEY (table_name) REFERENCES
3921 : // gpkg_contents(table_name) instead of the unique constraint.
3922 10 : if (OGRERR_NONE !=
3923 10 : SQLCommand(
3924 : GetDB(),
3925 : "CREATE TABLE gpkg_data_columns ("
3926 : "table_name TEXT NOT NULL,"
3927 : "column_name TEXT NOT NULL,"
3928 : "name TEXT,"
3929 : "title TEXT,"
3930 : "description TEXT,"
3931 : "mime_type TEXT,"
3932 : "constraint_name TEXT,"
3933 : "CONSTRAINT pk_gdc PRIMARY KEY (table_name, column_name),"
3934 : "CONSTRAINT gdc_tn UNIQUE (table_name, name));"))
3935 : {
3936 0 : return false;
3937 : }
3938 : }
3939 49 : if (!HasDataColumnConstraintsTable())
3940 : {
3941 22 : const char *min_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
3942 11 : ? "min_is_inclusive"
3943 : : "minIsInclusive";
3944 22 : const char *max_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
3945 11 : ? "max_is_inclusive"
3946 : : "maxIsInclusive";
3947 :
3948 : const std::string osSQL(
3949 : CPLSPrintf("CREATE TABLE gpkg_data_column_constraints ("
3950 : "constraint_name TEXT NOT NULL,"
3951 : "constraint_type TEXT NOT NULL,"
3952 : "value TEXT,"
3953 : "min NUMERIC,"
3954 : "%s BOOLEAN,"
3955 : "max NUMERIC,"
3956 : "%s BOOLEAN,"
3957 : "description TEXT,"
3958 : "CONSTRAINT gdcc_ntv UNIQUE (constraint_name, "
3959 : "constraint_type, value));",
3960 11 : min_is_inclusive, max_is_inclusive));
3961 11 : if (OGRERR_NONE != SQLCommand(GetDB(), osSQL.c_str()))
3962 : {
3963 0 : return false;
3964 : }
3965 : }
3966 49 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
3967 : {
3968 0 : return false;
3969 : }
3970 49 : if (SQLGetInteger(GetDB(),
3971 : "SELECT 1 FROM gpkg_extensions WHERE "
3972 : "table_name = 'gpkg_data_columns'",
3973 49 : nullptr) != 1)
3974 : {
3975 11 : if (OGRERR_NONE !=
3976 11 : SQLCommand(
3977 : GetDB(),
3978 : "INSERT INTO gpkg_extensions "
3979 : "(table_name,column_name,extension_name,definition,scope) "
3980 : "VALUES ('gpkg_data_columns', NULL, 'gpkg_schema', "
3981 : "'http://www.geopackage.org/spec121/#extension_schema', "
3982 : "'read-write')"))
3983 : {
3984 0 : return false;
3985 : }
3986 : }
3987 49 : if (SQLGetInteger(GetDB(),
3988 : "SELECT 1 FROM gpkg_extensions WHERE "
3989 : "table_name = 'gpkg_data_column_constraints'",
3990 49 : nullptr) != 1)
3991 : {
3992 11 : if (OGRERR_NONE !=
3993 11 : SQLCommand(
3994 : GetDB(),
3995 : "INSERT INTO gpkg_extensions "
3996 : "(table_name,column_name,extension_name,definition,scope) "
3997 : "VALUES ('gpkg_data_column_constraints', NULL, 'gpkg_schema', "
3998 : "'http://www.geopackage.org/spec121/#extension_schema', "
3999 : "'read-write')"))
4000 : {
4001 0 : return false;
4002 : }
4003 : }
4004 :
4005 49 : return true;
4006 : }
4007 :
4008 : /************************************************************************/
4009 : /* HasGpkgextRelationsTable() */
4010 : /************************************************************************/
4011 :
4012 1140 : bool GDALGeoPackageDataset::HasGpkgextRelationsTable() const
4013 : {
4014 2280 : const int nCount = SQLGetInteger(
4015 1140 : hDB,
4016 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkgext_relations'"
4017 : "AND type IN ('table', 'view')",
4018 : nullptr);
4019 1140 : return nCount == 1;
4020 : }
4021 :
4022 : /************************************************************************/
4023 : /* CreateRelationsTableIfNecessary() */
4024 : /************************************************************************/
4025 :
4026 9 : bool GDALGeoPackageDataset::CreateRelationsTableIfNecessary()
4027 : {
4028 9 : if (HasGpkgextRelationsTable())
4029 : {
4030 5 : return true;
4031 : }
4032 :
4033 4 : if (OGRERR_NONE !=
4034 4 : SQLCommand(GetDB(), "CREATE TABLE gpkgext_relations ("
4035 : "id INTEGER PRIMARY KEY AUTOINCREMENT,"
4036 : "base_table_name TEXT NOT NULL,"
4037 : "base_primary_column TEXT NOT NULL DEFAULT 'id',"
4038 : "related_table_name TEXT NOT NULL,"
4039 : "related_primary_column TEXT NOT NULL DEFAULT 'id',"
4040 : "relation_name TEXT NOT NULL,"
4041 : "mapping_table_name TEXT NOT NULL UNIQUE);"))
4042 : {
4043 0 : return false;
4044 : }
4045 :
4046 4 : return true;
4047 : }
4048 :
4049 : /************************************************************************/
4050 : /* HasQGISLayerStyles() */
4051 : /************************************************************************/
4052 :
4053 11 : bool GDALGeoPackageDataset::HasQGISLayerStyles() const
4054 : {
4055 : // QGIS layer_styles extension:
4056 : // https://github.com/pka/qgpkg/blob/master/qgis_geopackage_extension.md
4057 11 : bool bRet = false;
4058 : const int nCount =
4059 11 : SQLGetInteger(hDB,
4060 : "SELECT 1 FROM sqlite_master WHERE name = 'layer_styles'"
4061 : "AND type = 'table'",
4062 : nullptr);
4063 11 : if (nCount == 1)
4064 : {
4065 1 : sqlite3_stmt *hSQLStmt = nullptr;
4066 2 : int rc = sqlite3_prepare_v2(
4067 1 : hDB, "SELECT f_table_name, f_geometry_column FROM layer_styles", -1,
4068 : &hSQLStmt, nullptr);
4069 1 : if (rc == SQLITE_OK)
4070 : {
4071 1 : bRet = true;
4072 1 : sqlite3_finalize(hSQLStmt);
4073 : }
4074 : }
4075 11 : return bRet;
4076 : }
4077 :
4078 : /************************************************************************/
4079 : /* GetMetadata() */
4080 : /************************************************************************/
4081 :
4082 3470 : char **GDALGeoPackageDataset::GetMetadata(const char *pszDomain)
4083 :
4084 : {
4085 3470 : pszDomain = CheckMetadataDomain(pszDomain);
4086 3470 : if (pszDomain != nullptr && EQUAL(pszDomain, "SUBDATASETS"))
4087 61 : return m_aosSubDatasets.List();
4088 :
4089 3409 : if (m_bHasReadMetadataFromStorage)
4090 1521 : return GDALPamDataset::GetMetadata(pszDomain);
4091 :
4092 1888 : m_bHasReadMetadataFromStorage = true;
4093 :
4094 1888 : TryLoadXML();
4095 :
4096 1888 : if (!HasMetadataTables())
4097 1389 : return GDALPamDataset::GetMetadata(pszDomain);
4098 :
4099 499 : char *pszSQL = nullptr;
4100 499 : if (!m_osRasterTable.empty())
4101 : {
4102 169 : pszSQL = sqlite3_mprintf(
4103 : "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
4104 : "mdr.reference_scope FROM gpkg_metadata md "
4105 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4106 : "WHERE "
4107 : "(mdr.reference_scope = 'geopackage' OR "
4108 : "(mdr.reference_scope = 'table' AND lower(mdr.table_name) = "
4109 : "lower('%q'))) ORDER BY md.id "
4110 : "LIMIT 1000", // to avoid denial of service
4111 : m_osRasterTable.c_str());
4112 : }
4113 : else
4114 : {
4115 330 : pszSQL = sqlite3_mprintf(
4116 : "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
4117 : "mdr.reference_scope FROM gpkg_metadata md "
4118 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4119 : "WHERE "
4120 : "mdr.reference_scope = 'geopackage' ORDER BY md.id "
4121 : "LIMIT 1000" // to avoid denial of service
4122 : );
4123 : }
4124 :
4125 998 : auto oResult = SQLQuery(hDB, pszSQL);
4126 499 : sqlite3_free(pszSQL);
4127 499 : if (!oResult)
4128 : {
4129 0 : return GDALPamDataset::GetMetadata(pszDomain);
4130 : }
4131 :
4132 499 : char **papszMetadata = CSLDuplicate(GDALPamDataset::GetMetadata());
4133 :
4134 : /* GDAL metadata */
4135 688 : for (int i = 0; i < oResult->RowCount(); i++)
4136 : {
4137 189 : const char *pszMetadata = oResult->GetValue(0, i);
4138 189 : const char *pszMDStandardURI = oResult->GetValue(1, i);
4139 189 : const char *pszMimeType = oResult->GetValue(2, i);
4140 189 : const char *pszReferenceScope = oResult->GetValue(3, i);
4141 189 : if (pszMetadata && pszMDStandardURI && pszMimeType &&
4142 189 : pszReferenceScope && EQUAL(pszMDStandardURI, "http://gdal.org") &&
4143 173 : EQUAL(pszMimeType, "text/xml"))
4144 : {
4145 173 : CPLXMLNode *psXMLNode = CPLParseXMLString(pszMetadata);
4146 173 : if (psXMLNode)
4147 : {
4148 346 : GDALMultiDomainMetadata oLocalMDMD;
4149 173 : oLocalMDMD.XMLInit(psXMLNode, FALSE);
4150 331 : if (!m_osRasterTable.empty() &&
4151 158 : EQUAL(pszReferenceScope, "geopackage"))
4152 : {
4153 6 : oMDMD.SetMetadata(oLocalMDMD.GetMetadata(), "GEOPACKAGE");
4154 : }
4155 : else
4156 : {
4157 : papszMetadata =
4158 167 : CSLMerge(papszMetadata, oLocalMDMD.GetMetadata());
4159 167 : CSLConstList papszDomainList = oLocalMDMD.GetDomainList();
4160 167 : CSLConstList papszIter = papszDomainList;
4161 444 : while (papszIter && *papszIter)
4162 : {
4163 277 : if (EQUAL(*papszIter, "IMAGE_STRUCTURE"))
4164 : {
4165 : CSLConstList papszMD =
4166 125 : oLocalMDMD.GetMetadata(*papszIter);
4167 : const char *pszBAND_COUNT =
4168 125 : CSLFetchNameValue(papszMD, "BAND_COUNT");
4169 125 : if (pszBAND_COUNT)
4170 123 : m_nBandCountFromMetadata = atoi(pszBAND_COUNT);
4171 :
4172 : const char *pszCOLOR_TABLE =
4173 125 : CSLFetchNameValue(papszMD, "COLOR_TABLE");
4174 125 : if (pszCOLOR_TABLE)
4175 : {
4176 : const CPLStringList aosTokens(
4177 : CSLTokenizeString2(pszCOLOR_TABLE, "{,",
4178 26 : 0));
4179 13 : if ((aosTokens.size() % 4) == 0)
4180 : {
4181 13 : const int nColors = aosTokens.size() / 4;
4182 : m_poCTFromMetadata =
4183 13 : std::make_unique<GDALColorTable>();
4184 3341 : for (int iColor = 0; iColor < nColors;
4185 : ++iColor)
4186 : {
4187 : GDALColorEntry sEntry;
4188 3328 : sEntry.c1 = static_cast<short>(
4189 3328 : atoi(aosTokens[4 * iColor + 0]));
4190 3328 : sEntry.c2 = static_cast<short>(
4191 3328 : atoi(aosTokens[4 * iColor + 1]));
4192 3328 : sEntry.c3 = static_cast<short>(
4193 3328 : atoi(aosTokens[4 * iColor + 2]));
4194 3328 : sEntry.c4 = static_cast<short>(
4195 3328 : atoi(aosTokens[4 * iColor + 3]));
4196 3328 : m_poCTFromMetadata->SetColorEntry(
4197 : iColor, &sEntry);
4198 : }
4199 : }
4200 : }
4201 :
4202 : const char *pszTILE_FORMAT =
4203 125 : CSLFetchNameValue(papszMD, "TILE_FORMAT");
4204 125 : if (pszTILE_FORMAT)
4205 : {
4206 8 : m_osTFFromMetadata = pszTILE_FORMAT;
4207 8 : oMDMD.SetMetadataItem("TILE_FORMAT",
4208 : pszTILE_FORMAT,
4209 : "IMAGE_STRUCTURE");
4210 : }
4211 :
4212 : const char *pszNodataValue =
4213 125 : CSLFetchNameValue(papszMD, "NODATA_VALUE");
4214 125 : if (pszNodataValue)
4215 : {
4216 2 : m_osNodataValueFromMetadata = pszNodataValue;
4217 : }
4218 : }
4219 :
4220 152 : else if (!EQUAL(*papszIter, "") &&
4221 16 : !STARTS_WITH(*papszIter, "BAND_"))
4222 : {
4223 12 : oMDMD.SetMetadata(
4224 6 : oLocalMDMD.GetMetadata(*papszIter), *papszIter);
4225 : }
4226 277 : papszIter++;
4227 : }
4228 : }
4229 173 : CPLDestroyXMLNode(psXMLNode);
4230 : }
4231 : }
4232 : }
4233 :
4234 499 : GDALPamDataset::SetMetadata(papszMetadata);
4235 499 : CSLDestroy(papszMetadata);
4236 499 : papszMetadata = nullptr;
4237 :
4238 : /* Add non-GDAL metadata now */
4239 499 : int nNonGDALMDILocal = 1;
4240 499 : int nNonGDALMDIGeopackage = 1;
4241 688 : for (int i = 0; i < oResult->RowCount(); i++)
4242 : {
4243 189 : const char *pszMetadata = oResult->GetValue(0, i);
4244 189 : const char *pszMDStandardURI = oResult->GetValue(1, i);
4245 189 : const char *pszMimeType = oResult->GetValue(2, i);
4246 189 : const char *pszReferenceScope = oResult->GetValue(3, i);
4247 189 : if (pszMetadata == nullptr || pszMDStandardURI == nullptr ||
4248 189 : pszMimeType == nullptr || pszReferenceScope == nullptr)
4249 : {
4250 : // should not happen as there are NOT NULL constraints
4251 : // But a database could lack such NOT NULL constraints or have
4252 : // large values that would cause a memory allocation failure.
4253 0 : continue;
4254 : }
4255 189 : int bIsGPKGScope = EQUAL(pszReferenceScope, "geopackage");
4256 189 : if (EQUAL(pszMDStandardURI, "http://gdal.org") &&
4257 173 : EQUAL(pszMimeType, "text/xml"))
4258 173 : continue;
4259 :
4260 16 : if (!m_osRasterTable.empty() && bIsGPKGScope)
4261 : {
4262 8 : oMDMD.SetMetadataItem(
4263 : CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDIGeopackage),
4264 : pszMetadata, "GEOPACKAGE");
4265 8 : nNonGDALMDIGeopackage++;
4266 : }
4267 : /*else if( strcmp( pszMDStandardURI, "http://www.isotc211.org/2005/gmd"
4268 : ) == 0 && strcmp( pszMimeType, "text/xml" ) == 0 )
4269 : {
4270 : char* apszMD[2];
4271 : apszMD[0] = (char*)pszMetadata;
4272 : apszMD[1] = NULL;
4273 : oMDMD.SetMetadata(apszMD, "xml:MD_Metadata");
4274 : }*/
4275 : else
4276 : {
4277 8 : oMDMD.SetMetadataItem(
4278 : CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDILocal),
4279 : pszMetadata);
4280 8 : nNonGDALMDILocal++;
4281 : }
4282 : }
4283 :
4284 499 : return GDALPamDataset::GetMetadata(pszDomain);
4285 : }
4286 :
4287 : /************************************************************************/
4288 : /* WriteMetadata() */
4289 : /************************************************************************/
4290 :
4291 736 : void GDALGeoPackageDataset::WriteMetadata(
4292 : CPLXMLNode *psXMLNode, /* will be destroyed by the method */
4293 : const char *pszTableName)
4294 : {
4295 736 : const bool bIsEmpty = (psXMLNode == nullptr);
4296 736 : if (!HasMetadataTables())
4297 : {
4298 528 : if (bIsEmpty || !CreateMetadataTables())
4299 : {
4300 251 : CPLDestroyXMLNode(psXMLNode);
4301 251 : return;
4302 : }
4303 : }
4304 :
4305 485 : char *pszXML = nullptr;
4306 485 : if (!bIsEmpty)
4307 : {
4308 : CPLXMLNode *psMasterXMLNode =
4309 324 : CPLCreateXMLNode(nullptr, CXT_Element, "GDALMultiDomainMetadata");
4310 324 : psMasterXMLNode->psChild = psXMLNode;
4311 324 : pszXML = CPLSerializeXMLTree(psMasterXMLNode);
4312 324 : CPLDestroyXMLNode(psMasterXMLNode);
4313 : }
4314 : // cppcheck-suppress uselessAssignmentPtrArg
4315 485 : psXMLNode = nullptr;
4316 :
4317 485 : char *pszSQL = nullptr;
4318 485 : if (pszTableName && pszTableName[0] != '\0')
4319 : {
4320 337 : pszSQL = sqlite3_mprintf(
4321 : "SELECT md.id FROM gpkg_metadata md "
4322 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4323 : "WHERE md.md_scope = 'dataset' AND "
4324 : "md.md_standard_uri='http://gdal.org' "
4325 : "AND md.mime_type='text/xml' AND mdr.reference_scope = 'table' AND "
4326 : "lower(mdr.table_name) = lower('%q')",
4327 : pszTableName);
4328 : }
4329 : else
4330 : {
4331 148 : pszSQL = sqlite3_mprintf(
4332 : "SELECT md.id FROM gpkg_metadata md "
4333 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4334 : "WHERE md.md_scope = 'dataset' AND "
4335 : "md.md_standard_uri='http://gdal.org' "
4336 : "AND md.mime_type='text/xml' AND mdr.reference_scope = "
4337 : "'geopackage'");
4338 : }
4339 : OGRErr err;
4340 485 : int mdId = SQLGetInteger(hDB, pszSQL, &err);
4341 485 : if (err != OGRERR_NONE)
4342 453 : mdId = -1;
4343 485 : sqlite3_free(pszSQL);
4344 :
4345 485 : if (bIsEmpty)
4346 : {
4347 161 : if (mdId >= 0)
4348 : {
4349 6 : SQLCommand(
4350 : hDB,
4351 : CPLSPrintf(
4352 : "DELETE FROM gpkg_metadata_reference WHERE md_file_id = %d",
4353 : mdId));
4354 6 : SQLCommand(
4355 : hDB,
4356 : CPLSPrintf("DELETE FROM gpkg_metadata WHERE id = %d", mdId));
4357 : }
4358 : }
4359 : else
4360 : {
4361 324 : if (mdId >= 0)
4362 : {
4363 26 : pszSQL = sqlite3_mprintf(
4364 : "UPDATE gpkg_metadata SET metadata = '%q' WHERE id = %d",
4365 : pszXML, mdId);
4366 : }
4367 : else
4368 : {
4369 : pszSQL =
4370 298 : sqlite3_mprintf("INSERT INTO gpkg_metadata (md_scope, "
4371 : "md_standard_uri, mime_type, metadata) VALUES "
4372 : "('dataset','http://gdal.org','text/xml','%q')",
4373 : pszXML);
4374 : }
4375 324 : SQLCommand(hDB, pszSQL);
4376 324 : sqlite3_free(pszSQL);
4377 :
4378 324 : CPLFree(pszXML);
4379 :
4380 324 : if (mdId < 0)
4381 : {
4382 298 : const sqlite_int64 nFID = sqlite3_last_insert_rowid(hDB);
4383 298 : if (pszTableName != nullptr && pszTableName[0] != '\0')
4384 : {
4385 286 : pszSQL = sqlite3_mprintf(
4386 : "INSERT INTO gpkg_metadata_reference (reference_scope, "
4387 : "table_name, timestamp, md_file_id) VALUES "
4388 : "('table', '%q', %s, %d)",
4389 572 : pszTableName, GetCurrentDateEscapedSQL().c_str(),
4390 : static_cast<int>(nFID));
4391 : }
4392 : else
4393 : {
4394 12 : pszSQL = sqlite3_mprintf(
4395 : "INSERT INTO gpkg_metadata_reference (reference_scope, "
4396 : "timestamp, md_file_id) VALUES "
4397 : "('geopackage', %s, %d)",
4398 24 : GetCurrentDateEscapedSQL().c_str(), static_cast<int>(nFID));
4399 : }
4400 : }
4401 : else
4402 : {
4403 26 : pszSQL = sqlite3_mprintf("UPDATE gpkg_metadata_reference SET "
4404 : "timestamp = %s WHERE md_file_id = %d",
4405 52 : GetCurrentDateEscapedSQL().c_str(), mdId);
4406 : }
4407 324 : SQLCommand(hDB, pszSQL);
4408 324 : sqlite3_free(pszSQL);
4409 : }
4410 : }
4411 :
4412 : /************************************************************************/
4413 : /* CreateMetadataTables() */
4414 : /************************************************************************/
4415 :
4416 295 : bool GDALGeoPackageDataset::CreateMetadataTables()
4417 : {
4418 : const bool bCreateTriggers =
4419 295 : CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "NO"));
4420 :
4421 : /* From C.10. gpkg_metadata Table 35. gpkg_metadata Table Definition SQL */
4422 : CPLString osSQL = "CREATE TABLE gpkg_metadata ("
4423 : "id INTEGER CONSTRAINT m_pk PRIMARY KEY ASC NOT NULL,"
4424 : "md_scope TEXT NOT NULL DEFAULT 'dataset',"
4425 : "md_standard_uri TEXT NOT NULL,"
4426 : "mime_type TEXT NOT NULL DEFAULT 'text/xml',"
4427 : "metadata TEXT NOT NULL DEFAULT ''"
4428 590 : ")";
4429 :
4430 : /* From D.2. metadata Table 40. metadata Trigger Definition SQL */
4431 295 : const char *pszMetadataTriggers =
4432 : "CREATE TRIGGER 'gpkg_metadata_md_scope_insert' "
4433 : "BEFORE INSERT ON 'gpkg_metadata' "
4434 : "FOR EACH ROW BEGIN "
4435 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata violates "
4436 : "constraint: md_scope must be one of undefined | fieldSession | "
4437 : "collectionSession | series | dataset | featureType | feature | "
4438 : "attributeType | attribute | tile | model | catalogue | schema | "
4439 : "taxonomy software | service | collectionHardware | "
4440 : "nonGeographicDataset | dimensionGroup') "
4441 : "WHERE NOT(NEW.md_scope IN "
4442 : "('undefined','fieldSession','collectionSession','series','dataset', "
4443 : "'featureType','feature','attributeType','attribute','tile','model', "
4444 : "'catalogue','schema','taxonomy','software','service', "
4445 : "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
4446 : "END; "
4447 : "CREATE TRIGGER 'gpkg_metadata_md_scope_update' "
4448 : "BEFORE UPDATE OF 'md_scope' ON 'gpkg_metadata' "
4449 : "FOR EACH ROW BEGIN "
4450 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata violates "
4451 : "constraint: md_scope must be one of undefined | fieldSession | "
4452 : "collectionSession | series | dataset | featureType | feature | "
4453 : "attributeType | attribute | tile | model | catalogue | schema | "
4454 : "taxonomy software | service | collectionHardware | "
4455 : "nonGeographicDataset | dimensionGroup') "
4456 : "WHERE NOT(NEW.md_scope IN "
4457 : "('undefined','fieldSession','collectionSession','series','dataset', "
4458 : "'featureType','feature','attributeType','attribute','tile','model', "
4459 : "'catalogue','schema','taxonomy','software','service', "
4460 : "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
4461 : "END";
4462 295 : if (bCreateTriggers)
4463 : {
4464 0 : osSQL += ";";
4465 0 : osSQL += pszMetadataTriggers;
4466 : }
4467 :
4468 : /* From C.11. gpkg_metadata_reference Table 36. gpkg_metadata_reference
4469 : * Table Definition SQL */
4470 : osSQL += ";"
4471 : "CREATE TABLE gpkg_metadata_reference ("
4472 : "reference_scope TEXT NOT NULL,"
4473 : "table_name TEXT,"
4474 : "column_name TEXT,"
4475 : "row_id_value INTEGER,"
4476 : "timestamp DATETIME NOT NULL DEFAULT "
4477 : "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
4478 : "md_file_id INTEGER NOT NULL,"
4479 : "md_parent_id INTEGER,"
4480 : "CONSTRAINT crmr_mfi_fk FOREIGN KEY (md_file_id) REFERENCES "
4481 : "gpkg_metadata(id),"
4482 : "CONSTRAINT crmr_mpi_fk FOREIGN KEY (md_parent_id) REFERENCES "
4483 : "gpkg_metadata(id)"
4484 295 : ")";
4485 :
4486 : /* From D.3. metadata_reference Table 41. gpkg_metadata_reference Trigger
4487 : * Definition SQL */
4488 295 : const char *pszMetadataReferenceTriggers =
4489 : "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_insert' "
4490 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4491 : "FOR EACH ROW BEGIN "
4492 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4493 : "violates constraint: reference_scope must be one of \"geopackage\", "
4494 : "table\", \"column\", \"row\", \"row/col\"') "
4495 : "WHERE NOT NEW.reference_scope IN "
4496 : "('geopackage','table','column','row','row/col'); "
4497 : "END; "
4498 : "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_update' "
4499 : "BEFORE UPDATE OF 'reference_scope' ON 'gpkg_metadata_reference' "
4500 : "FOR EACH ROW BEGIN "
4501 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4502 : "violates constraint: reference_scope must be one of \"geopackage\", "
4503 : "\"table\", \"column\", \"row\", \"row/col\"') "
4504 : "WHERE NOT NEW.reference_scope IN "
4505 : "('geopackage','table','column','row','row/col'); "
4506 : "END; "
4507 : "CREATE TRIGGER 'gpkg_metadata_reference_column_name_insert' "
4508 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4509 : "FOR EACH ROW BEGIN "
4510 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4511 : "violates constraint: column name must be NULL when reference_scope "
4512 : "is \"geopackage\", \"table\" or \"row\"') "
4513 : "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
4514 : "AND NEW.column_name IS NOT NULL); "
4515 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4516 : "violates constraint: column name must be defined for the specified "
4517 : "table when reference_scope is \"column\" or \"row/col\"') "
4518 : "WHERE (NEW.reference_scope IN ('column','row/col') "
4519 : "AND NOT NEW.table_name IN ( "
4520 : "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
4521 : "AND name = NEW.table_name "
4522 : "AND sql LIKE ('%' || NEW.column_name || '%'))); "
4523 : "END; "
4524 : "CREATE TRIGGER 'gpkg_metadata_reference_column_name_update' "
4525 : "BEFORE UPDATE OF column_name ON 'gpkg_metadata_reference' "
4526 : "FOR EACH ROW BEGIN "
4527 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4528 : "violates constraint: column name must be NULL when reference_scope "
4529 : "is \"geopackage\", \"table\" or \"row\"') "
4530 : "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
4531 : "AND NEW.column_name IS NOT NULL); "
4532 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4533 : "violates constraint: column name must be defined for the specified "
4534 : "table when reference_scope is \"column\" or \"row/col\"') "
4535 : "WHERE (NEW.reference_scope IN ('column','row/col') "
4536 : "AND NOT NEW.table_name IN ( "
4537 : "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
4538 : "AND name = NEW.table_name "
4539 : "AND sql LIKE ('%' || NEW.column_name || '%'))); "
4540 : "END; "
4541 : "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_insert' "
4542 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4543 : "FOR EACH ROW BEGIN "
4544 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4545 : "violates constraint: row_id_value must be NULL when reference_scope "
4546 : "is \"geopackage\", \"table\" or \"column\"') "
4547 : "WHERE NEW.reference_scope IN ('geopackage','table','column') "
4548 : "AND NEW.row_id_value IS NOT NULL; "
4549 : "END; "
4550 : "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_update' "
4551 : "BEFORE UPDATE OF 'row_id_value' ON 'gpkg_metadata_reference' "
4552 : "FOR EACH ROW BEGIN "
4553 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4554 : "violates constraint: row_id_value must be NULL when reference_scope "
4555 : "is \"geopackage\", \"table\" or \"column\"') "
4556 : "WHERE NEW.reference_scope IN ('geopackage','table','column') "
4557 : "AND NEW.row_id_value IS NOT NULL; "
4558 : "END; "
4559 : "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_insert' "
4560 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4561 : "FOR EACH ROW BEGIN "
4562 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4563 : "violates constraint: timestamp must be a valid time in ISO 8601 "
4564 : "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
4565 : "WHERE NOT (NEW.timestamp GLOB "
4566 : "'[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-"
4567 : "5][0-9].[0-9][0-9][0-9]Z' "
4568 : "AND strftime('%s',NEW.timestamp) NOT NULL); "
4569 : "END; "
4570 : "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_update' "
4571 : "BEFORE UPDATE OF 'timestamp' ON 'gpkg_metadata_reference' "
4572 : "FOR EACH ROW BEGIN "
4573 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4574 : "violates constraint: timestamp must be a valid time in ISO 8601 "
4575 : "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
4576 : "WHERE NOT (NEW.timestamp GLOB "
4577 : "'[1-2][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9]T[0-2][0-9]:[0-5][0-9]:[0-"
4578 : "5][0-9].[0-9][0-9][0-9]Z' "
4579 : "AND strftime('%s',NEW.timestamp) NOT NULL); "
4580 : "END";
4581 295 : if (bCreateTriggers)
4582 : {
4583 0 : osSQL += ";";
4584 0 : osSQL += pszMetadataReferenceTriggers;
4585 : }
4586 :
4587 295 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
4588 2 : return false;
4589 :
4590 293 : osSQL += ";";
4591 : osSQL += "INSERT INTO gpkg_extensions "
4592 : "(table_name, column_name, extension_name, definition, scope) "
4593 : "VALUES "
4594 : "('gpkg_metadata', NULL, 'gpkg_metadata', "
4595 : "'http://www.geopackage.org/spec120/#extension_metadata', "
4596 293 : "'read-write')";
4597 :
4598 293 : osSQL += ";";
4599 : osSQL += "INSERT INTO gpkg_extensions "
4600 : "(table_name, column_name, extension_name, definition, scope) "
4601 : "VALUES "
4602 : "('gpkg_metadata_reference', NULL, 'gpkg_metadata', "
4603 : "'http://www.geopackage.org/spec120/#extension_metadata', "
4604 293 : "'read-write')";
4605 :
4606 293 : const bool bOK = SQLCommand(hDB, osSQL) == OGRERR_NONE;
4607 293 : m_nHasMetadataTables = bOK;
4608 293 : return bOK;
4609 : }
4610 :
4611 : /************************************************************************/
4612 : /* FlushMetadata() */
4613 : /************************************************************************/
4614 :
4615 8426 : void GDALGeoPackageDataset::FlushMetadata()
4616 : {
4617 8426 : if (!m_bMetadataDirty || m_poParentDS != nullptr ||
4618 370 : m_nCreateMetadataTables == FALSE)
4619 8062 : return;
4620 364 : m_bMetadataDirty = false;
4621 :
4622 364 : if (eAccess == GA_ReadOnly)
4623 : {
4624 3 : return;
4625 : }
4626 :
4627 361 : bool bCanWriteAreaOrPoint =
4628 720 : !m_bGridCellEncodingAsCO &&
4629 359 : (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT);
4630 361 : if (!m_osRasterTable.empty())
4631 : {
4632 : const char *pszIdentifier =
4633 142 : GDALGeoPackageDataset::GetMetadataItem("IDENTIFIER");
4634 : const char *pszDescription =
4635 142 : GDALGeoPackageDataset::GetMetadataItem("DESCRIPTION");
4636 171 : if (!m_bIdentifierAsCO && pszIdentifier != nullptr &&
4637 29 : pszIdentifier != m_osIdentifier)
4638 : {
4639 14 : m_osIdentifier = pszIdentifier;
4640 : char *pszSQL =
4641 14 : sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
4642 : "WHERE lower(table_name) = lower('%q')",
4643 : pszIdentifier, m_osRasterTable.c_str());
4644 14 : SQLCommand(hDB, pszSQL);
4645 14 : sqlite3_free(pszSQL);
4646 : }
4647 149 : if (!m_bDescriptionAsCO && pszDescription != nullptr &&
4648 7 : pszDescription != m_osDescription)
4649 : {
4650 7 : m_osDescription = pszDescription;
4651 : char *pszSQL =
4652 7 : sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
4653 : "WHERE lower(table_name) = lower('%q')",
4654 : pszDescription, m_osRasterTable.c_str());
4655 7 : SQLCommand(hDB, pszSQL);
4656 7 : sqlite3_free(pszSQL);
4657 : }
4658 142 : if (bCanWriteAreaOrPoint)
4659 : {
4660 : const char *pszAreaOrPoint =
4661 28 : GDALGeoPackageDataset::GetMetadataItem(GDALMD_AREA_OR_POINT);
4662 28 : if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_AREA))
4663 : {
4664 23 : bCanWriteAreaOrPoint = false;
4665 23 : char *pszSQL = sqlite3_mprintf(
4666 : "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
4667 : "grid_cell_encoding = 'grid-value-is-area' WHERE "
4668 : "lower(tile_matrix_set_name) = lower('%q')",
4669 : m_osRasterTable.c_str());
4670 23 : SQLCommand(hDB, pszSQL);
4671 23 : sqlite3_free(pszSQL);
4672 : }
4673 5 : else if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_POINT))
4674 : {
4675 1 : bCanWriteAreaOrPoint = false;
4676 1 : char *pszSQL = sqlite3_mprintf(
4677 : "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
4678 : "grid_cell_encoding = 'grid-value-is-center' WHERE "
4679 : "lower(tile_matrix_set_name) = lower('%q')",
4680 : m_osRasterTable.c_str());
4681 1 : SQLCommand(hDB, pszSQL);
4682 1 : sqlite3_free(pszSQL);
4683 : }
4684 : }
4685 : }
4686 :
4687 361 : char **papszMDDup = nullptr;
4688 564 : for (char **papszIter = GDALGeoPackageDataset::GetMetadata();
4689 564 : papszIter && *papszIter; ++papszIter)
4690 : {
4691 203 : if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
4692 29 : continue;
4693 174 : if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
4694 8 : continue;
4695 166 : if (STARTS_WITH_CI(*papszIter, "ZOOM_LEVEL="))
4696 14 : continue;
4697 152 : if (STARTS_WITH_CI(*papszIter, "GPKG_METADATA_ITEM_"))
4698 4 : continue;
4699 148 : if ((m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT) &&
4700 29 : !bCanWriteAreaOrPoint &&
4701 26 : STARTS_WITH_CI(*papszIter, GDALMD_AREA_OR_POINT))
4702 : {
4703 26 : continue;
4704 : }
4705 122 : papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
4706 : }
4707 :
4708 361 : CPLXMLNode *psXMLNode = nullptr;
4709 : {
4710 361 : GDALMultiDomainMetadata oLocalMDMD;
4711 361 : CSLConstList papszDomainList = oMDMD.GetDomainList();
4712 361 : CSLConstList papszIter = papszDomainList;
4713 361 : oLocalMDMD.SetMetadata(papszMDDup);
4714 701 : while (papszIter && *papszIter)
4715 : {
4716 340 : if (!EQUAL(*papszIter, "") &&
4717 172 : !EQUAL(*papszIter, "IMAGE_STRUCTURE") &&
4718 15 : !EQUAL(*papszIter, "GEOPACKAGE"))
4719 : {
4720 8 : oLocalMDMD.SetMetadata(oMDMD.GetMetadata(*papszIter),
4721 : *papszIter);
4722 : }
4723 340 : papszIter++;
4724 : }
4725 361 : if (m_nBandCountFromMetadata > 0)
4726 : {
4727 72 : oLocalMDMD.SetMetadataItem(
4728 : "BAND_COUNT", CPLSPrintf("%d", m_nBandCountFromMetadata),
4729 : "IMAGE_STRUCTURE");
4730 72 : if (nBands == 1)
4731 : {
4732 48 : const auto poCT = GetRasterBand(1)->GetColorTable();
4733 48 : if (poCT)
4734 : {
4735 16 : std::string osVal("{");
4736 8 : const int nColorCount = poCT->GetColorEntryCount();
4737 2056 : for (int i = 0; i < nColorCount; ++i)
4738 : {
4739 2048 : if (i > 0)
4740 2040 : osVal += ',';
4741 2048 : const GDALColorEntry *psEntry = poCT->GetColorEntry(i);
4742 : osVal +=
4743 2048 : CPLSPrintf("{%d,%d,%d,%d}", psEntry->c1,
4744 2048 : psEntry->c2, psEntry->c3, psEntry->c4);
4745 : }
4746 8 : osVal += '}';
4747 8 : oLocalMDMD.SetMetadataItem("COLOR_TABLE", osVal.c_str(),
4748 : "IMAGE_STRUCTURE");
4749 : }
4750 : }
4751 72 : if (nBands == 1)
4752 : {
4753 48 : const char *pszTILE_FORMAT = nullptr;
4754 48 : switch (m_eTF)
4755 : {
4756 0 : case GPKG_TF_PNG_JPEG:
4757 0 : pszTILE_FORMAT = "JPEG_PNG";
4758 0 : break;
4759 42 : case GPKG_TF_PNG:
4760 42 : break;
4761 0 : case GPKG_TF_PNG8:
4762 0 : pszTILE_FORMAT = "PNG8";
4763 0 : break;
4764 3 : case GPKG_TF_JPEG:
4765 3 : pszTILE_FORMAT = "JPEG";
4766 3 : break;
4767 3 : case GPKG_TF_WEBP:
4768 3 : pszTILE_FORMAT = "WEBP";
4769 3 : break;
4770 0 : case GPKG_TF_PNG_16BIT:
4771 0 : break;
4772 0 : case GPKG_TF_TIFF_32BIT_FLOAT:
4773 0 : break;
4774 : }
4775 48 : if (pszTILE_FORMAT)
4776 6 : oLocalMDMD.SetMetadataItem("TILE_FORMAT", pszTILE_FORMAT,
4777 : "IMAGE_STRUCTURE");
4778 : }
4779 : }
4780 503 : if (GetRasterCount() > 0 &&
4781 142 : GetRasterBand(1)->GetRasterDataType() == GDT_Byte)
4782 : {
4783 112 : int bHasNoData = FALSE;
4784 : const double dfNoDataValue =
4785 112 : GetRasterBand(1)->GetNoDataValue(&bHasNoData);
4786 112 : if (bHasNoData)
4787 : {
4788 3 : oLocalMDMD.SetMetadataItem("NODATA_VALUE",
4789 : CPLSPrintf("%.17g", dfNoDataValue),
4790 : "IMAGE_STRUCTURE");
4791 : }
4792 : }
4793 608 : for (int i = 1; i <= GetRasterCount(); ++i)
4794 : {
4795 : auto poBand =
4796 247 : cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
4797 247 : poBand->AddImplicitStatistics(false);
4798 247 : char **papszMD = GetRasterBand(i)->GetMetadata();
4799 247 : poBand->AddImplicitStatistics(true);
4800 247 : if (papszMD)
4801 : {
4802 14 : oLocalMDMD.SetMetadata(papszMD, CPLSPrintf("BAND_%d", i));
4803 : }
4804 : }
4805 361 : psXMLNode = oLocalMDMD.Serialize();
4806 : }
4807 :
4808 361 : CSLDestroy(papszMDDup);
4809 361 : papszMDDup = nullptr;
4810 :
4811 361 : WriteMetadata(psXMLNode, m_osRasterTable.c_str());
4812 :
4813 361 : if (!m_osRasterTable.empty())
4814 : {
4815 : char **papszGeopackageMD =
4816 142 : GDALGeoPackageDataset::GetMetadata("GEOPACKAGE");
4817 :
4818 142 : papszMDDup = nullptr;
4819 151 : for (char **papszIter = papszGeopackageMD; papszIter && *papszIter;
4820 : ++papszIter)
4821 : {
4822 9 : papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
4823 : }
4824 :
4825 284 : GDALMultiDomainMetadata oLocalMDMD;
4826 142 : oLocalMDMD.SetMetadata(papszMDDup);
4827 142 : CSLDestroy(papszMDDup);
4828 142 : papszMDDup = nullptr;
4829 142 : psXMLNode = oLocalMDMD.Serialize();
4830 :
4831 142 : WriteMetadata(psXMLNode, nullptr);
4832 : }
4833 :
4834 594 : for (auto &poLayer : m_apoLayers)
4835 : {
4836 233 : const char *pszIdentifier = poLayer->GetMetadataItem("IDENTIFIER");
4837 233 : const char *pszDescription = poLayer->GetMetadataItem("DESCRIPTION");
4838 233 : if (pszIdentifier != nullptr)
4839 : {
4840 : char *pszSQL =
4841 3 : sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
4842 : "WHERE lower(table_name) = lower('%q')",
4843 : pszIdentifier, poLayer->GetName());
4844 3 : SQLCommand(hDB, pszSQL);
4845 3 : sqlite3_free(pszSQL);
4846 : }
4847 233 : if (pszDescription != nullptr)
4848 : {
4849 : char *pszSQL =
4850 3 : sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
4851 : "WHERE lower(table_name) = lower('%q')",
4852 : pszDescription, poLayer->GetName());
4853 3 : SQLCommand(hDB, pszSQL);
4854 3 : sqlite3_free(pszSQL);
4855 : }
4856 :
4857 233 : papszMDDup = nullptr;
4858 617 : for (char **papszIter = poLayer->GetMetadata(); papszIter && *papszIter;
4859 : ++papszIter)
4860 : {
4861 384 : if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
4862 3 : continue;
4863 381 : if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
4864 3 : continue;
4865 378 : if (STARTS_WITH_CI(*papszIter, "OLMD_FID64="))
4866 0 : continue;
4867 378 : papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
4868 : }
4869 :
4870 : {
4871 233 : GDALMultiDomainMetadata oLocalMDMD;
4872 233 : char **papszDomainList = poLayer->GetMetadataDomainList();
4873 233 : char **papszIter = papszDomainList;
4874 233 : oLocalMDMD.SetMetadata(papszMDDup);
4875 501 : while (papszIter && *papszIter)
4876 : {
4877 268 : if (!EQUAL(*papszIter, ""))
4878 56 : oLocalMDMD.SetMetadata(poLayer->GetMetadata(*papszIter),
4879 : *papszIter);
4880 268 : papszIter++;
4881 : }
4882 233 : CSLDestroy(papszDomainList);
4883 233 : psXMLNode = oLocalMDMD.Serialize();
4884 : }
4885 :
4886 233 : CSLDestroy(papszMDDup);
4887 233 : papszMDDup = nullptr;
4888 :
4889 233 : WriteMetadata(psXMLNode, poLayer->GetName());
4890 : }
4891 : }
4892 :
4893 : /************************************************************************/
4894 : /* GetMetadataItem() */
4895 : /************************************************************************/
4896 :
4897 1526 : const char *GDALGeoPackageDataset::GetMetadataItem(const char *pszName,
4898 : const char *pszDomain)
4899 : {
4900 1526 : pszDomain = CheckMetadataDomain(pszDomain);
4901 1526 : return CSLFetchNameValue(GetMetadata(pszDomain), pszName);
4902 : }
4903 :
4904 : /************************************************************************/
4905 : /* SetMetadata() */
4906 : /************************************************************************/
4907 :
4908 146 : CPLErr GDALGeoPackageDataset::SetMetadata(char **papszMetadata,
4909 : const char *pszDomain)
4910 : {
4911 146 : pszDomain = CheckMetadataDomain(pszDomain);
4912 146 : m_bMetadataDirty = true;
4913 146 : GetMetadata(); /* force loading from storage if needed */
4914 146 : return GDALPamDataset::SetMetadata(papszMetadata, pszDomain);
4915 : }
4916 :
4917 : /************************************************************************/
4918 : /* SetMetadataItem() */
4919 : /************************************************************************/
4920 :
4921 21 : CPLErr GDALGeoPackageDataset::SetMetadataItem(const char *pszName,
4922 : const char *pszValue,
4923 : const char *pszDomain)
4924 : {
4925 21 : pszDomain = CheckMetadataDomain(pszDomain);
4926 21 : m_bMetadataDirty = true;
4927 21 : GetMetadata(); /* force loading from storage if needed */
4928 21 : return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
4929 : }
4930 :
4931 : /************************************************************************/
4932 : /* Create() */
4933 : /************************************************************************/
4934 :
4935 872 : int GDALGeoPackageDataset::Create(const char *pszFilename, int nXSize,
4936 : int nYSize, int nBandsIn, GDALDataType eDT,
4937 : char **papszOptions)
4938 : {
4939 1744 : CPLString osCommand;
4940 :
4941 : /* First, ensure there isn't any such file yet. */
4942 : VSIStatBufL sStatBuf;
4943 :
4944 872 : if (nBandsIn != 0)
4945 : {
4946 223 : if (eDT == GDT_Byte)
4947 : {
4948 153 : if (nBandsIn != 1 && nBandsIn != 2 && nBandsIn != 3 &&
4949 : nBandsIn != 4)
4950 : {
4951 1 : CPLError(CE_Failure, CPLE_NotSupported,
4952 : "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), "
4953 : "3 (RGB) or 4 (RGBA) band dataset supported for "
4954 : "Byte datatype");
4955 1 : return FALSE;
4956 : }
4957 : }
4958 70 : else if (eDT == GDT_Int16 || eDT == GDT_UInt16 || eDT == GDT_Float32)
4959 : {
4960 43 : if (nBandsIn != 1)
4961 : {
4962 3 : CPLError(CE_Failure, CPLE_NotSupported,
4963 : "Only single band dataset supported for non Byte "
4964 : "datatype");
4965 3 : return FALSE;
4966 : }
4967 : }
4968 : else
4969 : {
4970 27 : CPLError(CE_Failure, CPLE_NotSupported,
4971 : "Only Byte, Int16, UInt16 or Float32 supported");
4972 27 : return FALSE;
4973 : }
4974 : }
4975 :
4976 841 : const size_t nFilenameLen = strlen(pszFilename);
4977 841 : const bool bGpkgZip =
4978 836 : (nFilenameLen > strlen(".gpkg.zip") &&
4979 1677 : !STARTS_WITH(pszFilename, "/vsizip/") &&
4980 836 : EQUAL(pszFilename + nFilenameLen - strlen(".gpkg.zip"), ".gpkg.zip"));
4981 :
4982 : const bool bUseTempFile =
4983 842 : bGpkgZip || (CPLTestBool(CPLGetConfigOption(
4984 1 : "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", "NO")) &&
4985 1 : (VSIHasOptimizedReadMultiRange(pszFilename) != FALSE ||
4986 1 : EQUAL(CPLGetConfigOption(
4987 : "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", ""),
4988 841 : "FORCED")));
4989 :
4990 841 : bool bFileExists = false;
4991 841 : if (VSIStatL(pszFilename, &sStatBuf) == 0)
4992 : {
4993 10 : bFileExists = true;
4994 20 : if (nBandsIn == 0 || bUseTempFile ||
4995 10 : !CPLTestBool(
4996 : CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")))
4997 : {
4998 0 : CPLError(CE_Failure, CPLE_AppDefined,
4999 : "A file system object called '%s' already exists.",
5000 : pszFilename);
5001 :
5002 0 : return FALSE;
5003 : }
5004 : }
5005 :
5006 841 : if (bUseTempFile)
5007 : {
5008 3 : if (bGpkgZip)
5009 : {
5010 2 : std::string osFilenameInZip(CPLGetFilename(pszFilename));
5011 2 : osFilenameInZip.resize(osFilenameInZip.size() - strlen(".zip"));
5012 : m_osFinalFilename =
5013 2 : std::string("/vsizip/{") + pszFilename + "}/" + osFilenameInZip;
5014 : }
5015 : else
5016 : {
5017 1 : m_osFinalFilename = pszFilename;
5018 : }
5019 3 : m_pszFilename = CPLStrdup(
5020 6 : CPLGenerateTempFilenameSafe(CPLGetFilename(pszFilename)).c_str());
5021 3 : CPLDebug("GPKG", "Creating temporary file %s", m_pszFilename);
5022 : }
5023 : else
5024 : {
5025 838 : m_pszFilename = CPLStrdup(pszFilename);
5026 : }
5027 841 : m_bNew = true;
5028 841 : eAccess = GA_Update;
5029 841 : m_bDateTimeWithTZ =
5030 841 : EQUAL(CSLFetchNameValueDef(papszOptions, "DATETIME_FORMAT", "WITH_TZ"),
5031 : "WITH_TZ");
5032 :
5033 : // for test/debug purposes only. true is the nominal value
5034 841 : m_bPNGSupports2Bands =
5035 841 : CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_2BANDS", "TRUE"));
5036 841 : m_bPNGSupportsCT =
5037 841 : CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_CT", "TRUE"));
5038 :
5039 841 : if (!OpenOrCreateDB(bFileExists
5040 : ? SQLITE_OPEN_READWRITE
5041 : : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE))
5042 6 : return FALSE;
5043 :
5044 : /* Default to synchronous=off for performance for new file */
5045 1660 : if (!bFileExists &&
5046 825 : CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
5047 : {
5048 327 : SQLCommand(hDB, "PRAGMA synchronous = OFF");
5049 : }
5050 :
5051 : /* OGR UTF-8 support. If we set the UTF-8 Pragma early on, it */
5052 : /* will be written into the main file and supported henceforth */
5053 835 : SQLCommand(hDB, "PRAGMA encoding = \"UTF-8\"");
5054 :
5055 835 : if (bFileExists)
5056 : {
5057 10 : VSILFILE *fp = VSIFOpenL(pszFilename, "rb");
5058 10 : if (fp)
5059 : {
5060 : GByte abyHeader[100];
5061 10 : VSIFReadL(abyHeader, 1, sizeof(abyHeader), fp);
5062 10 : VSIFCloseL(fp);
5063 :
5064 10 : memcpy(&m_nApplicationId, abyHeader + knApplicationIdPos, 4);
5065 10 : m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
5066 10 : memcpy(&m_nUserVersion, abyHeader + knUserVersionPos, 4);
5067 10 : m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
5068 :
5069 10 : if (m_nApplicationId == GP10_APPLICATION_ID)
5070 : {
5071 0 : CPLDebug("GPKG", "GeoPackage v1.0");
5072 : }
5073 10 : else if (m_nApplicationId == GP11_APPLICATION_ID)
5074 : {
5075 0 : CPLDebug("GPKG", "GeoPackage v1.1");
5076 : }
5077 10 : else if (m_nApplicationId == GPKG_APPLICATION_ID &&
5078 10 : m_nUserVersion >= GPKG_1_2_VERSION)
5079 : {
5080 10 : CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
5081 10 : (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
5082 : }
5083 : }
5084 :
5085 10 : DetectSpatialRefSysColumns();
5086 : }
5087 :
5088 835 : const char *pszVersion = CSLFetchNameValue(papszOptions, "VERSION");
5089 835 : if (pszVersion && !EQUAL(pszVersion, "AUTO"))
5090 : {
5091 40 : if (EQUAL(pszVersion, "1.0"))
5092 : {
5093 2 : m_nApplicationId = GP10_APPLICATION_ID;
5094 2 : m_nUserVersion = 0;
5095 : }
5096 38 : else if (EQUAL(pszVersion, "1.1"))
5097 : {
5098 1 : m_nApplicationId = GP11_APPLICATION_ID;
5099 1 : m_nUserVersion = 0;
5100 : }
5101 37 : else if (EQUAL(pszVersion, "1.2"))
5102 : {
5103 15 : m_nApplicationId = GPKG_APPLICATION_ID;
5104 15 : m_nUserVersion = GPKG_1_2_VERSION;
5105 : }
5106 22 : else if (EQUAL(pszVersion, "1.3"))
5107 : {
5108 3 : m_nApplicationId = GPKG_APPLICATION_ID;
5109 3 : m_nUserVersion = GPKG_1_3_VERSION;
5110 : }
5111 19 : else if (EQUAL(pszVersion, "1.4"))
5112 : {
5113 19 : m_nApplicationId = GPKG_APPLICATION_ID;
5114 19 : m_nUserVersion = GPKG_1_4_VERSION;
5115 : }
5116 : }
5117 :
5118 835 : SoftStartTransaction();
5119 :
5120 1670 : CPLString osSQL;
5121 835 : if (!bFileExists)
5122 : {
5123 : /* Requirement 10: A GeoPackage SHALL include a gpkg_spatial_ref_sys
5124 : * table */
5125 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5126 : osSQL = "CREATE TABLE gpkg_spatial_ref_sys ("
5127 : "srs_name TEXT NOT NULL,"
5128 : "srs_id INTEGER NOT NULL PRIMARY KEY,"
5129 : "organization TEXT NOT NULL,"
5130 : "organization_coordsys_id INTEGER NOT NULL,"
5131 : "definition TEXT NOT NULL,"
5132 825 : "description TEXT";
5133 825 : if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "CRS_WKT_EXTENSION",
5134 1003 : "NO")) ||
5135 178 : (nBandsIn != 0 && eDT != GDT_Byte))
5136 : {
5137 42 : m_bHasDefinition12_063 = true;
5138 42 : osSQL += ", definition_12_063 TEXT NOT NULL";
5139 42 : if (m_nUserVersion >= GPKG_1_4_VERSION)
5140 : {
5141 40 : osSQL += ", epoch DOUBLE";
5142 40 : m_bHasEpochColumn = true;
5143 : }
5144 : }
5145 : osSQL += ")"
5146 : ";"
5147 : /* Requirement 11: The gpkg_spatial_ref_sys table in a
5148 : GeoPackage SHALL */
5149 : /* contain a record for EPSG:4326, the geodetic WGS84 SRS */
5150 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5151 :
5152 : "INSERT INTO gpkg_spatial_ref_sys ("
5153 : "srs_name, srs_id, organization, organization_coordsys_id, "
5154 825 : "definition, description";
5155 825 : if (m_bHasDefinition12_063)
5156 42 : osSQL += ", definition_12_063";
5157 : osSQL +=
5158 : ") VALUES ("
5159 : "'WGS 84 geodetic', 4326, 'EPSG', 4326, '"
5160 : "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS "
5161 : "84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],"
5162 : "AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY["
5163 : "\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY["
5164 : "\"EPSG\",\"9122\"]],AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\","
5165 : "EAST],AUTHORITY[\"EPSG\",\"4326\"]]"
5166 : "', 'longitude/latitude coordinates in decimal degrees on the WGS "
5167 825 : "84 spheroid'";
5168 825 : if (m_bHasDefinition12_063)
5169 : osSQL +=
5170 : ", 'GEODCRS[\"WGS 84\", DATUM[\"World Geodetic System 1984\", "
5171 : "ELLIPSOID[\"WGS 84\",6378137, 298.257223563, "
5172 : "LENGTHUNIT[\"metre\", 1.0]]], PRIMEM[\"Greenwich\", 0.0, "
5173 : "ANGLEUNIT[\"degree\",0.0174532925199433]], CS[ellipsoidal, "
5174 : "2], AXIS[\"latitude\", north, ORDER[1]], AXIS[\"longitude\", "
5175 : "east, ORDER[2]], ANGLEUNIT[\"degree\", 0.0174532925199433], "
5176 42 : "ID[\"EPSG\", 4326]]'";
5177 : osSQL +=
5178 : ")"
5179 : ";"
5180 : /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
5181 : SHALL */
5182 : /* contain a record with an srs_id of -1, an organization of “NONE”,
5183 : */
5184 : /* an organization_coordsys_id of -1, and definition “undefined” */
5185 : /* for undefined Cartesian coordinate reference systems */
5186 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5187 : "INSERT INTO gpkg_spatial_ref_sys ("
5188 : "srs_name, srs_id, organization, organization_coordsys_id, "
5189 825 : "definition, description";
5190 825 : if (m_bHasDefinition12_063)
5191 42 : osSQL += ", definition_12_063";
5192 : osSQL += ") VALUES ("
5193 : "'Undefined Cartesian SRS', -1, 'NONE', -1, 'undefined', "
5194 825 : "'undefined Cartesian coordinate reference system'";
5195 825 : if (m_bHasDefinition12_063)
5196 42 : osSQL += ", 'undefined'";
5197 : osSQL +=
5198 : ")"
5199 : ";"
5200 : /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
5201 : SHALL */
5202 : /* contain a record with an srs_id of 0, an organization of “NONE”,
5203 : */
5204 : /* an organization_coordsys_id of 0, and definition “undefined” */
5205 : /* for undefined geographic coordinate reference systems */
5206 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5207 : "INSERT INTO gpkg_spatial_ref_sys ("
5208 : "srs_name, srs_id, organization, organization_coordsys_id, "
5209 825 : "definition, description";
5210 825 : if (m_bHasDefinition12_063)
5211 42 : osSQL += ", definition_12_063";
5212 : osSQL += ") VALUES ("
5213 : "'Undefined geographic SRS', 0, 'NONE', 0, 'undefined', "
5214 825 : "'undefined geographic coordinate reference system'";
5215 825 : if (m_bHasDefinition12_063)
5216 42 : osSQL += ", 'undefined'";
5217 : osSQL += ")"
5218 : ";"
5219 : /* Requirement 13: A GeoPackage file SHALL include a
5220 : gpkg_contents table */
5221 : /* http://opengis.github.io/geopackage/#_contents */
5222 : "CREATE TABLE gpkg_contents ("
5223 : "table_name TEXT NOT NULL PRIMARY KEY,"
5224 : "data_type TEXT NOT NULL,"
5225 : "identifier TEXT UNIQUE,"
5226 : "description TEXT DEFAULT '',"
5227 : "last_change DATETIME NOT NULL DEFAULT "
5228 : "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
5229 : "min_x DOUBLE, min_y DOUBLE,"
5230 : "max_x DOUBLE, max_y DOUBLE,"
5231 : "srs_id INTEGER,"
5232 : "CONSTRAINT fk_gc_r_srs_id FOREIGN KEY (srs_id) REFERENCES "
5233 : "gpkg_spatial_ref_sys(srs_id)"
5234 825 : ")";
5235 :
5236 : #ifdef ENABLE_GPKG_OGR_CONTENTS
5237 825 : if (CPLFetchBool(papszOptions, "ADD_GPKG_OGR_CONTENTS", true))
5238 : {
5239 820 : m_bHasGPKGOGRContents = true;
5240 : osSQL += ";"
5241 : "CREATE TABLE gpkg_ogr_contents("
5242 : "table_name TEXT NOT NULL PRIMARY KEY,"
5243 : "feature_count INTEGER DEFAULT NULL"
5244 820 : ")";
5245 : }
5246 : #endif
5247 :
5248 : /* Requirement 21: A GeoPackage with a gpkg_contents table row with a
5249 : * “features” */
5250 : /* data_type SHALL contain a gpkg_geometry_columns table or updateable
5251 : * view */
5252 : /* http://opengis.github.io/geopackage/#_geometry_columns */
5253 : const bool bCreateGeometryColumns =
5254 825 : CPLTestBool(CPLGetConfigOption("CREATE_GEOMETRY_COLUMNS", "YES"));
5255 825 : if (bCreateGeometryColumns)
5256 : {
5257 824 : m_bHasGPKGGeometryColumns = true;
5258 824 : osSQL += ";";
5259 824 : osSQL += pszCREATE_GPKG_GEOMETRY_COLUMNS;
5260 : }
5261 : }
5262 :
5263 : const bool bCreateTriggers =
5264 835 : CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "YES"));
5265 10 : if ((bFileExists && nBandsIn != 0 &&
5266 10 : SQLGetInteger(
5267 : hDB,
5268 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_tile_matrix_set' "
5269 : "AND type in ('table', 'view')",
5270 1670 : nullptr) == 0) ||
5271 834 : (!bFileExists &&
5272 825 : CPLTestBool(CPLGetConfigOption("CREATE_RASTER_TABLES", "YES"))))
5273 : {
5274 825 : if (!osSQL.empty())
5275 824 : osSQL += ";";
5276 :
5277 : /* From C.5. gpkg_tile_matrix_set Table 28. gpkg_tile_matrix_set Table
5278 : * Creation SQL */
5279 : osSQL += "CREATE TABLE gpkg_tile_matrix_set ("
5280 : "table_name TEXT NOT NULL PRIMARY KEY,"
5281 : "srs_id INTEGER NOT NULL,"
5282 : "min_x DOUBLE NOT NULL,"
5283 : "min_y DOUBLE NOT NULL,"
5284 : "max_x DOUBLE NOT NULL,"
5285 : "max_y DOUBLE NOT NULL,"
5286 : "CONSTRAINT fk_gtms_table_name FOREIGN KEY (table_name) "
5287 : "REFERENCES gpkg_contents(table_name),"
5288 : "CONSTRAINT fk_gtms_srs FOREIGN KEY (srs_id) REFERENCES "
5289 : "gpkg_spatial_ref_sys (srs_id)"
5290 : ")"
5291 : ";"
5292 :
5293 : /* From C.6. gpkg_tile_matrix Table 29. gpkg_tile_matrix Table
5294 : Creation SQL */
5295 : "CREATE TABLE gpkg_tile_matrix ("
5296 : "table_name TEXT NOT NULL,"
5297 : "zoom_level INTEGER NOT NULL,"
5298 : "matrix_width INTEGER NOT NULL,"
5299 : "matrix_height INTEGER NOT NULL,"
5300 : "tile_width INTEGER NOT NULL,"
5301 : "tile_height INTEGER NOT NULL,"
5302 : "pixel_x_size DOUBLE NOT NULL,"
5303 : "pixel_y_size DOUBLE NOT NULL,"
5304 : "CONSTRAINT pk_ttm PRIMARY KEY (table_name, zoom_level),"
5305 : "CONSTRAINT fk_tmm_table_name FOREIGN KEY (table_name) "
5306 : "REFERENCES gpkg_contents(table_name)"
5307 825 : ")";
5308 :
5309 825 : if (bCreateTriggers)
5310 : {
5311 : /* From D.1. gpkg_tile_matrix Table 39. gpkg_tile_matrix Trigger
5312 : * Definition SQL */
5313 825 : const char *pszTileMatrixTrigger =
5314 : "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_insert' "
5315 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5316 : "FOR EACH ROW BEGIN "
5317 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5318 : "violates constraint: zoom_level cannot be less than 0') "
5319 : "WHERE (NEW.zoom_level < 0); "
5320 : "END; "
5321 : "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_update' "
5322 : "BEFORE UPDATE of zoom_level ON 'gpkg_tile_matrix' "
5323 : "FOR EACH ROW BEGIN "
5324 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5325 : "violates constraint: zoom_level cannot be less than 0') "
5326 : "WHERE (NEW.zoom_level < 0); "
5327 : "END; "
5328 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_insert' "
5329 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5330 : "FOR EACH ROW BEGIN "
5331 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5332 : "violates constraint: matrix_width cannot be less than 1') "
5333 : "WHERE (NEW.matrix_width < 1); "
5334 : "END; "
5335 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_update' "
5336 : "BEFORE UPDATE OF matrix_width ON 'gpkg_tile_matrix' "
5337 : "FOR EACH ROW BEGIN "
5338 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5339 : "violates constraint: matrix_width cannot be less than 1') "
5340 : "WHERE (NEW.matrix_width < 1); "
5341 : "END; "
5342 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_insert' "
5343 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5344 : "FOR EACH ROW BEGIN "
5345 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5346 : "violates constraint: matrix_height cannot be less than 1') "
5347 : "WHERE (NEW.matrix_height < 1); "
5348 : "END; "
5349 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_update' "
5350 : "BEFORE UPDATE OF matrix_height ON 'gpkg_tile_matrix' "
5351 : "FOR EACH ROW BEGIN "
5352 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5353 : "violates constraint: matrix_height cannot be less than 1') "
5354 : "WHERE (NEW.matrix_height < 1); "
5355 : "END; "
5356 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_insert' "
5357 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5358 : "FOR EACH ROW BEGIN "
5359 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5360 : "violates constraint: pixel_x_size must be greater than 0') "
5361 : "WHERE NOT (NEW.pixel_x_size > 0); "
5362 : "END; "
5363 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_update' "
5364 : "BEFORE UPDATE OF pixel_x_size ON 'gpkg_tile_matrix' "
5365 : "FOR EACH ROW BEGIN "
5366 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5367 : "violates constraint: pixel_x_size must be greater than 0') "
5368 : "WHERE NOT (NEW.pixel_x_size > 0); "
5369 : "END; "
5370 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_insert' "
5371 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5372 : "FOR EACH ROW BEGIN "
5373 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5374 : "violates constraint: pixel_y_size must be greater than 0') "
5375 : "WHERE NOT (NEW.pixel_y_size > 0); "
5376 : "END; "
5377 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_update' "
5378 : "BEFORE UPDATE OF pixel_y_size ON 'gpkg_tile_matrix' "
5379 : "FOR EACH ROW BEGIN "
5380 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5381 : "violates constraint: pixel_y_size must be greater than 0') "
5382 : "WHERE NOT (NEW.pixel_y_size > 0); "
5383 : "END;";
5384 825 : osSQL += ";";
5385 825 : osSQL += pszTileMatrixTrigger;
5386 : }
5387 : }
5388 :
5389 835 : if (!osSQL.empty() && OGRERR_NONE != SQLCommand(hDB, osSQL))
5390 1 : return FALSE;
5391 :
5392 834 : if (!bFileExists)
5393 : {
5394 : const char *pszMetadataTables =
5395 824 : CSLFetchNameValue(papszOptions, "METADATA_TABLES");
5396 824 : if (pszMetadataTables)
5397 9 : m_nCreateMetadataTables = int(CPLTestBool(pszMetadataTables));
5398 :
5399 824 : if (m_nCreateMetadataTables == TRUE && !CreateMetadataTables())
5400 0 : return FALSE;
5401 :
5402 824 : if (m_bHasDefinition12_063)
5403 : {
5404 84 : if (OGRERR_NONE != CreateExtensionsTableIfNecessary() ||
5405 : OGRERR_NONE !=
5406 42 : SQLCommand(hDB, "INSERT INTO gpkg_extensions "
5407 : "(table_name, column_name, extension_name, "
5408 : "definition, scope) "
5409 : "VALUES "
5410 : "('gpkg_spatial_ref_sys', "
5411 : "'definition_12_063', 'gpkg_crs_wkt', "
5412 : "'http://www.geopackage.org/spec120/"
5413 : "#extension_crs_wkt', 'read-write')"))
5414 : {
5415 0 : return FALSE;
5416 : }
5417 42 : if (m_bHasEpochColumn)
5418 : {
5419 40 : if (OGRERR_NONE !=
5420 40 : SQLCommand(
5421 : hDB, "UPDATE gpkg_extensions SET extension_name = "
5422 : "'gpkg_crs_wkt_1_1' "
5423 80 : "WHERE extension_name = 'gpkg_crs_wkt'") ||
5424 : OGRERR_NONE !=
5425 40 : SQLCommand(hDB, "INSERT INTO gpkg_extensions "
5426 : "(table_name, column_name, "
5427 : "extension_name, definition, scope) "
5428 : "VALUES "
5429 : "('gpkg_spatial_ref_sys', 'epoch', "
5430 : "'gpkg_crs_wkt_1_1', "
5431 : "'http://www.geopackage.org/spec/"
5432 : "#extension_crs_wkt', "
5433 : "'read-write')"))
5434 : {
5435 0 : return FALSE;
5436 : }
5437 : }
5438 : }
5439 : }
5440 :
5441 834 : if (nBandsIn != 0)
5442 : {
5443 187 : const std::string osTableName = CPLGetBasenameSafe(m_pszFilename);
5444 : m_osRasterTable = CSLFetchNameValueDef(papszOptions, "RASTER_TABLE",
5445 187 : osTableName.c_str());
5446 187 : if (m_osRasterTable.empty())
5447 : {
5448 0 : CPLError(CE_Failure, CPLE_AppDefined,
5449 : "RASTER_TABLE must be set to a non empty value");
5450 0 : return FALSE;
5451 : }
5452 187 : m_bIdentifierAsCO =
5453 187 : CSLFetchNameValue(papszOptions, "RASTER_IDENTIFIER") != nullptr;
5454 : m_osIdentifier = CSLFetchNameValueDef(papszOptions, "RASTER_IDENTIFIER",
5455 187 : m_osRasterTable);
5456 187 : m_bDescriptionAsCO =
5457 187 : CSLFetchNameValue(papszOptions, "RASTER_DESCRIPTION") != nullptr;
5458 : m_osDescription =
5459 187 : CSLFetchNameValueDef(papszOptions, "RASTER_DESCRIPTION", "");
5460 187 : SetDataType(eDT);
5461 187 : if (eDT == GDT_Int16)
5462 16 : SetGlobalOffsetScale(-32768.0, 1.0);
5463 :
5464 : /* From C.7. sample_tile_pyramid (Informative) Table 31. EXAMPLE: tiles
5465 : * table Create Table SQL (Informative) */
5466 : char *pszSQL =
5467 187 : sqlite3_mprintf("CREATE TABLE \"%w\" ("
5468 : "id INTEGER PRIMARY KEY AUTOINCREMENT,"
5469 : "zoom_level INTEGER NOT NULL,"
5470 : "tile_column INTEGER NOT NULL,"
5471 : "tile_row INTEGER NOT NULL,"
5472 : "tile_data BLOB NOT NULL,"
5473 : "UNIQUE (zoom_level, tile_column, tile_row)"
5474 : ")",
5475 : m_osRasterTable.c_str());
5476 187 : osSQL = pszSQL;
5477 187 : sqlite3_free(pszSQL);
5478 :
5479 187 : if (bCreateTriggers)
5480 : {
5481 187 : osSQL += ";";
5482 187 : osSQL += CreateRasterTriggersSQL(m_osRasterTable);
5483 : }
5484 :
5485 187 : OGRErr eErr = SQLCommand(hDB, osSQL);
5486 187 : if (OGRERR_NONE != eErr)
5487 0 : return FALSE;
5488 :
5489 187 : const char *pszTF = CSLFetchNameValue(papszOptions, "TILE_FORMAT");
5490 187 : if (eDT == GDT_Int16 || eDT == GDT_UInt16)
5491 : {
5492 27 : m_eTF = GPKG_TF_PNG_16BIT;
5493 27 : if (pszTF)
5494 : {
5495 1 : if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "PNG"))
5496 : {
5497 0 : CPLError(CE_Warning, CPLE_NotSupported,
5498 : "Only AUTO or PNG supported "
5499 : "as tile format for Int16 / UInt16");
5500 : }
5501 : }
5502 : }
5503 160 : else if (eDT == GDT_Float32)
5504 : {
5505 13 : m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
5506 13 : if (pszTF)
5507 : {
5508 5 : if (EQUAL(pszTF, "PNG"))
5509 5 : m_eTF = GPKG_TF_PNG_16BIT;
5510 0 : else if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "TIFF"))
5511 : {
5512 0 : CPLError(CE_Warning, CPLE_NotSupported,
5513 : "Only AUTO, PNG or TIFF supported "
5514 : "as tile format for Float32");
5515 : }
5516 : }
5517 : }
5518 : else
5519 : {
5520 147 : if (pszTF)
5521 : {
5522 71 : m_eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
5523 71 : if (nBandsIn == 1 && m_eTF != GPKG_TF_PNG)
5524 7 : m_bMetadataDirty = true;
5525 : }
5526 76 : else if (nBandsIn == 1)
5527 65 : m_eTF = GPKG_TF_PNG;
5528 : }
5529 :
5530 187 : if (eDT != GDT_Byte)
5531 : {
5532 40 : if (!CreateTileGriddedTable(papszOptions))
5533 0 : return FALSE;
5534 : }
5535 :
5536 187 : nRasterXSize = nXSize;
5537 187 : nRasterYSize = nYSize;
5538 :
5539 : const char *pszTileSize =
5540 187 : CSLFetchNameValueDef(papszOptions, "BLOCKSIZE", "256");
5541 : const char *pszTileWidth =
5542 187 : CSLFetchNameValueDef(papszOptions, "BLOCKXSIZE", pszTileSize);
5543 : const char *pszTileHeight =
5544 187 : CSLFetchNameValueDef(papszOptions, "BLOCKYSIZE", pszTileSize);
5545 187 : int nTileWidth = atoi(pszTileWidth);
5546 187 : int nTileHeight = atoi(pszTileHeight);
5547 187 : if ((nTileWidth < 8 || nTileWidth > 4096 || nTileHeight < 8 ||
5548 374 : nTileHeight > 4096) &&
5549 1 : !CPLTestBool(CPLGetConfigOption("GPKG_ALLOW_CRAZY_SETTINGS", "NO")))
5550 : {
5551 0 : CPLError(CE_Failure, CPLE_AppDefined,
5552 : "Invalid block dimensions: %dx%d", nTileWidth,
5553 : nTileHeight);
5554 0 : return FALSE;
5555 : }
5556 :
5557 507 : for (int i = 1; i <= nBandsIn; i++)
5558 : {
5559 320 : SetBand(i, std::make_unique<GDALGeoPackageRasterBand>(
5560 : this, nTileWidth, nTileHeight));
5561 : }
5562 :
5563 187 : GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL",
5564 : "IMAGE_STRUCTURE");
5565 187 : GDALPamDataset::SetMetadataItem("IDENTIFIER", m_osIdentifier);
5566 187 : if (!m_osDescription.empty())
5567 1 : GDALPamDataset::SetMetadataItem("DESCRIPTION", m_osDescription);
5568 :
5569 187 : ParseCompressionOptions(papszOptions);
5570 :
5571 187 : if (m_eTF == GPKG_TF_WEBP)
5572 : {
5573 10 : if (!RegisterWebPExtension())
5574 0 : return FALSE;
5575 : }
5576 :
5577 : m_osTilingScheme =
5578 187 : CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
5579 187 : if (!EQUAL(m_osTilingScheme, "CUSTOM"))
5580 : {
5581 22 : const auto poTS = GetTilingScheme(m_osTilingScheme);
5582 22 : if (!poTS)
5583 0 : return FALSE;
5584 :
5585 43 : if (nTileWidth != poTS->nTileWidth ||
5586 21 : nTileHeight != poTS->nTileHeight)
5587 : {
5588 2 : CPLError(CE_Failure, CPLE_NotSupported,
5589 : "Tile dimension should be %dx%d for %s tiling scheme",
5590 1 : poTS->nTileWidth, poTS->nTileHeight,
5591 : m_osTilingScheme.c_str());
5592 1 : return FALSE;
5593 : }
5594 :
5595 : const char *pszZoomLevel =
5596 21 : CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
5597 21 : if (pszZoomLevel)
5598 : {
5599 1 : m_nZoomLevel = atoi(pszZoomLevel);
5600 1 : int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
5601 1 : while ((1 << nMaxZoomLevelForThisTM) >
5602 2 : INT_MAX / poTS->nTileXCountZoomLevel0 ||
5603 1 : (1 << nMaxZoomLevelForThisTM) >
5604 1 : INT_MAX / poTS->nTileYCountZoomLevel0)
5605 : {
5606 0 : --nMaxZoomLevelForThisTM;
5607 : }
5608 :
5609 1 : if (m_nZoomLevel < 0 || m_nZoomLevel > nMaxZoomLevelForThisTM)
5610 : {
5611 0 : CPLError(CE_Failure, CPLE_AppDefined,
5612 : "ZOOM_LEVEL = %s is invalid. It should be in "
5613 : "[0,%d] range",
5614 : pszZoomLevel, nMaxZoomLevelForThisTM);
5615 0 : return FALSE;
5616 : }
5617 : }
5618 :
5619 : // Implicitly sets SRS.
5620 21 : OGRSpatialReference oSRS;
5621 21 : if (oSRS.importFromEPSG(poTS->nEPSGCode) != OGRERR_NONE)
5622 0 : return FALSE;
5623 21 : char *pszWKT = nullptr;
5624 21 : oSRS.exportToWkt(&pszWKT);
5625 21 : SetProjection(pszWKT);
5626 21 : CPLFree(pszWKT);
5627 : }
5628 : else
5629 : {
5630 165 : if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
5631 : {
5632 0 : CPLError(
5633 : CE_Failure, CPLE_NotSupported,
5634 : "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
5635 0 : return false;
5636 : }
5637 : }
5638 : }
5639 :
5640 833 : if (bFileExists && nBandsIn > 0 && eDT == GDT_Byte)
5641 : {
5642 : // If there was an ogr_empty_table table, we can remove it
5643 9 : RemoveOGREmptyTable();
5644 : }
5645 :
5646 833 : SoftCommitTransaction();
5647 :
5648 : /* Requirement 2 */
5649 : /* We have to do this after there's some content so the database file */
5650 : /* is not zero length */
5651 833 : SetApplicationAndUserVersionId();
5652 :
5653 : /* Default to synchronous=off for performance for new file */
5654 1656 : if (!bFileExists &&
5655 823 : CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
5656 : {
5657 327 : SQLCommand(hDB, "PRAGMA synchronous = OFF");
5658 : }
5659 :
5660 833 : return TRUE;
5661 : }
5662 :
5663 : /************************************************************************/
5664 : /* RemoveOGREmptyTable() */
5665 : /************************************************************************/
5666 :
5667 646 : void GDALGeoPackageDataset::RemoveOGREmptyTable()
5668 : {
5669 : // Run with sqlite3_exec since we don't want errors to be emitted
5670 646 : sqlite3_exec(hDB, "DROP TABLE IF EXISTS ogr_empty_table", nullptr, nullptr,
5671 : nullptr);
5672 646 : sqlite3_exec(
5673 : hDB, "DELETE FROM gpkg_contents WHERE table_name = 'ogr_empty_table'",
5674 : nullptr, nullptr, nullptr);
5675 : #ifdef ENABLE_GPKG_OGR_CONTENTS
5676 646 : if (m_bHasGPKGOGRContents)
5677 : {
5678 632 : sqlite3_exec(hDB,
5679 : "DELETE FROM gpkg_ogr_contents WHERE "
5680 : "table_name = 'ogr_empty_table'",
5681 : nullptr, nullptr, nullptr);
5682 : }
5683 : #endif
5684 646 : sqlite3_exec(hDB,
5685 : "DELETE FROM gpkg_geometry_columns WHERE "
5686 : "table_name = 'ogr_empty_table'",
5687 : nullptr, nullptr, nullptr);
5688 646 : }
5689 :
5690 : /************************************************************************/
5691 : /* CreateTileGriddedTable() */
5692 : /************************************************************************/
5693 :
5694 40 : bool GDALGeoPackageDataset::CreateTileGriddedTable(char **papszOptions)
5695 : {
5696 80 : CPLString osSQL;
5697 40 : if (!HasGriddedCoverageAncillaryTable())
5698 : {
5699 : // It doesn't exist. So create gpkg_extensions table if necessary, and
5700 : // gpkg_2d_gridded_coverage_ancillary & gpkg_2d_gridded_tile_ancillary,
5701 : // and register them as extensions.
5702 40 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
5703 0 : return false;
5704 :
5705 : // Req 1 /table-defs/coverage-ancillary
5706 : osSQL = "CREATE TABLE gpkg_2d_gridded_coverage_ancillary ("
5707 : "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
5708 : "tile_matrix_set_name TEXT NOT NULL UNIQUE,"
5709 : "datatype TEXT NOT NULL DEFAULT 'integer',"
5710 : "scale REAL NOT NULL DEFAULT 1.0,"
5711 : "offset REAL NOT NULL DEFAULT 0.0,"
5712 : "precision REAL DEFAULT 1.0,"
5713 : "data_null REAL,"
5714 : "grid_cell_encoding TEXT DEFAULT 'grid-value-is-center',"
5715 : "uom TEXT,"
5716 : "field_name TEXT DEFAULT 'Height',"
5717 : "quantity_definition TEXT DEFAULT 'Height',"
5718 : "CONSTRAINT fk_g2dgtct_name FOREIGN KEY(tile_matrix_set_name) "
5719 : "REFERENCES gpkg_tile_matrix_set ( table_name ) "
5720 : "CHECK (datatype in ('integer','float')))"
5721 : ";"
5722 : // Requirement 2 /table-defs/tile-ancillary
5723 : "CREATE TABLE gpkg_2d_gridded_tile_ancillary ("
5724 : "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
5725 : "tpudt_name TEXT NOT NULL,"
5726 : "tpudt_id INTEGER NOT NULL,"
5727 : "scale REAL NOT NULL DEFAULT 1.0,"
5728 : "offset REAL NOT NULL DEFAULT 0.0,"
5729 : "min REAL DEFAULT NULL,"
5730 : "max REAL DEFAULT NULL,"
5731 : "mean REAL DEFAULT NULL,"
5732 : "std_dev REAL DEFAULT NULL,"
5733 : "CONSTRAINT fk_g2dgtat_name FOREIGN KEY (tpudt_name) "
5734 : "REFERENCES gpkg_contents(table_name),"
5735 : "UNIQUE (tpudt_name, tpudt_id))"
5736 : ";"
5737 : // Requirement 6 /gpkg-extensions
5738 : "INSERT INTO gpkg_extensions "
5739 : "(table_name, column_name, extension_name, definition, scope) "
5740 : "VALUES ('gpkg_2d_gridded_coverage_ancillary', NULL, "
5741 : "'gpkg_2d_gridded_coverage', "
5742 : "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5743 : "'read-write')"
5744 : ";"
5745 : // Requirement 6 /gpkg-extensions
5746 : "INSERT INTO gpkg_extensions "
5747 : "(table_name, column_name, extension_name, definition, scope) "
5748 : "VALUES ('gpkg_2d_gridded_tile_ancillary', NULL, "
5749 : "'gpkg_2d_gridded_coverage', "
5750 : "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5751 : "'read-write')"
5752 40 : ";";
5753 : }
5754 :
5755 : // Requirement 6 /gpkg-extensions
5756 40 : char *pszSQL = sqlite3_mprintf(
5757 : "INSERT INTO gpkg_extensions "
5758 : "(table_name, column_name, extension_name, definition, scope) "
5759 : "VALUES ('%q', 'tile_data', "
5760 : "'gpkg_2d_gridded_coverage', "
5761 : "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5762 : "'read-write')",
5763 : m_osRasterTable.c_str());
5764 40 : osSQL += pszSQL;
5765 40 : osSQL += ";";
5766 40 : sqlite3_free(pszSQL);
5767 :
5768 : // Requirement 7 /gpkg-2d-gridded-coverage-ancillary
5769 : // Requirement 8 /gpkg-2d-gridded-coverage-ancillary-set-name
5770 : // Requirement 9 /gpkg-2d-gridded-coverage-ancillary-datatype
5771 40 : m_dfPrecision =
5772 40 : CPLAtof(CSLFetchNameValueDef(papszOptions, "PRECISION", "1"));
5773 : CPLString osGridCellEncoding(CSLFetchNameValueDef(
5774 80 : papszOptions, "GRID_CELL_ENCODING", "grid-value-is-center"));
5775 40 : m_bGridCellEncodingAsCO =
5776 40 : CSLFetchNameValue(papszOptions, "GRID_CELL_ENCODING") != nullptr;
5777 80 : CPLString osUom(CSLFetchNameValueDef(papszOptions, "UOM", ""));
5778 : CPLString osFieldName(
5779 80 : CSLFetchNameValueDef(papszOptions, "FIELD_NAME", "Height"));
5780 : CPLString osQuantityDefinition(
5781 80 : CSLFetchNameValueDef(papszOptions, "QUANTITY_DEFINITION", "Height"));
5782 :
5783 121 : pszSQL = sqlite3_mprintf(
5784 : "INSERT INTO gpkg_2d_gridded_coverage_ancillary "
5785 : "(tile_matrix_set_name, datatype, scale, offset, precision, "
5786 : "grid_cell_encoding, uom, field_name, quantity_definition) "
5787 : "VALUES (%Q, '%s', %.17g, %.17g, %.17g, %Q, %Q, %Q, %Q)",
5788 : m_osRasterTable.c_str(),
5789 40 : (m_eTF == GPKG_TF_PNG_16BIT) ? "integer" : "float", m_dfScale,
5790 : m_dfOffset, m_dfPrecision, osGridCellEncoding.c_str(),
5791 41 : osUom.empty() ? nullptr : osUom.c_str(), osFieldName.c_str(),
5792 : osQuantityDefinition.c_str());
5793 40 : m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary = pszSQL;
5794 40 : sqlite3_free(pszSQL);
5795 :
5796 : // Requirement 3 /gpkg-spatial-ref-sys-row
5797 : auto oResultTable = SQLQuery(
5798 80 : hDB, "SELECT * FROM gpkg_spatial_ref_sys WHERE srs_id = 4979 LIMIT 2");
5799 40 : bool bHasEPSG4979 = (oResultTable && oResultTable->RowCount() == 1);
5800 40 : if (!bHasEPSG4979)
5801 : {
5802 41 : if (!m_bHasDefinition12_063 &&
5803 1 : !ConvertGpkgSpatialRefSysToExtensionWkt2(/*bForceEpoch=*/false))
5804 : {
5805 0 : return false;
5806 : }
5807 :
5808 : // This is WKT 2...
5809 40 : const char *pszWKT =
5810 : "GEODCRS[\"WGS 84\","
5811 : "DATUM[\"World Geodetic System 1984\","
5812 : " ELLIPSOID[\"WGS 84\",6378137,298.257223563,"
5813 : "LENGTHUNIT[\"metre\",1.0]]],"
5814 : "CS[ellipsoidal,3],"
5815 : " AXIS[\"latitude\",north,ORDER[1],ANGLEUNIT[\"degree\","
5816 : "0.0174532925199433]],"
5817 : " AXIS[\"longitude\",east,ORDER[2],ANGLEUNIT[\"degree\","
5818 : "0.0174532925199433]],"
5819 : " AXIS[\"ellipsoidal height\",up,ORDER[3],"
5820 : "LENGTHUNIT[\"metre\",1.0]],"
5821 : "ID[\"EPSG\",4979]]";
5822 :
5823 40 : pszSQL = sqlite3_mprintf(
5824 : "INSERT INTO gpkg_spatial_ref_sys "
5825 : "(srs_name,srs_id,organization,organization_coordsys_id,"
5826 : "definition,definition_12_063) VALUES "
5827 : "('WGS 84 3D', 4979, 'EPSG', 4979, 'undefined', '%q')",
5828 : pszWKT);
5829 40 : osSQL += ";";
5830 40 : osSQL += pszSQL;
5831 40 : sqlite3_free(pszSQL);
5832 : }
5833 :
5834 40 : return SQLCommand(hDB, osSQL) == OGRERR_NONE;
5835 : }
5836 :
5837 : /************************************************************************/
5838 : /* HasGriddedCoverageAncillaryTable() */
5839 : /************************************************************************/
5840 :
5841 44 : bool GDALGeoPackageDataset::HasGriddedCoverageAncillaryTable()
5842 : {
5843 : auto oResultTable = SQLQuery(
5844 : hDB, "SELECT * FROM sqlite_master WHERE type IN ('table', 'view') AND "
5845 44 : "name = 'gpkg_2d_gridded_coverage_ancillary'");
5846 44 : bool bHasTable = (oResultTable && oResultTable->RowCount() == 1);
5847 88 : return bHasTable;
5848 : }
5849 :
5850 : /************************************************************************/
5851 : /* GetUnderlyingDataset() */
5852 : /************************************************************************/
5853 :
5854 3 : static GDALDataset *GetUnderlyingDataset(GDALDataset *poSrcDS)
5855 : {
5856 3 : if (auto poVRTDS = dynamic_cast<VRTDataset *>(poSrcDS))
5857 : {
5858 0 : auto poTmpDS = poVRTDS->GetSingleSimpleSource();
5859 0 : if (poTmpDS)
5860 0 : return poTmpDS;
5861 : }
5862 :
5863 3 : return poSrcDS;
5864 : }
5865 :
5866 : /************************************************************************/
5867 : /* CreateCopy() */
5868 : /************************************************************************/
5869 :
5870 : typedef struct
5871 : {
5872 : const char *pszName;
5873 : GDALResampleAlg eResampleAlg;
5874 : } WarpResamplingAlg;
5875 :
5876 : static const WarpResamplingAlg asResamplingAlg[] = {
5877 : {"NEAREST", GRA_NearestNeighbour},
5878 : {"BILINEAR", GRA_Bilinear},
5879 : {"CUBIC", GRA_Cubic},
5880 : {"CUBICSPLINE", GRA_CubicSpline},
5881 : {"LANCZOS", GRA_Lanczos},
5882 : {"MODE", GRA_Mode},
5883 : {"AVERAGE", GRA_Average},
5884 : {"RMS", GRA_RMS},
5885 : };
5886 :
5887 160 : GDALDataset *GDALGeoPackageDataset::CreateCopy(const char *pszFilename,
5888 : GDALDataset *poSrcDS,
5889 : int bStrict, char **papszOptions,
5890 : GDALProgressFunc pfnProgress,
5891 : void *pProgressData)
5892 : {
5893 160 : const int nBands = poSrcDS->GetRasterCount();
5894 160 : if (nBands == 0)
5895 : {
5896 2 : GDALDataset *poDS = nullptr;
5897 : GDALDriver *poThisDriver =
5898 2 : GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
5899 2 : if (poThisDriver != nullptr)
5900 : {
5901 2 : poDS = poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS,
5902 : bStrict, papszOptions,
5903 : pfnProgress, pProgressData);
5904 : }
5905 2 : return poDS;
5906 : }
5907 :
5908 : const char *pszTilingScheme =
5909 158 : CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
5910 :
5911 316 : CPLStringList apszUpdatedOptions(CSLDuplicate(papszOptions));
5912 158 : if (CPLTestBool(
5913 164 : CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")) &&
5914 6 : CSLFetchNameValue(papszOptions, "RASTER_TABLE") == nullptr)
5915 : {
5916 : const std::string osBasename(CPLGetBasenameSafe(
5917 6 : GetUnderlyingDataset(poSrcDS)->GetDescription()));
5918 3 : apszUpdatedOptions.SetNameValue("RASTER_TABLE", osBasename.c_str());
5919 : }
5920 :
5921 158 : if (nBands != 1 && nBands != 2 && nBands != 3 && nBands != 4)
5922 : {
5923 1 : CPLError(CE_Failure, CPLE_NotSupported,
5924 : "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), 3 (RGB) or "
5925 : "4 (RGBA) band dataset supported");
5926 1 : return nullptr;
5927 : }
5928 :
5929 157 : const char *pszUnitType = poSrcDS->GetRasterBand(1)->GetUnitType();
5930 314 : if (CSLFetchNameValue(papszOptions, "UOM") == nullptr && pszUnitType &&
5931 157 : !EQUAL(pszUnitType, ""))
5932 : {
5933 1 : apszUpdatedOptions.SetNameValue("UOM", pszUnitType);
5934 : }
5935 :
5936 157 : if (EQUAL(pszTilingScheme, "CUSTOM"))
5937 : {
5938 133 : if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
5939 : {
5940 0 : CPLError(CE_Failure, CPLE_NotSupported,
5941 : "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
5942 0 : return nullptr;
5943 : }
5944 :
5945 133 : GDALGeoPackageDataset *poDS = nullptr;
5946 : GDALDriver *poThisDriver =
5947 133 : GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
5948 133 : if (poThisDriver != nullptr)
5949 : {
5950 133 : apszUpdatedOptions.SetNameValue("SKIP_HOLES", "YES");
5951 133 : poDS = cpl::down_cast<GDALGeoPackageDataset *>(
5952 : poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
5953 : apszUpdatedOptions, pfnProgress,
5954 133 : pProgressData));
5955 :
5956 246 : if (poDS != nullptr &&
5957 133 : poSrcDS->GetRasterBand(1)->GetRasterDataType() == GDT_Byte &&
5958 : nBands <= 3)
5959 : {
5960 73 : poDS->m_nBandCountFromMetadata = nBands;
5961 73 : poDS->m_bMetadataDirty = true;
5962 : }
5963 : }
5964 133 : if (poDS)
5965 113 : poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
5966 133 : return poDS;
5967 : }
5968 :
5969 48 : const auto poTS = GetTilingScheme(pszTilingScheme);
5970 24 : if (!poTS)
5971 : {
5972 2 : return nullptr;
5973 : }
5974 22 : const int nEPSGCode = poTS->nEPSGCode;
5975 :
5976 44 : OGRSpatialReference oSRS;
5977 22 : if (oSRS.importFromEPSG(nEPSGCode) != OGRERR_NONE)
5978 : {
5979 0 : return nullptr;
5980 : }
5981 22 : char *pszWKT = nullptr;
5982 22 : oSRS.exportToWkt(&pszWKT);
5983 22 : char **papszTO = CSLSetNameValue(nullptr, "DST_SRS", pszWKT);
5984 :
5985 22 : void *hTransformArg = nullptr;
5986 :
5987 : // Hack to compensate for GDALSuggestedWarpOutput2() failure (or not
5988 : // ideal suggestion with PROJ 8) when reprojecting latitude = +/- 90 to
5989 : // EPSG:3857.
5990 : double adfSrcGeoTransform[6];
5991 22 : std::unique_ptr<GDALDataset> poTmpDS;
5992 22 : bool bEPSG3857Adjust = false;
5993 30 : if (nEPSGCode == 3857 &&
5994 8 : poSrcDS->GetGeoTransform(adfSrcGeoTransform) == CE_None &&
5995 38 : adfSrcGeoTransform[2] == 0 && adfSrcGeoTransform[4] == 0 &&
5996 8 : adfSrcGeoTransform[5] < 0)
5997 : {
5998 8 : const auto poSrcSRS = poSrcDS->GetSpatialRef();
5999 8 : if (poSrcSRS && poSrcSRS->IsGeographic())
6000 : {
6001 2 : double maxLat = adfSrcGeoTransform[3];
6002 2 : double minLat = adfSrcGeoTransform[3] +
6003 2 : poSrcDS->GetRasterYSize() * adfSrcGeoTransform[5];
6004 : // Corresponds to the latitude of below MAX_GM
6005 2 : constexpr double MAX_LAT = 85.0511287798066;
6006 2 : bool bModified = false;
6007 2 : if (maxLat > MAX_LAT)
6008 : {
6009 2 : maxLat = MAX_LAT;
6010 2 : bModified = true;
6011 : }
6012 2 : if (minLat < -MAX_LAT)
6013 : {
6014 2 : minLat = -MAX_LAT;
6015 2 : bModified = true;
6016 : }
6017 2 : if (bModified)
6018 : {
6019 4 : CPLStringList aosOptions;
6020 2 : aosOptions.AddString("-of");
6021 2 : aosOptions.AddString("VRT");
6022 2 : aosOptions.AddString("-projwin");
6023 : aosOptions.AddString(
6024 2 : CPLSPrintf("%.17g", adfSrcGeoTransform[0]));
6025 2 : aosOptions.AddString(CPLSPrintf("%.17g", maxLat));
6026 : aosOptions.AddString(
6027 2 : CPLSPrintf("%.17g", adfSrcGeoTransform[0] +
6028 2 : poSrcDS->GetRasterXSize() *
6029 2 : adfSrcGeoTransform[1]));
6030 2 : aosOptions.AddString(CPLSPrintf("%.17g", minLat));
6031 : auto psOptions =
6032 2 : GDALTranslateOptionsNew(aosOptions.List(), nullptr);
6033 2 : poTmpDS.reset(GDALDataset::FromHandle(GDALTranslate(
6034 : "", GDALDataset::ToHandle(poSrcDS), psOptions, nullptr)));
6035 2 : GDALTranslateOptionsFree(psOptions);
6036 2 : if (poTmpDS)
6037 : {
6038 2 : bEPSG3857Adjust = true;
6039 2 : hTransformArg = GDALCreateGenImgProjTransformer2(
6040 2 : GDALDataset::FromHandle(poTmpDS.get()), nullptr,
6041 : papszTO);
6042 : }
6043 : }
6044 : }
6045 : }
6046 22 : if (hTransformArg == nullptr)
6047 : {
6048 : hTransformArg =
6049 20 : GDALCreateGenImgProjTransformer2(poSrcDS, nullptr, papszTO);
6050 : }
6051 :
6052 22 : if (hTransformArg == nullptr)
6053 : {
6054 1 : CPLFree(pszWKT);
6055 1 : CSLDestroy(papszTO);
6056 1 : return nullptr;
6057 : }
6058 :
6059 21 : GDALTransformerInfo *psInfo =
6060 : static_cast<GDALTransformerInfo *>(hTransformArg);
6061 : double adfGeoTransform[6];
6062 : double adfExtent[4];
6063 : int nXSize, nYSize;
6064 :
6065 21 : if (GDALSuggestedWarpOutput2(poSrcDS, psInfo->pfnTransform, hTransformArg,
6066 : adfGeoTransform, &nXSize, &nYSize, adfExtent,
6067 21 : 0) != CE_None)
6068 : {
6069 0 : CPLFree(pszWKT);
6070 0 : CSLDestroy(papszTO);
6071 0 : GDALDestroyGenImgProjTransformer(hTransformArg);
6072 0 : return nullptr;
6073 : }
6074 :
6075 21 : GDALDestroyGenImgProjTransformer(hTransformArg);
6076 21 : hTransformArg = nullptr;
6077 21 : poTmpDS.reset();
6078 :
6079 21 : if (bEPSG3857Adjust)
6080 : {
6081 2 : constexpr double SPHERICAL_RADIUS = 6378137.0;
6082 2 : constexpr double MAX_GM =
6083 : SPHERICAL_RADIUS * M_PI; // 20037508.342789244
6084 2 : double maxNorthing = adfGeoTransform[3];
6085 2 : double minNorthing = adfGeoTransform[3] + adfGeoTransform[5] * nYSize;
6086 2 : bool bChanged = false;
6087 2 : if (maxNorthing > MAX_GM)
6088 : {
6089 2 : bChanged = true;
6090 2 : maxNorthing = MAX_GM;
6091 : }
6092 2 : if (minNorthing < -MAX_GM)
6093 : {
6094 2 : bChanged = true;
6095 2 : minNorthing = -MAX_GM;
6096 : }
6097 2 : if (bChanged)
6098 : {
6099 2 : adfGeoTransform[3] = maxNorthing;
6100 2 : nYSize =
6101 2 : int((maxNorthing - minNorthing) / (-adfGeoTransform[5]) + 0.5);
6102 2 : adfExtent[1] = maxNorthing + nYSize * adfGeoTransform[5];
6103 2 : adfExtent[3] = maxNorthing;
6104 : }
6105 : }
6106 :
6107 21 : double dfComputedRes = adfGeoTransform[1];
6108 21 : double dfPrevRes = 0.0;
6109 21 : double dfRes = 0.0;
6110 21 : int nZoomLevel = 0; // Used after for.
6111 21 : const char *pszZoomLevel = CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
6112 21 : if (pszZoomLevel)
6113 : {
6114 2 : nZoomLevel = atoi(pszZoomLevel);
6115 :
6116 2 : int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
6117 2 : while ((1 << nMaxZoomLevelForThisTM) >
6118 4 : INT_MAX / poTS->nTileXCountZoomLevel0 ||
6119 2 : (1 << nMaxZoomLevelForThisTM) >
6120 2 : INT_MAX / poTS->nTileYCountZoomLevel0)
6121 : {
6122 0 : --nMaxZoomLevelForThisTM;
6123 : }
6124 :
6125 2 : if (nZoomLevel < 0 || nZoomLevel > nMaxZoomLevelForThisTM)
6126 : {
6127 1 : CPLError(CE_Failure, CPLE_AppDefined,
6128 : "ZOOM_LEVEL = %s is invalid. It should be in [0,%d] range",
6129 : pszZoomLevel, nMaxZoomLevelForThisTM);
6130 1 : CPLFree(pszWKT);
6131 1 : CSLDestroy(papszTO);
6132 1 : return nullptr;
6133 : }
6134 : }
6135 : else
6136 : {
6137 171 : for (; nZoomLevel < MAX_ZOOM_LEVEL; nZoomLevel++)
6138 : {
6139 171 : dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
6140 171 : if (dfComputedRes > dfRes ||
6141 152 : fabs(dfComputedRes - dfRes) / dfRes <= 1e-8)
6142 : break;
6143 152 : dfPrevRes = dfRes;
6144 : }
6145 38 : if (nZoomLevel == MAX_ZOOM_LEVEL ||
6146 38 : (1 << nZoomLevel) > INT_MAX / poTS->nTileXCountZoomLevel0 ||
6147 19 : (1 << nZoomLevel) > INT_MAX / poTS->nTileYCountZoomLevel0)
6148 : {
6149 0 : CPLError(CE_Failure, CPLE_AppDefined,
6150 : "Could not find an appropriate zoom level");
6151 0 : CPLFree(pszWKT);
6152 0 : CSLDestroy(papszTO);
6153 0 : return nullptr;
6154 : }
6155 :
6156 19 : if (nZoomLevel > 0 && fabs(dfComputedRes - dfRes) / dfRes > 1e-8)
6157 : {
6158 17 : const char *pszZoomLevelStrategy = CSLFetchNameValueDef(
6159 : papszOptions, "ZOOM_LEVEL_STRATEGY", "AUTO");
6160 17 : if (EQUAL(pszZoomLevelStrategy, "LOWER"))
6161 : {
6162 1 : nZoomLevel--;
6163 : }
6164 16 : else if (EQUAL(pszZoomLevelStrategy, "UPPER"))
6165 : {
6166 : /* do nothing */
6167 : }
6168 : else
6169 : {
6170 15 : if (dfPrevRes / dfComputedRes < dfComputedRes / dfRes)
6171 13 : nZoomLevel--;
6172 : }
6173 : }
6174 : }
6175 :
6176 20 : dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
6177 :
6178 20 : double dfMinX = adfExtent[0];
6179 20 : double dfMinY = adfExtent[1];
6180 20 : double dfMaxX = adfExtent[2];
6181 20 : double dfMaxY = adfExtent[3];
6182 :
6183 20 : nXSize = static_cast<int>(0.5 + (dfMaxX - dfMinX) / dfRes);
6184 20 : nYSize = static_cast<int>(0.5 + (dfMaxY - dfMinY) / dfRes);
6185 20 : adfGeoTransform[1] = dfRes;
6186 20 : adfGeoTransform[5] = -dfRes;
6187 :
6188 20 : const GDALDataType eDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
6189 20 : int nTargetBands = nBands;
6190 : /* For grey level or RGB, if there's reprojection involved, add an alpha */
6191 : /* channel */
6192 37 : if (eDT == GDT_Byte &&
6193 13 : ((nBands == 1 &&
6194 17 : poSrcDS->GetRasterBand(1)->GetColorTable() == nullptr) ||
6195 : nBands == 3))
6196 : {
6197 30 : OGRSpatialReference oSrcSRS;
6198 15 : oSrcSRS.SetFromUserInput(poSrcDS->GetProjectionRef());
6199 15 : oSrcSRS.AutoIdentifyEPSG();
6200 30 : if (oSrcSRS.GetAuthorityCode(nullptr) == nullptr ||
6201 15 : atoi(oSrcSRS.GetAuthorityCode(nullptr)) != nEPSGCode)
6202 : {
6203 13 : nTargetBands++;
6204 : }
6205 : }
6206 :
6207 20 : GDALResampleAlg eResampleAlg = GRA_Bilinear;
6208 20 : const char *pszResampling = CSLFetchNameValue(papszOptions, "RESAMPLING");
6209 20 : if (pszResampling)
6210 : {
6211 6 : for (size_t iAlg = 0;
6212 6 : iAlg < sizeof(asResamplingAlg) / sizeof(asResamplingAlg[0]);
6213 : iAlg++)
6214 : {
6215 6 : if (EQUAL(pszResampling, asResamplingAlg[iAlg].pszName))
6216 : {
6217 3 : eResampleAlg = asResamplingAlg[iAlg].eResampleAlg;
6218 3 : break;
6219 : }
6220 : }
6221 : }
6222 :
6223 16 : if (nBands == 1 && poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr &&
6224 36 : eResampleAlg != GRA_NearestNeighbour && eResampleAlg != GRA_Mode)
6225 : {
6226 0 : CPLError(
6227 : CE_Warning, CPLE_AppDefined,
6228 : "Input dataset has a color table, which will likely lead to "
6229 : "bad results when using a resampling method other than "
6230 : "nearest neighbour or mode. Converting the dataset to 24/32 bit "
6231 : "(e.g. with gdal_translate -expand rgb/rgba) is advised.");
6232 : }
6233 :
6234 40 : auto poDS = std::make_unique<GDALGeoPackageDataset>();
6235 20 : if (!(poDS->Create(pszFilename, nXSize, nYSize, nTargetBands, eDT,
6236 : apszUpdatedOptions)))
6237 : {
6238 1 : CPLFree(pszWKT);
6239 1 : CSLDestroy(papszTO);
6240 1 : return nullptr;
6241 : }
6242 :
6243 : // Assign nodata values before the SetGeoTransform call.
6244 : // SetGeoTransform will trigger creation of the overview datasets for each
6245 : // zoom level and at that point the nodata value needs to be known.
6246 19 : int bHasNoData = FALSE;
6247 : double dfNoDataValue =
6248 19 : poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
6249 19 : if (eDT != GDT_Byte && bHasNoData)
6250 : {
6251 3 : poDS->GetRasterBand(1)->SetNoDataValue(dfNoDataValue);
6252 : }
6253 :
6254 19 : poDS->SetGeoTransform(adfGeoTransform);
6255 19 : poDS->SetProjection(pszWKT);
6256 19 : CPLFree(pszWKT);
6257 19 : pszWKT = nullptr;
6258 24 : if (nTargetBands == 1 && nBands == 1 &&
6259 5 : poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr)
6260 : {
6261 2 : poDS->GetRasterBand(1)->SetColorTable(
6262 1 : poSrcDS->GetRasterBand(1)->GetColorTable());
6263 : }
6264 :
6265 : hTransformArg =
6266 19 : GDALCreateGenImgProjTransformer2(poSrcDS, poDS.get(), papszTO);
6267 19 : CSLDestroy(papszTO);
6268 19 : if (hTransformArg == nullptr)
6269 : {
6270 0 : return nullptr;
6271 : }
6272 :
6273 19 : poDS->SetMetadata(poSrcDS->GetMetadata());
6274 :
6275 : /* -------------------------------------------------------------------- */
6276 : /* Warp the transformer with a linear approximator */
6277 : /* -------------------------------------------------------------------- */
6278 19 : hTransformArg = GDALCreateApproxTransformer(GDALGenImgProjTransform,
6279 : hTransformArg, 0.125);
6280 19 : GDALApproxTransformerOwnsSubtransformer(hTransformArg, TRUE);
6281 :
6282 : /* -------------------------------------------------------------------- */
6283 : /* Setup warp options. */
6284 : /* -------------------------------------------------------------------- */
6285 19 : GDALWarpOptions *psWO = GDALCreateWarpOptions();
6286 :
6287 19 : psWO->papszWarpOptions = CSLSetNameValue(nullptr, "OPTIMIZE_SIZE", "YES");
6288 19 : psWO->papszWarpOptions =
6289 19 : CSLSetNameValue(psWO->papszWarpOptions, "SAMPLE_GRID", "YES");
6290 19 : if (bHasNoData)
6291 : {
6292 3 : if (dfNoDataValue == 0.0)
6293 : {
6294 : // Do not initialize in the case where nodata != 0, since we
6295 : // want the GeoPackage driver to return empty tiles at the nodata
6296 : // value instead of 0 as GDAL core would
6297 0 : psWO->papszWarpOptions =
6298 0 : CSLSetNameValue(psWO->papszWarpOptions, "INIT_DEST", "0");
6299 : }
6300 :
6301 3 : psWO->padfSrcNoDataReal =
6302 3 : static_cast<double *>(CPLMalloc(sizeof(double)));
6303 3 : psWO->padfSrcNoDataReal[0] = dfNoDataValue;
6304 :
6305 3 : psWO->padfDstNoDataReal =
6306 3 : static_cast<double *>(CPLMalloc(sizeof(double)));
6307 3 : psWO->padfDstNoDataReal[0] = dfNoDataValue;
6308 : }
6309 19 : psWO->eWorkingDataType = eDT;
6310 19 : psWO->eResampleAlg = eResampleAlg;
6311 :
6312 19 : psWO->hSrcDS = poSrcDS;
6313 19 : psWO->hDstDS = poDS.get();
6314 :
6315 19 : psWO->pfnTransformer = GDALApproxTransform;
6316 19 : psWO->pTransformerArg = hTransformArg;
6317 :
6318 19 : psWO->pfnProgress = pfnProgress;
6319 19 : psWO->pProgressArg = pProgressData;
6320 :
6321 : /* -------------------------------------------------------------------- */
6322 : /* Setup band mapping. */
6323 : /* -------------------------------------------------------------------- */
6324 :
6325 19 : if (nBands == 2 || nBands == 4)
6326 1 : psWO->nBandCount = nBands - 1;
6327 : else
6328 18 : psWO->nBandCount = nBands;
6329 :
6330 19 : psWO->panSrcBands =
6331 19 : static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
6332 19 : psWO->panDstBands =
6333 19 : static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
6334 :
6335 46 : for (int i = 0; i < psWO->nBandCount; i++)
6336 : {
6337 27 : psWO->panSrcBands[i] = i + 1;
6338 27 : psWO->panDstBands[i] = i + 1;
6339 : }
6340 :
6341 19 : if (nBands == 2 || nBands == 4)
6342 : {
6343 1 : psWO->nSrcAlphaBand = nBands;
6344 : }
6345 19 : if (nTargetBands == 2 || nTargetBands == 4)
6346 : {
6347 13 : psWO->nDstAlphaBand = nTargetBands;
6348 : }
6349 :
6350 : /* -------------------------------------------------------------------- */
6351 : /* Initialize and execute the warp. */
6352 : /* -------------------------------------------------------------------- */
6353 38 : GDALWarpOperation oWO;
6354 :
6355 19 : CPLErr eErr = oWO.Initialize(psWO);
6356 19 : if (eErr == CE_None)
6357 : {
6358 : /*if( bMulti )
6359 : eErr = oWO.ChunkAndWarpMulti( 0, 0, nXSize, nYSize );
6360 : else*/
6361 19 : eErr = oWO.ChunkAndWarpImage(0, 0, nXSize, nYSize);
6362 : }
6363 19 : if (eErr != CE_None)
6364 : {
6365 0 : poDS.reset();
6366 : }
6367 :
6368 19 : GDALDestroyTransformer(hTransformArg);
6369 19 : GDALDestroyWarpOptions(psWO);
6370 :
6371 19 : if (poDS)
6372 19 : poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
6373 :
6374 19 : return poDS.release();
6375 : }
6376 :
6377 : /************************************************************************/
6378 : /* ParseCompressionOptions() */
6379 : /************************************************************************/
6380 :
6381 456 : void GDALGeoPackageDataset::ParseCompressionOptions(char **papszOptions)
6382 : {
6383 456 : const char *pszZLevel = CSLFetchNameValue(papszOptions, "ZLEVEL");
6384 456 : if (pszZLevel)
6385 0 : m_nZLevel = atoi(pszZLevel);
6386 :
6387 456 : const char *pszQuality = CSLFetchNameValue(papszOptions, "QUALITY");
6388 456 : if (pszQuality)
6389 0 : m_nQuality = atoi(pszQuality);
6390 :
6391 456 : const char *pszDither = CSLFetchNameValue(papszOptions, "DITHER");
6392 456 : if (pszDither)
6393 0 : m_bDither = CPLTestBool(pszDither);
6394 456 : }
6395 :
6396 : /************************************************************************/
6397 : /* RegisterWebPExtension() */
6398 : /************************************************************************/
6399 :
6400 11 : bool GDALGeoPackageDataset::RegisterWebPExtension()
6401 : {
6402 11 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
6403 0 : return false;
6404 :
6405 11 : char *pszSQL = sqlite3_mprintf(
6406 : "INSERT INTO gpkg_extensions "
6407 : "(table_name, column_name, extension_name, definition, scope) "
6408 : "VALUES "
6409 : "('%q', 'tile_data', 'gpkg_webp', "
6410 : "'http://www.geopackage.org/spec120/#extension_tiles_webp', "
6411 : "'read-write')",
6412 : m_osRasterTable.c_str());
6413 11 : const OGRErr eErr = SQLCommand(hDB, pszSQL);
6414 11 : sqlite3_free(pszSQL);
6415 :
6416 11 : return OGRERR_NONE == eErr;
6417 : }
6418 :
6419 : /************************************************************************/
6420 : /* RegisterZoomOtherExtension() */
6421 : /************************************************************************/
6422 :
6423 1 : bool GDALGeoPackageDataset::RegisterZoomOtherExtension()
6424 : {
6425 1 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
6426 0 : return false;
6427 :
6428 1 : char *pszSQL = sqlite3_mprintf(
6429 : "INSERT INTO gpkg_extensions "
6430 : "(table_name, column_name, extension_name, definition, scope) "
6431 : "VALUES "
6432 : "('%q', 'tile_data', 'gpkg_zoom_other', "
6433 : "'http://www.geopackage.org/spec120/#extension_zoom_other_intervals', "
6434 : "'read-write')",
6435 : m_osRasterTable.c_str());
6436 1 : const OGRErr eErr = SQLCommand(hDB, pszSQL);
6437 1 : sqlite3_free(pszSQL);
6438 1 : return OGRERR_NONE == eErr;
6439 : }
6440 :
6441 : /************************************************************************/
6442 : /* GetLayer() */
6443 : /************************************************************************/
6444 :
6445 15268 : OGRLayer *GDALGeoPackageDataset::GetLayer(int iLayer)
6446 :
6447 : {
6448 15268 : if (iLayer < 0 || iLayer >= static_cast<int>(m_apoLayers.size()))
6449 6 : return nullptr;
6450 : else
6451 15262 : return m_apoLayers[iLayer].get();
6452 : }
6453 :
6454 : /************************************************************************/
6455 : /* LaunderName() */
6456 : /************************************************************************/
6457 :
6458 : /** Launder identifiers (table, column names) according to guidance at
6459 : * https://www.geopackage.org/guidance/getting-started.html:
6460 : * "For maximum interoperability, start your database identifiers (table names,
6461 : * column names, etc.) with a lowercase character and only use lowercase
6462 : * characters, numbers 0-9, and underscores (_)."
6463 : */
6464 :
6465 : /* static */
6466 5 : std::string GDALGeoPackageDataset::LaunderName(const std::string &osStr)
6467 : {
6468 5 : char *pszASCII = CPLUTF8ForceToASCII(osStr.c_str(), '_');
6469 10 : const std::string osStrASCII(pszASCII);
6470 5 : CPLFree(pszASCII);
6471 :
6472 10 : std::string osRet;
6473 5 : osRet.reserve(osStrASCII.size());
6474 :
6475 29 : for (size_t i = 0; i < osStrASCII.size(); ++i)
6476 : {
6477 24 : if (osRet.empty())
6478 : {
6479 5 : if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
6480 : {
6481 2 : osRet += (osStrASCII[i] - 'A' + 'a');
6482 : }
6483 3 : else if (osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z')
6484 : {
6485 2 : osRet += osStrASCII[i];
6486 : }
6487 : else
6488 : {
6489 1 : continue;
6490 : }
6491 : }
6492 19 : else if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
6493 : {
6494 11 : osRet += (osStrASCII[i] - 'A' + 'a');
6495 : }
6496 9 : else if ((osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z') ||
6497 14 : (osStrASCII[i] >= '0' && osStrASCII[i] <= '9') ||
6498 5 : osStrASCII[i] == '_')
6499 : {
6500 7 : osRet += osStrASCII[i];
6501 : }
6502 : else
6503 : {
6504 1 : osRet += '_';
6505 : }
6506 : }
6507 :
6508 5 : if (osRet.empty() && !osStrASCII.empty())
6509 2 : return LaunderName(std::string("x").append(osStrASCII));
6510 :
6511 4 : if (osRet != osStr)
6512 : {
6513 3 : CPLDebug("PG", "LaunderName('%s') -> '%s'", osStr.c_str(),
6514 : osRet.c_str());
6515 : }
6516 :
6517 4 : return osRet;
6518 : }
6519 :
6520 : /************************************************************************/
6521 : /* ICreateLayer() */
6522 : /************************************************************************/
6523 :
6524 : OGRLayer *
6525 736 : GDALGeoPackageDataset::ICreateLayer(const char *pszLayerName,
6526 : const OGRGeomFieldDefn *poSrcGeomFieldDefn,
6527 : CSLConstList papszOptions)
6528 : {
6529 : /* -------------------------------------------------------------------- */
6530 : /* Verify we are in update mode. */
6531 : /* -------------------------------------------------------------------- */
6532 736 : if (!GetUpdate())
6533 : {
6534 0 : CPLError(CE_Failure, CPLE_NoWriteAccess,
6535 : "Data source %s opened read-only.\n"
6536 : "New layer %s cannot be created.\n",
6537 : m_pszFilename, pszLayerName);
6538 :
6539 0 : return nullptr;
6540 : }
6541 :
6542 : const bool bLaunder =
6543 736 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "LAUNDER", "NO"));
6544 : const std::string osTableName(bLaunder ? LaunderName(pszLayerName)
6545 2208 : : std::string(pszLayerName));
6546 :
6547 : const auto eGType =
6548 736 : poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone;
6549 : const auto poSpatialRef =
6550 736 : poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr;
6551 :
6552 736 : if (!m_bHasGPKGGeometryColumns)
6553 : {
6554 1 : if (SQLCommand(hDB, pszCREATE_GPKG_GEOMETRY_COLUMNS) != OGRERR_NONE)
6555 : {
6556 0 : return nullptr;
6557 : }
6558 1 : m_bHasGPKGGeometryColumns = true;
6559 : }
6560 :
6561 : // Check identifier unicity
6562 736 : const char *pszIdentifier = CSLFetchNameValue(papszOptions, "IDENTIFIER");
6563 736 : if (pszIdentifier != nullptr && pszIdentifier[0] == '\0')
6564 0 : pszIdentifier = nullptr;
6565 736 : if (pszIdentifier != nullptr)
6566 : {
6567 13 : for (auto &poLayer : m_apoLayers)
6568 : {
6569 : const char *pszOtherIdentifier =
6570 9 : poLayer->GetMetadataItem("IDENTIFIER");
6571 9 : if (pszOtherIdentifier == nullptr)
6572 6 : pszOtherIdentifier = poLayer->GetName();
6573 18 : if (pszOtherIdentifier != nullptr &&
6574 12 : EQUAL(pszOtherIdentifier, pszIdentifier) &&
6575 3 : !EQUAL(poLayer->GetName(), osTableName.c_str()))
6576 : {
6577 2 : CPLError(CE_Failure, CPLE_AppDefined,
6578 : "Identifier %s is already used by table %s",
6579 : pszIdentifier, poLayer->GetName());
6580 2 : return nullptr;
6581 : }
6582 : }
6583 :
6584 : // In case there would be table in gpkg_contents not listed as a
6585 : // vector layer
6586 4 : char *pszSQL = sqlite3_mprintf(
6587 : "SELECT table_name FROM gpkg_contents WHERE identifier = '%q' "
6588 : "LIMIT 2",
6589 : pszIdentifier);
6590 4 : auto oResult = SQLQuery(hDB, pszSQL);
6591 4 : sqlite3_free(pszSQL);
6592 8 : if (oResult && oResult->RowCount() > 0 &&
6593 9 : oResult->GetValue(0, 0) != nullptr &&
6594 1 : !EQUAL(oResult->GetValue(0, 0), osTableName.c_str()))
6595 : {
6596 1 : CPLError(CE_Failure, CPLE_AppDefined,
6597 : "Identifier %s is already used by table %s", pszIdentifier,
6598 : oResult->GetValue(0, 0));
6599 1 : return nullptr;
6600 : }
6601 : }
6602 :
6603 : /* Read GEOMETRY_NAME option */
6604 : const char *pszGeomColumnName =
6605 733 : CSLFetchNameValue(papszOptions, "GEOMETRY_NAME");
6606 733 : if (pszGeomColumnName == nullptr) /* deprecated name */
6607 652 : pszGeomColumnName = CSLFetchNameValue(papszOptions, "GEOMETRY_COLUMN");
6608 733 : if (pszGeomColumnName == nullptr && poSrcGeomFieldDefn)
6609 : {
6610 600 : pszGeomColumnName = poSrcGeomFieldDefn->GetNameRef();
6611 600 : if (pszGeomColumnName && pszGeomColumnName[0] == 0)
6612 596 : pszGeomColumnName = nullptr;
6613 : }
6614 733 : if (pszGeomColumnName == nullptr)
6615 648 : pszGeomColumnName = "geom";
6616 : const bool bGeomNullable =
6617 733 : CPLFetchBool(papszOptions, "GEOMETRY_NULLABLE", true);
6618 :
6619 : /* Read FID option */
6620 733 : const char *pszFIDColumnName = CSLFetchNameValue(papszOptions, "FID");
6621 733 : if (pszFIDColumnName == nullptr)
6622 698 : pszFIDColumnName = "fid";
6623 :
6624 733 : if (CPLTestBool(CPLGetConfigOption("GPKG_NAME_CHECK", "YES")))
6625 : {
6626 733 : if (strspn(pszFIDColumnName, "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") > 0)
6627 : {
6628 0 : CPLError(CE_Failure, CPLE_AppDefined,
6629 : "The primary key (%s) name may not contain special "
6630 : "characters or spaces",
6631 : pszFIDColumnName);
6632 0 : return nullptr;
6633 : }
6634 :
6635 : /* Avoiding gpkg prefixes is not an official requirement, but seems wise
6636 : */
6637 733 : if (STARTS_WITH(osTableName.c_str(), "gpkg"))
6638 : {
6639 0 : CPLError(CE_Failure, CPLE_AppDefined,
6640 : "The layer name may not begin with 'gpkg' as it is a "
6641 : "reserved geopackage prefix");
6642 0 : return nullptr;
6643 : }
6644 :
6645 : /* Preemptively try and avoid sqlite3 syntax errors due to */
6646 : /* illegal characters. */
6647 733 : if (strspn(osTableName.c_str(), "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") >
6648 : 0)
6649 : {
6650 0 : CPLError(
6651 : CE_Failure, CPLE_AppDefined,
6652 : "The layer name may not contain special characters or spaces");
6653 0 : return nullptr;
6654 : }
6655 : }
6656 :
6657 : /* Check for any existing layers that already use this name */
6658 934 : for (int iLayer = 0; iLayer < static_cast<int>(m_apoLayers.size());
6659 : iLayer++)
6660 : {
6661 202 : if (EQUAL(osTableName.c_str(), m_apoLayers[iLayer]->GetName()))
6662 : {
6663 : const char *pszOverwrite =
6664 2 : CSLFetchNameValue(papszOptions, "OVERWRITE");
6665 2 : if (pszOverwrite != nullptr && CPLTestBool(pszOverwrite))
6666 : {
6667 1 : DeleteLayer(iLayer);
6668 : }
6669 : else
6670 : {
6671 1 : CPLError(CE_Failure, CPLE_AppDefined,
6672 : "Layer %s already exists, CreateLayer failed.\n"
6673 : "Use the layer creation option OVERWRITE=YES to "
6674 : "replace it.",
6675 : osTableName.c_str());
6676 1 : return nullptr;
6677 : }
6678 : }
6679 : }
6680 :
6681 732 : if (m_apoLayers.size() == 1)
6682 : {
6683 : // Async RTree building doesn't play well with multiple layer:
6684 : // SQLite3 locks being hold for a long time, random failed commits,
6685 : // etc.
6686 75 : m_apoLayers[0]->FinishOrDisableThreadedRTree();
6687 : }
6688 :
6689 : /* Create a blank layer. */
6690 : auto poLayer =
6691 1464 : std::make_unique<OGRGeoPackageTableLayer>(this, osTableName.c_str());
6692 :
6693 732 : OGRSpatialReference *poSRS = nullptr;
6694 732 : if (poSpatialRef)
6695 : {
6696 227 : poSRS = poSpatialRef->Clone();
6697 227 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
6698 : }
6699 1465 : poLayer->SetCreationParameters(
6700 : eGType,
6701 733 : bLaunder ? LaunderName(pszGeomColumnName).c_str() : pszGeomColumnName,
6702 : bGeomNullable, poSRS, CSLFetchNameValue(papszOptions, "SRID"),
6703 1464 : poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetCoordinatePrecision()
6704 : : OGRGeomCoordinatePrecision(),
6705 732 : CPLTestBool(
6706 : CSLFetchNameValueDef(papszOptions, "DISCARD_COORD_LSB", "NO")),
6707 732 : CPLTestBool(CSLFetchNameValueDef(
6708 : papszOptions, "UNDO_DISCARD_COORD_LSB_ON_READING", "NO")),
6709 733 : bLaunder ? LaunderName(pszFIDColumnName).c_str() : pszFIDColumnName,
6710 : pszIdentifier, CSLFetchNameValue(papszOptions, "DESCRIPTION"));
6711 732 : if (poSRS)
6712 : {
6713 227 : poSRS->Release();
6714 : }
6715 :
6716 732 : poLayer->SetLaunder(bLaunder);
6717 :
6718 : /* Should we create a spatial index ? */
6719 732 : const char *pszSI = CSLFetchNameValue(papszOptions, "SPATIAL_INDEX");
6720 732 : int bCreateSpatialIndex = (pszSI == nullptr || CPLTestBool(pszSI));
6721 732 : if (eGType != wkbNone && bCreateSpatialIndex)
6722 : {
6723 658 : poLayer->SetDeferredSpatialIndexCreation(true);
6724 : }
6725 :
6726 732 : poLayer->SetPrecisionFlag(CPLFetchBool(papszOptions, "PRECISION", true));
6727 732 : poLayer->SetTruncateFieldsFlag(
6728 732 : CPLFetchBool(papszOptions, "TRUNCATE_FIELDS", false));
6729 732 : if (eGType == wkbNone)
6730 : {
6731 52 : const char *pszASpatialVariant = CSLFetchNameValueDef(
6732 : papszOptions, "ASPATIAL_VARIANT",
6733 52 : m_bNonSpatialTablesNonRegisteredInGpkgContentsFound
6734 : ? "NOT_REGISTERED"
6735 : : "GPKG_ATTRIBUTES");
6736 52 : GPKGASpatialVariant eASpatialVariant = GPKG_ATTRIBUTES;
6737 52 : if (EQUAL(pszASpatialVariant, "GPKG_ATTRIBUTES"))
6738 40 : eASpatialVariant = GPKG_ATTRIBUTES;
6739 12 : else if (EQUAL(pszASpatialVariant, "OGR_ASPATIAL"))
6740 : {
6741 0 : CPLError(CE_Failure, CPLE_NotSupported,
6742 : "ASPATIAL_VARIANT=OGR_ASPATIAL is no longer supported");
6743 0 : return nullptr;
6744 : }
6745 12 : else if (EQUAL(pszASpatialVariant, "NOT_REGISTERED"))
6746 12 : eASpatialVariant = NOT_REGISTERED;
6747 : else
6748 : {
6749 0 : CPLError(CE_Failure, CPLE_NotSupported,
6750 : "Unsupported value for ASPATIAL_VARIANT: %s",
6751 : pszASpatialVariant);
6752 0 : return nullptr;
6753 : }
6754 52 : poLayer->SetASpatialVariant(eASpatialVariant);
6755 : }
6756 :
6757 : const char *pszDateTimePrecision =
6758 732 : CSLFetchNameValueDef(papszOptions, "DATETIME_PRECISION", "AUTO");
6759 732 : if (EQUAL(pszDateTimePrecision, "MILLISECOND"))
6760 : {
6761 2 : poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
6762 : }
6763 730 : else if (EQUAL(pszDateTimePrecision, "SECOND"))
6764 : {
6765 1 : if (m_nUserVersion < GPKG_1_4_VERSION)
6766 0 : CPLError(
6767 : CE_Warning, CPLE_AppDefined,
6768 : "DATETIME_PRECISION=SECOND is only valid since GeoPackage 1.4");
6769 1 : poLayer->SetDateTimePrecision(OGRISO8601Precision::SECOND);
6770 : }
6771 729 : else if (EQUAL(pszDateTimePrecision, "MINUTE"))
6772 : {
6773 1 : if (m_nUserVersion < GPKG_1_4_VERSION)
6774 0 : CPLError(
6775 : CE_Warning, CPLE_AppDefined,
6776 : "DATETIME_PRECISION=MINUTE is only valid since GeoPackage 1.4");
6777 1 : poLayer->SetDateTimePrecision(OGRISO8601Precision::MINUTE);
6778 : }
6779 728 : else if (EQUAL(pszDateTimePrecision, "AUTO"))
6780 : {
6781 727 : if (m_nUserVersion < GPKG_1_4_VERSION)
6782 13 : poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
6783 : }
6784 : else
6785 : {
6786 1 : CPLError(CE_Failure, CPLE_NotSupported,
6787 : "Unsupported value for DATETIME_PRECISION: %s",
6788 : pszDateTimePrecision);
6789 1 : return nullptr;
6790 : }
6791 :
6792 : // If there was an ogr_empty_table table, we can remove it
6793 : // But do it at dataset closing, otherwise locking performance issues
6794 : // can arise (probably when transactions are used).
6795 731 : m_bRemoveOGREmptyTable = true;
6796 :
6797 731 : m_apoLayers.emplace_back(std::move(poLayer));
6798 731 : return m_apoLayers.back().get();
6799 : }
6800 :
6801 : /************************************************************************/
6802 : /* FindLayerIndex() */
6803 : /************************************************************************/
6804 :
6805 27 : int GDALGeoPackageDataset::FindLayerIndex(const char *pszLayerName)
6806 :
6807 : {
6808 42 : for (int iLayer = 0; iLayer < static_cast<int>(m_apoLayers.size());
6809 : iLayer++)
6810 : {
6811 28 : if (EQUAL(pszLayerName, m_apoLayers[iLayer]->GetName()))
6812 13 : return iLayer;
6813 : }
6814 14 : return -1;
6815 : }
6816 :
6817 : /************************************************************************/
6818 : /* DeleteLayerCommon() */
6819 : /************************************************************************/
6820 :
6821 39 : OGRErr GDALGeoPackageDataset::DeleteLayerCommon(const char *pszLayerName)
6822 : {
6823 : // Temporary remove foreign key checks
6824 : const GPKGTemporaryForeignKeyCheckDisabler
6825 39 : oGPKGTemporaryForeignKeyCheckDisabler(this);
6826 :
6827 39 : char *pszSQL = sqlite3_mprintf(
6828 : "DELETE FROM gpkg_contents WHERE lower(table_name) = lower('%q')",
6829 : pszLayerName);
6830 39 : OGRErr eErr = SQLCommand(hDB, pszSQL);
6831 39 : sqlite3_free(pszSQL);
6832 :
6833 39 : if (eErr == OGRERR_NONE && HasExtensionsTable())
6834 : {
6835 37 : pszSQL = sqlite3_mprintf(
6836 : "DELETE FROM gpkg_extensions WHERE lower(table_name) = lower('%q')",
6837 : pszLayerName);
6838 37 : eErr = SQLCommand(hDB, pszSQL);
6839 37 : sqlite3_free(pszSQL);
6840 : }
6841 :
6842 39 : if (eErr == OGRERR_NONE && HasMetadataTables())
6843 : {
6844 : // Delete from gpkg_metadata metadata records that are only referenced
6845 : // by the table we are about to drop
6846 11 : pszSQL = sqlite3_mprintf(
6847 : "DELETE FROM gpkg_metadata WHERE id IN ("
6848 : "SELECT DISTINCT md_file_id FROM "
6849 : "gpkg_metadata_reference WHERE "
6850 : "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
6851 : "AND id NOT IN ("
6852 : "SELECT DISTINCT md_file_id FROM gpkg_metadata_reference WHERE "
6853 : "md_file_id IN (SELECT DISTINCT md_file_id FROM "
6854 : "gpkg_metadata_reference WHERE "
6855 : "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
6856 : "AND lower(table_name) <> lower('%q'))",
6857 : pszLayerName, pszLayerName, pszLayerName);
6858 11 : eErr = SQLCommand(hDB, pszSQL);
6859 11 : sqlite3_free(pszSQL);
6860 :
6861 11 : if (eErr == OGRERR_NONE)
6862 : {
6863 : pszSQL =
6864 11 : sqlite3_mprintf("DELETE FROM gpkg_metadata_reference WHERE "
6865 : "lower(table_name) = lower('%q')",
6866 : pszLayerName);
6867 11 : eErr = SQLCommand(hDB, pszSQL);
6868 11 : sqlite3_free(pszSQL);
6869 : }
6870 : }
6871 :
6872 39 : if (eErr == OGRERR_NONE && HasGpkgextRelationsTable())
6873 : {
6874 : // Remove reference to potential corresponding mapping table in
6875 : // gpkg_extensions
6876 4 : pszSQL = sqlite3_mprintf(
6877 : "DELETE FROM gpkg_extensions WHERE "
6878 : "extension_name IN ('related_tables', "
6879 : "'gpkg_related_tables') AND lower(table_name) = "
6880 : "(SELECT lower(mapping_table_name) FROM gpkgext_relations WHERE "
6881 : "lower(base_table_name) = lower('%q') OR "
6882 : "lower(related_table_name) = lower('%q') OR "
6883 : "lower(mapping_table_name) = lower('%q'))",
6884 : pszLayerName, pszLayerName, pszLayerName);
6885 4 : eErr = SQLCommand(hDB, pszSQL);
6886 4 : sqlite3_free(pszSQL);
6887 :
6888 4 : if (eErr == OGRERR_NONE)
6889 : {
6890 : // Remove reference to potential corresponding mapping table in
6891 : // gpkgext_relations
6892 : pszSQL =
6893 4 : sqlite3_mprintf("DELETE FROM gpkgext_relations WHERE "
6894 : "lower(base_table_name) = lower('%q') OR "
6895 : "lower(related_table_name) = lower('%q') OR "
6896 : "lower(mapping_table_name) = lower('%q')",
6897 : pszLayerName, pszLayerName, pszLayerName);
6898 4 : eErr = SQLCommand(hDB, pszSQL);
6899 4 : sqlite3_free(pszSQL);
6900 : }
6901 :
6902 4 : if (eErr == OGRERR_NONE && HasExtensionsTable())
6903 : {
6904 : // If there is no longer any mapping table, then completely
6905 : // remove any reference to the extension in gpkg_extensions
6906 : // as mandated per the related table specification.
6907 : OGRErr err;
6908 4 : if (SQLGetInteger(hDB,
6909 : "SELECT COUNT(*) FROM gpkg_extensions WHERE "
6910 : "extension_name IN ('related_tables', "
6911 : "'gpkg_related_tables') AND "
6912 : "lower(table_name) != 'gpkgext_relations'",
6913 4 : &err) == 0)
6914 : {
6915 2 : eErr = SQLCommand(hDB, "DELETE FROM gpkg_extensions WHERE "
6916 : "extension_name IN ('related_tables', "
6917 : "'gpkg_related_tables')");
6918 : }
6919 :
6920 4 : ClearCachedRelationships();
6921 : }
6922 : }
6923 :
6924 39 : if (eErr == OGRERR_NONE)
6925 : {
6926 39 : pszSQL = sqlite3_mprintf("DROP TABLE \"%w\"", pszLayerName);
6927 39 : eErr = SQLCommand(hDB, pszSQL);
6928 39 : sqlite3_free(pszSQL);
6929 : }
6930 :
6931 : // Check foreign key integrity
6932 39 : if (eErr == OGRERR_NONE)
6933 : {
6934 39 : eErr = PragmaCheck("foreign_key_check", "", 0);
6935 : }
6936 :
6937 78 : return eErr;
6938 : }
6939 :
6940 : /************************************************************************/
6941 : /* DeleteLayer() */
6942 : /************************************************************************/
6943 :
6944 36 : OGRErr GDALGeoPackageDataset::DeleteLayer(int iLayer)
6945 : {
6946 71 : if (!GetUpdate() || iLayer < 0 ||
6947 35 : iLayer >= static_cast<int>(m_apoLayers.size()))
6948 2 : return OGRERR_FAILURE;
6949 :
6950 34 : m_apoLayers[iLayer]->ResetReading();
6951 34 : m_apoLayers[iLayer]->SyncToDisk();
6952 :
6953 68 : CPLString osLayerName = m_apoLayers[iLayer]->GetName();
6954 :
6955 34 : CPLDebug("GPKG", "DeleteLayer(%s)", osLayerName.c_str());
6956 :
6957 : // Temporary remove foreign key checks
6958 : const GPKGTemporaryForeignKeyCheckDisabler
6959 34 : oGPKGTemporaryForeignKeyCheckDisabler(this);
6960 :
6961 34 : OGRErr eErr = SoftStartTransaction();
6962 :
6963 34 : if (eErr == OGRERR_NONE)
6964 : {
6965 34 : if (m_apoLayers[iLayer]->HasSpatialIndex())
6966 31 : m_apoLayers[iLayer]->DropSpatialIndex();
6967 :
6968 : char *pszSQL =
6969 34 : sqlite3_mprintf("DELETE FROM gpkg_geometry_columns WHERE "
6970 : "lower(table_name) = lower('%q')",
6971 : osLayerName.c_str());
6972 34 : eErr = SQLCommand(hDB, pszSQL);
6973 34 : sqlite3_free(pszSQL);
6974 : }
6975 :
6976 34 : if (eErr == OGRERR_NONE && HasDataColumnsTable())
6977 : {
6978 1 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_data_columns WHERE "
6979 : "lower(table_name) = lower('%q')",
6980 : osLayerName.c_str());
6981 1 : eErr = SQLCommand(hDB, pszSQL);
6982 1 : sqlite3_free(pszSQL);
6983 : }
6984 :
6985 : #ifdef ENABLE_GPKG_OGR_CONTENTS
6986 34 : if (eErr == OGRERR_NONE && m_bHasGPKGOGRContents)
6987 : {
6988 34 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_ogr_contents WHERE "
6989 : "lower(table_name) = lower('%q')",
6990 : osLayerName.c_str());
6991 34 : eErr = SQLCommand(hDB, pszSQL);
6992 34 : sqlite3_free(pszSQL);
6993 : }
6994 : #endif
6995 :
6996 34 : if (eErr == OGRERR_NONE)
6997 : {
6998 34 : eErr = DeleteLayerCommon(osLayerName.c_str());
6999 : }
7000 :
7001 34 : if (eErr == OGRERR_NONE)
7002 : {
7003 34 : eErr = SoftCommitTransaction();
7004 34 : if (eErr == OGRERR_NONE)
7005 : {
7006 : /* Delete the layer object */
7007 34 : m_apoLayers.erase(m_apoLayers.begin() + iLayer);
7008 : }
7009 : }
7010 : else
7011 : {
7012 0 : SoftRollbackTransaction();
7013 : }
7014 :
7015 34 : return eErr;
7016 : }
7017 :
7018 : /************************************************************************/
7019 : /* DeleteRasterLayer() */
7020 : /************************************************************************/
7021 :
7022 2 : OGRErr GDALGeoPackageDataset::DeleteRasterLayer(const char *pszLayerName)
7023 : {
7024 : // Temporary remove foreign key checks
7025 : const GPKGTemporaryForeignKeyCheckDisabler
7026 2 : oGPKGTemporaryForeignKeyCheckDisabler(this);
7027 :
7028 2 : OGRErr eErr = SoftStartTransaction();
7029 :
7030 2 : if (eErr == OGRERR_NONE)
7031 : {
7032 2 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix 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)
7040 : {
7041 2 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix_set WHERE "
7042 : "lower(table_name) = lower('%q')",
7043 : pszLayerName);
7044 2 : eErr = SQLCommand(hDB, pszSQL);
7045 2 : sqlite3_free(pszSQL);
7046 : }
7047 :
7048 2 : if (eErr == OGRERR_NONE && HasGriddedCoverageAncillaryTable())
7049 : {
7050 : char *pszSQL =
7051 1 : sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_coverage_ancillary "
7052 : "WHERE lower(tile_matrix_set_name) = lower('%q')",
7053 : pszLayerName);
7054 1 : eErr = SQLCommand(hDB, pszSQL);
7055 1 : sqlite3_free(pszSQL);
7056 :
7057 1 : if (eErr == OGRERR_NONE)
7058 : {
7059 : pszSQL =
7060 1 : sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_tile_ancillary "
7061 : "WHERE lower(tpudt_name) = lower('%q')",
7062 : pszLayerName);
7063 1 : eErr = SQLCommand(hDB, pszSQL);
7064 1 : sqlite3_free(pszSQL);
7065 : }
7066 : }
7067 :
7068 2 : if (eErr == OGRERR_NONE)
7069 : {
7070 2 : eErr = DeleteLayerCommon(pszLayerName);
7071 : }
7072 :
7073 2 : if (eErr == OGRERR_NONE)
7074 : {
7075 2 : eErr = SoftCommitTransaction();
7076 : }
7077 : else
7078 : {
7079 0 : SoftRollbackTransaction();
7080 : }
7081 :
7082 4 : return eErr;
7083 : }
7084 :
7085 : /************************************************************************/
7086 : /* DeleteVectorOrRasterLayer() */
7087 : /************************************************************************/
7088 :
7089 13 : bool GDALGeoPackageDataset::DeleteVectorOrRasterLayer(const char *pszLayerName)
7090 : {
7091 :
7092 13 : int idx = FindLayerIndex(pszLayerName);
7093 13 : if (idx >= 0)
7094 : {
7095 5 : DeleteLayer(idx);
7096 5 : return true;
7097 : }
7098 :
7099 : char *pszSQL =
7100 8 : sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
7101 : "lower(table_name) = lower('%q') "
7102 : "AND data_type IN ('tiles', '2d-gridded-coverage')",
7103 : pszLayerName);
7104 8 : bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
7105 8 : sqlite3_free(pszSQL);
7106 8 : if (bIsRasterTable)
7107 : {
7108 2 : DeleteRasterLayer(pszLayerName);
7109 2 : return true;
7110 : }
7111 6 : return false;
7112 : }
7113 :
7114 7 : bool GDALGeoPackageDataset::RenameVectorOrRasterLayer(
7115 : const char *pszLayerName, const char *pszNewLayerName)
7116 : {
7117 7 : int idx = FindLayerIndex(pszLayerName);
7118 7 : if (idx >= 0)
7119 : {
7120 4 : m_apoLayers[idx]->Rename(pszNewLayerName);
7121 4 : return true;
7122 : }
7123 :
7124 : char *pszSQL =
7125 3 : sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
7126 : "lower(table_name) = lower('%q') "
7127 : "AND data_type IN ('tiles', '2d-gridded-coverage')",
7128 : pszLayerName);
7129 3 : const bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
7130 3 : sqlite3_free(pszSQL);
7131 :
7132 3 : if (bIsRasterTable)
7133 : {
7134 2 : return RenameRasterLayer(pszLayerName, pszNewLayerName);
7135 : }
7136 :
7137 1 : return false;
7138 : }
7139 :
7140 2 : bool GDALGeoPackageDataset::RenameRasterLayer(const char *pszLayerName,
7141 : const char *pszNewLayerName)
7142 : {
7143 4 : std::string osSQL;
7144 :
7145 2 : char *pszSQL = sqlite3_mprintf(
7146 : "SELECT 1 FROM sqlite_master WHERE lower(name) = lower('%q') "
7147 : "AND type IN ('table', 'view')",
7148 : pszNewLayerName);
7149 2 : const bool bAlreadyExists = SQLGetInteger(GetDB(), pszSQL, nullptr) == 1;
7150 2 : sqlite3_free(pszSQL);
7151 2 : if (bAlreadyExists)
7152 : {
7153 0 : CPLError(CE_Failure, CPLE_AppDefined, "Table %s already exists",
7154 : pszNewLayerName);
7155 0 : return false;
7156 : }
7157 :
7158 : // Temporary remove foreign key checks
7159 : const GPKGTemporaryForeignKeyCheckDisabler
7160 4 : oGPKGTemporaryForeignKeyCheckDisabler(this);
7161 :
7162 2 : if (SoftStartTransaction() != OGRERR_NONE)
7163 : {
7164 0 : return false;
7165 : }
7166 :
7167 2 : pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET table_name = '%q' WHERE "
7168 : "lower(table_name) = lower('%q');",
7169 : pszNewLayerName, pszLayerName);
7170 2 : osSQL = pszSQL;
7171 2 : sqlite3_free(pszSQL);
7172 :
7173 2 : pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' WHERE "
7174 : "lower(identifier) = lower('%q');",
7175 : pszNewLayerName, pszLayerName);
7176 2 : osSQL += pszSQL;
7177 2 : sqlite3_free(pszSQL);
7178 :
7179 : pszSQL =
7180 2 : sqlite3_mprintf("UPDATE gpkg_tile_matrix SET table_name = '%q' WHERE "
7181 : "lower(table_name) = lower('%q');",
7182 : pszNewLayerName, pszLayerName);
7183 2 : osSQL += pszSQL;
7184 2 : sqlite3_free(pszSQL);
7185 :
7186 2 : pszSQL = sqlite3_mprintf(
7187 : "UPDATE gpkg_tile_matrix_set SET table_name = '%q' WHERE "
7188 : "lower(table_name) = lower('%q');",
7189 : pszNewLayerName, pszLayerName);
7190 2 : osSQL += pszSQL;
7191 2 : sqlite3_free(pszSQL);
7192 :
7193 2 : if (HasGriddedCoverageAncillaryTable())
7194 : {
7195 1 : pszSQL = sqlite3_mprintf("UPDATE gpkg_2d_gridded_coverage_ancillary "
7196 : "SET tile_matrix_set_name = '%q' WHERE "
7197 : "lower(tile_matrix_set_name) = lower('%q');",
7198 : pszNewLayerName, pszLayerName);
7199 1 : osSQL += pszSQL;
7200 1 : sqlite3_free(pszSQL);
7201 :
7202 1 : pszSQL = sqlite3_mprintf(
7203 : "UPDATE gpkg_2d_gridded_tile_ancillary SET tpudt_name = '%q' WHERE "
7204 : "lower(tpudt_name) = lower('%q');",
7205 : pszNewLayerName, pszLayerName);
7206 1 : osSQL += pszSQL;
7207 1 : sqlite3_free(pszSQL);
7208 : }
7209 :
7210 2 : if (HasExtensionsTable())
7211 : {
7212 2 : pszSQL = sqlite3_mprintf(
7213 : "UPDATE gpkg_extensions SET table_name = '%q' WHERE "
7214 : "lower(table_name) = lower('%q');",
7215 : pszNewLayerName, pszLayerName);
7216 2 : osSQL += pszSQL;
7217 2 : sqlite3_free(pszSQL);
7218 : }
7219 :
7220 2 : if (HasMetadataTables())
7221 : {
7222 1 : pszSQL = sqlite3_mprintf(
7223 : "UPDATE gpkg_metadata_reference SET table_name = '%q' WHERE "
7224 : "lower(table_name) = lower('%q');",
7225 : pszNewLayerName, pszLayerName);
7226 1 : osSQL += pszSQL;
7227 1 : sqlite3_free(pszSQL);
7228 : }
7229 :
7230 2 : if (HasDataColumnsTable())
7231 : {
7232 0 : pszSQL = sqlite3_mprintf(
7233 : "UPDATE gpkg_data_columns SET table_name = '%q' WHERE "
7234 : "lower(table_name) = lower('%q');",
7235 : pszNewLayerName, pszLayerName);
7236 0 : osSQL += pszSQL;
7237 0 : sqlite3_free(pszSQL);
7238 : }
7239 :
7240 2 : if (HasQGISLayerStyles())
7241 : {
7242 : // Update QGIS styles
7243 : pszSQL =
7244 0 : sqlite3_mprintf("UPDATE layer_styles SET f_table_name = '%q' WHERE "
7245 : "lower(f_table_name) = lower('%q');",
7246 : pszNewLayerName, pszLayerName);
7247 0 : osSQL += pszSQL;
7248 0 : sqlite3_free(pszSQL);
7249 : }
7250 :
7251 : #ifdef ENABLE_GPKG_OGR_CONTENTS
7252 2 : if (m_bHasGPKGOGRContents)
7253 : {
7254 2 : pszSQL = sqlite3_mprintf(
7255 : "UPDATE gpkg_ogr_contents SET table_name = '%q' WHERE "
7256 : "lower(table_name) = lower('%q');",
7257 : pszNewLayerName, pszLayerName);
7258 2 : osSQL += pszSQL;
7259 2 : sqlite3_free(pszSQL);
7260 : }
7261 : #endif
7262 :
7263 2 : if (HasGpkgextRelationsTable())
7264 : {
7265 0 : pszSQL = sqlite3_mprintf(
7266 : "UPDATE gpkgext_relations SET base_table_name = '%q' WHERE "
7267 : "lower(base_table_name) = lower('%q');",
7268 : pszNewLayerName, pszLayerName);
7269 0 : osSQL += pszSQL;
7270 0 : sqlite3_free(pszSQL);
7271 :
7272 0 : pszSQL = sqlite3_mprintf(
7273 : "UPDATE gpkgext_relations SET related_table_name = '%q' WHERE "
7274 : "lower(related_table_name) = lower('%q');",
7275 : pszNewLayerName, pszLayerName);
7276 0 : osSQL += pszSQL;
7277 0 : sqlite3_free(pszSQL);
7278 :
7279 0 : pszSQL = sqlite3_mprintf(
7280 : "UPDATE gpkgext_relations SET mapping_table_name = '%q' WHERE "
7281 : "lower(mapping_table_name) = lower('%q');",
7282 : pszNewLayerName, pszLayerName);
7283 0 : osSQL += pszSQL;
7284 0 : sqlite3_free(pszSQL);
7285 : }
7286 :
7287 : // Drop all triggers for the layer
7288 2 : pszSQL = sqlite3_mprintf("SELECT name FROM sqlite_master WHERE type = "
7289 : "'trigger' AND tbl_name = '%q'",
7290 : pszLayerName);
7291 2 : auto oTriggerResult = SQLQuery(GetDB(), pszSQL);
7292 2 : sqlite3_free(pszSQL);
7293 2 : if (oTriggerResult)
7294 : {
7295 14 : for (int i = 0; i < oTriggerResult->RowCount(); i++)
7296 : {
7297 12 : const char *pszTriggerName = oTriggerResult->GetValue(0, i);
7298 12 : pszSQL = sqlite3_mprintf("DROP TRIGGER IF EXISTS \"%w\";",
7299 : pszTriggerName);
7300 12 : osSQL += pszSQL;
7301 12 : sqlite3_free(pszSQL);
7302 : }
7303 : }
7304 :
7305 2 : pszSQL = sqlite3_mprintf("ALTER TABLE \"%w\" RENAME TO \"%w\";",
7306 : pszLayerName, pszNewLayerName);
7307 2 : osSQL += pszSQL;
7308 2 : sqlite3_free(pszSQL);
7309 :
7310 : // Recreate all zoom/tile triggers
7311 2 : if (oTriggerResult)
7312 : {
7313 2 : osSQL += CreateRasterTriggersSQL(pszNewLayerName);
7314 : }
7315 :
7316 2 : OGRErr eErr = SQLCommand(GetDB(), osSQL.c_str());
7317 :
7318 : // Check foreign key integrity
7319 2 : if (eErr == OGRERR_NONE)
7320 : {
7321 2 : eErr = PragmaCheck("foreign_key_check", "", 0);
7322 : }
7323 :
7324 2 : if (eErr == OGRERR_NONE)
7325 : {
7326 2 : eErr = SoftCommitTransaction();
7327 : }
7328 : else
7329 : {
7330 0 : SoftRollbackTransaction();
7331 : }
7332 :
7333 2 : return eErr == OGRERR_NONE;
7334 : }
7335 :
7336 : /************************************************************************/
7337 : /* TestCapability() */
7338 : /************************************************************************/
7339 :
7340 433 : int GDALGeoPackageDataset::TestCapability(const char *pszCap)
7341 : {
7342 433 : if (EQUAL(pszCap, ODsCCreateLayer) || EQUAL(pszCap, ODsCDeleteLayer) ||
7343 272 : EQUAL(pszCap, "RenameLayer"))
7344 : {
7345 161 : return GetUpdate();
7346 : }
7347 272 : else if (EQUAL(pszCap, ODsCCurveGeometries))
7348 12 : return TRUE;
7349 260 : else if (EQUAL(pszCap, ODsCMeasuredGeometries))
7350 8 : return TRUE;
7351 252 : else if (EQUAL(pszCap, ODsCZGeometries))
7352 8 : return TRUE;
7353 244 : else if (EQUAL(pszCap, ODsCRandomLayerWrite) ||
7354 244 : EQUAL(pszCap, GDsCAddRelationship) ||
7355 244 : EQUAL(pszCap, GDsCDeleteRelationship) ||
7356 244 : EQUAL(pszCap, GDsCUpdateRelationship) ||
7357 244 : EQUAL(pszCap, ODsCAddFieldDomain))
7358 1 : return GetUpdate();
7359 :
7360 243 : return OGRSQLiteBaseDataSource::TestCapability(pszCap);
7361 : }
7362 :
7363 : /************************************************************************/
7364 : /* ResetReadingAllLayers() */
7365 : /************************************************************************/
7366 :
7367 204 : void GDALGeoPackageDataset::ResetReadingAllLayers()
7368 : {
7369 413 : for (auto &poLayer : m_apoLayers)
7370 : {
7371 209 : poLayer->ResetReading();
7372 : }
7373 204 : }
7374 :
7375 : /************************************************************************/
7376 : /* ExecuteSQL() */
7377 : /************************************************************************/
7378 :
7379 : static const char *const apszFuncsWithSideEffects[] = {
7380 : "CreateSpatialIndex",
7381 : "DisableSpatialIndex",
7382 : "HasSpatialIndex",
7383 : "RegisterGeometryExtension",
7384 : };
7385 :
7386 5640 : OGRLayer *GDALGeoPackageDataset::ExecuteSQL(const char *pszSQLCommand,
7387 : OGRGeometry *poSpatialFilter,
7388 : const char *pszDialect)
7389 :
7390 : {
7391 5640 : m_bHasReadMetadataFromStorage = false;
7392 :
7393 5640 : FlushMetadata();
7394 :
7395 5658 : while (*pszSQLCommand != '\0' &&
7396 5658 : isspace(static_cast<unsigned char>(*pszSQLCommand)))
7397 18 : pszSQLCommand++;
7398 :
7399 11280 : CPLString osSQLCommand(pszSQLCommand);
7400 5640 : if (!osSQLCommand.empty() && osSQLCommand.back() == ';')
7401 48 : osSQLCommand.pop_back();
7402 :
7403 5640 : if (pszDialect == nullptr || !EQUAL(pszDialect, "DEBUG"))
7404 : {
7405 : // Some SQL commands will influence the feature count behind our
7406 : // back, so disable it in that case.
7407 : #ifdef ENABLE_GPKG_OGR_CONTENTS
7408 : const bool bInsertOrDelete =
7409 5571 : osSQLCommand.ifind("insert into ") != std::string::npos ||
7410 2450 : osSQLCommand.ifind("insert or replace into ") !=
7411 8021 : std::string::npos ||
7412 2413 : osSQLCommand.ifind("delete from ") != std::string::npos;
7413 : const bool bRollback =
7414 5571 : osSQLCommand.ifind("rollback ") != std::string::npos;
7415 : #endif
7416 :
7417 7389 : for (auto &poLayer : m_apoLayers)
7418 : {
7419 1818 : if (poLayer->SyncToDisk() != OGRERR_NONE)
7420 0 : return nullptr;
7421 : #ifdef ENABLE_GPKG_OGR_CONTENTS
7422 2020 : if (bRollback ||
7423 202 : (bInsertOrDelete &&
7424 202 : osSQLCommand.ifind(poLayer->GetName()) != std::string::npos))
7425 : {
7426 200 : poLayer->DisableFeatureCount();
7427 : }
7428 : #endif
7429 : }
7430 : }
7431 :
7432 5640 : if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 0") ||
7433 5639 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=0") ||
7434 5639 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =0") ||
7435 5639 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 0"))
7436 : {
7437 1 : OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, false);
7438 : }
7439 5639 : else if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 1") ||
7440 5638 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=1") ||
7441 5638 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =1") ||
7442 5638 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 1"))
7443 : {
7444 1 : OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, true);
7445 : }
7446 :
7447 : /* -------------------------------------------------------------------- */
7448 : /* DEBUG "SELECT nolock" command. */
7449 : /* -------------------------------------------------------------------- */
7450 5709 : if (pszDialect != nullptr && EQUAL(pszDialect, "DEBUG") &&
7451 69 : EQUAL(osSQLCommand, "SELECT nolock"))
7452 : {
7453 3 : return new OGRSQLiteSingleFeatureLayer(osSQLCommand, m_bNoLock ? 1 : 0);
7454 : }
7455 :
7456 : /* -------------------------------------------------------------------- */
7457 : /* Special case DELLAYER: command. */
7458 : /* -------------------------------------------------------------------- */
7459 5637 : if (STARTS_WITH_CI(osSQLCommand, "DELLAYER:"))
7460 : {
7461 4 : const char *pszLayerName = osSQLCommand.c_str() + strlen("DELLAYER:");
7462 :
7463 4 : while (*pszLayerName == ' ')
7464 0 : pszLayerName++;
7465 :
7466 4 : if (!DeleteVectorOrRasterLayer(pszLayerName))
7467 : {
7468 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
7469 : pszLayerName);
7470 : }
7471 4 : return nullptr;
7472 : }
7473 :
7474 : /* -------------------------------------------------------------------- */
7475 : /* Special case RECOMPUTE EXTENT ON command. */
7476 : /* -------------------------------------------------------------------- */
7477 5633 : if (STARTS_WITH_CI(osSQLCommand, "RECOMPUTE EXTENT ON "))
7478 : {
7479 : const char *pszLayerName =
7480 4 : osSQLCommand.c_str() + strlen("RECOMPUTE EXTENT ON ");
7481 :
7482 4 : while (*pszLayerName == ' ')
7483 0 : pszLayerName++;
7484 :
7485 4 : int idx = FindLayerIndex(pszLayerName);
7486 4 : if (idx >= 0)
7487 : {
7488 4 : m_apoLayers[idx]->RecomputeExtent();
7489 : }
7490 : else
7491 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
7492 : pszLayerName);
7493 4 : return nullptr;
7494 : }
7495 :
7496 : /* -------------------------------------------------------------------- */
7497 : /* Intercept DROP TABLE */
7498 : /* -------------------------------------------------------------------- */
7499 5629 : if (STARTS_WITH_CI(osSQLCommand, "DROP TABLE "))
7500 : {
7501 9 : const char *pszLayerName = osSQLCommand.c_str() + strlen("DROP TABLE ");
7502 :
7503 9 : while (*pszLayerName == ' ')
7504 0 : pszLayerName++;
7505 :
7506 9 : if (DeleteVectorOrRasterLayer(SQLUnescape(pszLayerName)))
7507 4 : return nullptr;
7508 : }
7509 :
7510 : /* -------------------------------------------------------------------- */
7511 : /* Intercept ALTER TABLE src_table RENAME TO dst_table */
7512 : /* and ALTER TABLE table RENAME COLUMN src_name TO dst_name */
7513 : /* and ALTER TABLE table DROP COLUMN col_name */
7514 : /* */
7515 : /* We do this because SQLite mechanisms can't deal with updating */
7516 : /* literal values in gpkg_ tables that refer to table and column */
7517 : /* names. */
7518 : /* -------------------------------------------------------------------- */
7519 5625 : if (STARTS_WITH_CI(osSQLCommand, "ALTER TABLE "))
7520 : {
7521 9 : char **papszTokens = SQLTokenize(osSQLCommand);
7522 : /* ALTER TABLE src_table RENAME TO dst_table */
7523 16 : if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "RENAME") &&
7524 7 : EQUAL(papszTokens[4], "TO"))
7525 : {
7526 7 : const char *pszSrcTableName = papszTokens[2];
7527 7 : const char *pszDstTableName = papszTokens[5];
7528 7 : if (RenameVectorOrRasterLayer(SQLUnescape(pszSrcTableName),
7529 14 : SQLUnescape(pszDstTableName)))
7530 : {
7531 6 : CSLDestroy(papszTokens);
7532 6 : return nullptr;
7533 : }
7534 : }
7535 : /* ALTER TABLE table RENAME COLUMN src_name TO dst_name */
7536 2 : else if (CSLCount(papszTokens) == 8 &&
7537 1 : EQUAL(papszTokens[3], "RENAME") &&
7538 3 : EQUAL(papszTokens[4], "COLUMN") && EQUAL(papszTokens[6], "TO"))
7539 : {
7540 1 : const char *pszTableName = papszTokens[2];
7541 1 : const char *pszSrcColumn = papszTokens[5];
7542 1 : const char *pszDstColumn = papszTokens[7];
7543 : OGRGeoPackageTableLayer *poLayer =
7544 0 : dynamic_cast<OGRGeoPackageTableLayer *>(
7545 1 : GetLayerByName(SQLUnescape(pszTableName)));
7546 1 : if (poLayer)
7547 : {
7548 2 : int nSrcFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
7549 2 : SQLUnescape(pszSrcColumn));
7550 1 : if (nSrcFieldIdx >= 0)
7551 : {
7552 : // OFTString or any type will do as we just alter the name
7553 : // so it will be ignored.
7554 1 : OGRFieldDefn oFieldDefn(SQLUnescape(pszDstColumn),
7555 1 : OFTString);
7556 1 : poLayer->AlterFieldDefn(nSrcFieldIdx, &oFieldDefn,
7557 : ALTER_NAME_FLAG);
7558 1 : CSLDestroy(papszTokens);
7559 1 : return nullptr;
7560 : }
7561 : }
7562 : }
7563 : /* ALTER TABLE table DROP COLUMN col_name */
7564 2 : else if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "DROP") &&
7565 1 : EQUAL(papszTokens[4], "COLUMN"))
7566 : {
7567 1 : const char *pszTableName = papszTokens[2];
7568 1 : const char *pszColumnName = papszTokens[5];
7569 : OGRGeoPackageTableLayer *poLayer =
7570 0 : dynamic_cast<OGRGeoPackageTableLayer *>(
7571 1 : GetLayerByName(SQLUnescape(pszTableName)));
7572 1 : if (poLayer)
7573 : {
7574 2 : int nFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
7575 2 : SQLUnescape(pszColumnName));
7576 1 : if (nFieldIdx >= 0)
7577 : {
7578 1 : poLayer->DeleteField(nFieldIdx);
7579 1 : CSLDestroy(papszTokens);
7580 1 : return nullptr;
7581 : }
7582 : }
7583 : }
7584 1 : CSLDestroy(papszTokens);
7585 : }
7586 :
7587 5617 : if (ProcessTransactionSQL(osSQLCommand))
7588 : {
7589 253 : return nullptr;
7590 : }
7591 :
7592 5364 : if (EQUAL(osSQLCommand, "VACUUM"))
7593 : {
7594 13 : ResetReadingAllLayers();
7595 : }
7596 5351 : else if (STARTS_WITH_CI(osSQLCommand, "DELETE FROM "))
7597 : {
7598 : // Optimize truncation of a table, especially if it has a spatial
7599 : // index.
7600 21 : const CPLStringList aosTokens(SQLTokenize(osSQLCommand));
7601 21 : if (aosTokens.size() == 3)
7602 : {
7603 15 : const char *pszTableName = aosTokens[2];
7604 : OGRGeoPackageTableLayer *poLayer =
7605 8 : dynamic_cast<OGRGeoPackageTableLayer *>(
7606 23 : GetLayerByName(SQLUnescape(pszTableName)));
7607 15 : if (poLayer)
7608 : {
7609 7 : poLayer->Truncate();
7610 7 : return nullptr;
7611 : }
7612 : }
7613 : }
7614 5330 : else if (pszDialect != nullptr && EQUAL(pszDialect, "INDIRECT_SQLITE"))
7615 1 : return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter, "SQLITE");
7616 5329 : else if (pszDialect != nullptr && !EQUAL(pszDialect, "") &&
7617 66 : !EQUAL(pszDialect, "NATIVE") && !EQUAL(pszDialect, "SQLITE") &&
7618 66 : !EQUAL(pszDialect, "DEBUG"))
7619 0 : return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter,
7620 0 : pszDialect);
7621 :
7622 : /* -------------------------------------------------------------------- */
7623 : /* Prepare statement. */
7624 : /* -------------------------------------------------------------------- */
7625 5356 : sqlite3_stmt *hSQLStmt = nullptr;
7626 :
7627 : /* This will speed-up layer creation */
7628 : /* ORDER BY are costly to evaluate and are not necessary to establish */
7629 : /* the layer definition. */
7630 5356 : bool bUseStatementForGetNextFeature = true;
7631 5356 : bool bEmptyLayer = false;
7632 10712 : CPLString osSQLCommandTruncated(osSQLCommand);
7633 :
7634 17670 : if (osSQLCommand.ifind("SELECT ") == 0 &&
7635 6157 : CPLString(osSQLCommand.substr(1)).ifind("SELECT ") ==
7636 767 : std::string::npos &&
7637 767 : osSQLCommand.ifind(" UNION ") == std::string::npos &&
7638 6924 : osSQLCommand.ifind(" INTERSECT ") == std::string::npos &&
7639 767 : osSQLCommand.ifind(" EXCEPT ") == std::string::npos)
7640 : {
7641 767 : size_t nOrderByPos = osSQLCommand.ifind(" ORDER BY ");
7642 767 : if (nOrderByPos != std::string::npos)
7643 : {
7644 9 : osSQLCommandTruncated.resize(nOrderByPos);
7645 9 : bUseStatementForGetNextFeature = false;
7646 : }
7647 : }
7648 :
7649 5356 : int rc = prepareSql(hDB, osSQLCommandTruncated.c_str(),
7650 5356 : static_cast<int>(osSQLCommandTruncated.size()),
7651 : &hSQLStmt, nullptr);
7652 :
7653 5356 : if (rc != SQLITE_OK)
7654 : {
7655 9 : CPLError(CE_Failure, CPLE_AppDefined,
7656 : "In ExecuteSQL(): sqlite3_prepare_v2(%s): %s",
7657 : osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
7658 :
7659 9 : if (hSQLStmt != nullptr)
7660 : {
7661 0 : sqlite3_finalize(hSQLStmt);
7662 : }
7663 :
7664 9 : return nullptr;
7665 : }
7666 :
7667 : /* -------------------------------------------------------------------- */
7668 : /* Do we get a resultset? */
7669 : /* -------------------------------------------------------------------- */
7670 5347 : rc = sqlite3_step(hSQLStmt);
7671 :
7672 6933 : for (auto &poLayer : m_apoLayers)
7673 : {
7674 1586 : poLayer->RunDeferredDropRTreeTableIfNecessary();
7675 : }
7676 :
7677 5347 : if (rc != SQLITE_ROW)
7678 : {
7679 4627 : if (rc != SQLITE_DONE)
7680 : {
7681 7 : CPLError(CE_Failure, CPLE_AppDefined,
7682 : "In ExecuteSQL(): sqlite3_step(%s):\n %s",
7683 : osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
7684 :
7685 7 : sqlite3_finalize(hSQLStmt);
7686 7 : return nullptr;
7687 : }
7688 :
7689 4620 : if (EQUAL(osSQLCommand, "VACUUM"))
7690 : {
7691 13 : sqlite3_finalize(hSQLStmt);
7692 : /* VACUUM rewrites the DB, so we need to reset the application id */
7693 13 : SetApplicationAndUserVersionId();
7694 13 : return nullptr;
7695 : }
7696 :
7697 4607 : if (!STARTS_WITH_CI(osSQLCommand, "SELECT "))
7698 : {
7699 4482 : sqlite3_finalize(hSQLStmt);
7700 4482 : return nullptr;
7701 : }
7702 :
7703 125 : bUseStatementForGetNextFeature = false;
7704 125 : bEmptyLayer = true;
7705 : }
7706 :
7707 : /* -------------------------------------------------------------------- */
7708 : /* Special case for some functions which must be run */
7709 : /* only once */
7710 : /* -------------------------------------------------------------------- */
7711 845 : if (STARTS_WITH_CI(osSQLCommand, "SELECT "))
7712 : {
7713 3849 : for (unsigned int i = 0; i < sizeof(apszFuncsWithSideEffects) /
7714 : sizeof(apszFuncsWithSideEffects[0]);
7715 : i++)
7716 : {
7717 3105 : if (EQUALN(apszFuncsWithSideEffects[i], osSQLCommand.c_str() + 7,
7718 : strlen(apszFuncsWithSideEffects[i])))
7719 : {
7720 112 : if (sqlite3_column_count(hSQLStmt) == 1 &&
7721 56 : sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
7722 : {
7723 56 : int ret = sqlite3_column_int(hSQLStmt, 0);
7724 :
7725 56 : sqlite3_finalize(hSQLStmt);
7726 :
7727 : return new OGRSQLiteSingleFeatureLayer(
7728 56 : apszFuncsWithSideEffects[i], ret);
7729 : }
7730 : }
7731 : }
7732 : }
7733 45 : else if (STARTS_WITH_CI(osSQLCommand, "PRAGMA "))
7734 : {
7735 63 : if (sqlite3_column_count(hSQLStmt) == 1 &&
7736 18 : sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
7737 : {
7738 15 : int ret = sqlite3_column_int(hSQLStmt, 0);
7739 :
7740 15 : sqlite3_finalize(hSQLStmt);
7741 :
7742 15 : return new OGRSQLiteSingleFeatureLayer(osSQLCommand.c_str() + 7,
7743 15 : ret);
7744 : }
7745 33 : else if (sqlite3_column_count(hSQLStmt) == 1 &&
7746 3 : sqlite3_column_type(hSQLStmt, 0) == SQLITE_TEXT)
7747 : {
7748 : const char *pszRet = reinterpret_cast<const char *>(
7749 3 : sqlite3_column_text(hSQLStmt, 0));
7750 :
7751 : OGRLayer *poRet = new OGRSQLiteSingleFeatureLayer(
7752 3 : osSQLCommand.c_str() + 7, pszRet);
7753 :
7754 3 : sqlite3_finalize(hSQLStmt);
7755 :
7756 3 : return poRet;
7757 : }
7758 : }
7759 :
7760 : /* -------------------------------------------------------------------- */
7761 : /* Create layer. */
7762 : /* -------------------------------------------------------------------- */
7763 :
7764 : auto poLayer = std::make_unique<OGRGeoPackageSelectLayer>(
7765 : this, osSQLCommand, hSQLStmt, bUseStatementForGetNextFeature,
7766 1542 : bEmptyLayer);
7767 :
7768 774 : if (poSpatialFilter != nullptr &&
7769 3 : poLayer->GetLayerDefn()->GetGeomFieldCount() > 0)
7770 3 : poLayer->SetSpatialFilter(0, poSpatialFilter);
7771 :
7772 771 : return poLayer.release();
7773 : }
7774 :
7775 : /************************************************************************/
7776 : /* ReleaseResultSet() */
7777 : /************************************************************************/
7778 :
7779 801 : void GDALGeoPackageDataset::ReleaseResultSet(OGRLayer *poLayer)
7780 :
7781 : {
7782 801 : delete poLayer;
7783 801 : }
7784 :
7785 : /************************************************************************/
7786 : /* HasExtensionsTable() */
7787 : /************************************************************************/
7788 :
7789 6363 : bool GDALGeoPackageDataset::HasExtensionsTable()
7790 : {
7791 6363 : return SQLGetInteger(
7792 : hDB,
7793 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_extensions' "
7794 : "AND type IN ('table', 'view')",
7795 6363 : nullptr) == 1;
7796 : }
7797 :
7798 : /************************************************************************/
7799 : /* CheckUnknownExtensions() */
7800 : /************************************************************************/
7801 :
7802 1443 : void GDALGeoPackageDataset::CheckUnknownExtensions(bool bCheckRasterTable)
7803 : {
7804 1443 : if (!HasExtensionsTable())
7805 195 : return;
7806 :
7807 1248 : char *pszSQL = nullptr;
7808 1248 : if (!bCheckRasterTable)
7809 1039 : pszSQL = sqlite3_mprintf(
7810 : "SELECT extension_name, definition, scope FROM gpkg_extensions "
7811 : "WHERE (table_name IS NULL "
7812 : "AND extension_name IS NOT NULL "
7813 : "AND definition IS NOT NULL "
7814 : "AND scope IS NOT NULL "
7815 : "AND extension_name NOT IN ("
7816 : "'gdal_aspatial', "
7817 : "'gpkg_elevation_tiles', " // Old name before GPKG 1.2 approval
7818 : "'2d_gridded_coverage', " // Old name after GPKG 1.2 and before OGC
7819 : // 17-066r1 finalization
7820 : "'gpkg_2d_gridded_coverage', " // Name in OGC 17-066r1 final
7821 : "'gpkg_metadata', "
7822 : "'gpkg_schema', "
7823 : "'gpkg_crs_wkt', "
7824 : "'gpkg_crs_wkt_1_1', "
7825 : "'related_tables', 'gpkg_related_tables')) "
7826 : #ifdef WORKAROUND_SQLITE3_BUGS
7827 : "OR 0 "
7828 : #endif
7829 : "LIMIT 1000");
7830 : else
7831 209 : pszSQL = sqlite3_mprintf(
7832 : "SELECT extension_name, definition, scope FROM gpkg_extensions "
7833 : "WHERE (lower(table_name) = lower('%q') "
7834 : "AND extension_name IS NOT NULL "
7835 : "AND definition IS NOT NULL "
7836 : "AND scope IS NOT NULL "
7837 : "AND extension_name NOT IN ("
7838 : "'gpkg_elevation_tiles', " // Old name before GPKG 1.2 approval
7839 : "'2d_gridded_coverage', " // Old name after GPKG 1.2 and before OGC
7840 : // 17-066r1 finalization
7841 : "'gpkg_2d_gridded_coverage', " // Name in OGC 17-066r1 final
7842 : "'gpkg_metadata', "
7843 : "'gpkg_schema', "
7844 : "'gpkg_crs_wkt', "
7845 : "'gpkg_crs_wkt_1_1', "
7846 : "'related_tables', 'gpkg_related_tables')) "
7847 : #ifdef WORKAROUND_SQLITE3_BUGS
7848 : "OR 0 "
7849 : #endif
7850 : "LIMIT 1000",
7851 : m_osRasterTable.c_str());
7852 :
7853 2496 : auto oResultTable = SQLQuery(GetDB(), pszSQL);
7854 1248 : sqlite3_free(pszSQL);
7855 1248 : if (oResultTable && oResultTable->RowCount() > 0)
7856 : {
7857 44 : for (int i = 0; i < oResultTable->RowCount(); i++)
7858 : {
7859 22 : const char *pszExtName = oResultTable->GetValue(0, i);
7860 22 : const char *pszDefinition = oResultTable->GetValue(1, i);
7861 22 : const char *pszScope = oResultTable->GetValue(2, i);
7862 22 : if (pszExtName == nullptr || pszDefinition == nullptr ||
7863 : pszScope == nullptr)
7864 : {
7865 0 : continue;
7866 : }
7867 :
7868 22 : if (EQUAL(pszExtName, "gpkg_webp"))
7869 : {
7870 16 : if (GDALGetDriverByName("WEBP") == nullptr)
7871 : {
7872 1 : CPLError(
7873 : CE_Warning, CPLE_AppDefined,
7874 : "Table %s contains WEBP tiles, but GDAL configured "
7875 : "without WEBP support. Data will be missing",
7876 : m_osRasterTable.c_str());
7877 : }
7878 16 : m_eTF = GPKG_TF_WEBP;
7879 16 : continue;
7880 : }
7881 6 : if (EQUAL(pszExtName, "gpkg_zoom_other"))
7882 : {
7883 2 : m_bZoomOther = true;
7884 2 : continue;
7885 : }
7886 :
7887 4 : if (GetUpdate() && EQUAL(pszScope, "write-only"))
7888 : {
7889 1 : CPLError(
7890 : CE_Warning, CPLE_AppDefined,
7891 : "Database relies on the '%s' (%s) extension that should "
7892 : "be implemented for safe write-support, but is not "
7893 : "currently. "
7894 : "Update of that database are strongly discouraged to avoid "
7895 : "corruption.",
7896 : pszExtName, pszDefinition);
7897 : }
7898 3 : else if (GetUpdate() && EQUAL(pszScope, "read-write"))
7899 : {
7900 1 : CPLError(
7901 : CE_Warning, CPLE_AppDefined,
7902 : "Database relies on the '%s' (%s) extension that should "
7903 : "be implemented in order to read/write it safely, but is "
7904 : "not currently. "
7905 : "Some data may be missing while reading that database, and "
7906 : "updates are strongly discouraged.",
7907 : pszExtName, pszDefinition);
7908 : }
7909 2 : else if (EQUAL(pszScope, "read-write") &&
7910 : // None of the NGA extensions at
7911 : // http://ngageoint.github.io/GeoPackage/docs/extensions/
7912 : // affect read-only scenarios
7913 1 : !STARTS_WITH(pszExtName, "nga_"))
7914 : {
7915 1 : CPLError(
7916 : CE_Warning, CPLE_AppDefined,
7917 : "Database relies on the '%s' (%s) extension that should "
7918 : "be implemented in order to read it safely, but is not "
7919 : "currently. "
7920 : "Some data may be missing while reading that database.",
7921 : pszExtName, pszDefinition);
7922 : }
7923 : }
7924 : }
7925 : }
7926 :
7927 : /************************************************************************/
7928 : /* HasGDALAspatialExtension() */
7929 : /************************************************************************/
7930 :
7931 986 : bool GDALGeoPackageDataset::HasGDALAspatialExtension()
7932 : {
7933 986 : if (!HasExtensionsTable())
7934 88 : return false;
7935 :
7936 : auto oResultTable = SQLQuery(hDB, "SELECT * FROM gpkg_extensions "
7937 : "WHERE (extension_name = 'gdal_aspatial' "
7938 : "AND table_name IS NULL "
7939 : "AND column_name IS NULL)"
7940 : #ifdef WORKAROUND_SQLITE3_BUGS
7941 : " OR 0"
7942 : #endif
7943 898 : );
7944 898 : bool bHasExtension = (oResultTable && oResultTable->RowCount() == 1);
7945 898 : return bHasExtension;
7946 : }
7947 :
7948 : std::string
7949 189 : GDALGeoPackageDataset::CreateRasterTriggersSQL(const std::string &osTableName)
7950 : {
7951 : char *pszSQL;
7952 189 : std::string osSQL;
7953 : /* From D.5. sample_tile_pyramid Table 43. tiles table Trigger
7954 : * Definition SQL */
7955 189 : pszSQL = sqlite3_mprintf(
7956 : "CREATE TRIGGER \"%w_zoom_insert\" "
7957 : "BEFORE INSERT ON \"%w\" "
7958 : "FOR EACH ROW BEGIN "
7959 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
7960 : "constraint: zoom_level not specified for table in "
7961 : "gpkg_tile_matrix') "
7962 : "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
7963 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
7964 : "END; "
7965 : "CREATE TRIGGER \"%w_zoom_update\" "
7966 : "BEFORE UPDATE OF zoom_level ON \"%w\" "
7967 : "FOR EACH ROW BEGIN "
7968 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
7969 : "constraint: zoom_level not specified for table in "
7970 : "gpkg_tile_matrix') "
7971 : "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
7972 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
7973 : "END; "
7974 : "CREATE TRIGGER \"%w_tile_column_insert\" "
7975 : "BEFORE INSERT ON \"%w\" "
7976 : "FOR EACH ROW BEGIN "
7977 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
7978 : "constraint: tile_column cannot be < 0') "
7979 : "WHERE (NEW.tile_column < 0) ; "
7980 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
7981 : "constraint: tile_column must by < matrix_width specified for "
7982 : "table and zoom level in gpkg_tile_matrix') "
7983 : "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
7984 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
7985 : "zoom_level = NEW.zoom_level)); "
7986 : "END; "
7987 : "CREATE TRIGGER \"%w_tile_column_update\" "
7988 : "BEFORE UPDATE OF tile_column ON \"%w\" "
7989 : "FOR EACH ROW BEGIN "
7990 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
7991 : "constraint: tile_column cannot be < 0') "
7992 : "WHERE (NEW.tile_column < 0) ; "
7993 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
7994 : "constraint: tile_column must by < matrix_width specified for "
7995 : "table and zoom level in gpkg_tile_matrix') "
7996 : "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
7997 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
7998 : "zoom_level = NEW.zoom_level)); "
7999 : "END; "
8000 : "CREATE TRIGGER \"%w_tile_row_insert\" "
8001 : "BEFORE INSERT ON \"%w\" "
8002 : "FOR EACH ROW BEGIN "
8003 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
8004 : "constraint: tile_row cannot be < 0') "
8005 : "WHERE (NEW.tile_row < 0) ; "
8006 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
8007 : "constraint: tile_row must by < matrix_height specified for "
8008 : "table and zoom level in gpkg_tile_matrix') "
8009 : "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
8010 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8011 : "zoom_level = NEW.zoom_level)); "
8012 : "END; "
8013 : "CREATE TRIGGER \"%w_tile_row_update\" "
8014 : "BEFORE UPDATE OF tile_row ON \"%w\" "
8015 : "FOR EACH ROW BEGIN "
8016 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8017 : "constraint: tile_row cannot be < 0') "
8018 : "WHERE (NEW.tile_row < 0) ; "
8019 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8020 : "constraint: tile_row must by < matrix_height specified for "
8021 : "table and zoom level in gpkg_tile_matrix') "
8022 : "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
8023 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8024 : "zoom_level = NEW.zoom_level)); "
8025 : "END; ",
8026 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8027 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8028 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8029 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8030 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8031 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
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());
8036 189 : osSQL = pszSQL;
8037 189 : sqlite3_free(pszSQL);
8038 189 : return osSQL;
8039 : }
8040 :
8041 : /************************************************************************/
8042 : /* CreateExtensionsTableIfNecessary() */
8043 : /************************************************************************/
8044 :
8045 1135 : OGRErr GDALGeoPackageDataset::CreateExtensionsTableIfNecessary()
8046 : {
8047 : /* Check if the table gpkg_extensions exists */
8048 1135 : if (HasExtensionsTable())
8049 403 : return OGRERR_NONE;
8050 :
8051 : /* Requirement 79 : Every extension of a GeoPackage SHALL be registered */
8052 : /* in a corresponding row in the gpkg_extensions table. The absence of a */
8053 : /* gpkg_extensions table or the absence of rows in gpkg_extensions table */
8054 : /* SHALL both indicate the absence of extensions to a GeoPackage. */
8055 732 : const char *pszCreateGpkgExtensions =
8056 : "CREATE TABLE gpkg_extensions ("
8057 : "table_name TEXT,"
8058 : "column_name TEXT,"
8059 : "extension_name TEXT NOT NULL,"
8060 : "definition TEXT NOT NULL,"
8061 : "scope TEXT NOT NULL,"
8062 : "CONSTRAINT ge_tce UNIQUE (table_name, column_name, extension_name)"
8063 : ")";
8064 :
8065 732 : return SQLCommand(hDB, pszCreateGpkgExtensions);
8066 : }
8067 :
8068 : /************************************************************************/
8069 : /* OGR_GPKG_Intersects_Spatial_Filter() */
8070 : /************************************************************************/
8071 :
8072 23135 : void OGR_GPKG_Intersects_Spatial_Filter(sqlite3_context *pContext, int argc,
8073 : sqlite3_value **argv)
8074 : {
8075 23135 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8076 : {
8077 0 : sqlite3_result_int(pContext, 0);
8078 23125 : return;
8079 : }
8080 :
8081 : auto poLayer =
8082 23135 : static_cast<OGRGeoPackageTableLayer *>(sqlite3_user_data(pContext));
8083 :
8084 23135 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8085 : const GByte *pabyBLOB =
8086 23135 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8087 :
8088 : GPkgHeader sHeader;
8089 46270 : if (poLayer->m_bFilterIsEnvelope &&
8090 23135 : OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false, 0))
8091 : {
8092 23135 : if (sHeader.bExtentHasXY)
8093 : {
8094 95 : OGREnvelope sEnvelope;
8095 95 : sEnvelope.MinX = sHeader.MinX;
8096 95 : sEnvelope.MinY = sHeader.MinY;
8097 95 : sEnvelope.MaxX = sHeader.MaxX;
8098 95 : sEnvelope.MaxY = sHeader.MaxY;
8099 95 : if (poLayer->m_sFilterEnvelope.Contains(sEnvelope))
8100 : {
8101 31 : sqlite3_result_int(pContext, 1);
8102 31 : return;
8103 : }
8104 : }
8105 :
8106 : // Check if at least one point falls into the layer filter envelope
8107 : // nHeaderLen is > 0 for GeoPackage geometries
8108 46208 : if (sHeader.nHeaderLen > 0 &&
8109 23104 : OGRWKBIntersectsPessimistic(pabyBLOB + sHeader.nHeaderLen,
8110 23104 : nBLOBLen - sHeader.nHeaderLen,
8111 23104 : poLayer->m_sFilterEnvelope))
8112 : {
8113 23094 : sqlite3_result_int(pContext, 1);
8114 23094 : return;
8115 : }
8116 : }
8117 :
8118 : auto poGeom = std::unique_ptr<OGRGeometry>(
8119 10 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8120 10 : if (poGeom == nullptr)
8121 : {
8122 : // Try also spatialite geometry blobs
8123 0 : OGRGeometry *poGeomSpatialite = nullptr;
8124 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8125 0 : &poGeomSpatialite) != OGRERR_NONE)
8126 : {
8127 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8128 0 : sqlite3_result_int(pContext, 0);
8129 0 : return;
8130 : }
8131 0 : poGeom.reset(poGeomSpatialite);
8132 : }
8133 :
8134 10 : sqlite3_result_int(pContext, poLayer->FilterGeometry(poGeom.get()));
8135 : }
8136 :
8137 : /************************************************************************/
8138 : /* OGRGeoPackageSTMinX() */
8139 : /************************************************************************/
8140 :
8141 243198 : static void OGRGeoPackageSTMinX(sqlite3_context *pContext, int argc,
8142 : sqlite3_value **argv)
8143 : {
8144 : GPkgHeader sHeader;
8145 243198 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8146 : {
8147 3 : sqlite3_result_null(pContext);
8148 3 : return;
8149 : }
8150 243195 : sqlite3_result_double(pContext, sHeader.MinX);
8151 : }
8152 :
8153 : /************************************************************************/
8154 : /* OGRGeoPackageSTMinY() */
8155 : /************************************************************************/
8156 :
8157 243196 : static void OGRGeoPackageSTMinY(sqlite3_context *pContext, int argc,
8158 : sqlite3_value **argv)
8159 : {
8160 : GPkgHeader sHeader;
8161 243196 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8162 : {
8163 1 : sqlite3_result_null(pContext);
8164 1 : return;
8165 : }
8166 243195 : sqlite3_result_double(pContext, sHeader.MinY);
8167 : }
8168 :
8169 : /************************************************************************/
8170 : /* OGRGeoPackageSTMaxX() */
8171 : /************************************************************************/
8172 :
8173 243196 : static void OGRGeoPackageSTMaxX(sqlite3_context *pContext, int argc,
8174 : sqlite3_value **argv)
8175 : {
8176 : GPkgHeader sHeader;
8177 243196 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8178 : {
8179 1 : sqlite3_result_null(pContext);
8180 1 : return;
8181 : }
8182 243195 : sqlite3_result_double(pContext, sHeader.MaxX);
8183 : }
8184 :
8185 : /************************************************************************/
8186 : /* OGRGeoPackageSTMaxY() */
8187 : /************************************************************************/
8188 :
8189 243196 : static void OGRGeoPackageSTMaxY(sqlite3_context *pContext, int argc,
8190 : sqlite3_value **argv)
8191 : {
8192 : GPkgHeader sHeader;
8193 243196 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8194 : {
8195 1 : sqlite3_result_null(pContext);
8196 1 : return;
8197 : }
8198 243195 : sqlite3_result_double(pContext, sHeader.MaxY);
8199 : }
8200 :
8201 : /************************************************************************/
8202 : /* OGRGeoPackageSTIsEmpty() */
8203 : /************************************************************************/
8204 :
8205 244599 : static void OGRGeoPackageSTIsEmpty(sqlite3_context *pContext, int argc,
8206 : sqlite3_value **argv)
8207 : {
8208 : GPkgHeader sHeader;
8209 244599 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8210 : {
8211 2 : sqlite3_result_null(pContext);
8212 2 : return;
8213 : }
8214 244597 : sqlite3_result_int(pContext, sHeader.bEmpty);
8215 : }
8216 :
8217 : /************************************************************************/
8218 : /* OGRGeoPackageSTGeometryType() */
8219 : /************************************************************************/
8220 :
8221 7 : static void OGRGeoPackageSTGeometryType(sqlite3_context *pContext, int /*argc*/,
8222 : sqlite3_value **argv)
8223 : {
8224 : GPkgHeader sHeader;
8225 :
8226 7 : int nBLOBLen = sqlite3_value_bytes(argv[0]);
8227 : const GByte *pabyBLOB =
8228 7 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8229 : OGRwkbGeometryType eGeometryType;
8230 :
8231 13 : if (nBLOBLen < 8 ||
8232 6 : GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
8233 : {
8234 2 : if (OGRSQLiteGetSpatialiteGeometryHeader(
8235 : pabyBLOB, nBLOBLen, nullptr, &eGeometryType, nullptr, nullptr,
8236 2 : nullptr, nullptr, nullptr) == OGRERR_NONE)
8237 : {
8238 1 : sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
8239 : SQLITE_TRANSIENT);
8240 4 : return;
8241 : }
8242 : else
8243 : {
8244 1 : sqlite3_result_null(pContext);
8245 1 : return;
8246 : }
8247 : }
8248 :
8249 5 : if (static_cast<size_t>(nBLOBLen) < sHeader.nHeaderLen + 5)
8250 : {
8251 2 : sqlite3_result_null(pContext);
8252 2 : return;
8253 : }
8254 :
8255 3 : OGRErr err = OGRReadWKBGeometryType(pabyBLOB + sHeader.nHeaderLen,
8256 : wkbVariantIso, &eGeometryType);
8257 3 : if (err != OGRERR_NONE)
8258 1 : sqlite3_result_null(pContext);
8259 : else
8260 2 : sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
8261 : SQLITE_TRANSIENT);
8262 : }
8263 :
8264 : /************************************************************************/
8265 : /* OGRGeoPackageSTEnvelopesIntersects() */
8266 : /************************************************************************/
8267 :
8268 118 : static void OGRGeoPackageSTEnvelopesIntersects(sqlite3_context *pContext,
8269 : int argc, sqlite3_value **argv)
8270 : {
8271 : GPkgHeader sHeader;
8272 118 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8273 : {
8274 2 : sqlite3_result_int(pContext, FALSE);
8275 107 : return;
8276 : }
8277 116 : const double dfMinX = sqlite3_value_double(argv[1]);
8278 116 : if (sHeader.MaxX < dfMinX)
8279 : {
8280 93 : sqlite3_result_int(pContext, FALSE);
8281 93 : return;
8282 : }
8283 23 : const double dfMinY = sqlite3_value_double(argv[2]);
8284 23 : if (sHeader.MaxY < dfMinY)
8285 : {
8286 11 : sqlite3_result_int(pContext, FALSE);
8287 11 : return;
8288 : }
8289 12 : const double dfMaxX = sqlite3_value_double(argv[3]);
8290 12 : if (sHeader.MinX > dfMaxX)
8291 : {
8292 1 : sqlite3_result_int(pContext, FALSE);
8293 1 : return;
8294 : }
8295 11 : const double dfMaxY = sqlite3_value_double(argv[4]);
8296 11 : sqlite3_result_int(pContext, sHeader.MinY <= dfMaxY);
8297 : }
8298 :
8299 : /************************************************************************/
8300 : /* OGRGeoPackageSTEnvelopesIntersectsTwoParams() */
8301 : /************************************************************************/
8302 :
8303 : static void
8304 3 : OGRGeoPackageSTEnvelopesIntersectsTwoParams(sqlite3_context *pContext, int argc,
8305 : sqlite3_value **argv)
8306 : {
8307 : GPkgHeader sHeader;
8308 3 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false, 0))
8309 : {
8310 0 : sqlite3_result_int(pContext, FALSE);
8311 2 : return;
8312 : }
8313 : GPkgHeader sHeader2;
8314 3 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader2, true, false,
8315 : 1))
8316 : {
8317 0 : sqlite3_result_int(pContext, FALSE);
8318 0 : return;
8319 : }
8320 3 : if (sHeader.MaxX < sHeader2.MinX)
8321 : {
8322 1 : sqlite3_result_int(pContext, FALSE);
8323 1 : return;
8324 : }
8325 2 : if (sHeader.MaxY < sHeader2.MinY)
8326 : {
8327 0 : sqlite3_result_int(pContext, FALSE);
8328 0 : return;
8329 : }
8330 2 : if (sHeader.MinX > sHeader2.MaxX)
8331 : {
8332 1 : sqlite3_result_int(pContext, FALSE);
8333 1 : return;
8334 : }
8335 1 : sqlite3_result_int(pContext, sHeader.MinY <= sHeader2.MaxY);
8336 : }
8337 :
8338 : /************************************************************************/
8339 : /* OGRGeoPackageGPKGIsAssignable() */
8340 : /************************************************************************/
8341 :
8342 8 : static void OGRGeoPackageGPKGIsAssignable(sqlite3_context *pContext,
8343 : int /*argc*/, sqlite3_value **argv)
8344 : {
8345 15 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8346 7 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
8347 : {
8348 2 : sqlite3_result_int(pContext, 0);
8349 2 : return;
8350 : }
8351 :
8352 : const char *pszExpected =
8353 6 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8354 : const char *pszActual =
8355 6 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8356 6 : int bIsAssignable = OGR_GT_IsSubClassOf(OGRFromOGCGeomType(pszActual),
8357 : OGRFromOGCGeomType(pszExpected));
8358 6 : sqlite3_result_int(pContext, bIsAssignable);
8359 : }
8360 :
8361 : /************************************************************************/
8362 : /* OGRGeoPackageSTSRID() */
8363 : /************************************************************************/
8364 :
8365 12 : static void OGRGeoPackageSTSRID(sqlite3_context *pContext, int argc,
8366 : sqlite3_value **argv)
8367 : {
8368 : GPkgHeader sHeader;
8369 12 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8370 : {
8371 2 : sqlite3_result_null(pContext);
8372 2 : return;
8373 : }
8374 10 : sqlite3_result_int(pContext, sHeader.iSrsId);
8375 : }
8376 :
8377 : /************************************************************************/
8378 : /* OGRGeoPackageSetSRID() */
8379 : /************************************************************************/
8380 :
8381 28 : static void OGRGeoPackageSetSRID(sqlite3_context *pContext, int /* argc */,
8382 : sqlite3_value **argv)
8383 : {
8384 28 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8385 : {
8386 1 : sqlite3_result_null(pContext);
8387 1 : return;
8388 : }
8389 27 : const int nDestSRID = sqlite3_value_int(argv[1]);
8390 : GPkgHeader sHeader;
8391 27 : int nBLOBLen = sqlite3_value_bytes(argv[0]);
8392 : const GByte *pabyBLOB =
8393 27 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8394 :
8395 54 : if (nBLOBLen < 8 ||
8396 27 : GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
8397 : {
8398 : // Try also spatialite geometry blobs
8399 0 : OGRGeometry *poGeom = nullptr;
8400 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeom) !=
8401 : OGRERR_NONE)
8402 : {
8403 0 : sqlite3_result_null(pContext);
8404 0 : return;
8405 : }
8406 0 : size_t nBLOBDestLen = 0;
8407 : GByte *pabyDestBLOB =
8408 0 : GPkgGeometryFromOGR(poGeom, nDestSRID, nullptr, &nBLOBDestLen);
8409 0 : if (!pabyDestBLOB)
8410 : {
8411 0 : sqlite3_result_null(pContext);
8412 0 : return;
8413 : }
8414 0 : sqlite3_result_blob(pContext, pabyDestBLOB,
8415 : static_cast<int>(nBLOBDestLen), VSIFree);
8416 0 : return;
8417 : }
8418 :
8419 27 : GByte *pabyDestBLOB = static_cast<GByte *>(CPLMalloc(nBLOBLen));
8420 27 : memcpy(pabyDestBLOB, pabyBLOB, nBLOBLen);
8421 27 : int32_t nSRIDToSerialize = nDestSRID;
8422 27 : if (OGR_SWAP(sHeader.eByteOrder))
8423 0 : nSRIDToSerialize = CPL_SWAP32(nSRIDToSerialize);
8424 27 : memcpy(pabyDestBLOB + 4, &nSRIDToSerialize, 4);
8425 27 : sqlite3_result_blob(pContext, pabyDestBLOB, nBLOBLen, VSIFree);
8426 : }
8427 :
8428 : /************************************************************************/
8429 : /* OGRGeoPackageSTMakeValid() */
8430 : /************************************************************************/
8431 :
8432 3 : static void OGRGeoPackageSTMakeValid(sqlite3_context *pContext, int argc,
8433 : sqlite3_value **argv)
8434 : {
8435 3 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8436 : {
8437 2 : sqlite3_result_null(pContext);
8438 2 : return;
8439 : }
8440 1 : int nBLOBLen = sqlite3_value_bytes(argv[0]);
8441 : const GByte *pabyBLOB =
8442 1 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8443 :
8444 : GPkgHeader sHeader;
8445 1 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8446 : {
8447 0 : sqlite3_result_null(pContext);
8448 0 : return;
8449 : }
8450 :
8451 : auto poGeom = std::unique_ptr<OGRGeometry>(
8452 1 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8453 1 : if (poGeom == nullptr)
8454 : {
8455 : // Try also spatialite geometry blobs
8456 0 : OGRGeometry *poGeomPtr = nullptr;
8457 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
8458 : OGRERR_NONE)
8459 : {
8460 0 : sqlite3_result_null(pContext);
8461 0 : return;
8462 : }
8463 0 : poGeom.reset(poGeomPtr);
8464 : }
8465 1 : auto poValid = std::unique_ptr<OGRGeometry>(poGeom->MakeValid());
8466 1 : if (poValid == nullptr)
8467 : {
8468 0 : sqlite3_result_null(pContext);
8469 0 : return;
8470 : }
8471 :
8472 1 : size_t nBLOBDestLen = 0;
8473 1 : GByte *pabyDestBLOB = GPkgGeometryFromOGR(poValid.get(), sHeader.iSrsId,
8474 : nullptr, &nBLOBDestLen);
8475 1 : if (!pabyDestBLOB)
8476 : {
8477 0 : sqlite3_result_null(pContext);
8478 0 : return;
8479 : }
8480 1 : sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
8481 : VSIFree);
8482 : }
8483 :
8484 : /************************************************************************/
8485 : /* OGRGeoPackageSTArea() */
8486 : /************************************************************************/
8487 :
8488 19 : static void OGRGeoPackageSTArea(sqlite3_context *pContext, int /*argc*/,
8489 : sqlite3_value **argv)
8490 : {
8491 19 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8492 : {
8493 1 : sqlite3_result_null(pContext);
8494 15 : return;
8495 : }
8496 18 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8497 : const GByte *pabyBLOB =
8498 18 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8499 :
8500 : GPkgHeader sHeader;
8501 0 : std::unique_ptr<OGRGeometry> poGeom;
8502 18 : if (GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) == OGRERR_NONE)
8503 : {
8504 16 : if (sHeader.bEmpty)
8505 : {
8506 3 : sqlite3_result_double(pContext, 0);
8507 13 : return;
8508 : }
8509 13 : const GByte *pabyWkb = pabyBLOB + sHeader.nHeaderLen;
8510 13 : size_t nWKBSize = nBLOBLen - sHeader.nHeaderLen;
8511 : bool bNeedSwap;
8512 : uint32_t nType;
8513 13 : if (OGRWKBGetGeomType(pabyWkb, nWKBSize, bNeedSwap, nType))
8514 : {
8515 13 : if (nType == wkbPolygon || nType == wkbPolygon25D ||
8516 11 : nType == wkbPolygon + 1000 || // wkbPolygonZ
8517 10 : nType == wkbPolygonM || nType == wkbPolygonZM)
8518 : {
8519 : double dfArea;
8520 5 : if (OGRWKBPolygonGetArea(pabyWkb, nWKBSize, dfArea))
8521 : {
8522 5 : sqlite3_result_double(pContext, dfArea);
8523 5 : return;
8524 0 : }
8525 : }
8526 8 : else if (nType == wkbMultiPolygon || nType == wkbMultiPolygon25D ||
8527 6 : nType == wkbMultiPolygon + 1000 || // wkbMultiPolygonZ
8528 5 : nType == wkbMultiPolygonM || nType == wkbMultiPolygonZM)
8529 : {
8530 : double dfArea;
8531 5 : if (OGRWKBMultiPolygonGetArea(pabyWkb, nWKBSize, dfArea))
8532 : {
8533 5 : sqlite3_result_double(pContext, dfArea);
8534 5 : return;
8535 : }
8536 : }
8537 : }
8538 :
8539 : // For curve geometries, fallback to OGRGeometry methods
8540 3 : poGeom.reset(GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8541 : }
8542 : else
8543 : {
8544 : // Try also spatialite geometry blobs
8545 2 : OGRGeometry *poGeomPtr = nullptr;
8546 2 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
8547 : OGRERR_NONE)
8548 : {
8549 1 : sqlite3_result_null(pContext);
8550 1 : return;
8551 : }
8552 1 : poGeom.reset(poGeomPtr);
8553 : }
8554 4 : auto poSurface = dynamic_cast<OGRSurface *>(poGeom.get());
8555 4 : if (poSurface == nullptr)
8556 : {
8557 2 : auto poMultiSurface = dynamic_cast<OGRMultiSurface *>(poGeom.get());
8558 2 : if (poMultiSurface == nullptr)
8559 : {
8560 1 : sqlite3_result_double(pContext, 0);
8561 : }
8562 : else
8563 : {
8564 1 : sqlite3_result_double(pContext, poMultiSurface->get_Area());
8565 : }
8566 : }
8567 : else
8568 : {
8569 2 : sqlite3_result_double(pContext, poSurface->get_Area());
8570 : }
8571 : }
8572 :
8573 : /************************************************************************/
8574 : /* OGRGeoPackageGeodesicArea() */
8575 : /************************************************************************/
8576 :
8577 5 : static void OGRGeoPackageGeodesicArea(sqlite3_context *pContext, int argc,
8578 : sqlite3_value **argv)
8579 : {
8580 5 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8581 : {
8582 1 : sqlite3_result_null(pContext);
8583 3 : return;
8584 : }
8585 4 : if (sqlite3_value_int(argv[1]) != 1)
8586 : {
8587 2 : CPLError(CE_Warning, CPLE_NotSupported,
8588 : "ST_Area(geom, use_ellipsoid) is only supported for "
8589 : "use_ellipsoid = 1");
8590 : }
8591 :
8592 4 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8593 : const GByte *pabyBLOB =
8594 4 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8595 : GPkgHeader sHeader;
8596 4 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8597 : {
8598 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8599 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8600 1 : return;
8601 : }
8602 :
8603 : GDALGeoPackageDataset *poDS =
8604 3 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8605 :
8606 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSrcSRS(
8607 3 : poDS->GetSpatialRef(sHeader.iSrsId, true));
8608 3 : if (poSrcSRS == nullptr)
8609 : {
8610 1 : CPLError(CE_Failure, CPLE_AppDefined,
8611 : "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8612 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8613 1 : return;
8614 : }
8615 :
8616 : auto poGeom = std::unique_ptr<OGRGeometry>(
8617 2 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8618 2 : if (poGeom == nullptr)
8619 : {
8620 : // Try also spatialite geometry blobs
8621 0 : OGRGeometry *poGeomSpatialite = nullptr;
8622 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8623 0 : &poGeomSpatialite) != OGRERR_NONE)
8624 : {
8625 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8626 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8627 0 : return;
8628 : }
8629 0 : poGeom.reset(poGeomSpatialite);
8630 : }
8631 :
8632 2 : poGeom->assignSpatialReference(poSrcSRS.get());
8633 2 : sqlite3_result_double(
8634 : pContext, OGR_G_GeodesicArea(OGRGeometry::ToHandle(poGeom.get())));
8635 : }
8636 :
8637 : /************************************************************************/
8638 : /* OGRGeoPackageLengthOrGeodesicLength() */
8639 : /************************************************************************/
8640 :
8641 8 : static void OGRGeoPackageLengthOrGeodesicLength(sqlite3_context *pContext,
8642 : int argc, sqlite3_value **argv)
8643 : {
8644 8 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8645 : {
8646 2 : sqlite3_result_null(pContext);
8647 5 : return;
8648 : }
8649 6 : if (argc == 2 && sqlite3_value_int(argv[1]) != 1)
8650 : {
8651 2 : CPLError(CE_Warning, CPLE_NotSupported,
8652 : "ST_Length(geom, use_ellipsoid) is only supported for "
8653 : "use_ellipsoid = 1");
8654 : }
8655 :
8656 6 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8657 : const GByte *pabyBLOB =
8658 6 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8659 : GPkgHeader sHeader;
8660 6 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8661 : {
8662 2 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8663 2 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8664 2 : return;
8665 : }
8666 :
8667 : GDALGeoPackageDataset *poDS =
8668 4 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8669 :
8670 0 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSrcSRS;
8671 4 : if (argc == 2)
8672 : {
8673 3 : poSrcSRS = poDS->GetSpatialRef(sHeader.iSrsId, true);
8674 3 : if (!poSrcSRS)
8675 : {
8676 1 : CPLError(CE_Failure, CPLE_AppDefined,
8677 : "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8678 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8679 1 : return;
8680 : }
8681 : }
8682 :
8683 : auto poGeom = std::unique_ptr<OGRGeometry>(
8684 3 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8685 3 : if (poGeom == nullptr)
8686 : {
8687 : // Try also spatialite geometry blobs
8688 0 : OGRGeometry *poGeomSpatialite = nullptr;
8689 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8690 0 : &poGeomSpatialite) != OGRERR_NONE)
8691 : {
8692 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8693 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8694 0 : return;
8695 : }
8696 0 : poGeom.reset(poGeomSpatialite);
8697 : }
8698 :
8699 3 : if (argc == 2)
8700 2 : poGeom->assignSpatialReference(poSrcSRS.get());
8701 :
8702 6 : sqlite3_result_double(
8703 : pContext,
8704 1 : argc == 1 ? OGR_G_Length(OGRGeometry::ToHandle(poGeom.get()))
8705 2 : : OGR_G_GeodesicLength(OGRGeometry::ToHandle(poGeom.get())));
8706 : }
8707 :
8708 : /************************************************************************/
8709 : /* OGRGeoPackageTransform() */
8710 : /************************************************************************/
8711 :
8712 : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
8713 : sqlite3_value **argv);
8714 :
8715 32 : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
8716 : sqlite3_value **argv)
8717 : {
8718 63 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB ||
8719 31 : sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
8720 : {
8721 2 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8722 32 : return;
8723 : }
8724 :
8725 30 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8726 : const GByte *pabyBLOB =
8727 30 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8728 : GPkgHeader sHeader;
8729 30 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8730 : {
8731 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8732 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8733 1 : return;
8734 : }
8735 :
8736 29 : const int nDestSRID = sqlite3_value_int(argv[1]);
8737 29 : if (sHeader.iSrsId == nDestSRID)
8738 : {
8739 : // Return blob unmodified
8740 3 : sqlite3_result_blob(pContext, pabyBLOB, nBLOBLen, SQLITE_TRANSIENT);
8741 3 : return;
8742 : }
8743 :
8744 : GDALGeoPackageDataset *poDS =
8745 26 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8746 :
8747 : // Try to get the cached coordinate transformation
8748 : OGRCoordinateTransformation *poCT;
8749 26 : if (poDS->m_nLastCachedCTSrcSRId == sHeader.iSrsId &&
8750 20 : poDS->m_nLastCachedCTDstSRId == nDestSRID)
8751 : {
8752 20 : poCT = poDS->m_poLastCachedCT.get();
8753 : }
8754 : else
8755 : {
8756 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
8757 6 : poSrcSRS(poDS->GetSpatialRef(sHeader.iSrsId, true));
8758 6 : if (poSrcSRS == nullptr)
8759 : {
8760 0 : CPLError(CE_Failure, CPLE_AppDefined,
8761 : "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8762 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8763 0 : return;
8764 : }
8765 :
8766 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
8767 6 : poDstSRS(poDS->GetSpatialRef(nDestSRID, true));
8768 6 : if (poDstSRS == nullptr)
8769 : {
8770 0 : CPLError(CE_Failure, CPLE_AppDefined, "Target SRID (%d) is invalid",
8771 : nDestSRID);
8772 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8773 0 : return;
8774 : }
8775 : poCT =
8776 6 : OGRCreateCoordinateTransformation(poSrcSRS.get(), poDstSRS.get());
8777 6 : if (poCT == nullptr)
8778 : {
8779 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8780 0 : return;
8781 : }
8782 :
8783 : // Cache coordinate transformation for potential later reuse
8784 6 : poDS->m_nLastCachedCTSrcSRId = sHeader.iSrsId;
8785 6 : poDS->m_nLastCachedCTDstSRId = nDestSRID;
8786 6 : poDS->m_poLastCachedCT.reset(poCT);
8787 6 : poCT = poDS->m_poLastCachedCT.get();
8788 : }
8789 :
8790 26 : if (sHeader.nHeaderLen >= 8)
8791 : {
8792 26 : std::vector<GByte> &abyNewBLOB = poDS->m_abyWKBTransformCache;
8793 26 : abyNewBLOB.resize(nBLOBLen);
8794 26 : memcpy(abyNewBLOB.data(), pabyBLOB, nBLOBLen);
8795 :
8796 26 : OGREnvelope3D oEnv3d;
8797 26 : if (!OGRWKBTransform(abyNewBLOB.data() + sHeader.nHeaderLen,
8798 26 : nBLOBLen - sHeader.nHeaderLen, poCT,
8799 78 : poDS->m_oWKBTransformCache, oEnv3d) ||
8800 26 : !GPkgUpdateHeader(abyNewBLOB.data(), nBLOBLen, nDestSRID,
8801 : oEnv3d.MinX, oEnv3d.MaxX, oEnv3d.MinY,
8802 : oEnv3d.MaxY, oEnv3d.MinZ, oEnv3d.MaxZ))
8803 : {
8804 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8805 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8806 0 : return;
8807 : }
8808 :
8809 26 : sqlite3_result_blob(pContext, abyNewBLOB.data(), nBLOBLen,
8810 : SQLITE_TRANSIENT);
8811 26 : return;
8812 : }
8813 :
8814 : // Try also spatialite geometry blobs
8815 0 : OGRGeometry *poGeomSpatialite = nullptr;
8816 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8817 0 : &poGeomSpatialite) != OGRERR_NONE)
8818 : {
8819 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8820 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8821 0 : return;
8822 : }
8823 0 : auto poGeom = std::unique_ptr<OGRGeometry>(poGeomSpatialite);
8824 :
8825 0 : if (poGeom->transform(poCT) != OGRERR_NONE)
8826 : {
8827 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8828 0 : return;
8829 : }
8830 :
8831 0 : size_t nBLOBDestLen = 0;
8832 : GByte *pabyDestBLOB =
8833 0 : GPkgGeometryFromOGR(poGeom.get(), nDestSRID, nullptr, &nBLOBDestLen);
8834 0 : if (!pabyDestBLOB)
8835 : {
8836 0 : sqlite3_result_null(pContext);
8837 0 : return;
8838 : }
8839 0 : sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
8840 : VSIFree);
8841 : }
8842 :
8843 : /************************************************************************/
8844 : /* OGRGeoPackageSridFromAuthCRS() */
8845 : /************************************************************************/
8846 :
8847 4 : static void OGRGeoPackageSridFromAuthCRS(sqlite3_context *pContext,
8848 : int /*argc*/, sqlite3_value **argv)
8849 : {
8850 7 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8851 3 : sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
8852 : {
8853 2 : sqlite3_result_int(pContext, -1);
8854 2 : return;
8855 : }
8856 :
8857 : GDALGeoPackageDataset *poDS =
8858 2 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8859 :
8860 2 : char *pszSQL = sqlite3_mprintf(
8861 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
8862 : "lower(organization) = lower('%q') AND organization_coordsys_id = %d",
8863 2 : sqlite3_value_text(argv[0]), sqlite3_value_int(argv[1]));
8864 2 : OGRErr err = OGRERR_NONE;
8865 2 : int nSRSId = SQLGetInteger(poDS->GetDB(), pszSQL, &err);
8866 2 : sqlite3_free(pszSQL);
8867 2 : if (err != OGRERR_NONE)
8868 1 : nSRSId = -1;
8869 2 : sqlite3_result_int(pContext, nSRSId);
8870 : }
8871 :
8872 : /************************************************************************/
8873 : /* OGRGeoPackageImportFromEPSG() */
8874 : /************************************************************************/
8875 :
8876 4 : static void OGRGeoPackageImportFromEPSG(sqlite3_context *pContext, int /*argc*/,
8877 : sqlite3_value **argv)
8878 : {
8879 4 : if (sqlite3_value_type(argv[0]) != SQLITE_INTEGER)
8880 : {
8881 1 : sqlite3_result_int(pContext, -1);
8882 2 : return;
8883 : }
8884 :
8885 : GDALGeoPackageDataset *poDS =
8886 3 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8887 3 : OGRSpatialReference oSRS;
8888 3 : if (oSRS.importFromEPSG(sqlite3_value_int(argv[0])) != OGRERR_NONE)
8889 : {
8890 1 : sqlite3_result_int(pContext, -1);
8891 1 : return;
8892 : }
8893 :
8894 2 : sqlite3_result_int(pContext, poDS->GetSrsId(&oSRS));
8895 : }
8896 :
8897 : /************************************************************************/
8898 : /* OGRGeoPackageRegisterGeometryExtension() */
8899 : /************************************************************************/
8900 :
8901 1 : static void OGRGeoPackageRegisterGeometryExtension(sqlite3_context *pContext,
8902 : int /*argc*/,
8903 : sqlite3_value **argv)
8904 : {
8905 1 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8906 2 : sqlite3_value_type(argv[1]) != SQLITE_TEXT ||
8907 1 : sqlite3_value_type(argv[2]) != SQLITE_TEXT)
8908 : {
8909 0 : sqlite3_result_int(pContext, 0);
8910 0 : return;
8911 : }
8912 :
8913 : const char *pszTableName =
8914 1 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8915 : const char *pszGeomName =
8916 1 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8917 : const char *pszGeomType =
8918 1 : reinterpret_cast<const char *>(sqlite3_value_text(argv[2]));
8919 :
8920 : GDALGeoPackageDataset *poDS =
8921 1 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8922 :
8923 1 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
8924 1 : poDS->GetLayerByName(pszTableName));
8925 1 : if (poLyr == nullptr)
8926 : {
8927 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
8928 0 : sqlite3_result_int(pContext, 0);
8929 0 : return;
8930 : }
8931 1 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
8932 : {
8933 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
8934 0 : sqlite3_result_int(pContext, 0);
8935 0 : return;
8936 : }
8937 1 : const OGRwkbGeometryType eGeomType = OGRFromOGCGeomType(pszGeomType);
8938 1 : if (eGeomType == wkbUnknown)
8939 : {
8940 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry type name");
8941 0 : sqlite3_result_int(pContext, 0);
8942 0 : return;
8943 : }
8944 :
8945 1 : sqlite3_result_int(
8946 : pContext,
8947 1 : static_cast<int>(poLyr->CreateGeometryExtensionIfNecessary(eGeomType)));
8948 : }
8949 :
8950 : /************************************************************************/
8951 : /* OGRGeoPackageCreateSpatialIndex() */
8952 : /************************************************************************/
8953 :
8954 14 : static void OGRGeoPackageCreateSpatialIndex(sqlite3_context *pContext,
8955 : int /*argc*/, sqlite3_value **argv)
8956 : {
8957 27 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8958 13 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
8959 : {
8960 2 : sqlite3_result_int(pContext, 0);
8961 2 : return;
8962 : }
8963 :
8964 : const char *pszTableName =
8965 12 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8966 : const char *pszGeomName =
8967 12 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8968 : GDALGeoPackageDataset *poDS =
8969 12 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8970 :
8971 12 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
8972 12 : poDS->GetLayerByName(pszTableName));
8973 12 : if (poLyr == nullptr)
8974 : {
8975 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
8976 1 : sqlite3_result_int(pContext, 0);
8977 1 : return;
8978 : }
8979 11 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
8980 : {
8981 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
8982 1 : sqlite3_result_int(pContext, 0);
8983 1 : return;
8984 : }
8985 :
8986 10 : sqlite3_result_int(pContext, poLyr->CreateSpatialIndex());
8987 : }
8988 :
8989 : /************************************************************************/
8990 : /* OGRGeoPackageDisableSpatialIndex() */
8991 : /************************************************************************/
8992 :
8993 12 : static void OGRGeoPackageDisableSpatialIndex(sqlite3_context *pContext,
8994 : int /*argc*/, sqlite3_value **argv)
8995 : {
8996 23 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8997 11 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
8998 : {
8999 2 : sqlite3_result_int(pContext, 0);
9000 2 : return;
9001 : }
9002 :
9003 : const char *pszTableName =
9004 10 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9005 : const char *pszGeomName =
9006 10 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9007 : GDALGeoPackageDataset *poDS =
9008 10 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9009 :
9010 10 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
9011 10 : poDS->GetLayerByName(pszTableName));
9012 10 : if (poLyr == nullptr)
9013 : {
9014 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
9015 1 : sqlite3_result_int(pContext, 0);
9016 1 : return;
9017 : }
9018 9 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
9019 : {
9020 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
9021 1 : sqlite3_result_int(pContext, 0);
9022 1 : return;
9023 : }
9024 :
9025 8 : sqlite3_result_int(pContext, poLyr->DropSpatialIndex(true));
9026 : }
9027 :
9028 : /************************************************************************/
9029 : /* OGRGeoPackageHasSpatialIndex() */
9030 : /************************************************************************/
9031 :
9032 29 : static void OGRGeoPackageHasSpatialIndex(sqlite3_context *pContext,
9033 : int /*argc*/, sqlite3_value **argv)
9034 : {
9035 57 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9036 28 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9037 : {
9038 2 : sqlite3_result_int(pContext, 0);
9039 2 : return;
9040 : }
9041 :
9042 : const char *pszTableName =
9043 27 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9044 : const char *pszGeomName =
9045 27 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9046 : GDALGeoPackageDataset *poDS =
9047 27 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9048 :
9049 27 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
9050 27 : poDS->GetLayerByName(pszTableName));
9051 27 : if (poLyr == nullptr)
9052 : {
9053 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
9054 1 : sqlite3_result_int(pContext, 0);
9055 1 : return;
9056 : }
9057 26 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
9058 : {
9059 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
9060 1 : sqlite3_result_int(pContext, 0);
9061 1 : return;
9062 : }
9063 :
9064 25 : poLyr->RunDeferredCreationIfNecessary();
9065 25 : poLyr->CreateSpatialIndexIfNecessary();
9066 :
9067 25 : sqlite3_result_int(pContext, poLyr->HasSpatialIndex());
9068 : }
9069 :
9070 : /************************************************************************/
9071 : /* GPKG_hstore_get_value() */
9072 : /************************************************************************/
9073 :
9074 4 : static void GPKG_hstore_get_value(sqlite3_context *pContext, int /*argc*/,
9075 : sqlite3_value **argv)
9076 : {
9077 7 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9078 3 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9079 : {
9080 2 : sqlite3_result_null(pContext);
9081 2 : return;
9082 : }
9083 :
9084 : const char *pszHStore =
9085 2 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9086 : const char *pszSearchedKey =
9087 2 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9088 2 : char *pszValue = OGRHStoreGetValue(pszHStore, pszSearchedKey);
9089 2 : if (pszValue != nullptr)
9090 1 : sqlite3_result_text(pContext, pszValue, -1, CPLFree);
9091 : else
9092 1 : sqlite3_result_null(pContext);
9093 : }
9094 :
9095 : /************************************************************************/
9096 : /* GPKG_GDAL_GetMemFileFromBlob() */
9097 : /************************************************************************/
9098 :
9099 105 : static CPLString GPKG_GDAL_GetMemFileFromBlob(sqlite3_value **argv)
9100 : {
9101 105 : int nBytes = sqlite3_value_bytes(argv[0]);
9102 : const GByte *pabyBLOB =
9103 105 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
9104 : const CPLString osMemFileName(
9105 105 : VSIMemGenerateHiddenFilename("GPKG_GDAL_GetMemFileFromBlob"));
9106 105 : VSILFILE *fp = VSIFileFromMemBuffer(
9107 : osMemFileName.c_str(), const_cast<GByte *>(pabyBLOB), nBytes, FALSE);
9108 105 : VSIFCloseL(fp);
9109 105 : return osMemFileName;
9110 : }
9111 :
9112 : /************************************************************************/
9113 : /* GPKG_GDAL_GetMimeType() */
9114 : /************************************************************************/
9115 :
9116 35 : static void GPKG_GDAL_GetMimeType(sqlite3_context *pContext, int /*argc*/,
9117 : sqlite3_value **argv)
9118 : {
9119 35 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
9120 : {
9121 0 : sqlite3_result_null(pContext);
9122 0 : return;
9123 : }
9124 :
9125 70 : CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
9126 : GDALDriver *poDriver =
9127 35 : GDALDriver::FromHandle(GDALIdentifyDriver(osMemFileName, nullptr));
9128 35 : if (poDriver != nullptr)
9129 : {
9130 35 : const char *pszRes = nullptr;
9131 35 : if (EQUAL(poDriver->GetDescription(), "PNG"))
9132 23 : pszRes = "image/png";
9133 12 : else if (EQUAL(poDriver->GetDescription(), "JPEG"))
9134 6 : pszRes = "image/jpeg";
9135 6 : else if (EQUAL(poDriver->GetDescription(), "WEBP"))
9136 6 : pszRes = "image/x-webp";
9137 0 : else if (EQUAL(poDriver->GetDescription(), "GTIFF"))
9138 0 : pszRes = "image/tiff";
9139 : else
9140 0 : pszRes = CPLSPrintf("gdal/%s", poDriver->GetDescription());
9141 35 : sqlite3_result_text(pContext, pszRes, -1, SQLITE_TRANSIENT);
9142 : }
9143 : else
9144 0 : sqlite3_result_null(pContext);
9145 35 : VSIUnlink(osMemFileName);
9146 : }
9147 :
9148 : /************************************************************************/
9149 : /* GPKG_GDAL_GetBandCount() */
9150 : /************************************************************************/
9151 :
9152 35 : static void GPKG_GDAL_GetBandCount(sqlite3_context *pContext, int /*argc*/,
9153 : sqlite3_value **argv)
9154 : {
9155 35 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
9156 : {
9157 0 : sqlite3_result_null(pContext);
9158 0 : return;
9159 : }
9160 :
9161 70 : CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
9162 : auto poDS = std::unique_ptr<GDALDataset>(
9163 : GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
9164 70 : nullptr, nullptr, nullptr));
9165 35 : if (poDS != nullptr)
9166 : {
9167 35 : sqlite3_result_int(pContext, poDS->GetRasterCount());
9168 : }
9169 : else
9170 0 : sqlite3_result_null(pContext);
9171 35 : VSIUnlink(osMemFileName);
9172 : }
9173 :
9174 : /************************************************************************/
9175 : /* GPKG_GDAL_HasColorTable() */
9176 : /************************************************************************/
9177 :
9178 35 : static void GPKG_GDAL_HasColorTable(sqlite3_context *pContext, int /*argc*/,
9179 : sqlite3_value **argv)
9180 : {
9181 35 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
9182 : {
9183 0 : sqlite3_result_null(pContext);
9184 0 : return;
9185 : }
9186 :
9187 70 : CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
9188 : auto poDS = std::unique_ptr<GDALDataset>(
9189 : GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
9190 70 : nullptr, nullptr, nullptr));
9191 35 : if (poDS != nullptr)
9192 : {
9193 35 : sqlite3_result_int(
9194 46 : pContext, poDS->GetRasterCount() == 1 &&
9195 11 : poDS->GetRasterBand(1)->GetColorTable() != nullptr);
9196 : }
9197 : else
9198 0 : sqlite3_result_null(pContext);
9199 35 : VSIUnlink(osMemFileName);
9200 : }
9201 :
9202 : /************************************************************************/
9203 : /* GetRasterLayerDataset() */
9204 : /************************************************************************/
9205 :
9206 : GDALDataset *
9207 12 : GDALGeoPackageDataset::GetRasterLayerDataset(const char *pszLayerName)
9208 : {
9209 12 : const auto oIter = m_oCachedRasterDS.find(pszLayerName);
9210 12 : if (oIter != m_oCachedRasterDS.end())
9211 10 : return oIter->second.get();
9212 :
9213 : auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
9214 4 : (std::string("GPKG:\"") + m_pszFilename + "\":" + pszLayerName).c_str(),
9215 4 : GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
9216 2 : if (!poDS)
9217 : {
9218 0 : return nullptr;
9219 : }
9220 2 : m_oCachedRasterDS[pszLayerName] = std::move(poDS);
9221 2 : return m_oCachedRasterDS[pszLayerName].get();
9222 : }
9223 :
9224 : /************************************************************************/
9225 : /* GPKG_gdal_get_layer_pixel_value() */
9226 : /************************************************************************/
9227 :
9228 : // NOTE: keep in sync implementations in ogrsqlitesqlfunctionscommon.cpp
9229 : // and ogrgeopackagedatasource.cpp
9230 13 : static void GPKG_gdal_get_layer_pixel_value(sqlite3_context *pContext, int argc,
9231 : sqlite3_value **argv)
9232 : {
9233 13 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
9234 : {
9235 1 : CPLError(CE_Failure, CPLE_AppDefined,
9236 : "Invalid arguments to gdal_get_layer_pixel_value()");
9237 1 : sqlite3_result_null(pContext);
9238 1 : return;
9239 : }
9240 :
9241 : const char *pszLayerName =
9242 12 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9243 :
9244 : GDALGeoPackageDataset *poGlobalDS =
9245 12 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9246 12 : auto poDS = poGlobalDS->GetRasterLayerDataset(pszLayerName);
9247 12 : if (!poDS)
9248 : {
9249 0 : sqlite3_result_null(pContext);
9250 0 : return;
9251 : }
9252 :
9253 12 : OGRSQLite_gdal_get_pixel_value_common("gdal_get_layer_pixel_value",
9254 : pContext, argc, argv, poDS);
9255 : }
9256 :
9257 : /************************************************************************/
9258 : /* GPKG_ogr_layer_Extent() */
9259 : /************************************************************************/
9260 :
9261 3 : static void GPKG_ogr_layer_Extent(sqlite3_context *pContext, int /*argc*/,
9262 : sqlite3_value **argv)
9263 : {
9264 3 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
9265 : {
9266 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s: Invalid argument type",
9267 : "ogr_layer_Extent");
9268 1 : sqlite3_result_null(pContext);
9269 2 : return;
9270 : }
9271 :
9272 : const char *pszLayerName =
9273 2 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9274 : GDALGeoPackageDataset *poDS =
9275 2 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9276 2 : OGRLayer *poLayer = poDS->GetLayerByName(pszLayerName);
9277 2 : if (!poLayer)
9278 : {
9279 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s: unknown layer",
9280 : "ogr_layer_Extent");
9281 1 : sqlite3_result_null(pContext);
9282 1 : return;
9283 : }
9284 :
9285 1 : if (poLayer->GetGeomType() == wkbNone)
9286 : {
9287 0 : sqlite3_result_null(pContext);
9288 0 : return;
9289 : }
9290 :
9291 1 : OGREnvelope sExtent;
9292 1 : if (poLayer->GetExtent(&sExtent) != OGRERR_NONE)
9293 : {
9294 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s: Cannot fetch layer extent",
9295 : "ogr_layer_Extent");
9296 0 : sqlite3_result_null(pContext);
9297 0 : return;
9298 : }
9299 :
9300 1 : OGRPolygon oPoly;
9301 1 : auto poRing = std::make_unique<OGRLinearRing>();
9302 1 : poRing->addPoint(sExtent.MinX, sExtent.MinY);
9303 1 : poRing->addPoint(sExtent.MaxX, sExtent.MinY);
9304 1 : poRing->addPoint(sExtent.MaxX, sExtent.MaxY);
9305 1 : poRing->addPoint(sExtent.MinX, sExtent.MaxY);
9306 1 : poRing->addPoint(sExtent.MinX, sExtent.MinY);
9307 1 : oPoly.addRing(std::move(poRing));
9308 :
9309 1 : const auto poSRS = poLayer->GetSpatialRef();
9310 1 : const int nSRID = poDS->GetSrsId(poSRS);
9311 1 : size_t nBLOBDestLen = 0;
9312 : GByte *pabyDestBLOB =
9313 1 : GPkgGeometryFromOGR(&oPoly, nSRID, nullptr, &nBLOBDestLen);
9314 1 : if (!pabyDestBLOB)
9315 : {
9316 0 : sqlite3_result_null(pContext);
9317 0 : return;
9318 : }
9319 1 : sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
9320 : VSIFree);
9321 : }
9322 :
9323 : /************************************************************************/
9324 : /* InstallSQLFunctions() */
9325 : /************************************************************************/
9326 :
9327 : #ifndef SQLITE_DETERMINISTIC
9328 : #define SQLITE_DETERMINISTIC 0
9329 : #endif
9330 :
9331 : #ifndef SQLITE_INNOCUOUS
9332 : #define SQLITE_INNOCUOUS 0
9333 : #endif
9334 :
9335 : #ifndef UTF8_INNOCUOUS
9336 : #define UTF8_INNOCUOUS (SQLITE_UTF8 | SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS)
9337 : #endif
9338 :
9339 2020 : void GDALGeoPackageDataset::InstallSQLFunctions()
9340 : {
9341 2020 : InitSpatialite();
9342 :
9343 : // Enable SpatiaLite 4.3 "amphibious" mode, i.e. that SpatiaLite functions
9344 : // that take geometries will accept GPKG encoded geometries without
9345 : // explicit conversion.
9346 : // Use sqlite3_exec() instead of SQLCommand() since we don't want verbose
9347 : // error.
9348 2020 : sqlite3_exec(hDB, "SELECT EnableGpkgAmphibiousMode()", nullptr, nullptr,
9349 : nullptr);
9350 :
9351 : /* Used by RTree Spatial Index Extension */
9352 2020 : sqlite3_create_function(hDB, "ST_MinX", 1, UTF8_INNOCUOUS, nullptr,
9353 : OGRGeoPackageSTMinX, nullptr, nullptr);
9354 2020 : sqlite3_create_function(hDB, "ST_MinY", 1, UTF8_INNOCUOUS, nullptr,
9355 : OGRGeoPackageSTMinY, nullptr, nullptr);
9356 2020 : sqlite3_create_function(hDB, "ST_MaxX", 1, UTF8_INNOCUOUS, nullptr,
9357 : OGRGeoPackageSTMaxX, nullptr, nullptr);
9358 2020 : sqlite3_create_function(hDB, "ST_MaxY", 1, UTF8_INNOCUOUS, nullptr,
9359 : OGRGeoPackageSTMaxY, nullptr, nullptr);
9360 2020 : sqlite3_create_function(hDB, "ST_IsEmpty", 1, UTF8_INNOCUOUS, nullptr,
9361 : OGRGeoPackageSTIsEmpty, nullptr, nullptr);
9362 :
9363 : /* Used by Geometry Type Triggers Extension */
9364 2020 : sqlite3_create_function(hDB, "ST_GeometryType", 1, UTF8_INNOCUOUS, nullptr,
9365 : OGRGeoPackageSTGeometryType, nullptr, nullptr);
9366 2020 : sqlite3_create_function(hDB, "GPKG_IsAssignable", 2, UTF8_INNOCUOUS,
9367 : nullptr, OGRGeoPackageGPKGIsAssignable, nullptr,
9368 : nullptr);
9369 :
9370 : /* Used by Geometry SRS ID Triggers Extension */
9371 2020 : sqlite3_create_function(hDB, "ST_SRID", 1, UTF8_INNOCUOUS, nullptr,
9372 : OGRGeoPackageSTSRID, nullptr, nullptr);
9373 :
9374 : /* Spatialite-like functions */
9375 2020 : sqlite3_create_function(hDB, "CreateSpatialIndex", 2, SQLITE_UTF8, this,
9376 : OGRGeoPackageCreateSpatialIndex, nullptr, nullptr);
9377 2020 : sqlite3_create_function(hDB, "DisableSpatialIndex", 2, SQLITE_UTF8, this,
9378 : OGRGeoPackageDisableSpatialIndex, nullptr, nullptr);
9379 2020 : sqlite3_create_function(hDB, "HasSpatialIndex", 2, SQLITE_UTF8, this,
9380 : OGRGeoPackageHasSpatialIndex, nullptr, nullptr);
9381 :
9382 : // HSTORE functions
9383 2020 : sqlite3_create_function(hDB, "hstore_get_value", 2, UTF8_INNOCUOUS, nullptr,
9384 : GPKG_hstore_get_value, nullptr, nullptr);
9385 :
9386 : // Override a few Spatialite functions to work with gpkg_spatial_ref_sys
9387 2020 : sqlite3_create_function(hDB, "ST_Transform", 2, UTF8_INNOCUOUS, this,
9388 : OGRGeoPackageTransform, nullptr, nullptr);
9389 2020 : sqlite3_create_function(hDB, "Transform", 2, UTF8_INNOCUOUS, this,
9390 : OGRGeoPackageTransform, nullptr, nullptr);
9391 2020 : sqlite3_create_function(hDB, "SridFromAuthCRS", 2, SQLITE_UTF8, this,
9392 : OGRGeoPackageSridFromAuthCRS, nullptr, nullptr);
9393 :
9394 2020 : sqlite3_create_function(hDB, "ST_EnvIntersects", 2, UTF8_INNOCUOUS, nullptr,
9395 : OGRGeoPackageSTEnvelopesIntersectsTwoParams,
9396 : nullptr, nullptr);
9397 2020 : sqlite3_create_function(
9398 : hDB, "ST_EnvelopesIntersects", 2, UTF8_INNOCUOUS, nullptr,
9399 : OGRGeoPackageSTEnvelopesIntersectsTwoParams, nullptr, nullptr);
9400 :
9401 2020 : sqlite3_create_function(hDB, "ST_EnvIntersects", 5, UTF8_INNOCUOUS, nullptr,
9402 : OGRGeoPackageSTEnvelopesIntersects, nullptr,
9403 : nullptr);
9404 2020 : sqlite3_create_function(hDB, "ST_EnvelopesIntersects", 5, UTF8_INNOCUOUS,
9405 : nullptr, OGRGeoPackageSTEnvelopesIntersects,
9406 : nullptr, nullptr);
9407 :
9408 : // Implementation that directly hacks the GeoPackage geometry blob header
9409 2020 : sqlite3_create_function(hDB, "SetSRID", 2, UTF8_INNOCUOUS, nullptr,
9410 : OGRGeoPackageSetSRID, nullptr, nullptr);
9411 :
9412 : // GDAL specific function
9413 2020 : sqlite3_create_function(hDB, "ImportFromEPSG", 1, SQLITE_UTF8, this,
9414 : OGRGeoPackageImportFromEPSG, nullptr, nullptr);
9415 :
9416 : // May be used by ogrmerge.py
9417 2020 : sqlite3_create_function(hDB, "RegisterGeometryExtension", 3, SQLITE_UTF8,
9418 : this, OGRGeoPackageRegisterGeometryExtension,
9419 : nullptr, nullptr);
9420 :
9421 2020 : if (OGRGeometryFactory::haveGEOS())
9422 : {
9423 2020 : sqlite3_create_function(hDB, "ST_MakeValid", 1, UTF8_INNOCUOUS, nullptr,
9424 : OGRGeoPackageSTMakeValid, nullptr, nullptr);
9425 : }
9426 :
9427 2020 : sqlite3_create_function(hDB, "ST_Length", 1, UTF8_INNOCUOUS, nullptr,
9428 : OGRGeoPackageLengthOrGeodesicLength, nullptr,
9429 : nullptr);
9430 2020 : sqlite3_create_function(hDB, "ST_Length", 2, UTF8_INNOCUOUS, this,
9431 : OGRGeoPackageLengthOrGeodesicLength, nullptr,
9432 : nullptr);
9433 :
9434 2020 : sqlite3_create_function(hDB, "ST_Area", 1, UTF8_INNOCUOUS, nullptr,
9435 : OGRGeoPackageSTArea, nullptr, nullptr);
9436 2020 : sqlite3_create_function(hDB, "ST_Area", 2, UTF8_INNOCUOUS, this,
9437 : OGRGeoPackageGeodesicArea, nullptr, nullptr);
9438 :
9439 : // Debug functions
9440 2020 : if (CPLTestBool(CPLGetConfigOption("GPKG_DEBUG", "FALSE")))
9441 : {
9442 423 : sqlite3_create_function(hDB, "GDAL_GetMimeType", 1,
9443 : SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9444 : GPKG_GDAL_GetMimeType, nullptr, nullptr);
9445 423 : sqlite3_create_function(hDB, "GDAL_GetBandCount", 1,
9446 : SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9447 : GPKG_GDAL_GetBandCount, nullptr, nullptr);
9448 423 : sqlite3_create_function(hDB, "GDAL_HasColorTable", 1,
9449 : SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9450 : GPKG_GDAL_HasColorTable, nullptr, nullptr);
9451 : }
9452 :
9453 2020 : sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 5, SQLITE_UTF8,
9454 : this, GPKG_gdal_get_layer_pixel_value, nullptr,
9455 : nullptr);
9456 2020 : sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 6, SQLITE_UTF8,
9457 : this, GPKG_gdal_get_layer_pixel_value, nullptr,
9458 : nullptr);
9459 :
9460 : // Function from VirtualOGR
9461 2020 : sqlite3_create_function(hDB, "ogr_layer_Extent", 1, SQLITE_UTF8, this,
9462 : GPKG_ogr_layer_Extent, nullptr, nullptr);
9463 :
9464 2020 : m_pSQLFunctionData = OGRSQLiteRegisterSQLFunctionsCommon(hDB);
9465 2020 : }
9466 :
9467 : /************************************************************************/
9468 : /* OpenOrCreateDB() */
9469 : /************************************************************************/
9470 :
9471 2023 : bool GDALGeoPackageDataset::OpenOrCreateDB(int flags)
9472 : {
9473 2023 : const bool bSuccess = OGRSQLiteBaseDataSource::OpenOrCreateDB(
9474 : flags, /*bRegisterOGR2SQLiteExtensions=*/false,
9475 : /*bLoadExtensions=*/true);
9476 2023 : if (!bSuccess)
9477 8 : return false;
9478 :
9479 : // Turning on recursive_triggers is needed so that DELETE triggers fire
9480 : // in a INSERT OR REPLACE statement. In particular this is needed to
9481 : // make sure gpkg_ogr_contents.feature_count is properly updated.
9482 2015 : SQLCommand(hDB, "PRAGMA recursive_triggers = 1");
9483 :
9484 2015 : InstallSQLFunctions();
9485 :
9486 : const char *pszSqlitePragma =
9487 2015 : CPLGetConfigOption("OGR_SQLITE_PRAGMA", nullptr);
9488 2015 : OGRErr eErr = OGRERR_NONE;
9489 6 : if ((!pszSqlitePragma || !strstr(pszSqlitePragma, "trusted_schema")) &&
9490 : // Older sqlite versions don't have this pragma
9491 4036 : SQLGetInteger(hDB, "PRAGMA trusted_schema", &eErr) == 0 &&
9492 2015 : eErr == OGRERR_NONE)
9493 : {
9494 2015 : bool bNeedsTrustedSchema = false;
9495 :
9496 : // Current SQLite versions require PRAGMA trusted_schema = 1 to be
9497 : // able to use the RTree from triggers, which is only needed when
9498 : // modifying the RTree.
9499 4975 : if (((flags & SQLITE_OPEN_READWRITE) != 0 ||
9500 3085 : (flags & SQLITE_OPEN_CREATE) != 0) &&
9501 1070 : OGRSQLiteRTreeRequiresTrustedSchemaOn())
9502 : {
9503 1070 : bNeedsTrustedSchema = true;
9504 : }
9505 :
9506 : #ifdef HAVE_SPATIALITE
9507 : // Spatialite <= 5.1.0 doesn't declare its functions as SQLITE_INNOCUOUS
9508 945 : if (!bNeedsTrustedSchema && HasExtensionsTable() &&
9509 866 : SQLGetInteger(
9510 : hDB,
9511 : "SELECT 1 FROM gpkg_extensions WHERE "
9512 : "extension_name ='gdal_spatialite_computed_geom_column'",
9513 1 : nullptr) == 1 &&
9514 2960 : SpatialiteRequiresTrustedSchemaOn() && AreSpatialiteTriggersSafe())
9515 : {
9516 1 : bNeedsTrustedSchema = true;
9517 : }
9518 : #endif
9519 :
9520 2015 : if (bNeedsTrustedSchema)
9521 : {
9522 1071 : CPLDebug("GPKG", "Setting PRAGMA trusted_schema = 1");
9523 1071 : SQLCommand(hDB, "PRAGMA trusted_schema = 1");
9524 : }
9525 : }
9526 :
9527 : const char *pszPreludeStatements =
9528 2015 : CSLFetchNameValue(papszOpenOptions, "PRELUDE_STATEMENTS");
9529 2015 : if (pszPreludeStatements)
9530 : {
9531 2 : if (SQLCommand(hDB, pszPreludeStatements) != OGRERR_NONE)
9532 0 : return false;
9533 : }
9534 :
9535 2015 : return true;
9536 : }
9537 :
9538 : /************************************************************************/
9539 : /* GetLayerWithGetSpatialWhereByName() */
9540 : /************************************************************************/
9541 :
9542 : std::pair<OGRLayer *, IOGRSQLiteGetSpatialWhere *>
9543 90 : GDALGeoPackageDataset::GetLayerWithGetSpatialWhereByName(const char *pszName)
9544 : {
9545 : OGRGeoPackageLayer *poRet =
9546 90 : cpl::down_cast<OGRGeoPackageLayer *>(GetLayerByName(pszName));
9547 90 : return std::pair(poRet, poRet);
9548 : }
9549 :
9550 : /************************************************************************/
9551 : /* CommitTransaction() */
9552 : /************************************************************************/
9553 :
9554 203 : OGRErr GDALGeoPackageDataset::CommitTransaction()
9555 :
9556 : {
9557 203 : if (m_nSoftTransactionLevel == 1)
9558 : {
9559 202 : FlushMetadata();
9560 453 : for (auto &poLayer : m_apoLayers)
9561 : {
9562 251 : poLayer->DoJobAtTransactionCommit();
9563 : }
9564 : }
9565 :
9566 203 : return OGRSQLiteBaseDataSource::CommitTransaction();
9567 : }
9568 :
9569 : /************************************************************************/
9570 : /* RollbackTransaction() */
9571 : /************************************************************************/
9572 :
9573 35 : OGRErr GDALGeoPackageDataset::RollbackTransaction()
9574 :
9575 : {
9576 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9577 70 : std::vector<bool> abAddTriggers;
9578 35 : std::vector<bool> abTriggersDeletedInTransaction;
9579 : #endif
9580 35 : if (m_nSoftTransactionLevel == 1)
9581 : {
9582 34 : FlushMetadata();
9583 70 : for (auto &poLayer : m_apoLayers)
9584 : {
9585 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9586 36 : abAddTriggers.push_back(poLayer->GetAddOGRFeatureCountTriggers());
9587 36 : abTriggersDeletedInTransaction.push_back(
9588 36 : poLayer->GetOGRFeatureCountTriggersDeletedInTransaction());
9589 36 : poLayer->SetAddOGRFeatureCountTriggers(false);
9590 : #endif
9591 36 : poLayer->DoJobAtTransactionRollback();
9592 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9593 36 : poLayer->DisableFeatureCount();
9594 : #endif
9595 : }
9596 : }
9597 :
9598 35 : const OGRErr eErr = OGRSQLiteBaseDataSource::RollbackTransaction();
9599 :
9600 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9601 35 : if (!abAddTriggers.empty())
9602 : {
9603 68 : for (size_t i = 0; i < m_apoLayers.size(); ++i)
9604 : {
9605 36 : auto &poLayer = m_apoLayers[i];
9606 36 : if (abTriggersDeletedInTransaction[i])
9607 : {
9608 7 : poLayer->SetOGRFeatureCountTriggersEnabled(true);
9609 : }
9610 : else
9611 : {
9612 29 : poLayer->SetAddOGRFeatureCountTriggers(abAddTriggers[i]);
9613 : }
9614 : }
9615 : }
9616 : #endif
9617 70 : return eErr;
9618 : }
9619 :
9620 : /************************************************************************/
9621 : /* GetGeometryTypeString() */
9622 : /************************************************************************/
9623 :
9624 : const char *
9625 1407 : GDALGeoPackageDataset::GetGeometryTypeString(OGRwkbGeometryType eType)
9626 : {
9627 1407 : const char *pszGPKGGeomType = OGRToOGCGeomType(eType);
9628 1419 : if (EQUAL(pszGPKGGeomType, "GEOMETRYCOLLECTION") &&
9629 12 : CPLTestBool(CPLGetConfigOption("OGR_GPKG_GEOMCOLLECTION", "NO")))
9630 : {
9631 0 : pszGPKGGeomType = "GEOMCOLLECTION";
9632 : }
9633 1407 : return pszGPKGGeomType;
9634 : }
9635 :
9636 : /************************************************************************/
9637 : /* GetFieldDomainNames() */
9638 : /************************************************************************/
9639 :
9640 : std::vector<std::string>
9641 10 : GDALGeoPackageDataset::GetFieldDomainNames(CSLConstList) const
9642 : {
9643 10 : if (!HasDataColumnConstraintsTable())
9644 3 : return std::vector<std::string>();
9645 :
9646 14 : std::vector<std::string> oDomainNamesList;
9647 :
9648 7 : std::unique_ptr<SQLResult> oResultTable;
9649 : {
9650 : std::string osSQL =
9651 : "SELECT DISTINCT constraint_name "
9652 : "FROM gpkg_data_column_constraints "
9653 : "WHERE constraint_name NOT LIKE '_%_domain_description' "
9654 : "ORDER BY constraint_name "
9655 7 : "LIMIT 10000" // to avoid denial of service
9656 : ;
9657 7 : oResultTable = SQLQuery(hDB, osSQL.c_str());
9658 7 : if (!oResultTable)
9659 0 : return oDomainNamesList;
9660 : }
9661 :
9662 7 : if (oResultTable->RowCount() == 10000)
9663 : {
9664 0 : CPLError(CE_Warning, CPLE_AppDefined,
9665 : "Number of rows returned for field domain names has been "
9666 : "truncated.");
9667 : }
9668 7 : else if (oResultTable->RowCount() > 0)
9669 : {
9670 7 : oDomainNamesList.reserve(oResultTable->RowCount());
9671 89 : for (int i = 0; i < oResultTable->RowCount(); i++)
9672 : {
9673 82 : const char *pszConstraintName = oResultTable->GetValue(0, i);
9674 82 : if (!pszConstraintName)
9675 0 : continue;
9676 :
9677 82 : oDomainNamesList.emplace_back(pszConstraintName);
9678 : }
9679 : }
9680 :
9681 7 : return oDomainNamesList;
9682 : }
9683 :
9684 : /************************************************************************/
9685 : /* GetFieldDomain() */
9686 : /************************************************************************/
9687 :
9688 : const OGRFieldDomain *
9689 102 : GDALGeoPackageDataset::GetFieldDomain(const std::string &name) const
9690 : {
9691 102 : const auto baseRet = GDALDataset::GetFieldDomain(name);
9692 102 : if (baseRet)
9693 42 : return baseRet;
9694 :
9695 60 : if (!HasDataColumnConstraintsTable())
9696 4 : return nullptr;
9697 :
9698 56 : const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
9699 56 : const char *min_is_inclusive =
9700 56 : bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
9701 56 : const char *max_is_inclusive =
9702 56 : bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
9703 :
9704 56 : std::unique_ptr<SQLResult> oResultTable;
9705 : // Note: for coded domains, we use a little trick by using a dummy
9706 : // _{domainname}_domain_description enum that has a single entry whose
9707 : // description is the description of the main domain.
9708 : {
9709 56 : char *pszSQL = sqlite3_mprintf(
9710 : "SELECT constraint_type, value, min, %s, "
9711 : "max, %s, description, constraint_name "
9712 : "FROM gpkg_data_column_constraints "
9713 : "WHERE constraint_name IN ('%q', "
9714 : "'_%q_domain_description') "
9715 : "AND length(constraint_type) < 100 " // to
9716 : // avoid
9717 : // denial
9718 : // of
9719 : // service
9720 : "AND (value IS NULL OR length(value) < "
9721 : "10000) " // to avoid denial
9722 : // of service
9723 : "AND (description IS NULL OR "
9724 : "length(description) < 10000) " // to
9725 : // avoid
9726 : // denial
9727 : // of
9728 : // service
9729 : "ORDER BY value "
9730 : "LIMIT 10000", // to avoid denial of
9731 : // service
9732 : min_is_inclusive, max_is_inclusive, name.c_str(), name.c_str());
9733 56 : oResultTable = SQLQuery(hDB, pszSQL);
9734 56 : sqlite3_free(pszSQL);
9735 56 : if (!oResultTable)
9736 0 : return nullptr;
9737 : }
9738 56 : if (oResultTable->RowCount() == 0)
9739 : {
9740 15 : return nullptr;
9741 : }
9742 41 : if (oResultTable->RowCount() == 10000)
9743 : {
9744 0 : CPLError(CE_Warning, CPLE_AppDefined,
9745 : "Number of rows returned for field domain %s has been "
9746 : "truncated.",
9747 : name.c_str());
9748 : }
9749 :
9750 : // Try to find the field domain data type from fields that implement it
9751 41 : int nFieldType = -1;
9752 41 : OGRFieldSubType eSubType = OFSTNone;
9753 41 : if (HasDataColumnsTable())
9754 : {
9755 36 : char *pszSQL = sqlite3_mprintf(
9756 : "SELECT table_name, column_name FROM gpkg_data_columns WHERE "
9757 : "constraint_name = '%q' LIMIT 10",
9758 : name.c_str());
9759 72 : auto oResultTable2 = SQLQuery(hDB, pszSQL);
9760 36 : sqlite3_free(pszSQL);
9761 36 : if (oResultTable2 && oResultTable2->RowCount() >= 1)
9762 : {
9763 46 : for (int iRecord = 0; iRecord < oResultTable2->RowCount();
9764 : iRecord++)
9765 : {
9766 23 : const char *pszTableName = oResultTable2->GetValue(0, iRecord);
9767 23 : const char *pszColumnName = oResultTable2->GetValue(1, iRecord);
9768 23 : if (pszTableName == nullptr || pszColumnName == nullptr)
9769 0 : continue;
9770 : OGRLayer *poLayer =
9771 46 : const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
9772 23 : pszTableName);
9773 23 : if (poLayer)
9774 : {
9775 23 : const auto poFDefn = poLayer->GetLayerDefn();
9776 23 : int nIdx = poFDefn->GetFieldIndex(pszColumnName);
9777 23 : if (nIdx >= 0)
9778 : {
9779 23 : const auto poFieldDefn = poFDefn->GetFieldDefn(nIdx);
9780 23 : const auto eType = poFieldDefn->GetType();
9781 23 : if (nFieldType < 0)
9782 : {
9783 23 : nFieldType = eType;
9784 23 : eSubType = poFieldDefn->GetSubType();
9785 : }
9786 0 : else if ((eType == OFTInteger64 || eType == OFTReal) &&
9787 : nFieldType == OFTInteger)
9788 : {
9789 : // ok
9790 : }
9791 0 : else if (eType == OFTInteger &&
9792 0 : (nFieldType == OFTInteger64 ||
9793 : nFieldType == OFTReal))
9794 : {
9795 0 : nFieldType = OFTInteger;
9796 0 : eSubType = OFSTNone;
9797 : }
9798 0 : else if (nFieldType != eType)
9799 : {
9800 0 : nFieldType = -1;
9801 0 : eSubType = OFSTNone;
9802 0 : break;
9803 : }
9804 : }
9805 : }
9806 : }
9807 : }
9808 : }
9809 :
9810 41 : std::unique_ptr<OGRFieldDomain> poDomain;
9811 82 : std::vector<OGRCodedValue> asValues;
9812 41 : bool error = false;
9813 82 : CPLString osLastConstraintType;
9814 41 : int nFieldTypeFromEnumCode = -1;
9815 82 : std::string osConstraintDescription;
9816 82 : std::string osDescrConstraintName("_");
9817 41 : osDescrConstraintName += name;
9818 41 : osDescrConstraintName += "_domain_description";
9819 100 : for (int iRecord = 0; iRecord < oResultTable->RowCount(); iRecord++)
9820 : {
9821 63 : const char *pszConstraintType = oResultTable->GetValue(0, iRecord);
9822 63 : if (pszConstraintType == nullptr)
9823 1 : continue;
9824 63 : const char *pszValue = oResultTable->GetValue(1, iRecord);
9825 63 : const char *pszMin = oResultTable->GetValue(2, iRecord);
9826 : const bool bIsMinIncluded =
9827 63 : oResultTable->GetValueAsInteger(3, iRecord) == 1;
9828 63 : const char *pszMax = oResultTable->GetValue(4, iRecord);
9829 : const bool bIsMaxIncluded =
9830 63 : oResultTable->GetValueAsInteger(5, iRecord) == 1;
9831 63 : const char *pszDescription = oResultTable->GetValue(6, iRecord);
9832 63 : const char *pszConstraintName = oResultTable->GetValue(7, iRecord);
9833 :
9834 63 : if (!osLastConstraintType.empty() && osLastConstraintType != "enum")
9835 : {
9836 1 : CPLError(CE_Failure, CPLE_AppDefined,
9837 : "Only constraint of type 'enum' can have multiple rows");
9838 1 : error = true;
9839 4 : break;
9840 : }
9841 :
9842 62 : if (strcmp(pszConstraintType, "enum") == 0)
9843 : {
9844 42 : if (pszValue == nullptr)
9845 : {
9846 1 : CPLError(CE_Failure, CPLE_AppDefined,
9847 : "NULL in 'value' column of enumeration");
9848 1 : error = true;
9849 1 : break;
9850 : }
9851 41 : if (osDescrConstraintName == pszConstraintName)
9852 : {
9853 1 : if (pszDescription)
9854 : {
9855 1 : osConstraintDescription = pszDescription;
9856 : }
9857 1 : continue;
9858 : }
9859 40 : if (asValues.empty())
9860 : {
9861 20 : asValues.reserve(oResultTable->RowCount() + 1);
9862 : }
9863 : OGRCodedValue cv;
9864 : // intended: the 'value' column in GPKG is actually the code
9865 40 : cv.pszCode = VSI_STRDUP_VERBOSE(pszValue);
9866 40 : if (cv.pszCode == nullptr)
9867 : {
9868 0 : error = true;
9869 0 : break;
9870 : }
9871 40 : if (pszDescription)
9872 : {
9873 29 : cv.pszValue = VSI_STRDUP_VERBOSE(pszDescription);
9874 29 : if (cv.pszValue == nullptr)
9875 : {
9876 0 : VSIFree(cv.pszCode);
9877 0 : error = true;
9878 0 : break;
9879 : }
9880 : }
9881 : else
9882 : {
9883 11 : cv.pszValue = nullptr;
9884 : }
9885 :
9886 : // If we can't get the data type from field definition, guess it
9887 : // from code.
9888 40 : if (nFieldType < 0 && nFieldTypeFromEnumCode != OFTString)
9889 : {
9890 18 : switch (CPLGetValueType(cv.pszCode))
9891 : {
9892 13 : case CPL_VALUE_INTEGER:
9893 : {
9894 13 : if (nFieldTypeFromEnumCode != OFTReal &&
9895 : nFieldTypeFromEnumCode != OFTInteger64)
9896 : {
9897 9 : const auto nVal = CPLAtoGIntBig(cv.pszCode);
9898 17 : if (nVal < std::numeric_limits<int>::min() ||
9899 8 : nVal > std::numeric_limits<int>::max())
9900 : {
9901 3 : nFieldTypeFromEnumCode = OFTInteger64;
9902 : }
9903 : else
9904 : {
9905 6 : nFieldTypeFromEnumCode = OFTInteger;
9906 : }
9907 : }
9908 13 : break;
9909 : }
9910 :
9911 3 : case CPL_VALUE_REAL:
9912 3 : nFieldTypeFromEnumCode = OFTReal;
9913 3 : break;
9914 :
9915 2 : case CPL_VALUE_STRING:
9916 2 : nFieldTypeFromEnumCode = OFTString;
9917 2 : break;
9918 : }
9919 : }
9920 :
9921 40 : asValues.emplace_back(cv);
9922 : }
9923 20 : else if (strcmp(pszConstraintType, "range") == 0)
9924 : {
9925 : OGRField sMin;
9926 : OGRField sMax;
9927 14 : OGR_RawField_SetUnset(&sMin);
9928 14 : OGR_RawField_SetUnset(&sMax);
9929 14 : if (nFieldType != OFTInteger && nFieldType != OFTInteger64)
9930 8 : nFieldType = OFTReal;
9931 27 : if (pszMin != nullptr &&
9932 13 : CPLAtof(pszMin) != -std::numeric_limits<double>::infinity())
9933 : {
9934 10 : if (nFieldType == OFTInteger)
9935 3 : sMin.Integer = atoi(pszMin);
9936 7 : else if (nFieldType == OFTInteger64)
9937 3 : sMin.Integer64 = CPLAtoGIntBig(pszMin);
9938 : else /* if( nFieldType == OFTReal ) */
9939 4 : sMin.Real = CPLAtof(pszMin);
9940 : }
9941 27 : if (pszMax != nullptr &&
9942 13 : CPLAtof(pszMax) != std::numeric_limits<double>::infinity())
9943 : {
9944 10 : if (nFieldType == OFTInteger)
9945 3 : sMax.Integer = atoi(pszMax);
9946 7 : else if (nFieldType == OFTInteger64)
9947 3 : sMax.Integer64 = CPLAtoGIntBig(pszMax);
9948 : else /* if( nFieldType == OFTReal ) */
9949 4 : sMax.Real = CPLAtof(pszMax);
9950 : }
9951 14 : poDomain = std::make_unique<OGRRangeFieldDomain>(
9952 14 : name, pszDescription ? pszDescription : "",
9953 28 : static_cast<OGRFieldType>(nFieldType), eSubType, sMin,
9954 14 : bIsMinIncluded, sMax, bIsMaxIncluded);
9955 : }
9956 6 : else if (strcmp(pszConstraintType, "glob") == 0)
9957 : {
9958 5 : if (pszValue == nullptr)
9959 : {
9960 1 : CPLError(CE_Failure, CPLE_AppDefined,
9961 : "NULL in 'value' column of glob");
9962 1 : error = true;
9963 1 : break;
9964 : }
9965 4 : if (nFieldType < 0)
9966 1 : nFieldType = OFTString;
9967 4 : poDomain = std::make_unique<OGRGlobFieldDomain>(
9968 4 : name, pszDescription ? pszDescription : "",
9969 12 : static_cast<OGRFieldType>(nFieldType), eSubType, pszValue);
9970 : }
9971 : else
9972 : {
9973 1 : CPLError(CE_Failure, CPLE_AppDefined,
9974 : "Unhandled constraint_type: %s", pszConstraintType);
9975 1 : error = true;
9976 1 : break;
9977 : }
9978 :
9979 58 : osLastConstraintType = pszConstraintType;
9980 : }
9981 :
9982 41 : if (!asValues.empty())
9983 : {
9984 20 : if (nFieldType < 0)
9985 9 : nFieldType = nFieldTypeFromEnumCode;
9986 20 : poDomain = std::make_unique<OGRCodedFieldDomain>(
9987 : name, osConstraintDescription,
9988 40 : static_cast<OGRFieldType>(nFieldType), eSubType,
9989 40 : std::move(asValues));
9990 : }
9991 :
9992 41 : if (error)
9993 : {
9994 4 : return nullptr;
9995 : }
9996 :
9997 37 : m_oMapFieldDomains[name] = std::move(poDomain);
9998 37 : return GDALDataset::GetFieldDomain(name);
9999 : }
10000 :
10001 : /************************************************************************/
10002 : /* AddFieldDomain() */
10003 : /************************************************************************/
10004 :
10005 18 : bool GDALGeoPackageDataset::AddFieldDomain(
10006 : std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
10007 : {
10008 36 : const std::string domainName(domain->GetName());
10009 18 : if (!GetUpdate())
10010 : {
10011 0 : CPLError(CE_Failure, CPLE_NotSupported,
10012 : "AddFieldDomain() not supported on read-only dataset");
10013 0 : return false;
10014 : }
10015 18 : if (GetFieldDomain(domainName) != nullptr)
10016 : {
10017 1 : failureReason = "A domain of identical name already exists";
10018 1 : return false;
10019 : }
10020 17 : if (!CreateColumnsTableAndColumnConstraintsTablesIfNecessary())
10021 0 : return false;
10022 :
10023 17 : const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
10024 17 : const char *min_is_inclusive =
10025 17 : bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
10026 17 : const char *max_is_inclusive =
10027 17 : bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
10028 :
10029 17 : const auto &osDescription = domain->GetDescription();
10030 17 : switch (domain->GetDomainType())
10031 : {
10032 11 : case OFDT_CODED:
10033 : {
10034 : const auto poCodedDomain =
10035 11 : cpl::down_cast<const OGRCodedFieldDomain *>(domain.get());
10036 11 : if (!osDescription.empty())
10037 : {
10038 : // We use a little trick by using a dummy
10039 : // _{domainname}_domain_description enum that has a single
10040 : // entry whose description is the description of the main
10041 : // domain.
10042 1 : char *pszSQL = sqlite3_mprintf(
10043 : "INSERT INTO gpkg_data_column_constraints ("
10044 : "constraint_name, constraint_type, value, "
10045 : "min, %s, max, %s, "
10046 : "description) VALUES ("
10047 : "'_%q_domain_description', 'enum', '', NULL, NULL, NULL, "
10048 : "NULL, %Q)",
10049 : min_is_inclusive, max_is_inclusive, domainName.c_str(),
10050 : osDescription.c_str());
10051 1 : CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
10052 1 : sqlite3_free(pszSQL);
10053 : }
10054 11 : const auto &enumeration = poCodedDomain->GetEnumeration();
10055 33 : for (int i = 0; enumeration[i].pszCode != nullptr; ++i)
10056 : {
10057 22 : char *pszSQL = sqlite3_mprintf(
10058 : "INSERT INTO gpkg_data_column_constraints ("
10059 : "constraint_name, constraint_type, value, "
10060 : "min, %s, max, %s, "
10061 : "description) VALUES ("
10062 : "'%q', 'enum', '%q', NULL, NULL, NULL, NULL, %Q)",
10063 : min_is_inclusive, max_is_inclusive, domainName.c_str(),
10064 22 : enumeration[i].pszCode, enumeration[i].pszValue);
10065 22 : bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
10066 22 : sqlite3_free(pszSQL);
10067 22 : if (!ok)
10068 0 : return false;
10069 : }
10070 11 : break;
10071 : }
10072 :
10073 5 : case OFDT_RANGE:
10074 : {
10075 : const auto poRangeDomain =
10076 5 : cpl::down_cast<const OGRRangeFieldDomain *>(domain.get());
10077 5 : const auto eFieldType = poRangeDomain->GetFieldType();
10078 5 : if (eFieldType != OFTInteger && eFieldType != OFTInteger64 &&
10079 : eFieldType != OFTReal)
10080 : {
10081 : failureReason = "Only range domains of numeric type are "
10082 0 : "supported in GeoPackage";
10083 0 : return false;
10084 : }
10085 :
10086 5 : double dfMin = -std::numeric_limits<double>::infinity();
10087 5 : double dfMax = std::numeric_limits<double>::infinity();
10088 5 : bool bMinIsInclusive = true;
10089 5 : const auto &sMin = poRangeDomain->GetMin(bMinIsInclusive);
10090 5 : bool bMaxIsInclusive = true;
10091 5 : const auto &sMax = poRangeDomain->GetMax(bMaxIsInclusive);
10092 5 : if (eFieldType == OFTInteger)
10093 : {
10094 1 : if (!OGR_RawField_IsUnset(&sMin))
10095 1 : dfMin = sMin.Integer;
10096 1 : if (!OGR_RawField_IsUnset(&sMax))
10097 1 : dfMax = sMax.Integer;
10098 : }
10099 4 : else if (eFieldType == OFTInteger64)
10100 : {
10101 1 : if (!OGR_RawField_IsUnset(&sMin))
10102 1 : dfMin = static_cast<double>(sMin.Integer64);
10103 1 : if (!OGR_RawField_IsUnset(&sMax))
10104 1 : dfMax = static_cast<double>(sMax.Integer64);
10105 : }
10106 : else /* if( eFieldType == OFTReal ) */
10107 : {
10108 3 : if (!OGR_RawField_IsUnset(&sMin))
10109 3 : dfMin = sMin.Real;
10110 3 : if (!OGR_RawField_IsUnset(&sMax))
10111 3 : dfMax = sMax.Real;
10112 : }
10113 :
10114 5 : sqlite3_stmt *hInsertStmt = nullptr;
10115 : const char *pszSQL =
10116 5 : CPLSPrintf("INSERT INTO gpkg_data_column_constraints ("
10117 : "constraint_name, constraint_type, value, "
10118 : "min, %s, max, %s, "
10119 : "description) VALUES ("
10120 : "?, 'range', NULL, ?, ?, ?, ?, ?)",
10121 : min_is_inclusive, max_is_inclusive);
10122 5 : if (SQLPrepareWithError(hDB, pszSQL, -1, &hInsertStmt, nullptr) !=
10123 : SQLITE_OK)
10124 : {
10125 0 : return false;
10126 : }
10127 5 : sqlite3_bind_text(hInsertStmt, 1, domainName.c_str(),
10128 5 : static_cast<int>(domainName.size()),
10129 : SQLITE_TRANSIENT);
10130 5 : sqlite3_bind_double(hInsertStmt, 2, dfMin);
10131 5 : sqlite3_bind_int(hInsertStmt, 3, bMinIsInclusive ? 1 : 0);
10132 5 : sqlite3_bind_double(hInsertStmt, 4, dfMax);
10133 5 : sqlite3_bind_int(hInsertStmt, 5, bMaxIsInclusive ? 1 : 0);
10134 5 : if (osDescription.empty())
10135 : {
10136 3 : sqlite3_bind_null(hInsertStmt, 6);
10137 : }
10138 : else
10139 : {
10140 2 : sqlite3_bind_text(hInsertStmt, 6, osDescription.c_str(),
10141 2 : static_cast<int>(osDescription.size()),
10142 : SQLITE_TRANSIENT);
10143 : }
10144 5 : const int sqlite_err = sqlite3_step(hInsertStmt);
10145 5 : sqlite3_finalize(hInsertStmt);
10146 5 : if (sqlite_err != SQLITE_OK && sqlite_err != SQLITE_DONE)
10147 : {
10148 0 : CPLError(CE_Failure, CPLE_AppDefined,
10149 : "failed to execute insertion '%s': %s", pszSQL,
10150 : sqlite3_errmsg(hDB));
10151 0 : return false;
10152 : }
10153 :
10154 5 : break;
10155 : }
10156 :
10157 1 : case OFDT_GLOB:
10158 : {
10159 : const auto poGlobDomain =
10160 1 : cpl::down_cast<const OGRGlobFieldDomain *>(domain.get());
10161 2 : char *pszSQL = sqlite3_mprintf(
10162 : "INSERT INTO gpkg_data_column_constraints ("
10163 : "constraint_name, constraint_type, value, "
10164 : "min, %s, max, %s, "
10165 : "description) VALUES ("
10166 : "'%q', 'glob', '%q', NULL, NULL, NULL, NULL, %Q)",
10167 : min_is_inclusive, max_is_inclusive, domainName.c_str(),
10168 1 : poGlobDomain->GetGlob().c_str(),
10169 2 : osDescription.empty() ? nullptr : osDescription.c_str());
10170 1 : bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
10171 1 : sqlite3_free(pszSQL);
10172 1 : if (!ok)
10173 0 : return false;
10174 :
10175 1 : break;
10176 : }
10177 : }
10178 :
10179 17 : m_oMapFieldDomains[domainName] = std::move(domain);
10180 17 : return true;
10181 : }
10182 :
10183 : /************************************************************************/
10184 : /* AddRelationship() */
10185 : /************************************************************************/
10186 :
10187 24 : bool GDALGeoPackageDataset::AddRelationship(
10188 : std::unique_ptr<GDALRelationship> &&relationship,
10189 : std::string &failureReason)
10190 : {
10191 24 : if (!GetUpdate())
10192 : {
10193 0 : CPLError(CE_Failure, CPLE_NotSupported,
10194 : "AddRelationship() not supported on read-only dataset");
10195 0 : return false;
10196 : }
10197 :
10198 : const std::string osRelationshipName = GenerateNameForRelationship(
10199 24 : relationship->GetLeftTableName().c_str(),
10200 24 : relationship->GetRightTableName().c_str(),
10201 96 : relationship->GetRelatedTableType().c_str());
10202 : // sanity checks
10203 24 : if (GetRelationship(osRelationshipName) != nullptr)
10204 : {
10205 1 : failureReason = "A relationship of identical name already exists";
10206 1 : return false;
10207 : }
10208 :
10209 23 : if (!ValidateRelationship(relationship.get(), failureReason))
10210 : {
10211 14 : return false;
10212 : }
10213 :
10214 9 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
10215 : {
10216 0 : return false;
10217 : }
10218 9 : if (!CreateRelationsTableIfNecessary())
10219 : {
10220 0 : failureReason = "Could not create gpkgext_relations table";
10221 0 : return false;
10222 : }
10223 9 : if (SQLGetInteger(GetDB(),
10224 : "SELECT 1 FROM gpkg_extensions WHERE "
10225 : "table_name = 'gpkgext_relations'",
10226 9 : nullptr) != 1)
10227 : {
10228 4 : if (OGRERR_NONE !=
10229 4 : SQLCommand(
10230 : GetDB(),
10231 : "INSERT INTO gpkg_extensions "
10232 : "(table_name,column_name,extension_name,definition,scope) "
10233 : "VALUES ('gpkgext_relations', NULL, 'gpkg_related_tables', "
10234 : "'http://www.geopackage.org/18-000.html', "
10235 : "'read-write')"))
10236 : {
10237 : failureReason =
10238 0 : "Could not create gpkg_extensions entry for gpkgext_relations";
10239 0 : return false;
10240 : }
10241 : }
10242 :
10243 9 : const std::string &osLeftTableName = relationship->GetLeftTableName();
10244 9 : const std::string &osRightTableName = relationship->GetRightTableName();
10245 9 : const auto &aosLeftTableFields = relationship->GetLeftTableFields();
10246 9 : const auto &aosRightTableFields = relationship->GetRightTableFields();
10247 :
10248 18 : std::string osRelatedTableType = relationship->GetRelatedTableType();
10249 9 : if (osRelatedTableType.empty())
10250 : {
10251 5 : osRelatedTableType = "features";
10252 : }
10253 :
10254 : // generate mapping table if not set
10255 18 : CPLString osMappingTableName = relationship->GetMappingTableName();
10256 9 : if (osMappingTableName.empty())
10257 : {
10258 3 : int nIndex = 1;
10259 3 : osMappingTableName = osLeftTableName + "_" + osRightTableName;
10260 3 : while (FindLayerIndex(osMappingTableName.c_str()) >= 0)
10261 : {
10262 0 : nIndex += 1;
10263 : osMappingTableName.Printf("%s_%s_%d", osLeftTableName.c_str(),
10264 0 : osRightTableName.c_str(), nIndex);
10265 : }
10266 :
10267 : // determine whether base/related keys are unique
10268 3 : bool bBaseKeyIsUnique = false;
10269 : {
10270 : const std::set<std::string> uniqueBaseFieldsUC =
10271 : SQLGetUniqueFieldUCConstraints(GetDB(),
10272 6 : osLeftTableName.c_str());
10273 6 : if (uniqueBaseFieldsUC.find(
10274 3 : CPLString(aosLeftTableFields[0]).toupper()) !=
10275 6 : uniqueBaseFieldsUC.end())
10276 : {
10277 2 : bBaseKeyIsUnique = true;
10278 : }
10279 : }
10280 3 : bool bRelatedKeyIsUnique = false;
10281 : {
10282 : const std::set<std::string> uniqueRelatedFieldsUC =
10283 : SQLGetUniqueFieldUCConstraints(GetDB(),
10284 6 : osRightTableName.c_str());
10285 6 : if (uniqueRelatedFieldsUC.find(
10286 3 : CPLString(aosRightTableFields[0]).toupper()) !=
10287 6 : uniqueRelatedFieldsUC.end())
10288 : {
10289 2 : bRelatedKeyIsUnique = true;
10290 : }
10291 : }
10292 :
10293 : // create mapping table
10294 :
10295 3 : std::string osBaseIdDefinition = "base_id INTEGER";
10296 3 : if (bBaseKeyIsUnique)
10297 : {
10298 2 : char *pszSQL = sqlite3_mprintf(
10299 : " CONSTRAINT 'fk_base_id_%q' REFERENCES \"%w\"(\"%w\") ON "
10300 : "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
10301 : "DEFERRED",
10302 : osMappingTableName.c_str(), osLeftTableName.c_str(),
10303 2 : aosLeftTableFields[0].c_str());
10304 2 : osBaseIdDefinition += pszSQL;
10305 2 : sqlite3_free(pszSQL);
10306 : }
10307 :
10308 3 : std::string osRelatedIdDefinition = "related_id INTEGER";
10309 3 : if (bRelatedKeyIsUnique)
10310 : {
10311 2 : char *pszSQL = sqlite3_mprintf(
10312 : " CONSTRAINT 'fk_related_id_%q' REFERENCES \"%w\"(\"%w\") ON "
10313 : "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
10314 : "DEFERRED",
10315 : osMappingTableName.c_str(), osRightTableName.c_str(),
10316 2 : aosRightTableFields[0].c_str());
10317 2 : osRelatedIdDefinition += pszSQL;
10318 2 : sqlite3_free(pszSQL);
10319 : }
10320 :
10321 3 : char *pszSQL = sqlite3_mprintf("CREATE TABLE \"%w\" ("
10322 : "id INTEGER PRIMARY KEY AUTOINCREMENT, "
10323 : "%s, %s);",
10324 : osMappingTableName.c_str(),
10325 : osBaseIdDefinition.c_str(),
10326 : osRelatedIdDefinition.c_str());
10327 3 : OGRErr eErr = SQLCommand(hDB, pszSQL);
10328 3 : sqlite3_free(pszSQL);
10329 3 : if (eErr != OGRERR_NONE)
10330 : {
10331 : failureReason =
10332 0 : ("Could not create mapping table " + osMappingTableName)
10333 0 : .c_str();
10334 0 : return false;
10335 : }
10336 :
10337 : /*
10338 : * Strictly speaking we should NOT be inserting the mapping table into gpkg_contents.
10339 : * The related tables extension explicitly states that the mapping table should only be
10340 : * in the gpkgext_relations table and not in gpkg_contents. (See also discussion at
10341 : * https://github.com/opengeospatial/geopackage/issues/679).
10342 : *
10343 : * However, if we don't insert the mapping table into gpkg_contents then it is no longer
10344 : * visible to some clients (eg ESRI software only allows opening tables that are present
10345 : * in gpkg_contents). So we'll do this anyway, for maximum compatibility and flexibility.
10346 : *
10347 : * More related discussion is at https://github.com/OSGeo/gdal/pull/9258
10348 : */
10349 3 : pszSQL = sqlite3_mprintf(
10350 : "INSERT INTO gpkg_contents "
10351 : "(table_name,data_type,identifier,description,last_change,srs_id) "
10352 : "VALUES "
10353 : "('%q','attributes','%q','Mapping table for relationship between "
10354 : "%q and %q',%s,0)",
10355 : osMappingTableName.c_str(), /*table_name*/
10356 : osMappingTableName.c_str(), /*identifier*/
10357 : osLeftTableName.c_str(), /*description left table name*/
10358 : osRightTableName.c_str(), /*description right table name*/
10359 6 : GDALGeoPackageDataset::GetCurrentDateEscapedSQL().c_str());
10360 :
10361 : // Note -- we explicitly ignore failures here, because hey, we aren't really
10362 : // supposed to be adding this table to gpkg_contents anyway!
10363 3 : (void)SQLCommand(hDB, pszSQL);
10364 3 : sqlite3_free(pszSQL);
10365 :
10366 3 : pszSQL = sqlite3_mprintf(
10367 : "CREATE INDEX \"idx_%w_base_id\" ON \"%w\" (base_id);",
10368 : osMappingTableName.c_str(), osMappingTableName.c_str());
10369 3 : eErr = SQLCommand(hDB, pszSQL);
10370 3 : sqlite3_free(pszSQL);
10371 3 : if (eErr != OGRERR_NONE)
10372 : {
10373 0 : failureReason = ("Could not create index for " +
10374 0 : osMappingTableName + " (base_id)")
10375 0 : .c_str();
10376 0 : return false;
10377 : }
10378 :
10379 3 : pszSQL = sqlite3_mprintf(
10380 : "CREATE INDEX \"idx_%qw_related_id\" ON \"%w\" (related_id);",
10381 : osMappingTableName.c_str(), osMappingTableName.c_str());
10382 3 : eErr = SQLCommand(hDB, pszSQL);
10383 3 : sqlite3_free(pszSQL);
10384 3 : if (eErr != OGRERR_NONE)
10385 : {
10386 0 : failureReason = ("Could not create index for " +
10387 0 : osMappingTableName + " (related_id)")
10388 0 : .c_str();
10389 0 : return false;
10390 : }
10391 : }
10392 : else
10393 : {
10394 : // validate mapping table structure
10395 6 : if (OGRGeoPackageTableLayer *poLayer =
10396 6 : cpl::down_cast<OGRGeoPackageTableLayer *>(
10397 6 : GetLayerByName(osMappingTableName)))
10398 : {
10399 4 : if (poLayer->GetLayerDefn()->GetFieldIndex("base_id") < 0)
10400 : {
10401 : failureReason =
10402 2 : ("Field base_id must exist in " + osMappingTableName)
10403 1 : .c_str();
10404 1 : return false;
10405 : }
10406 3 : if (poLayer->GetLayerDefn()->GetFieldIndex("related_id") < 0)
10407 : {
10408 : failureReason =
10409 2 : ("Field related_id must exist in " + osMappingTableName)
10410 1 : .c_str();
10411 1 : return false;
10412 : }
10413 : }
10414 : else
10415 : {
10416 : failureReason =
10417 2 : ("Could not retrieve table " + osMappingTableName).c_str();
10418 2 : return false;
10419 : }
10420 : }
10421 :
10422 5 : char *pszSQL = sqlite3_mprintf(
10423 : "INSERT INTO gpkg_extensions "
10424 : "(table_name,column_name,extension_name,definition,scope) "
10425 : "VALUES ('%q', NULL, 'gpkg_related_tables', "
10426 : "'http://www.geopackage.org/18-000.html', "
10427 : "'read-write')",
10428 : osMappingTableName.c_str());
10429 5 : OGRErr eErr = SQLCommand(hDB, pszSQL);
10430 5 : sqlite3_free(pszSQL);
10431 5 : if (eErr != OGRERR_NONE)
10432 : {
10433 0 : failureReason = ("Could not insert mapping table " +
10434 0 : osMappingTableName + " into gpkg_extensions")
10435 0 : .c_str();
10436 0 : return false;
10437 : }
10438 :
10439 15 : pszSQL = sqlite3_mprintf(
10440 : "INSERT INTO gpkgext_relations "
10441 : "(base_table_name,base_primary_column,related_table_name,related_"
10442 : "primary_column,relation_name,mapping_table_name) "
10443 : "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
10444 5 : osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
10445 5 : osRightTableName.c_str(), aosRightTableFields[0].c_str(),
10446 : osRelatedTableType.c_str(), osMappingTableName.c_str());
10447 5 : eErr = SQLCommand(hDB, pszSQL);
10448 5 : sqlite3_free(pszSQL);
10449 5 : if (eErr != OGRERR_NONE)
10450 : {
10451 0 : failureReason = "Could not insert relationship into gpkgext_relations";
10452 0 : return false;
10453 : }
10454 :
10455 5 : ClearCachedRelationships();
10456 5 : LoadRelationships();
10457 5 : return true;
10458 : }
10459 :
10460 : /************************************************************************/
10461 : /* DeleteRelationship() */
10462 : /************************************************************************/
10463 :
10464 4 : bool GDALGeoPackageDataset::DeleteRelationship(const std::string &name,
10465 : std::string &failureReason)
10466 : {
10467 4 : if (eAccess != GA_Update)
10468 : {
10469 0 : CPLError(CE_Failure, CPLE_NotSupported,
10470 : "DeleteRelationship() not supported on read-only dataset");
10471 0 : return false;
10472 : }
10473 :
10474 : // ensure relationships are up to date before we try to remove one
10475 4 : ClearCachedRelationships();
10476 4 : LoadRelationships();
10477 :
10478 8 : std::string osMappingTableName;
10479 : {
10480 4 : const GDALRelationship *poRelationship = GetRelationship(name);
10481 4 : if (poRelationship == nullptr)
10482 : {
10483 1 : failureReason = "Could not find relationship with name " + name;
10484 1 : return false;
10485 : }
10486 :
10487 3 : osMappingTableName = poRelationship->GetMappingTableName();
10488 : }
10489 :
10490 : // DeleteLayerCommon will delete existing relationship objects, so we can't
10491 : // refer to poRelationship or any of its members previously obtained here
10492 3 : if (DeleteLayerCommon(osMappingTableName.c_str()) != OGRERR_NONE)
10493 : {
10494 : failureReason =
10495 0 : "Could not remove mapping layer name " + osMappingTableName;
10496 :
10497 : // relationships may have been left in an inconsistent state -- reload
10498 : // them now
10499 0 : ClearCachedRelationships();
10500 0 : LoadRelationships();
10501 0 : return false;
10502 : }
10503 :
10504 3 : ClearCachedRelationships();
10505 3 : LoadRelationships();
10506 3 : return true;
10507 : }
10508 :
10509 : /************************************************************************/
10510 : /* UpdateRelationship() */
10511 : /************************************************************************/
10512 :
10513 6 : bool GDALGeoPackageDataset::UpdateRelationship(
10514 : std::unique_ptr<GDALRelationship> &&relationship,
10515 : std::string &failureReason)
10516 : {
10517 6 : if (eAccess != GA_Update)
10518 : {
10519 0 : CPLError(CE_Failure, CPLE_NotSupported,
10520 : "UpdateRelationship() not supported on read-only dataset");
10521 0 : return false;
10522 : }
10523 :
10524 : // ensure relationships are up to date before we try to update one
10525 6 : ClearCachedRelationships();
10526 6 : LoadRelationships();
10527 :
10528 6 : const std::string &osRelationshipName = relationship->GetName();
10529 6 : const std::string &osLeftTableName = relationship->GetLeftTableName();
10530 6 : const std::string &osRightTableName = relationship->GetRightTableName();
10531 6 : const std::string &osMappingTableName = relationship->GetMappingTableName();
10532 6 : const auto &aosLeftTableFields = relationship->GetLeftTableFields();
10533 6 : const auto &aosRightTableFields = relationship->GetRightTableFields();
10534 :
10535 : // sanity checks
10536 : {
10537 : const GDALRelationship *poExistingRelationship =
10538 6 : GetRelationship(osRelationshipName);
10539 6 : if (poExistingRelationship == nullptr)
10540 : {
10541 : failureReason =
10542 1 : "The relationship should already exist to be updated";
10543 1 : return false;
10544 : }
10545 :
10546 5 : if (!ValidateRelationship(relationship.get(), failureReason))
10547 : {
10548 2 : return false;
10549 : }
10550 :
10551 : // we don't permit changes to the participating tables
10552 3 : if (osLeftTableName != poExistingRelationship->GetLeftTableName())
10553 : {
10554 0 : failureReason = ("Cannot change base table from " +
10555 0 : poExistingRelationship->GetLeftTableName() +
10556 0 : " to " + osLeftTableName)
10557 0 : .c_str();
10558 0 : return false;
10559 : }
10560 3 : if (osRightTableName != poExistingRelationship->GetRightTableName())
10561 : {
10562 0 : failureReason = ("Cannot change related table from " +
10563 0 : poExistingRelationship->GetRightTableName() +
10564 0 : " to " + osRightTableName)
10565 0 : .c_str();
10566 0 : return false;
10567 : }
10568 3 : if (osMappingTableName != poExistingRelationship->GetMappingTableName())
10569 : {
10570 0 : failureReason = ("Cannot change mapping table from " +
10571 0 : poExistingRelationship->GetMappingTableName() +
10572 0 : " to " + osMappingTableName)
10573 0 : .c_str();
10574 0 : return false;
10575 : }
10576 : }
10577 :
10578 6 : std::string osRelatedTableType = relationship->GetRelatedTableType();
10579 3 : if (osRelatedTableType.empty())
10580 : {
10581 0 : osRelatedTableType = "features";
10582 : }
10583 :
10584 3 : char *pszSQL = sqlite3_mprintf(
10585 : "DELETE FROM gpkgext_relations WHERE mapping_table_name='%q'",
10586 : osMappingTableName.c_str());
10587 3 : OGRErr eErr = SQLCommand(hDB, pszSQL);
10588 3 : sqlite3_free(pszSQL);
10589 3 : if (eErr != OGRERR_NONE)
10590 : {
10591 : failureReason =
10592 0 : "Could not delete old relationship from gpkgext_relations";
10593 0 : return false;
10594 : }
10595 :
10596 9 : pszSQL = sqlite3_mprintf(
10597 : "INSERT INTO gpkgext_relations "
10598 : "(base_table_name,base_primary_column,related_table_name,related_"
10599 : "primary_column,relation_name,mapping_table_name) "
10600 : "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
10601 3 : osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
10602 3 : osRightTableName.c_str(), aosRightTableFields[0].c_str(),
10603 : osRelatedTableType.c_str(), osMappingTableName.c_str());
10604 3 : eErr = SQLCommand(hDB, pszSQL);
10605 3 : sqlite3_free(pszSQL);
10606 3 : if (eErr != OGRERR_NONE)
10607 : {
10608 : failureReason =
10609 0 : "Could not insert updated relationship into gpkgext_relations";
10610 0 : return false;
10611 : }
10612 :
10613 3 : ClearCachedRelationships();
10614 3 : LoadRelationships();
10615 3 : return true;
10616 : }
10617 :
10618 : /************************************************************************/
10619 : /* GetSqliteMasterContent() */
10620 : /************************************************************************/
10621 :
10622 : const std::vector<SQLSqliteMasterContent> &
10623 2 : GDALGeoPackageDataset::GetSqliteMasterContent()
10624 : {
10625 2 : if (m_aoSqliteMasterContent.empty())
10626 : {
10627 : auto oResultTable =
10628 2 : SQLQuery(hDB, "SELECT sql, type, tbl_name FROM sqlite_master");
10629 1 : if (oResultTable)
10630 : {
10631 58 : for (int rowCnt = 0; rowCnt < oResultTable->RowCount(); ++rowCnt)
10632 : {
10633 114 : SQLSqliteMasterContent row;
10634 57 : const char *pszSQL = oResultTable->GetValue(0, rowCnt);
10635 57 : row.osSQL = pszSQL ? pszSQL : "";
10636 57 : const char *pszType = oResultTable->GetValue(1, rowCnt);
10637 57 : row.osType = pszType ? pszType : "";
10638 57 : const char *pszTableName = oResultTable->GetValue(2, rowCnt);
10639 57 : row.osTableName = pszTableName ? pszTableName : "";
10640 57 : m_aoSqliteMasterContent.emplace_back(std::move(row));
10641 : }
10642 : }
10643 : }
10644 2 : return m_aoSqliteMasterContent;
10645 : }
|