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 564 : GetTilingScheme(const char *pszName)
82 : {
83 564 : if (EQUAL(pszName, "CUSTOM"))
84 436 : 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 865 : OGRErr GDALGeoPackageDataset::SetApplicationAndUserVersionId()
191 : {
192 865 : CPLAssert(hDB != nullptr);
193 :
194 865 : const CPLString osPragma(CPLString().Printf("PRAGMA application_id = %u;"
195 : "PRAGMA user_version = %u",
196 : m_nApplicationId,
197 1730 : m_nUserVersion));
198 1730 : return SQLCommand(hDB, osPragma.c_str());
199 : }
200 :
201 2438 : bool GDALGeoPackageDataset::CloseDB()
202 : {
203 2438 : OGRSQLiteUnregisterSQLFunctions(m_pSQLFunctionData);
204 2438 : m_pSQLFunctionData = nullptr;
205 2438 : 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 802 : static OGRErr GDALGPKGImportFromEPSG(OGRSpatialReference *poSpatialRef,
222 : int nEPSGCode)
223 : {
224 802 : CPLPushErrorHandler(CPLQuietErrorHandler);
225 802 : const OGRErr eErr = poSpatialRef->importFromEPSG(nEPSGCode);
226 802 : CPLPopErrorHandler();
227 802 : CPLErrorReset();
228 802 : return eErr;
229 : }
230 :
231 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
232 1208 : GDALGeoPackageDataset::GetSpatialRef(int iSrsId, bool bFallbackToEPSG,
233 : bool bEmitErrorIfNotFound)
234 : {
235 1208 : const auto oIter = m_oMapSrsIdToSrs.find(iSrsId);
236 1208 : if (oIter != m_oMapSrsIdToSrs.end())
237 : {
238 88 : if (oIter->second == nullptr)
239 31 : return nullptr;
240 57 : oIter->second->Reference();
241 : return std::unique_ptr<OGRSpatialReference,
242 57 : OGRSpatialReferenceReleaser>(oIter->second);
243 : }
244 :
245 1120 : 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 2000 : CPLString oSQL;
270 1000 : 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 1000 : m_bHasDefinition12_063 ? ", definition_12_063" : "",
275 1000 : m_bHasEpochColumn ? ", epoch" : "", iSrsId);
276 :
277 2000 : auto oResult = SQLQuery(hDB, oSQL.c_str());
278 :
279 1000 : 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 988 : const char *pszName = oResult->GetValue(0, 0);
306 988 : if (pszName && EQUAL(pszName, "Undefined SRS"))
307 : {
308 408 : m_oMapSrsIdToSrs[iSrsId] = nullptr;
309 408 : return nullptr;
310 : }
311 580 : const char *pszWkt = oResult->GetValue(1, 0);
312 580 : if (pszWkt == nullptr)
313 0 : return nullptr;
314 580 : const char *pszOrganization = oResult->GetValue(2, 0);
315 580 : const char *pszOrganizationCoordsysID = oResult->GetValue(3, 0);
316 : const char *pszWkt2 =
317 580 : m_bHasDefinition12_063 ? oResult->GetValue(4, 0) : nullptr;
318 580 : if (pszWkt2 && !EQUAL(pszWkt2, "undefined"))
319 76 : pszWkt = pszWkt2;
320 : const char *pszCoordinateEpoch =
321 580 : m_bHasEpochColumn ? oResult->GetValue(5, 0) : nullptr;
322 : const double dfCoordinateEpoch =
323 580 : pszCoordinateEpoch ? CPLAtof(pszCoordinateEpoch) : 0.0;
324 :
325 580 : OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
326 580 : poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
327 : // Try to import first from EPSG code, and then from WKT
328 580 : if (!(pszOrganization && pszOrganizationCoordsysID &&
329 580 : EQUAL(pszOrganization, "EPSG") &&
330 560 : (atoi(pszOrganizationCoordsysID) == iSrsId ||
331 4 : (dfCoordinateEpoch > 0 && strstr(pszWkt, "DYNAMIC[") == nullptr)) &&
332 560 : GDALGPKGImportFromEPSG(
333 1160 : 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 580 : poSpatialRef->StripTOWGS84IfKnownDatumAndAllowed();
345 580 : poSpatialRef->SetCoordinateEpoch(dfCoordinateEpoch);
346 580 : m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
347 580 : poSpatialRef->Reference();
348 : return std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>(
349 580 : poSpatialRef);
350 : }
351 :
352 271 : const char *GDALGeoPackageDataset::GetSrsName(const OGRSpatialReference &oSRS)
353 : {
354 271 : const char *pszName = oSRS.GetName();
355 271 : if (pszName)
356 271 : 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 850 : int GDALGeoPackageDataset::GetSrsId(const OGRSpatialReference *poSRSIn)
529 : {
530 850 : const char *pszName = poSRSIn ? poSRSIn->GetName() : nullptr;
531 1244 : if (!poSRSIn || poSRSIn->IsEmpty() ||
532 394 : (pszName && EQUAL(pszName, "Undefined SRS")))
533 : {
534 458 : OGRErr err = OGRERR_NONE;
535 458 : 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 458 : if (err == OGRERR_NONE)
541 55 : 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 403 : 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 402 : 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 403 : if (SQLCommand(hDB, pszSQL) == OGRERR_NONE)
585 403 : return UNDEFINED_CRS_SRS_ID;
586 : #undef UNDEFINED_CRS_SRS_ID
587 : #undef XSTRINGIFY
588 : #undef STRINGIFY
589 0 : return -1;
590 : }
591 :
592 784 : std::unique_ptr<OGRSpatialReference> poSRS(poSRSIn->Clone());
593 :
594 392 : 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 389 : const char *pszAuthorityName = poSRS->GetAuthorityName(nullptr);
608 :
609 389 : 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 389 : char *pszSQL = nullptr;
633 389 : int nSRSId = DEFAULT_SRID;
634 389 : int nAuthorityCode = 0;
635 389 : OGRErr err = OGRERR_NONE;
636 389 : bool bCanUseAuthorityCode = false;
637 389 : const char *const apszIsSameOptions[] = {
638 : "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES",
639 : "IGNORE_COORDINATE_EPOCH=YES", nullptr};
640 389 : if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0)
641 : {
642 363 : const char *pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
643 363 : if (pszAuthorityCode)
644 : {
645 363 : if (CPLGetValueType(pszAuthorityCode) == CPL_VALUE_INTEGER)
646 : {
647 363 : 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 752 : if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
662 363 : poSRSIn->GetCoordinateEpoch() == 0)
663 : {
664 : pszSQL =
665 358 : 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 358 : nSRSId = SQLGetInteger(hDB, pszSQL, &err);
671 358 : sqlite3_free(pszSQL);
672 :
673 : // Got a match? Return it!
674 358 : if (OGRERR_NONE == err)
675 : {
676 114 : auto poRefSRS = GetSpatialRef(nSRSId);
677 : bool bOK =
678 114 : (poRefSRS == nullptr ||
679 115 : poSRS->IsSame(poRefSRS.get(), apszIsSameOptions) ||
680 1 : !CPLTestBool(CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")));
681 114 : if (bOK)
682 : {
683 113 : 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 276 : CPLCharUniquePtr pszWKT1;
701 276 : CPLCharUniquePtr pszWKT2_2015;
702 276 : CPLCharUniquePtr pszWKT2_2019;
703 276 : const char *const apszOptionsWkt1[] = {"FORMAT=WKT1_GDAL", nullptr};
704 276 : const char *const apszOptionsWkt2_2015[] = {"FORMAT=WKT2_2015", nullptr};
705 276 : const char *const apszOptionsWkt2_2019[] = {"FORMAT=WKT2_2019", nullptr};
706 :
707 552 : std::string osEpochTest;
708 276 : if (poSRSIn->GetCoordinateEpoch() > 0 && m_bHasEpochColumn)
709 : {
710 : osEpochTest =
711 3 : CPLSPrintf(" AND epoch = %.17g", poSRSIn->GetCoordinateEpoch());
712 : }
713 :
714 276 : if (!(poSRS->IsGeographic() && poSRS->GetAxesCount() == 3))
715 : {
716 267 : char *pszTmp = nullptr;
717 267 : poSRS->exportToWkt(&pszTmp, apszOptionsWkt1);
718 267 : pszWKT1.reset(pszTmp);
719 267 : if (pszWKT1 && pszWKT1.get()[0] == '\0')
720 : {
721 0 : pszWKT1.reset();
722 : }
723 : }
724 : {
725 276 : char *pszTmp = nullptr;
726 276 : poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2015);
727 276 : pszWKT2_2015.reset(pszTmp);
728 276 : if (pszWKT2_2015 && pszWKT2_2015.get()[0] == '\0')
729 : {
730 0 : pszWKT2_2015.reset();
731 : }
732 : }
733 : {
734 276 : char *pszTmp = nullptr;
735 276 : poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2019);
736 276 : pszWKT2_2019.reset(pszTmp);
737 276 : if (pszWKT2_2019 && pszWKT2_2019.get()[0] == '\0')
738 : {
739 0 : pszWKT2_2019.reset();
740 : }
741 : }
742 :
743 276 : if (!pszWKT1 && !pszWKT2_2015 && !pszWKT2_2019)
744 : {
745 0 : return DEFAULT_SRID;
746 : }
747 :
748 276 : if (poSRSIn->GetCoordinateEpoch() == 0 || m_bHasEpochColumn)
749 : {
750 : // Search if there is already an existing entry with this WKT
751 273 : 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 231 : else if (pszWKT1)
774 : {
775 : pszSQL =
776 228 : 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 273 : if (pszSQL)
785 : {
786 270 : nSRSId = SQLGetInteger(hDB, pszSQL, &err);
787 270 : sqlite3_free(pszSQL);
788 270 : if (OGRERR_NONE == err)
789 : {
790 5 : return nSRSId;
791 : }
792 : }
793 : }
794 :
795 518 : if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
796 247 : poSRSIn->GetCoordinateEpoch() == 0)
797 : {
798 243 : bool bTryToReuseSRSId = true;
799 243 : if (EQUAL(pszAuthorityName, "EPSG"))
800 : {
801 484 : OGRSpatialReference oSRS_EPSG;
802 242 : if (GDALGPKGImportFromEPSG(&oSRS_EPSG, nAuthorityCode) ==
803 : OGRERR_NONE)
804 : {
805 243 : 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 243 : if (bTryToReuseSRSId)
823 : {
824 : // No match, but maybe we can use the nAuthorityCode as the nSRSId?
825 242 : pszSQL = sqlite3_mprintf(
826 : "SELECT Count(*) FROM gpkg_spatial_ref_sys WHERE "
827 : "srs_id = %d",
828 : nAuthorityCode);
829 :
830 : // Yep, we can!
831 242 : if (SQLGetInteger(hDB, pszSQL, nullptr) == 0)
832 241 : bCanUseAuthorityCode = true;
833 242 : sqlite3_free(pszSQL);
834 : }
835 : }
836 :
837 271 : bool bConvertGpkgSpatialRefSysToExtensionWkt2 = false;
838 271 : bool bForceEpoch = false;
839 274 : 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 271 : 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 277 : 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 271 : if (bCanUseAuthorityCode)
892 : {
893 241 : 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 542 : std::string osEpochColumn;
905 271 : std::string osEpochVal;
906 271 : 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 271 : 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 226 : if (pszAuthorityName != nullptr && nAuthorityCode > 0)
950 : {
951 426 : 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 213 : GetSrsName(*poSRS), nSRSId, pszAuthorityName, nAuthorityCode,
956 426 : 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 271 : CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
971 :
972 : // Free everything that was allocated.
973 271 : sqlite3_free(pszSQL);
974 :
975 271 : return nSRSId;
976 : }
977 :
978 : /************************************************************************/
979 : /* ~GDALGeoPackageDataset() */
980 : /************************************************************************/
981 :
982 4854 : GDALGeoPackageDataset::~GDALGeoPackageDataset()
983 : {
984 2427 : GDALGeoPackageDataset::Close();
985 4854 : }
986 :
987 : /************************************************************************/
988 : /* Close() */
989 : /************************************************************************/
990 :
991 4087 : CPLErr GDALGeoPackageDataset::Close()
992 : {
993 4087 : CPLErr eErr = CE_None;
994 4087 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
995 : {
996 1421 : if (eAccess == GA_Update && m_poParentDS == nullptr &&
997 3848 : !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 2427 : 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 4245 : for (int i = 0; i < nBands; i++)
1012 1818 : delete papoBands[i];
1013 2427 : nBands = 0;
1014 2427 : CPLFree(papoBands);
1015 2427 : papoBands = nullptr;
1016 :
1017 : // Destroy overviews before cleaning m_hTempDB as they could still
1018 : // need it
1019 2427 : m_apoOverviewDS.clear();
1020 :
1021 2427 : if (m_poParentDS)
1022 : {
1023 325 : hDB = nullptr;
1024 : }
1025 :
1026 2427 : m_apoLayers.clear();
1027 :
1028 : std::map<int, OGRSpatialReference *>::iterator oIter =
1029 2427 : m_oMapSrsIdToSrs.begin();
1030 3537 : for (; oIter != m_oMapSrsIdToSrs.end(); ++oIter)
1031 : {
1032 1110 : OGRSpatialReference *poSRS = oIter->second;
1033 1110 : if (poSRS)
1034 700 : poSRS->Release();
1035 : }
1036 :
1037 2427 : if (!CloseDB())
1038 0 : eErr = CE_Failure;
1039 :
1040 2427 : if (OGRSQLiteBaseDataSource::Close() != CE_None)
1041 0 : eErr = CE_Failure;
1042 : }
1043 4087 : 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 3954 : static int GetOGRTableLimit()
1138 : {
1139 3954 : return atoi(CPLGetConfigOption("OGR_TABLE_LIMIT", "10000"));
1140 : }
1141 :
1142 : /************************************************************************/
1143 : /* GetNameTypeMapFromSQliteMaster() */
1144 : /************************************************************************/
1145 :
1146 : const std::map<CPLString, CPLString> &
1147 1227 : GDALGeoPackageDataset::GetNameTypeMapFromSQliteMaster()
1148 : {
1149 1227 : if (!m_oMapNameToType.empty())
1150 337 : return m_oMapNameToType;
1151 :
1152 : CPLString osSQL(
1153 : "SELECT name, type FROM sqlite_master WHERE "
1154 : "type IN ('view', 'table') OR "
1155 1780 : "(name LIKE 'trigger_%_feature_count_%' AND type = 'trigger')");
1156 890 : const int nTableLimit = GetOGRTableLimit();
1157 890 : if (nTableLimit > 0)
1158 : {
1159 890 : osSQL += " LIMIT ";
1160 890 : osSQL += CPLSPrintf("%d", 1 + 3 * nTableLimit);
1161 : }
1162 :
1163 890 : auto oResult = SQLQuery(hDB, osSQL);
1164 890 : if (oResult)
1165 : {
1166 14877 : for (int i = 0; i < oResult->RowCount(); i++)
1167 : {
1168 13987 : const char *pszName = oResult->GetValue(0, i);
1169 13987 : const char *pszType = oResult->GetValue(1, i);
1170 13987 : m_oMapNameToType[CPLString(pszName).toupper()] = pszType;
1171 : }
1172 : }
1173 :
1174 890 : return m_oMapNameToType;
1175 : }
1176 :
1177 : /************************************************************************/
1178 : /* RemoveTableFromSQLiteMasterCache() */
1179 : /************************************************************************/
1180 :
1181 55 : void GDALGeoPackageDataset::RemoveTableFromSQLiteMasterCache(
1182 : const char *pszTableName)
1183 : {
1184 55 : m_oMapNameToType.erase(CPLString(pszTableName).toupper());
1185 55 : }
1186 :
1187 : /************************************************************************/
1188 : /* GetUnknownExtensionsTableSpecific() */
1189 : /************************************************************************/
1190 :
1191 : const std::map<CPLString, std::vector<GPKGExtensionDesc>> &
1192 848 : GDALGeoPackageDataset::GetUnknownExtensionsTableSpecific()
1193 : {
1194 848 : if (m_bMapTableToExtensionsBuilt)
1195 89 : return m_oMapTableToExtensions;
1196 759 : m_bMapTableToExtensionsBuilt = true;
1197 :
1198 759 : if (!HasExtensionsTable())
1199 40 : 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 1438 : ")");
1221 719 : const int nTableLimit = GetOGRTableLimit();
1222 719 : if (nTableLimit > 0)
1223 : {
1224 719 : osSQL += " LIMIT ";
1225 719 : osSQL += CPLSPrintf("%d", 1 + 10 * nTableLimit);
1226 : }
1227 :
1228 719 : auto oResult = SQLQuery(hDB, osSQL);
1229 719 : if (oResult)
1230 : {
1231 1374 : for (int i = 0; i < oResult->RowCount(); i++)
1232 : {
1233 655 : const char *pszTableName = oResult->GetValue(0, i);
1234 655 : const char *pszExtensionName = oResult->GetValue(1, i);
1235 655 : const char *pszDefinition = oResult->GetValue(2, i);
1236 655 : const char *pszScope = oResult->GetValue(3, i);
1237 655 : if (pszTableName && pszExtensionName && pszDefinition && pszScope)
1238 : {
1239 655 : GPKGExtensionDesc oDesc;
1240 655 : oDesc.osExtensionName = pszExtensionName;
1241 655 : oDesc.osDefinition = pszDefinition;
1242 655 : oDesc.osScope = pszScope;
1243 1310 : m_oMapTableToExtensions[CPLString(pszTableName).toupper()]
1244 655 : .push_back(std::move(oDesc));
1245 : }
1246 : }
1247 : }
1248 :
1249 719 : return m_oMapTableToExtensions;
1250 : }
1251 :
1252 : /************************************************************************/
1253 : /* GetContents() */
1254 : /************************************************************************/
1255 :
1256 : const std::map<CPLString, GPKGContentsDesc> &
1257 830 : GDALGeoPackageDataset::GetContents()
1258 : {
1259 830 : if (m_bMapTableToContentsBuilt)
1260 73 : return m_oMapTableToContents;
1261 757 : m_bMapTableToContentsBuilt = true;
1262 :
1263 : CPLString osSQL("SELECT table_name, data_type, identifier, "
1264 : "description, min_x, min_y, max_x, max_y "
1265 1514 : "FROM gpkg_contents");
1266 757 : const int nTableLimit = GetOGRTableLimit();
1267 757 : if (nTableLimit > 0)
1268 : {
1269 757 : osSQL += " LIMIT ";
1270 757 : osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1271 : }
1272 :
1273 757 : auto oResult = SQLQuery(hDB, osSQL);
1274 757 : if (oResult)
1275 : {
1276 1632 : for (int i = 0; i < oResult->RowCount(); i++)
1277 : {
1278 875 : const char *pszTableName = oResult->GetValue(0, i);
1279 875 : if (pszTableName == nullptr)
1280 0 : continue;
1281 875 : const char *pszDataType = oResult->GetValue(1, i);
1282 875 : const char *pszIdentifier = oResult->GetValue(2, i);
1283 875 : const char *pszDescription = oResult->GetValue(3, i);
1284 875 : const char *pszMinX = oResult->GetValue(4, i);
1285 875 : const char *pszMinY = oResult->GetValue(5, i);
1286 875 : const char *pszMaxX = oResult->GetValue(6, i);
1287 875 : const char *pszMaxY = oResult->GetValue(7, i);
1288 875 : GPKGContentsDesc oDesc;
1289 875 : if (pszDataType)
1290 875 : oDesc.osDataType = pszDataType;
1291 875 : if (pszIdentifier)
1292 875 : oDesc.osIdentifier = pszIdentifier;
1293 875 : if (pszDescription)
1294 874 : oDesc.osDescription = pszDescription;
1295 875 : if (pszMinX)
1296 597 : oDesc.osMinX = pszMinX;
1297 875 : if (pszMinY)
1298 597 : oDesc.osMinY = pszMinY;
1299 875 : if (pszMaxX)
1300 597 : oDesc.osMaxX = pszMaxX;
1301 875 : if (pszMaxY)
1302 597 : oDesc.osMaxY = pszMaxY;
1303 1750 : m_oMapTableToContents[CPLString(pszTableName).toupper()] =
1304 1750 : std::move(oDesc);
1305 : }
1306 : }
1307 :
1308 757 : return m_oMapTableToContents;
1309 : }
1310 :
1311 : /************************************************************************/
1312 : /* Open() */
1313 : /************************************************************************/
1314 :
1315 1210 : int GDALGeoPackageDataset::Open(GDALOpenInfo *poOpenInfo,
1316 : const std::string &osFilenameInZip)
1317 : {
1318 1210 : m_osFilenameInZip = osFilenameInZip;
1319 1210 : CPLAssert(m_apoLayers.empty());
1320 1210 : CPLAssert(hDB == nullptr);
1321 :
1322 1210 : SetDescription(poOpenInfo->pszFilename);
1323 2420 : CPLString osFilename(poOpenInfo->pszFilename);
1324 2420 : CPLString osSubdatasetTableName;
1325 : GByte abyHeaderLetMeHerePlease[100];
1326 1210 : const GByte *pabyHeader = poOpenInfo->pabyHeader;
1327 1210 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GPKG:"))
1328 : {
1329 245 : char **papszTokens = CSLTokenizeString2(poOpenInfo->pszFilename, ":",
1330 : CSLT_HONOURSTRINGS);
1331 245 : int nCount = CSLCount(papszTokens);
1332 245 : if (nCount < 2)
1333 : {
1334 0 : CSLDestroy(papszTokens);
1335 0 : return FALSE;
1336 : }
1337 :
1338 245 : if (nCount <= 3)
1339 : {
1340 243 : 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 245 : if (nCount >= 3)
1361 14 : osSubdatasetTableName = papszTokens[nCount - 1];
1362 :
1363 245 : CSLDestroy(papszTokens);
1364 245 : VSILFILE *fp = VSIFOpenL(osFilename, "rb");
1365 245 : if (fp != nullptr)
1366 : {
1367 245 : VSIFReadL(abyHeaderLetMeHerePlease, 1, 100, fp);
1368 245 : VSIFCloseL(fp);
1369 : }
1370 245 : pabyHeader = abyHeaderLetMeHerePlease;
1371 : }
1372 965 : else if (poOpenInfo->pabyHeader &&
1373 965 : STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
1374 : "SQLite format 3"))
1375 : {
1376 958 : m_bCallUndeclareFileNotToOpen = true;
1377 958 : GDALOpenInfoDeclareFileNotToOpen(osFilename, poOpenInfo->pabyHeader,
1378 : poOpenInfo->nHeaderBytes);
1379 : }
1380 :
1381 1210 : eAccess = poOpenInfo->eAccess;
1382 1210 : if (!m_osFilenameInZip.empty())
1383 : {
1384 2 : m_pszFilename = CPLStrdup(CPLSPrintf(
1385 : "/vsizip/{%s}/%s", osFilename.c_str(), m_osFilenameInZip.c_str()));
1386 : }
1387 : else
1388 : {
1389 1208 : m_pszFilename = CPLStrdup(osFilename);
1390 : }
1391 :
1392 1210 : if (poOpenInfo->papszOpenOptions)
1393 : {
1394 100 : CSLDestroy(papszOpenOptions);
1395 100 : papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
1396 : }
1397 :
1398 : #ifdef ENABLE_SQL_GPKG_FORMAT
1399 1210 : if (poOpenInfo->pabyHeader &&
1400 965 : 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 1205 : else if (pabyHeader != nullptr)
1435 : #endif
1436 : {
1437 1205 : 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 860 : VSIFCloseL(poOpenInfo->fpL);
1442 860 : poOpenInfo->fpL = nullptr;
1443 : }
1444 :
1445 : /* See if we can open the SQLite database */
1446 1205 : if (!OpenOrCreateDB(GetUpdate() ? SQLITE_OPEN_READWRITE
1447 : : SQLITE_OPEN_READONLY))
1448 2 : return FALSE;
1449 :
1450 1203 : memcpy(&m_nApplicationId, pabyHeader + knApplicationIdPos, 4);
1451 1203 : m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
1452 1203 : memcpy(&m_nUserVersion, pabyHeader + knUserVersionPos, 4);
1453 1203 : m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
1454 1203 : if (m_nApplicationId == GP10_APPLICATION_ID)
1455 : {
1456 7 : CPLDebug("GPKG", "GeoPackage v1.0");
1457 : }
1458 1196 : else if (m_nApplicationId == GP11_APPLICATION_ID)
1459 : {
1460 2 : CPLDebug("GPKG", "GeoPackage v1.1");
1461 : }
1462 1194 : else if (m_nApplicationId == GPKG_APPLICATION_ID &&
1463 1190 : m_nUserVersion >= GPKG_1_2_VERSION)
1464 : {
1465 1188 : CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
1466 1188 : (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 1208 : 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 1208 : 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 1208 : 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 1208 : 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 1208 : DetectSpatialRefSysColumns();
1511 :
1512 : #ifdef ENABLE_GPKG_OGR_CONTENTS
1513 1208 : if (SQLGetInteger(hDB,
1514 : "SELECT 1 FROM sqlite_master WHERE "
1515 : "name = 'gpkg_ogr_contents' AND type = 'table'",
1516 1208 : nullptr) == 1)
1517 : {
1518 1200 : m_bHasGPKGOGRContents = true;
1519 : }
1520 : #endif
1521 :
1522 1208 : CheckUnknownExtensions();
1523 :
1524 1208 : int bRet = FALSE;
1525 1208 : bool bHasGPKGExtRelations = false;
1526 1208 : if (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR)
1527 : {
1528 1021 : m_bHasGPKGGeometryColumns =
1529 1021 : SQLGetInteger(hDB,
1530 : "SELECT 1 FROM sqlite_master WHERE "
1531 : "name = 'gpkg_geometry_columns' AND "
1532 : "type IN ('table', 'view')",
1533 1021 : nullptr) == 1;
1534 1021 : bHasGPKGExtRelations = HasGpkgextRelationsTable();
1535 : }
1536 1208 : 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 1020 : " WHERE data_type IN ('aspatial', 'attributes') ";
1562 :
1563 2040 : const char *pszListAllTables = CSLFetchNameValueDef(
1564 1020 : poOpenInfo->papszOpenOptions, "LIST_ALL_TABLES", "AUTO");
1565 1020 : bool bHasASpatialOrAttributes = HasGDALAspatialExtension();
1566 1020 : if (!bHasASpatialOrAttributes)
1567 : {
1568 : auto oResultTable =
1569 : SQLQuery(hDB, "SELECT * FROM gpkg_contents WHERE "
1570 1019 : "data_type = 'attributes' LIMIT 1");
1571 1019 : bHasASpatialOrAttributes =
1572 1019 : (oResultTable && oResultTable->RowCount() == 1);
1573 : }
1574 1020 : 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 1020 : if (EQUAL(pszListAllTables, "YES") ||
1589 1019 : (!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 961 : "gpkg_contents)";
1606 961 : if (bHasGPKGExtRelations)
1607 : {
1608 : osSQL += " AND lower(name) NOT IN (SELECT "
1609 : "lower(mapping_table_name) FROM "
1610 13 : "gpkgext_relations)";
1611 : }
1612 : }
1613 1020 : const int nTableLimit = GetOGRTableLimit();
1614 1020 : if (nTableLimit > 0)
1615 : {
1616 1020 : osSQL += " LIMIT ";
1617 1020 : osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1618 : }
1619 :
1620 1020 : auto oResult = SQLQuery(hDB, osSQL.c_str());
1621 1020 : if (!oResult)
1622 : {
1623 0 : return FALSE;
1624 : }
1625 :
1626 1020 : 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 1020 : if (oResult->RowCount() > 0)
1637 : {
1638 904 : bRet = TRUE;
1639 :
1640 904 : m_apoLayers.reserve(oResult->RowCount());
1641 :
1642 1808 : std::map<std::string, int> oMapTableRefCount;
1643 4004 : for (int i = 0; i < oResult->RowCount(); i++)
1644 : {
1645 3100 : const char *pszTableName = oResult->GetValue(0, i);
1646 3100 : if (pszTableName == nullptr)
1647 0 : continue;
1648 3100 : 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 1808 : std::set<std::string> oExistingLayers;
1660 4004 : for (int i = 0; i < oResult->RowCount(); i++)
1661 : {
1662 3100 : const char *pszTableName = oResult->GetValue(0, i);
1663 3100 : if (pszTableName == nullptr)
1664 2 : continue;
1665 : const bool bTableHasSeveralGeomColumns =
1666 3100 : oMapTableRefCount[pszTableName] > 1;
1667 3100 : bool bIsSpatial = CPL_TO_BOOL(oResult->GetValueAsInteger(2, i));
1668 3100 : const char *pszGeomColName = oResult->GetValue(3, i);
1669 3100 : const char *pszGeomType = oResult->GetValue(4, i);
1670 3100 : const char *pszZ = oResult->GetValue(5, i);
1671 3100 : const char *pszM = oResult->GetValue(6, i);
1672 : bool bIsInGpkgContents =
1673 3100 : CPL_TO_BOOL(oResult->GetValueAsInteger(11, i));
1674 3100 : if (!bIsInGpkgContents)
1675 44 : m_bNonSpatialTablesNonRegisteredInGpkgContentsFound = true;
1676 3100 : const char *pszObjectType = oResult->GetValue(12, i);
1677 3100 : if (pszObjectType == nullptr ||
1678 3099 : !(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 : std::string osLayerNameWithGeomColName =
1694 6078 : pszGeomColName ? std::string(pszTableName) + " (" +
1695 : pszGeomColName + ')'
1696 6198 : : std::string(pszTableName);
1697 3099 : if (cpl::contains(oExistingLayers, osLayerNameWithGeomColName))
1698 1 : continue;
1699 3098 : oExistingLayers.insert(osLayerNameWithGeomColName);
1700 : const std::string osLayerName =
1701 : bTableHasSeveralGeomColumns
1702 3 : ? std::move(osLayerNameWithGeomColName)
1703 6199 : : std::string(pszTableName);
1704 : auto poLayer = std::make_unique<OGRGeoPackageTableLayer>(
1705 6196 : this, osLayerName.c_str());
1706 3098 : bool bHasZ = pszZ && atoi(pszZ) > 0;
1707 3098 : bool bHasM = pszM && atoi(pszM) > 0;
1708 3098 : if (pszGeomType && EQUAL(pszGeomType, "GEOMETRY"))
1709 : {
1710 615 : if (pszZ && atoi(pszZ) == 2)
1711 7 : bHasZ = false;
1712 615 : if (pszM && atoi(pszM) == 2)
1713 6 : bHasM = false;
1714 : }
1715 3098 : poLayer->SetOpeningParameters(
1716 : pszTableName, pszObjectType, bIsInGpkgContents, bIsSpatial,
1717 : pszGeomColName, pszGeomType, bHasZ, bHasM);
1718 3098 : m_apoLayers.push_back(std::move(poLayer));
1719 : }
1720 : }
1721 : }
1722 :
1723 1208 : bool bHasTileMatrixSet = false;
1724 1208 : if (poOpenInfo->nOpenFlags & GDAL_OF_RASTER)
1725 : {
1726 570 : bHasTileMatrixSet = SQLGetInteger(hDB,
1727 : "SELECT 1 FROM sqlite_master WHERE "
1728 : "name = 'gpkg_tile_matrix_set' AND "
1729 : "type IN ('table', 'view')",
1730 : nullptr) == 1;
1731 : }
1732 1208 : if (bHasTileMatrixSet)
1733 : {
1734 : std::string osSQL =
1735 : "SELECT c.table_name, c.identifier, c.description, c.srs_id, "
1736 : "c.min_x, c.min_y, c.max_x, c.max_y, "
1737 : "tms.min_x, tms.min_y, tms.max_x, tms.max_y, c.data_type "
1738 : "FROM gpkg_contents c JOIN gpkg_tile_matrix_set tms ON "
1739 : "c.table_name = tms.table_name WHERE "
1740 568 : "data_type IN ('tiles', '2d-gridded-coverage')";
1741 568 : if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE"))
1742 : osSubdatasetTableName =
1743 2 : CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE");
1744 568 : if (!osSubdatasetTableName.empty())
1745 : {
1746 16 : char *pszTmp = sqlite3_mprintf(" AND c.table_name='%q'",
1747 : osSubdatasetTableName.c_str());
1748 16 : osSQL += pszTmp;
1749 16 : sqlite3_free(pszTmp);
1750 16 : SetPhysicalFilename(osFilename.c_str());
1751 : }
1752 568 : const int nTableLimit = GetOGRTableLimit();
1753 568 : if (nTableLimit > 0)
1754 : {
1755 568 : osSQL += " LIMIT ";
1756 568 : osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1757 : }
1758 :
1759 568 : auto oResult = SQLQuery(hDB, osSQL.c_str());
1760 568 : if (!oResult)
1761 : {
1762 0 : return FALSE;
1763 : }
1764 :
1765 568 : if (oResult->RowCount() == 0 && !osSubdatasetTableName.empty())
1766 : {
1767 1 : CPLError(CE_Failure, CPLE_AppDefined,
1768 : "Cannot find table '%s' in GeoPackage dataset",
1769 : osSubdatasetTableName.c_str());
1770 : }
1771 567 : else if (oResult->RowCount() == 1)
1772 : {
1773 274 : const char *pszTableName = oResult->GetValue(0, 0);
1774 274 : const char *pszIdentifier = oResult->GetValue(1, 0);
1775 274 : const char *pszDescription = oResult->GetValue(2, 0);
1776 274 : const char *pszSRSId = oResult->GetValue(3, 0);
1777 274 : const char *pszMinX = oResult->GetValue(4, 0);
1778 274 : const char *pszMinY = oResult->GetValue(5, 0);
1779 274 : const char *pszMaxX = oResult->GetValue(6, 0);
1780 274 : const char *pszMaxY = oResult->GetValue(7, 0);
1781 274 : const char *pszTMSMinX = oResult->GetValue(8, 0);
1782 274 : const char *pszTMSMinY = oResult->GetValue(9, 0);
1783 274 : const char *pszTMSMaxX = oResult->GetValue(10, 0);
1784 274 : const char *pszTMSMaxY = oResult->GetValue(11, 0);
1785 274 : const char *pszDataType = oResult->GetValue(12, 0);
1786 274 : if (pszTableName && pszTMSMinX && pszTMSMinY && pszTMSMaxX &&
1787 : pszTMSMaxY)
1788 : {
1789 548 : bRet = OpenRaster(
1790 : pszTableName, pszIdentifier, pszDescription,
1791 274 : pszSRSId ? atoi(pszSRSId) : 0, CPLAtof(pszTMSMinX),
1792 : CPLAtof(pszTMSMinY), CPLAtof(pszTMSMaxX),
1793 : CPLAtof(pszTMSMaxY), pszMinX, pszMinY, pszMaxX, pszMaxY,
1794 274 : EQUAL(pszDataType, "tiles"), poOpenInfo->papszOpenOptions);
1795 : }
1796 : }
1797 293 : else if (oResult->RowCount() >= 1)
1798 : {
1799 5 : bRet = TRUE;
1800 :
1801 5 : if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
1802 : {
1803 1 : CPLError(CE_Warning, CPLE_AppDefined,
1804 : "File has more than %d raster tables. "
1805 : "Limiting to first %d (can be overridden with "
1806 : "OGR_TABLE_LIMIT config option)",
1807 : nTableLimit, nTableLimit);
1808 1 : oResult->LimitRowCount(nTableLimit);
1809 : }
1810 :
1811 5 : int nSDSCount = 0;
1812 2013 : for (int i = 0; i < oResult->RowCount(); i++)
1813 : {
1814 2008 : const char *pszTableName = oResult->GetValue(0, i);
1815 2008 : const char *pszIdentifier = oResult->GetValue(1, i);
1816 2008 : if (pszTableName == nullptr)
1817 0 : continue;
1818 : m_aosSubDatasets.AddNameValue(
1819 : CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
1820 2008 : CPLSPrintf("GPKG:%s:%s", m_pszFilename, pszTableName));
1821 : m_aosSubDatasets.AddNameValue(
1822 : CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
1823 : pszIdentifier
1824 2008 : ? CPLSPrintf("%s - %s", pszTableName, pszIdentifier)
1825 4016 : : pszTableName);
1826 2008 : nSDSCount++;
1827 : }
1828 : }
1829 : }
1830 :
1831 1208 : if (!bRet && (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR))
1832 : {
1833 32 : if ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE))
1834 : {
1835 21 : bRet = TRUE;
1836 : }
1837 : else
1838 : {
1839 11 : CPLDebug("GPKG",
1840 : "This GeoPackage has no vector content and is opened "
1841 : "in read-only mode. If you open it in update mode, "
1842 : "opening will be successful.");
1843 : }
1844 : }
1845 :
1846 1208 : if (eAccess == GA_Update)
1847 : {
1848 246 : FixupWrongRTreeTrigger();
1849 246 : FixupWrongMedataReferenceColumnNameUpdate();
1850 : }
1851 :
1852 1208 : SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
1853 :
1854 1208 : return bRet;
1855 : }
1856 :
1857 : /************************************************************************/
1858 : /* DetectSpatialRefSysColumns() */
1859 : /************************************************************************/
1860 :
1861 1218 : void GDALGeoPackageDataset::DetectSpatialRefSysColumns()
1862 : {
1863 : // Detect definition_12_063 column
1864 : {
1865 1218 : sqlite3_stmt *hSQLStmt = nullptr;
1866 1218 : int rc = sqlite3_prepare_v2(
1867 : hDB, "SELECT definition_12_063 FROM gpkg_spatial_ref_sys ", -1,
1868 : &hSQLStmt, nullptr);
1869 1218 : if (rc == SQLITE_OK)
1870 : {
1871 85 : m_bHasDefinition12_063 = true;
1872 85 : sqlite3_finalize(hSQLStmt);
1873 : }
1874 : }
1875 :
1876 : // Detect epoch column
1877 1218 : if (m_bHasDefinition12_063)
1878 : {
1879 85 : sqlite3_stmt *hSQLStmt = nullptr;
1880 : int rc =
1881 85 : sqlite3_prepare_v2(hDB, "SELECT epoch FROM gpkg_spatial_ref_sys ",
1882 : -1, &hSQLStmt, nullptr);
1883 85 : if (rc == SQLITE_OK)
1884 : {
1885 76 : m_bHasEpochColumn = true;
1886 76 : sqlite3_finalize(hSQLStmt);
1887 : }
1888 : }
1889 1218 : }
1890 :
1891 : /************************************************************************/
1892 : /* FixupWrongRTreeTrigger() */
1893 : /************************************************************************/
1894 :
1895 246 : void GDALGeoPackageDataset::FixupWrongRTreeTrigger()
1896 : {
1897 : auto oResult = SQLQuery(
1898 : hDB,
1899 : "SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND "
1900 246 : "NAME LIKE 'rtree_%_update3' AND sql LIKE '% AFTER UPDATE OF % ON %'");
1901 246 : if (oResult == nullptr)
1902 0 : return;
1903 246 : if (oResult->RowCount() > 0)
1904 : {
1905 1 : CPLDebug("GPKG", "Fixing incorrect trigger(s) related to RTree");
1906 : }
1907 248 : for (int i = 0; i < oResult->RowCount(); i++)
1908 : {
1909 2 : const char *pszName = oResult->GetValue(0, i);
1910 2 : const char *pszSQL = oResult->GetValue(1, i);
1911 2 : const char *pszPtr1 = strstr(pszSQL, " AFTER UPDATE OF ");
1912 2 : if (pszPtr1)
1913 : {
1914 2 : const char *pszPtr = pszPtr1 + strlen(" AFTER UPDATE OF ");
1915 : // Skipping over geometry column name
1916 4 : while (*pszPtr == ' ')
1917 2 : pszPtr++;
1918 2 : if (pszPtr[0] == '"' || pszPtr[0] == '\'')
1919 : {
1920 1 : char chStringDelim = pszPtr[0];
1921 1 : pszPtr++;
1922 9 : while (*pszPtr != '\0' && *pszPtr != chStringDelim)
1923 : {
1924 8 : if (*pszPtr == '\\' && pszPtr[1] == chStringDelim)
1925 0 : pszPtr += 2;
1926 : else
1927 8 : pszPtr += 1;
1928 : }
1929 1 : if (*pszPtr == chStringDelim)
1930 1 : pszPtr++;
1931 : }
1932 : else
1933 : {
1934 1 : pszPtr++;
1935 8 : while (*pszPtr != ' ')
1936 7 : pszPtr++;
1937 : }
1938 2 : if (*pszPtr == ' ')
1939 : {
1940 2 : SQLCommand(hDB,
1941 4 : ("DROP TRIGGER \"" + SQLEscapeName(pszName) + "\"")
1942 : .c_str());
1943 4 : CPLString newSQL;
1944 2 : newSQL.assign(pszSQL, pszPtr1 - pszSQL);
1945 2 : newSQL += " AFTER UPDATE";
1946 2 : newSQL += pszPtr;
1947 2 : SQLCommand(hDB, newSQL);
1948 : }
1949 : }
1950 : }
1951 : }
1952 :
1953 : /************************************************************************/
1954 : /* FixupWrongMedataReferenceColumnNameUpdate() */
1955 : /************************************************************************/
1956 :
1957 246 : void GDALGeoPackageDataset::FixupWrongMedataReferenceColumnNameUpdate()
1958 : {
1959 : // Fix wrong trigger that was generated by GDAL < 2.4.0
1960 : // See https://github.com/qgis/QGIS/issues/42768
1961 : auto oResult = SQLQuery(
1962 : hDB, "SELECT sql FROM sqlite_master WHERE type = 'trigger' AND "
1963 : "NAME ='gpkg_metadata_reference_column_name_update' AND "
1964 246 : "sql LIKE '%column_nameIS%'");
1965 246 : if (oResult == nullptr)
1966 0 : return;
1967 246 : if (oResult->RowCount() == 1)
1968 : {
1969 1 : CPLDebug("GPKG", "Fixing incorrect trigger "
1970 : "gpkg_metadata_reference_column_name_update");
1971 1 : const char *pszSQL = oResult->GetValue(0, 0);
1972 : std::string osNewSQL(
1973 3 : CPLString(pszSQL).replaceAll("column_nameIS", "column_name IS"));
1974 :
1975 1 : SQLCommand(hDB,
1976 : "DROP TRIGGER gpkg_metadata_reference_column_name_update");
1977 1 : SQLCommand(hDB, osNewSQL.c_str());
1978 : }
1979 : }
1980 :
1981 : /************************************************************************/
1982 : /* ClearCachedRelationships() */
1983 : /************************************************************************/
1984 :
1985 36 : void GDALGeoPackageDataset::ClearCachedRelationships()
1986 : {
1987 36 : m_bHasPopulatedRelationships = false;
1988 36 : m_osMapRelationships.clear();
1989 36 : }
1990 :
1991 : /************************************************************************/
1992 : /* LoadRelationships() */
1993 : /************************************************************************/
1994 :
1995 83 : void GDALGeoPackageDataset::LoadRelationships() const
1996 : {
1997 83 : m_osMapRelationships.clear();
1998 :
1999 83 : std::vector<std::string> oExcludedTables;
2000 83 : if (HasGpkgextRelationsTable())
2001 : {
2002 37 : LoadRelationshipsUsingRelatedTablesExtension();
2003 :
2004 89 : for (const auto &oRelationship : m_osMapRelationships)
2005 : {
2006 : oExcludedTables.emplace_back(
2007 52 : oRelationship.second->GetMappingTableName());
2008 : }
2009 : }
2010 :
2011 : // Also load relationships defined using foreign keys (i.e. one-to-many
2012 : // relationships). Here we must exclude any relationships defined from the
2013 : // related tables extension, we don't want them included twice.
2014 83 : LoadRelationshipsFromForeignKeys(oExcludedTables);
2015 83 : m_bHasPopulatedRelationships = true;
2016 83 : }
2017 :
2018 : /************************************************************************/
2019 : /* LoadRelationshipsUsingRelatedTablesExtension() */
2020 : /************************************************************************/
2021 :
2022 37 : void GDALGeoPackageDataset::LoadRelationshipsUsingRelatedTablesExtension() const
2023 : {
2024 37 : m_osMapRelationships.clear();
2025 :
2026 : auto oResultTable = SQLQuery(
2027 37 : hDB, "SELECT base_table_name, base_primary_column, "
2028 : "related_table_name, related_primary_column, relation_name, "
2029 74 : "mapping_table_name FROM gpkgext_relations");
2030 37 : if (oResultTable && oResultTable->RowCount() > 0)
2031 : {
2032 86 : for (int i = 0; i < oResultTable->RowCount(); i++)
2033 : {
2034 53 : const char *pszBaseTableName = oResultTable->GetValue(0, i);
2035 53 : if (!pszBaseTableName)
2036 : {
2037 0 : CPLError(CE_Warning, CPLE_AppDefined,
2038 : "Could not retrieve base_table_name from "
2039 : "gpkgext_relations");
2040 1 : continue;
2041 : }
2042 53 : const char *pszBasePrimaryColumn = oResultTable->GetValue(1, i);
2043 53 : if (!pszBasePrimaryColumn)
2044 : {
2045 0 : CPLError(CE_Warning, CPLE_AppDefined,
2046 : "Could not retrieve base_primary_column from "
2047 : "gpkgext_relations");
2048 0 : continue;
2049 : }
2050 53 : const char *pszRelatedTableName = oResultTable->GetValue(2, i);
2051 53 : if (!pszRelatedTableName)
2052 : {
2053 0 : CPLError(CE_Warning, CPLE_AppDefined,
2054 : "Could not retrieve related_table_name from "
2055 : "gpkgext_relations");
2056 0 : continue;
2057 : }
2058 53 : const char *pszRelatedPrimaryColumn = oResultTable->GetValue(3, i);
2059 53 : if (!pszRelatedPrimaryColumn)
2060 : {
2061 0 : CPLError(CE_Warning, CPLE_AppDefined,
2062 : "Could not retrieve related_primary_column from "
2063 : "gpkgext_relations");
2064 0 : continue;
2065 : }
2066 53 : const char *pszRelationName = oResultTable->GetValue(4, i);
2067 53 : if (!pszRelationName)
2068 : {
2069 0 : CPLError(
2070 : CE_Warning, CPLE_AppDefined,
2071 : "Could not retrieve relation_name from gpkgext_relations");
2072 0 : continue;
2073 : }
2074 53 : const char *pszMappingTableName = oResultTable->GetValue(5, i);
2075 53 : if (!pszMappingTableName)
2076 : {
2077 0 : CPLError(CE_Warning, CPLE_AppDefined,
2078 : "Could not retrieve mapping_table_name from "
2079 : "gpkgext_relations");
2080 0 : continue;
2081 : }
2082 :
2083 : // confirm that mapping table exists
2084 : char *pszSQL =
2085 53 : sqlite3_mprintf("SELECT 1 FROM sqlite_master WHERE "
2086 : "name='%q' AND type IN ('table', 'view')",
2087 : pszMappingTableName);
2088 53 : const int nMappingTableCount = SQLGetInteger(hDB, pszSQL, nullptr);
2089 53 : sqlite3_free(pszSQL);
2090 :
2091 55 : if (nMappingTableCount < 1 &&
2092 2 : !const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
2093 2 : pszMappingTableName))
2094 : {
2095 1 : CPLError(CE_Warning, CPLE_AppDefined,
2096 : "Relationship mapping table %s does not exist",
2097 : pszMappingTableName);
2098 1 : continue;
2099 : }
2100 :
2101 : const std::string osRelationName = GenerateNameForRelationship(
2102 104 : pszBaseTableName, pszRelatedTableName, pszRelationName);
2103 :
2104 104 : std::string osType{};
2105 : // defined requirement classes -- for these types the relation name
2106 : // will be specific string value from the related tables extension.
2107 : // In this case we need to construct a unique relationship name
2108 : // based on the related tables
2109 52 : if (EQUAL(pszRelationName, "media") ||
2110 40 : EQUAL(pszRelationName, "simple_attributes") ||
2111 40 : EQUAL(pszRelationName, "features") ||
2112 18 : EQUAL(pszRelationName, "attributes") ||
2113 2 : EQUAL(pszRelationName, "tiles"))
2114 : {
2115 50 : osType = pszRelationName;
2116 : }
2117 : else
2118 : {
2119 : // user defined types default to features
2120 2 : osType = "features";
2121 : }
2122 :
2123 : auto poRelationship = std::make_unique<GDALRelationship>(
2124 : osRelationName, pszBaseTableName, pszRelatedTableName,
2125 104 : GRC_MANY_TO_MANY);
2126 :
2127 104 : poRelationship->SetLeftTableFields({pszBasePrimaryColumn});
2128 104 : poRelationship->SetRightTableFields({pszRelatedPrimaryColumn});
2129 104 : poRelationship->SetLeftMappingTableFields({"base_id"});
2130 104 : poRelationship->SetRightMappingTableFields({"related_id"});
2131 52 : poRelationship->SetMappingTableName(pszMappingTableName);
2132 52 : poRelationship->SetRelatedTableType(osType);
2133 :
2134 52 : m_osMapRelationships[osRelationName] = std::move(poRelationship);
2135 : }
2136 : }
2137 37 : }
2138 :
2139 : /************************************************************************/
2140 : /* GenerateNameForRelationship() */
2141 : /************************************************************************/
2142 :
2143 76 : std::string GDALGeoPackageDataset::GenerateNameForRelationship(
2144 : const char *pszBaseTableName, const char *pszRelatedTableName,
2145 : const char *pszType)
2146 : {
2147 : // defined requirement classes -- for these types the relation name will be
2148 : // specific string value from the related tables extension. In this case we
2149 : // need to construct a unique relationship name based on the related tables
2150 76 : if (EQUAL(pszType, "media") || EQUAL(pszType, "simple_attributes") ||
2151 53 : EQUAL(pszType, "features") || EQUAL(pszType, "attributes") ||
2152 8 : EQUAL(pszType, "tiles"))
2153 : {
2154 136 : std::ostringstream stream;
2155 : stream << pszBaseTableName << '_' << pszRelatedTableName << '_'
2156 68 : << pszType;
2157 68 : return stream.str();
2158 : }
2159 : else
2160 : {
2161 : // user defined types default to features
2162 8 : return pszType;
2163 : }
2164 : }
2165 :
2166 : /************************************************************************/
2167 : /* ValidateRelationship() */
2168 : /************************************************************************/
2169 :
2170 28 : bool GDALGeoPackageDataset::ValidateRelationship(
2171 : const GDALRelationship *poRelationship, std::string &failureReason)
2172 : {
2173 :
2174 28 : if (poRelationship->GetCardinality() !=
2175 : GDALRelationshipCardinality::GRC_MANY_TO_MANY)
2176 : {
2177 3 : failureReason = "Only many to many relationships are supported";
2178 3 : return false;
2179 : }
2180 :
2181 50 : std::string osRelatedTableType = poRelationship->GetRelatedTableType();
2182 65 : if (!osRelatedTableType.empty() && osRelatedTableType != "features" &&
2183 30 : osRelatedTableType != "media" &&
2184 20 : osRelatedTableType != "simple_attributes" &&
2185 55 : osRelatedTableType != "attributes" && osRelatedTableType != "tiles")
2186 : {
2187 : failureReason =
2188 4 : ("Related table type " + osRelatedTableType +
2189 : " is not a valid value for the GeoPackage specification. "
2190 : "Valid values are: features, media, simple_attributes, "
2191 : "attributes, tiles.")
2192 2 : .c_str();
2193 2 : return false;
2194 : }
2195 :
2196 23 : const std::string &osLeftTableName = poRelationship->GetLeftTableName();
2197 23 : OGRGeoPackageLayer *poLeftTable = cpl::down_cast<OGRGeoPackageLayer *>(
2198 23 : GetLayerByName(osLeftTableName.c_str()));
2199 23 : if (!poLeftTable)
2200 : {
2201 4 : failureReason = ("Left table " + osLeftTableName +
2202 : " is not an existing layer in the dataset")
2203 2 : .c_str();
2204 2 : return false;
2205 : }
2206 21 : const std::string &osRightTableName = poRelationship->GetRightTableName();
2207 21 : OGRGeoPackageLayer *poRightTable = cpl::down_cast<OGRGeoPackageLayer *>(
2208 21 : GetLayerByName(osRightTableName.c_str()));
2209 21 : if (!poRightTable)
2210 : {
2211 4 : failureReason = ("Right table " + osRightTableName +
2212 : " is not an existing layer in the dataset")
2213 2 : .c_str();
2214 2 : return false;
2215 : }
2216 :
2217 19 : const auto &aosLeftTableFields = poRelationship->GetLeftTableFields();
2218 19 : if (aosLeftTableFields.empty())
2219 : {
2220 1 : failureReason = "No left table fields were specified";
2221 1 : return false;
2222 : }
2223 18 : else if (aosLeftTableFields.size() > 1)
2224 : {
2225 : failureReason = "Only a single left table field is permitted for the "
2226 1 : "GeoPackage specification";
2227 1 : return false;
2228 : }
2229 : else
2230 : {
2231 : // validate left field exists
2232 34 : if (poLeftTable->GetLayerDefn()->GetFieldIndex(
2233 37 : aosLeftTableFields[0].c_str()) < 0 &&
2234 3 : !EQUAL(poLeftTable->GetFIDColumn(), aosLeftTableFields[0].c_str()))
2235 : {
2236 2 : failureReason = ("Left table field " + aosLeftTableFields[0] +
2237 2 : " does not exist in " + osLeftTableName)
2238 1 : .c_str();
2239 1 : return false;
2240 : }
2241 : }
2242 :
2243 16 : const auto &aosRightTableFields = poRelationship->GetRightTableFields();
2244 16 : if (aosRightTableFields.empty())
2245 : {
2246 1 : failureReason = "No right table fields were specified";
2247 1 : return false;
2248 : }
2249 15 : else if (aosRightTableFields.size() > 1)
2250 : {
2251 : failureReason = "Only a single right table field is permitted for the "
2252 1 : "GeoPackage specification";
2253 1 : return false;
2254 : }
2255 : else
2256 : {
2257 : // validate right field exists
2258 28 : if (poRightTable->GetLayerDefn()->GetFieldIndex(
2259 32 : aosRightTableFields[0].c_str()) < 0 &&
2260 4 : !EQUAL(poRightTable->GetFIDColumn(),
2261 : aosRightTableFields[0].c_str()))
2262 : {
2263 4 : failureReason = ("Right table field " + aosRightTableFields[0] +
2264 4 : " does not exist in " + osRightTableName)
2265 2 : .c_str();
2266 2 : return false;
2267 : }
2268 : }
2269 :
2270 12 : return true;
2271 : }
2272 :
2273 : /************************************************************************/
2274 : /* InitRaster() */
2275 : /************************************************************************/
2276 :
2277 358 : bool GDALGeoPackageDataset::InitRaster(
2278 : GDALGeoPackageDataset *poParentDS, const char *pszTableName, double dfMinX,
2279 : double dfMinY, double dfMaxX, double dfMaxY, const char *pszContentsMinX,
2280 : const char *pszContentsMinY, const char *pszContentsMaxX,
2281 : const char *pszContentsMaxY, char **papszOpenOptionsIn,
2282 : const SQLResult &oResult, int nIdxInResult)
2283 : {
2284 358 : m_osRasterTable = pszTableName;
2285 358 : m_dfTMSMinX = dfMinX;
2286 358 : m_dfTMSMaxY = dfMaxY;
2287 :
2288 : // Despite prior checking, the type might be Binary and
2289 : // SQLResultGetValue() not working properly on it
2290 358 : int nZoomLevel = atoi(oResult.GetValue(0, nIdxInResult));
2291 358 : if (nZoomLevel < 0 || nZoomLevel > 65536)
2292 : {
2293 0 : return false;
2294 : }
2295 358 : double dfPixelXSize = CPLAtof(oResult.GetValue(1, nIdxInResult));
2296 358 : double dfPixelYSize = CPLAtof(oResult.GetValue(2, nIdxInResult));
2297 358 : if (dfPixelXSize <= 0 || dfPixelYSize <= 0)
2298 : {
2299 0 : return false;
2300 : }
2301 358 : int nTileWidth = atoi(oResult.GetValue(3, nIdxInResult));
2302 358 : int nTileHeight = atoi(oResult.GetValue(4, nIdxInResult));
2303 358 : if (nTileWidth <= 0 || nTileWidth > 65536 || nTileHeight <= 0 ||
2304 : nTileHeight > 65536)
2305 : {
2306 0 : return false;
2307 : }
2308 : int nTileMatrixWidth = static_cast<int>(
2309 716 : std::min(static_cast<GIntBig>(INT_MAX),
2310 358 : CPLAtoGIntBig(oResult.GetValue(5, nIdxInResult))));
2311 : int nTileMatrixHeight = static_cast<int>(
2312 716 : std::min(static_cast<GIntBig>(INT_MAX),
2313 358 : CPLAtoGIntBig(oResult.GetValue(6, nIdxInResult))));
2314 358 : if (nTileMatrixWidth <= 0 || nTileMatrixHeight <= 0)
2315 : {
2316 0 : return false;
2317 : }
2318 :
2319 : /* Use content bounds in priority over tile_matrix_set bounds */
2320 358 : double dfGDALMinX = dfMinX;
2321 358 : double dfGDALMinY = dfMinY;
2322 358 : double dfGDALMaxX = dfMaxX;
2323 358 : double dfGDALMaxY = dfMaxY;
2324 : pszContentsMinX =
2325 358 : CSLFetchNameValueDef(papszOpenOptionsIn, "MINX", pszContentsMinX);
2326 : pszContentsMinY =
2327 358 : CSLFetchNameValueDef(papszOpenOptionsIn, "MINY", pszContentsMinY);
2328 : pszContentsMaxX =
2329 358 : CSLFetchNameValueDef(papszOpenOptionsIn, "MAXX", pszContentsMaxX);
2330 : pszContentsMaxY =
2331 358 : CSLFetchNameValueDef(papszOpenOptionsIn, "MAXY", pszContentsMaxY);
2332 358 : if (pszContentsMinX != nullptr && pszContentsMinY != nullptr &&
2333 358 : pszContentsMaxX != nullptr && pszContentsMaxY != nullptr)
2334 : {
2335 715 : if (CPLAtof(pszContentsMinX) < CPLAtof(pszContentsMaxX) &&
2336 357 : CPLAtof(pszContentsMinY) < CPLAtof(pszContentsMaxY))
2337 : {
2338 357 : dfGDALMinX = CPLAtof(pszContentsMinX);
2339 357 : dfGDALMinY = CPLAtof(pszContentsMinY);
2340 357 : dfGDALMaxX = CPLAtof(pszContentsMaxX);
2341 357 : dfGDALMaxY = CPLAtof(pszContentsMaxY);
2342 : }
2343 : else
2344 : {
2345 1 : CPLError(CE_Warning, CPLE_AppDefined,
2346 : "Illegal min_x/min_y/max_x/max_y values for %s in open "
2347 : "options and/or gpkg_contents. Using bounds of "
2348 : "gpkg_tile_matrix_set instead",
2349 : pszTableName);
2350 : }
2351 : }
2352 358 : if (dfGDALMinX >= dfGDALMaxX || dfGDALMinY >= dfGDALMaxY)
2353 : {
2354 0 : CPLError(CE_Failure, CPLE_AppDefined,
2355 : "Illegal min_x/min_y/max_x/max_y values for %s", pszTableName);
2356 0 : return false;
2357 : }
2358 :
2359 358 : int nBandCount = 0;
2360 : const char *pszBAND_COUNT =
2361 358 : CSLFetchNameValue(papszOpenOptionsIn, "BAND_COUNT");
2362 358 : if (poParentDS)
2363 : {
2364 86 : nBandCount = poParentDS->GetRasterCount();
2365 : }
2366 272 : else if (m_eDT != GDT_Byte)
2367 : {
2368 65 : if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO") &&
2369 0 : !EQUAL(pszBAND_COUNT, "1"))
2370 : {
2371 0 : CPLError(CE_Warning, CPLE_AppDefined,
2372 : "BAND_COUNT ignored for non-Byte data");
2373 : }
2374 65 : nBandCount = 1;
2375 : }
2376 : else
2377 : {
2378 207 : if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO"))
2379 : {
2380 69 : nBandCount = atoi(pszBAND_COUNT);
2381 69 : if (nBandCount == 1)
2382 5 : GetMetadata("IMAGE_STRUCTURE");
2383 : }
2384 : else
2385 : {
2386 138 : GetMetadata("IMAGE_STRUCTURE");
2387 138 : nBandCount = m_nBandCountFromMetadata;
2388 138 : if (nBandCount == 1)
2389 38 : m_eTF = GPKG_TF_PNG;
2390 : }
2391 207 : if (nBandCount == 1 && !m_osTFFromMetadata.empty())
2392 : {
2393 2 : m_eTF = GDALGPKGMBTilesGetTileFormat(m_osTFFromMetadata.c_str());
2394 : }
2395 207 : if (nBandCount <= 0 || nBandCount > 4)
2396 86 : nBandCount = 4;
2397 : }
2398 :
2399 358 : return InitRaster(poParentDS, pszTableName, nZoomLevel, nBandCount, dfMinX,
2400 : dfMaxY, dfPixelXSize, dfPixelYSize, nTileWidth,
2401 : nTileHeight, nTileMatrixWidth, nTileMatrixHeight,
2402 358 : dfGDALMinX, dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
2403 : }
2404 :
2405 : /************************************************************************/
2406 : /* ComputeTileAndPixelShifts() */
2407 : /************************************************************************/
2408 :
2409 782 : bool GDALGeoPackageDataset::ComputeTileAndPixelShifts()
2410 : {
2411 : int nTileWidth, nTileHeight;
2412 782 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
2413 :
2414 : // Compute shift between GDAL origin and TileMatrixSet origin
2415 782 : const double dfShiftXPixels = (m_gt[0] - m_dfTMSMinX) / m_gt[1];
2416 782 : if (!(dfShiftXPixels / nTileWidth >= INT_MIN &&
2417 779 : dfShiftXPixels / nTileWidth < INT_MAX))
2418 : {
2419 3 : return false;
2420 : }
2421 779 : const int64_t nShiftXPixels =
2422 779 : static_cast<int64_t>(floor(0.5 + dfShiftXPixels));
2423 779 : m_nShiftXTiles = static_cast<int>(nShiftXPixels / nTileWidth);
2424 779 : if (nShiftXPixels < 0 && (nShiftXPixels % nTileWidth) != 0)
2425 11 : m_nShiftXTiles--;
2426 779 : m_nShiftXPixelsMod =
2427 779 : (static_cast<int>(nShiftXPixels % nTileWidth) + nTileWidth) %
2428 : nTileWidth;
2429 :
2430 779 : const double dfShiftYPixels = (m_gt[3] - m_dfTMSMaxY) / m_gt[5];
2431 779 : if (!(dfShiftYPixels / nTileHeight >= INT_MIN &&
2432 779 : dfShiftYPixels / nTileHeight < INT_MAX))
2433 : {
2434 1 : return false;
2435 : }
2436 778 : const int64_t nShiftYPixels =
2437 778 : static_cast<int64_t>(floor(0.5 + dfShiftYPixels));
2438 778 : m_nShiftYTiles = static_cast<int>(nShiftYPixels / nTileHeight);
2439 778 : if (nShiftYPixels < 0 && (nShiftYPixels % nTileHeight) != 0)
2440 11 : m_nShiftYTiles--;
2441 778 : m_nShiftYPixelsMod =
2442 778 : (static_cast<int>(nShiftYPixels % nTileHeight) + nTileHeight) %
2443 : nTileHeight;
2444 778 : return true;
2445 : }
2446 :
2447 : /************************************************************************/
2448 : /* AllocCachedTiles() */
2449 : /************************************************************************/
2450 :
2451 778 : bool GDALGeoPackageDataset::AllocCachedTiles()
2452 : {
2453 : int nTileWidth, nTileHeight;
2454 778 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
2455 :
2456 : // We currently need 4 caches because of
2457 : // GDALGPKGMBTilesLikePseudoDataset::ReadTile(int nRow, int nCol)
2458 778 : const int nCacheCount = 4;
2459 : /*
2460 : (m_nShiftXPixelsMod != 0 || m_nShiftYPixelsMod != 0) ? 4 :
2461 : (GetUpdate() && m_eDT == GDT_Byte) ? 2 : 1;
2462 : */
2463 778 : m_pabyCachedTiles = static_cast<GByte *>(VSI_MALLOC3_VERBOSE(
2464 : cpl::fits_on<int>(nCacheCount * (m_eDT == GDT_Byte ? 4 : 1) *
2465 : m_nDTSize),
2466 : nTileWidth, nTileHeight));
2467 778 : if (m_pabyCachedTiles == nullptr)
2468 : {
2469 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too big tiles: %d x %d",
2470 : nTileWidth, nTileHeight);
2471 0 : return false;
2472 : }
2473 :
2474 778 : return true;
2475 : }
2476 :
2477 : /************************************************************************/
2478 : /* InitRaster() */
2479 : /************************************************************************/
2480 :
2481 597 : bool GDALGeoPackageDataset::InitRaster(
2482 : GDALGeoPackageDataset *poParentDS, const char *pszTableName, int nZoomLevel,
2483 : int nBandCount, double dfTMSMinX, double dfTMSMaxY, double dfPixelXSize,
2484 : double dfPixelYSize, int nTileWidth, int nTileHeight, int nTileMatrixWidth,
2485 : int nTileMatrixHeight, double dfGDALMinX, double dfGDALMinY,
2486 : double dfGDALMaxX, double dfGDALMaxY)
2487 : {
2488 597 : m_osRasterTable = pszTableName;
2489 597 : m_dfTMSMinX = dfTMSMinX;
2490 597 : m_dfTMSMaxY = dfTMSMaxY;
2491 597 : m_nZoomLevel = nZoomLevel;
2492 597 : m_nTileMatrixWidth = nTileMatrixWidth;
2493 597 : m_nTileMatrixHeight = nTileMatrixHeight;
2494 :
2495 597 : m_bGeoTransformValid = true;
2496 597 : m_gt[0] = dfGDALMinX;
2497 597 : m_gt[1] = dfPixelXSize;
2498 597 : m_gt[3] = dfGDALMaxY;
2499 597 : m_gt[5] = -dfPixelYSize;
2500 597 : double dfRasterXSize = 0.5 + (dfGDALMaxX - dfGDALMinX) / dfPixelXSize;
2501 597 : double dfRasterYSize = 0.5 + (dfGDALMaxY - dfGDALMinY) / dfPixelYSize;
2502 597 : if (dfRasterXSize > INT_MAX || dfRasterYSize > INT_MAX)
2503 : {
2504 0 : CPLError(CE_Failure, CPLE_NotSupported, "Too big raster: %f x %f",
2505 : dfRasterXSize, dfRasterYSize);
2506 0 : return false;
2507 : }
2508 597 : nRasterXSize = std::max(1, static_cast<int>(dfRasterXSize));
2509 597 : nRasterYSize = std::max(1, static_cast<int>(dfRasterYSize));
2510 :
2511 597 : if (poParentDS)
2512 : {
2513 325 : m_poParentDS = poParentDS;
2514 325 : eAccess = poParentDS->eAccess;
2515 325 : hDB = poParentDS->hDB;
2516 325 : m_eTF = poParentDS->m_eTF;
2517 325 : m_eDT = poParentDS->m_eDT;
2518 325 : m_nDTSize = poParentDS->m_nDTSize;
2519 325 : m_dfScale = poParentDS->m_dfScale;
2520 325 : m_dfOffset = poParentDS->m_dfOffset;
2521 325 : m_dfPrecision = poParentDS->m_dfPrecision;
2522 325 : m_usGPKGNull = poParentDS->m_usGPKGNull;
2523 325 : m_nQuality = poParentDS->m_nQuality;
2524 325 : m_nZLevel = poParentDS->m_nZLevel;
2525 325 : m_bDither = poParentDS->m_bDither;
2526 : /*m_nSRID = poParentDS->m_nSRID;*/
2527 325 : m_osWHERE = poParentDS->m_osWHERE;
2528 325 : SetDescription(CPLSPrintf("%s - zoom_level=%d",
2529 325 : poParentDS->GetDescription(), m_nZoomLevel));
2530 : }
2531 :
2532 2094 : for (int i = 1; i <= nBandCount; i++)
2533 : {
2534 : auto poNewBand = std::make_unique<GDALGeoPackageRasterBand>(
2535 1497 : this, nTileWidth, nTileHeight);
2536 1497 : if (poParentDS)
2537 : {
2538 761 : int bHasNoData = FALSE;
2539 : double dfNoDataValue =
2540 761 : poParentDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
2541 761 : if (bHasNoData)
2542 24 : poNewBand->SetNoDataValueInternal(dfNoDataValue);
2543 : }
2544 :
2545 1497 : if (nBandCount == 1 && m_poCTFromMetadata)
2546 : {
2547 3 : poNewBand->AssignColorTable(m_poCTFromMetadata.get());
2548 : }
2549 1497 : if (!m_osNodataValueFromMetadata.empty())
2550 : {
2551 8 : poNewBand->SetNoDataValueInternal(
2552 : CPLAtof(m_osNodataValueFromMetadata.c_str()));
2553 : }
2554 :
2555 1497 : SetBand(i, std::move(poNewBand));
2556 : }
2557 :
2558 597 : if (!ComputeTileAndPixelShifts())
2559 : {
2560 3 : CPLError(CE_Failure, CPLE_AppDefined,
2561 : "Overflow occurred in ComputeTileAndPixelShifts()");
2562 3 : return false;
2563 : }
2564 :
2565 594 : GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
2566 594 : GDALPamDataset::SetMetadataItem("ZOOM_LEVEL",
2567 : CPLSPrintf("%d", m_nZoomLevel));
2568 :
2569 594 : return AllocCachedTiles();
2570 : }
2571 :
2572 : /************************************************************************/
2573 : /* GDALGPKGMBTilesGetTileFormat() */
2574 : /************************************************************************/
2575 :
2576 80 : GPKGTileFormat GDALGPKGMBTilesGetTileFormat(const char *pszTF)
2577 : {
2578 80 : GPKGTileFormat eTF = GPKG_TF_PNG_JPEG;
2579 80 : if (pszTF)
2580 : {
2581 80 : if (EQUAL(pszTF, "PNG_JPEG") || EQUAL(pszTF, "AUTO"))
2582 1 : eTF = GPKG_TF_PNG_JPEG;
2583 79 : else if (EQUAL(pszTF, "PNG"))
2584 46 : eTF = GPKG_TF_PNG;
2585 33 : else if (EQUAL(pszTF, "PNG8"))
2586 6 : eTF = GPKG_TF_PNG8;
2587 27 : else if (EQUAL(pszTF, "JPEG"))
2588 14 : eTF = GPKG_TF_JPEG;
2589 13 : else if (EQUAL(pszTF, "WEBP"))
2590 13 : eTF = GPKG_TF_WEBP;
2591 : else
2592 : {
2593 0 : CPLError(CE_Failure, CPLE_NotSupported,
2594 : "Unsuppoted value for TILE_FORMAT: %s", pszTF);
2595 : }
2596 : }
2597 80 : return eTF;
2598 : }
2599 :
2600 28 : const char *GDALMBTilesGetTileFormatName(GPKGTileFormat eTF)
2601 : {
2602 28 : switch (eTF)
2603 : {
2604 26 : case GPKG_TF_PNG:
2605 : case GPKG_TF_PNG8:
2606 26 : return "png";
2607 1 : case GPKG_TF_JPEG:
2608 1 : return "jpg";
2609 1 : case GPKG_TF_WEBP:
2610 1 : return "webp";
2611 0 : default:
2612 0 : break;
2613 : }
2614 0 : CPLError(CE_Failure, CPLE_NotSupported,
2615 : "Unsuppoted value for TILE_FORMAT: %d", static_cast<int>(eTF));
2616 0 : return nullptr;
2617 : }
2618 :
2619 : /************************************************************************/
2620 : /* OpenRaster() */
2621 : /************************************************************************/
2622 :
2623 274 : bool GDALGeoPackageDataset::OpenRaster(
2624 : const char *pszTableName, const char *pszIdentifier,
2625 : const char *pszDescription, int nSRSId, double dfMinX, double dfMinY,
2626 : double dfMaxX, double dfMaxY, const char *pszContentsMinX,
2627 : const char *pszContentsMinY, const char *pszContentsMaxX,
2628 : const char *pszContentsMaxY, bool bIsTiles, char **papszOpenOptionsIn)
2629 : {
2630 274 : if (dfMinX >= dfMaxX || dfMinY >= dfMaxY)
2631 0 : return false;
2632 :
2633 : // Config option just for debug, and for example force set to NaN
2634 : // which is not supported
2635 548 : CPLString osDataNull = CPLGetConfigOption("GPKG_NODATA", "");
2636 548 : CPLString osUom;
2637 548 : CPLString osFieldName;
2638 548 : CPLString osGridCellEncoding;
2639 274 : if (!bIsTiles)
2640 : {
2641 65 : char *pszSQL = sqlite3_mprintf(
2642 : "SELECT datatype, scale, offset, data_null, precision FROM "
2643 : "gpkg_2d_gridded_coverage_ancillary "
2644 : "WHERE tile_matrix_set_name = '%q' "
2645 : "AND datatype IN ('integer', 'float')"
2646 : "AND (scale > 0 OR scale IS NULL)",
2647 : pszTableName);
2648 65 : auto oResult = SQLQuery(hDB, pszSQL);
2649 65 : sqlite3_free(pszSQL);
2650 65 : if (!oResult || oResult->RowCount() == 0)
2651 : {
2652 0 : return false;
2653 : }
2654 65 : const char *pszDataType = oResult->GetValue(0, 0);
2655 65 : const char *pszScale = oResult->GetValue(1, 0);
2656 65 : const char *pszOffset = oResult->GetValue(2, 0);
2657 65 : const char *pszDataNull = oResult->GetValue(3, 0);
2658 65 : const char *pszPrecision = oResult->GetValue(4, 0);
2659 65 : if (pszDataNull)
2660 23 : osDataNull = pszDataNull;
2661 65 : if (EQUAL(pszDataType, "float"))
2662 : {
2663 6 : SetDataType(GDT_Float32);
2664 6 : m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
2665 : }
2666 : else
2667 : {
2668 59 : SetDataType(GDT_Float32);
2669 59 : m_eTF = GPKG_TF_PNG_16BIT;
2670 59 : const double dfScale = pszScale ? CPLAtof(pszScale) : 1.0;
2671 59 : const double dfOffset = pszOffset ? CPLAtof(pszOffset) : 0.0;
2672 59 : if (dfScale == 1.0)
2673 : {
2674 59 : if (dfOffset == 0.0)
2675 : {
2676 24 : SetDataType(GDT_UInt16);
2677 : }
2678 35 : else if (dfOffset == -32768.0)
2679 : {
2680 35 : SetDataType(GDT_Int16);
2681 : }
2682 : // coverity[tainted_data]
2683 0 : else if (dfOffset == -32767.0 && !osDataNull.empty() &&
2684 0 : CPLAtof(osDataNull) == 65535.0)
2685 : // Given that we will map the nodata value to -32768
2686 : {
2687 0 : SetDataType(GDT_Int16);
2688 : }
2689 : }
2690 :
2691 : // Check that the tile offset and scales are compatible of a
2692 : // final integer result.
2693 59 : if (m_eDT != GDT_Float32)
2694 : {
2695 : // coverity[tainted_data]
2696 59 : if (dfScale == 1.0 && dfOffset == -32768.0 &&
2697 118 : !osDataNull.empty() && CPLAtof(osDataNull) == 65535.0)
2698 : {
2699 : // Given that we will map the nodata value to -32768
2700 9 : pszSQL = sqlite3_mprintf(
2701 : "SELECT 1 FROM "
2702 : "gpkg_2d_gridded_tile_ancillary WHERE "
2703 : "tpudt_name = '%q' "
2704 : "AND NOT ((offset = 0.0 or offset = 1.0) "
2705 : "AND scale = 1.0) "
2706 : "LIMIT 1",
2707 : pszTableName);
2708 : }
2709 : else
2710 : {
2711 50 : pszSQL = sqlite3_mprintf(
2712 : "SELECT 1 FROM "
2713 : "gpkg_2d_gridded_tile_ancillary WHERE "
2714 : "tpudt_name = '%q' "
2715 : "AND NOT (offset = 0.0 AND scale = 1.0) LIMIT 1",
2716 : pszTableName);
2717 : }
2718 59 : sqlite3_stmt *hSQLStmt = nullptr;
2719 : int rc =
2720 59 : SQLPrepareWithError(hDB, pszSQL, -1, &hSQLStmt, nullptr);
2721 :
2722 59 : if (rc == SQLITE_OK)
2723 : {
2724 59 : if (sqlite3_step(hSQLStmt) == SQLITE_ROW)
2725 : {
2726 8 : SetDataType(GDT_Float32);
2727 : }
2728 59 : sqlite3_finalize(hSQLStmt);
2729 : }
2730 59 : sqlite3_free(pszSQL);
2731 : }
2732 :
2733 59 : SetGlobalOffsetScale(dfOffset, dfScale);
2734 : }
2735 65 : if (pszPrecision)
2736 65 : m_dfPrecision = CPLAtof(pszPrecision);
2737 :
2738 : // Request those columns in a separate query, so as to keep
2739 : // compatibility with pre OGC 17-066r1 databases
2740 : pszSQL =
2741 65 : sqlite3_mprintf("SELECT uom, field_name, grid_cell_encoding FROM "
2742 : "gpkg_2d_gridded_coverage_ancillary "
2743 : "WHERE tile_matrix_set_name = '%q'",
2744 : pszTableName);
2745 65 : CPLPushErrorHandler(CPLQuietErrorHandler);
2746 65 : oResult = SQLQuery(hDB, pszSQL);
2747 65 : CPLPopErrorHandler();
2748 65 : sqlite3_free(pszSQL);
2749 65 : if (oResult && oResult->RowCount() == 1)
2750 : {
2751 64 : const char *pszUom = oResult->GetValue(0, 0);
2752 64 : if (pszUom)
2753 2 : osUom = pszUom;
2754 64 : const char *pszFieldName = oResult->GetValue(1, 0);
2755 64 : if (pszFieldName)
2756 64 : osFieldName = pszFieldName;
2757 64 : const char *pszGridCellEncoding = oResult->GetValue(2, 0);
2758 64 : if (pszGridCellEncoding)
2759 64 : osGridCellEncoding = pszGridCellEncoding;
2760 : }
2761 : }
2762 :
2763 274 : m_bRecordInsertedInGPKGContent = true;
2764 274 : m_nSRID = nSRSId;
2765 :
2766 547 : if (auto poSRS = GetSpatialRef(nSRSId))
2767 : {
2768 273 : m_oSRS = *(poSRS.get());
2769 : }
2770 :
2771 : /* Various sanity checks added in the SELECT */
2772 274 : char *pszQuotedTableName = sqlite3_mprintf("'%q'", pszTableName);
2773 548 : CPLString osQuotedTableName(pszQuotedTableName);
2774 274 : sqlite3_free(pszQuotedTableName);
2775 274 : char *pszSQL = sqlite3_mprintf(
2776 : "SELECT zoom_level, pixel_x_size, pixel_y_size, tile_width, "
2777 : "tile_height, matrix_width, matrix_height "
2778 : "FROM gpkg_tile_matrix tm "
2779 : "WHERE table_name = %s "
2780 : // INT_MAX would be the theoretical maximum value to avoid
2781 : // overflows, but that's already a insane value.
2782 : "AND zoom_level >= 0 AND zoom_level <= 65536 "
2783 : "AND pixel_x_size > 0 AND pixel_y_size > 0 "
2784 : "AND tile_width >= 1 AND tile_width <= 65536 "
2785 : "AND tile_height >= 1 AND tile_height <= 65536 "
2786 : "AND matrix_width >= 1 AND matrix_height >= 1",
2787 : osQuotedTableName.c_str());
2788 548 : CPLString osSQL(pszSQL);
2789 : const char *pszZoomLevel =
2790 274 : CSLFetchNameValue(papszOpenOptionsIn, "ZOOM_LEVEL");
2791 274 : if (pszZoomLevel)
2792 : {
2793 5 : if (GetUpdate())
2794 1 : osSQL += CPLSPrintf(" AND zoom_level <= %d", atoi(pszZoomLevel));
2795 : else
2796 : {
2797 : osSQL += CPLSPrintf(
2798 : " AND (zoom_level = %d OR (zoom_level < %d AND EXISTS(SELECT 1 "
2799 : "FROM %s WHERE zoom_level = tm.zoom_level LIMIT 1)))",
2800 : atoi(pszZoomLevel), atoi(pszZoomLevel),
2801 4 : osQuotedTableName.c_str());
2802 : }
2803 : }
2804 : // In read-only mode, only lists non empty zoom levels
2805 269 : else if (!GetUpdate())
2806 : {
2807 : osSQL += CPLSPrintf(" AND EXISTS(SELECT 1 FROM %s WHERE zoom_level = "
2808 : "tm.zoom_level LIMIT 1)",
2809 215 : osQuotedTableName.c_str());
2810 : }
2811 : else // if( pszZoomLevel == nullptr )
2812 : {
2813 : osSQL +=
2814 : CPLSPrintf(" AND zoom_level <= (SELECT MAX(zoom_level) FROM %s)",
2815 54 : osQuotedTableName.c_str());
2816 : }
2817 274 : osSQL += " ORDER BY zoom_level DESC";
2818 : // To avoid denial of service.
2819 274 : osSQL += " LIMIT 100";
2820 :
2821 548 : auto oResult = SQLQuery(hDB, osSQL.c_str());
2822 274 : if (!oResult || oResult->RowCount() == 0)
2823 : {
2824 114 : if (oResult && oResult->RowCount() == 0 && pszContentsMinX != nullptr &&
2825 114 : pszContentsMinY != nullptr && pszContentsMaxX != nullptr &&
2826 : pszContentsMaxY != nullptr)
2827 : {
2828 56 : osSQL = pszSQL;
2829 56 : osSQL += " ORDER BY zoom_level DESC";
2830 56 : if (!GetUpdate())
2831 30 : osSQL += " LIMIT 1";
2832 56 : oResult = SQLQuery(hDB, osSQL.c_str());
2833 : }
2834 57 : if (!oResult || oResult->RowCount() == 0)
2835 : {
2836 1 : if (oResult && pszZoomLevel != nullptr)
2837 : {
2838 1 : CPLError(CE_Failure, CPLE_AppDefined,
2839 : "ZOOM_LEVEL is probably not valid w.r.t tile "
2840 : "table content");
2841 : }
2842 1 : sqlite3_free(pszSQL);
2843 1 : return false;
2844 : }
2845 : }
2846 273 : sqlite3_free(pszSQL);
2847 :
2848 : // If USE_TILE_EXTENT=YES, then query the tile table to find which tiles
2849 : // actually exist.
2850 :
2851 : // CAUTION: Do not move those variables inside inner scope !
2852 546 : CPLString osContentsMinX, osContentsMinY, osContentsMaxX, osContentsMaxY;
2853 :
2854 273 : if (CPLTestBool(
2855 : CSLFetchNameValueDef(papszOpenOptionsIn, "USE_TILE_EXTENT", "NO")))
2856 : {
2857 13 : pszSQL = sqlite3_mprintf(
2858 : "SELECT MIN(tile_column), MIN(tile_row), MAX(tile_column), "
2859 : "MAX(tile_row) FROM \"%w\" WHERE zoom_level = %d",
2860 : pszTableName, atoi(oResult->GetValue(0, 0)));
2861 13 : auto oResult2 = SQLQuery(hDB, pszSQL);
2862 13 : sqlite3_free(pszSQL);
2863 26 : if (!oResult2 || oResult2->RowCount() == 0 ||
2864 : // Can happen if table is empty
2865 38 : oResult2->GetValue(0, 0) == nullptr ||
2866 : // Can happen if table has no NOT NULL constraint on tile_row
2867 : // and that all tile_row are NULL
2868 12 : oResult2->GetValue(1, 0) == nullptr)
2869 : {
2870 1 : return false;
2871 : }
2872 12 : const double dfPixelXSize = CPLAtof(oResult->GetValue(1, 0));
2873 12 : const double dfPixelYSize = CPLAtof(oResult->GetValue(2, 0));
2874 12 : const int nTileWidth = atoi(oResult->GetValue(3, 0));
2875 12 : const int nTileHeight = atoi(oResult->GetValue(4, 0));
2876 : osContentsMinX =
2877 24 : CPLSPrintf("%.17g", dfMinX + dfPixelXSize * nTileWidth *
2878 12 : atoi(oResult2->GetValue(0, 0)));
2879 : osContentsMaxY =
2880 24 : CPLSPrintf("%.17g", dfMaxY - dfPixelYSize * nTileHeight *
2881 12 : atoi(oResult2->GetValue(1, 0)));
2882 : osContentsMaxX = CPLSPrintf(
2883 24 : "%.17g", dfMinX + dfPixelXSize * nTileWidth *
2884 12 : (1 + atoi(oResult2->GetValue(2, 0))));
2885 : osContentsMinY = CPLSPrintf(
2886 24 : "%.17g", dfMaxY - dfPixelYSize * nTileHeight *
2887 12 : (1 + atoi(oResult2->GetValue(3, 0))));
2888 12 : pszContentsMinX = osContentsMinX.c_str();
2889 12 : pszContentsMinY = osContentsMinY.c_str();
2890 12 : pszContentsMaxX = osContentsMaxX.c_str();
2891 12 : pszContentsMaxY = osContentsMaxY.c_str();
2892 : }
2893 :
2894 272 : if (!InitRaster(nullptr, pszTableName, dfMinX, dfMinY, dfMaxX, dfMaxY,
2895 : pszContentsMinX, pszContentsMinY, pszContentsMaxX,
2896 272 : pszContentsMaxY, papszOpenOptionsIn, *oResult, 0))
2897 : {
2898 3 : return false;
2899 : }
2900 :
2901 : auto poBand =
2902 269 : reinterpret_cast<GDALGeoPackageRasterBand *>(GetRasterBand(1));
2903 269 : if (!osDataNull.empty())
2904 : {
2905 23 : double dfGPKGNoDataValue = CPLAtof(osDataNull);
2906 23 : if (m_eTF == GPKG_TF_PNG_16BIT)
2907 : {
2908 21 : if (dfGPKGNoDataValue < 0 || dfGPKGNoDataValue > 65535 ||
2909 21 : static_cast<int>(dfGPKGNoDataValue) != dfGPKGNoDataValue)
2910 : {
2911 0 : CPLError(CE_Warning, CPLE_AppDefined,
2912 : "data_null = %.17g is invalid for integer data_type",
2913 : dfGPKGNoDataValue);
2914 : }
2915 : else
2916 : {
2917 21 : m_usGPKGNull = static_cast<GUInt16>(dfGPKGNoDataValue);
2918 21 : if (m_eDT == GDT_Int16 && m_usGPKGNull > 32767)
2919 9 : dfGPKGNoDataValue = -32768.0;
2920 12 : else if (m_eDT == GDT_Float32)
2921 : {
2922 : // Pick a value that is unlikely to be hit with offset &
2923 : // scale
2924 4 : dfGPKGNoDataValue = -std::numeric_limits<float>::max();
2925 : }
2926 21 : poBand->SetNoDataValueInternal(dfGPKGNoDataValue);
2927 : }
2928 : }
2929 : else
2930 : {
2931 2 : poBand->SetNoDataValueInternal(
2932 2 : static_cast<float>(dfGPKGNoDataValue));
2933 : }
2934 : }
2935 269 : if (!osUom.empty())
2936 : {
2937 2 : poBand->SetUnitTypeInternal(osUom);
2938 : }
2939 269 : if (!osFieldName.empty())
2940 : {
2941 64 : GetRasterBand(1)->GDALRasterBand::SetDescription(osFieldName);
2942 : }
2943 269 : if (!osGridCellEncoding.empty())
2944 : {
2945 64 : if (osGridCellEncoding == "grid-value-is-center")
2946 : {
2947 15 : GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
2948 : GDALMD_AOP_POINT);
2949 : }
2950 49 : else if (osGridCellEncoding == "grid-value-is-area")
2951 : {
2952 45 : GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
2953 : GDALMD_AOP_AREA);
2954 : }
2955 : else
2956 : {
2957 4 : GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
2958 : GDALMD_AOP_POINT);
2959 4 : GetRasterBand(1)->GDALRasterBand::SetMetadataItem(
2960 : "GRID_CELL_ENCODING", osGridCellEncoding);
2961 : }
2962 : }
2963 :
2964 269 : CheckUnknownExtensions(true);
2965 :
2966 : // Do this after CheckUnknownExtensions() so that m_eTF is set to
2967 : // GPKG_TF_WEBP if the table already registers the gpkg_webp extension
2968 269 : const char *pszTF = CSLFetchNameValue(papszOpenOptionsIn, "TILE_FORMAT");
2969 269 : if (pszTF)
2970 : {
2971 4 : if (!GetUpdate())
2972 : {
2973 0 : CPLError(CE_Warning, CPLE_AppDefined,
2974 : "TILE_FORMAT open option ignored in read-only mode");
2975 : }
2976 4 : else if (m_eTF == GPKG_TF_PNG_16BIT ||
2977 4 : m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
2978 : {
2979 0 : CPLError(CE_Warning, CPLE_AppDefined,
2980 : "TILE_FORMAT open option ignored on gridded coverages");
2981 : }
2982 : else
2983 : {
2984 4 : GPKGTileFormat eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
2985 4 : if (eTF == GPKG_TF_WEBP && m_eTF != eTF)
2986 : {
2987 1 : if (!RegisterWebPExtension())
2988 0 : return false;
2989 : }
2990 4 : m_eTF = eTF;
2991 : }
2992 : }
2993 :
2994 269 : ParseCompressionOptions(papszOpenOptionsIn);
2995 :
2996 269 : m_osWHERE = CSLFetchNameValueDef(papszOpenOptionsIn, "WHERE", "");
2997 :
2998 : // Set metadata
2999 269 : if (pszIdentifier && pszIdentifier[0])
3000 269 : GDALPamDataset::SetMetadataItem("IDENTIFIER", pszIdentifier);
3001 269 : if (pszDescription && pszDescription[0])
3002 21 : GDALPamDataset::SetMetadataItem("DESCRIPTION", pszDescription);
3003 :
3004 : // Add overviews
3005 354 : for (int i = 1; i < oResult->RowCount(); i++)
3006 : {
3007 86 : auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
3008 86 : poOvrDS->ShareLockWithParentDataset(this);
3009 172 : if (!poOvrDS->InitRaster(this, pszTableName, dfMinX, dfMinY, dfMaxX,
3010 : dfMaxY, pszContentsMinX, pszContentsMinY,
3011 : pszContentsMaxX, pszContentsMaxY,
3012 86 : papszOpenOptionsIn, *oResult, i))
3013 : {
3014 0 : break;
3015 : }
3016 :
3017 : int nTileWidth, nTileHeight;
3018 86 : poOvrDS->GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3019 : const bool bStop =
3020 87 : (eAccess == GA_ReadOnly && poOvrDS->GetRasterXSize() < nTileWidth &&
3021 1 : poOvrDS->GetRasterYSize() < nTileHeight);
3022 :
3023 86 : m_apoOverviewDS.push_back(std::move(poOvrDS));
3024 :
3025 86 : if (bStop)
3026 : {
3027 1 : break;
3028 : }
3029 : }
3030 :
3031 269 : return true;
3032 : }
3033 :
3034 : /************************************************************************/
3035 : /* GetSpatialRef() */
3036 : /************************************************************************/
3037 :
3038 17 : const OGRSpatialReference *GDALGeoPackageDataset::GetSpatialRef() const
3039 : {
3040 17 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
3041 : }
3042 :
3043 : /************************************************************************/
3044 : /* SetSpatialRef() */
3045 : /************************************************************************/
3046 :
3047 150 : CPLErr GDALGeoPackageDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
3048 : {
3049 150 : if (nBands == 0)
3050 : {
3051 1 : CPLError(CE_Failure, CPLE_NotSupported,
3052 : "SetProjection() not supported on a dataset with 0 band");
3053 1 : return CE_Failure;
3054 : }
3055 149 : if (eAccess != GA_Update)
3056 : {
3057 1 : CPLError(CE_Failure, CPLE_NotSupported,
3058 : "SetProjection() not supported on read-only dataset");
3059 1 : return CE_Failure;
3060 : }
3061 :
3062 148 : const int nSRID = GetSrsId(poSRS);
3063 296 : const auto poTS = GetTilingScheme(m_osTilingScheme);
3064 148 : if (poTS && nSRID != poTS->nEPSGCode)
3065 : {
3066 2 : CPLError(CE_Failure, CPLE_NotSupported,
3067 : "Projection should be EPSG:%d for %s tiling scheme",
3068 1 : poTS->nEPSGCode, m_osTilingScheme.c_str());
3069 1 : return CE_Failure;
3070 : }
3071 :
3072 147 : m_nSRID = nSRID;
3073 147 : m_oSRS.Clear();
3074 147 : if (poSRS)
3075 146 : m_oSRS = *poSRS;
3076 :
3077 147 : if (m_bRecordInsertedInGPKGContent)
3078 : {
3079 119 : char *pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET srs_id = %d "
3080 : "WHERE lower(table_name) = lower('%q')",
3081 : m_nSRID, m_osRasterTable.c_str());
3082 119 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3083 119 : sqlite3_free(pszSQL);
3084 119 : if (eErr != OGRERR_NONE)
3085 0 : return CE_Failure;
3086 :
3087 119 : pszSQL = sqlite3_mprintf("UPDATE gpkg_tile_matrix_set SET srs_id = %d "
3088 : "WHERE lower(table_name) = lower('%q')",
3089 : m_nSRID, m_osRasterTable.c_str());
3090 119 : eErr = SQLCommand(hDB, pszSQL);
3091 119 : sqlite3_free(pszSQL);
3092 119 : if (eErr != OGRERR_NONE)
3093 0 : return CE_Failure;
3094 : }
3095 :
3096 147 : return CE_None;
3097 : }
3098 :
3099 : /************************************************************************/
3100 : /* GetGeoTransform() */
3101 : /************************************************************************/
3102 :
3103 33 : CPLErr GDALGeoPackageDataset::GetGeoTransform(GDALGeoTransform >) const
3104 : {
3105 33 : gt = m_gt;
3106 33 : if (!m_bGeoTransformValid)
3107 2 : return CE_Failure;
3108 : else
3109 31 : return CE_None;
3110 : }
3111 :
3112 : /************************************************************************/
3113 : /* SetGeoTransform() */
3114 : /************************************************************************/
3115 :
3116 190 : CPLErr GDALGeoPackageDataset::SetGeoTransform(const GDALGeoTransform >)
3117 : {
3118 190 : if (nBands == 0)
3119 : {
3120 2 : CPLError(CE_Failure, CPLE_NotSupported,
3121 : "SetGeoTransform() not supported on a dataset with 0 band");
3122 2 : return CE_Failure;
3123 : }
3124 188 : if (eAccess != GA_Update)
3125 : {
3126 1 : CPLError(CE_Failure, CPLE_NotSupported,
3127 : "SetGeoTransform() not supported on read-only dataset");
3128 1 : return CE_Failure;
3129 : }
3130 187 : if (m_bGeoTransformValid)
3131 : {
3132 1 : CPLError(CE_Failure, CPLE_NotSupported,
3133 : "Cannot modify geotransform once set");
3134 1 : return CE_Failure;
3135 : }
3136 186 : if (gt[2] != 0.0 || gt[4] != 0 || gt[5] > 0.0)
3137 : {
3138 0 : CPLError(CE_Failure, CPLE_NotSupported,
3139 : "Only north-up non rotated geotransform supported");
3140 0 : return CE_Failure;
3141 : }
3142 :
3143 186 : if (m_nZoomLevel < 0)
3144 : {
3145 185 : const auto poTS = GetTilingScheme(m_osTilingScheme);
3146 185 : if (poTS)
3147 : {
3148 20 : double dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
3149 20 : double dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
3150 199 : for (m_nZoomLevel = 0; m_nZoomLevel < MAX_ZOOM_LEVEL;
3151 179 : m_nZoomLevel++)
3152 : {
3153 198 : double dfExpectedPixelXSize =
3154 198 : dfPixelXSizeZoomLevel0 / (1 << m_nZoomLevel);
3155 198 : double dfExpectedPixelYSize =
3156 198 : dfPixelYSizeZoomLevel0 / (1 << m_nZoomLevel);
3157 198 : if (fabs(gt[1] - dfExpectedPixelXSize) <
3158 217 : 1e-8 * dfExpectedPixelXSize &&
3159 19 : fabs(fabs(gt[5]) - dfExpectedPixelYSize) <
3160 19 : 1e-8 * dfExpectedPixelYSize)
3161 : {
3162 19 : break;
3163 : }
3164 : }
3165 20 : if (m_nZoomLevel == MAX_ZOOM_LEVEL)
3166 : {
3167 1 : m_nZoomLevel = -1;
3168 1 : CPLError(
3169 : CE_Failure, CPLE_NotSupported,
3170 : "Could not find an appropriate zoom level of %s tiling "
3171 : "scheme that matches raster pixel size",
3172 : m_osTilingScheme.c_str());
3173 1 : return CE_Failure;
3174 : }
3175 : }
3176 : }
3177 :
3178 185 : m_gt = gt;
3179 185 : m_bGeoTransformValid = true;
3180 :
3181 185 : return FinalizeRasterRegistration();
3182 : }
3183 :
3184 : /************************************************************************/
3185 : /* FinalizeRasterRegistration() */
3186 : /************************************************************************/
3187 :
3188 185 : CPLErr GDALGeoPackageDataset::FinalizeRasterRegistration()
3189 : {
3190 : OGRErr eErr;
3191 :
3192 185 : m_dfTMSMinX = m_gt[0];
3193 185 : m_dfTMSMaxY = m_gt[3];
3194 :
3195 : int nTileWidth, nTileHeight;
3196 185 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3197 :
3198 185 : if (m_nZoomLevel < 0)
3199 : {
3200 165 : m_nZoomLevel = 0;
3201 239 : while ((nRasterXSize >> m_nZoomLevel) > nTileWidth ||
3202 165 : (nRasterYSize >> m_nZoomLevel) > nTileHeight)
3203 74 : m_nZoomLevel++;
3204 : }
3205 :
3206 185 : double dfPixelXSizeZoomLevel0 = m_gt[1] * (1 << m_nZoomLevel);
3207 185 : double dfPixelYSizeZoomLevel0 = fabs(m_gt[5]) * (1 << m_nZoomLevel);
3208 : int nTileXCountZoomLevel0 =
3209 185 : std::max(1, DIV_ROUND_UP((nRasterXSize >> m_nZoomLevel), nTileWidth));
3210 : int nTileYCountZoomLevel0 =
3211 185 : std::max(1, DIV_ROUND_UP((nRasterYSize >> m_nZoomLevel), nTileHeight));
3212 :
3213 370 : const auto poTS = GetTilingScheme(m_osTilingScheme);
3214 185 : if (poTS)
3215 : {
3216 20 : CPLAssert(m_nZoomLevel >= 0);
3217 20 : m_dfTMSMinX = poTS->dfMinX;
3218 20 : m_dfTMSMaxY = poTS->dfMaxY;
3219 20 : dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
3220 20 : dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
3221 20 : nTileXCountZoomLevel0 = poTS->nTileXCountZoomLevel0;
3222 20 : nTileYCountZoomLevel0 = poTS->nTileYCountZoomLevel0;
3223 : }
3224 185 : m_nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << m_nZoomLevel);
3225 185 : m_nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << m_nZoomLevel);
3226 :
3227 185 : if (!ComputeTileAndPixelShifts())
3228 : {
3229 1 : CPLError(CE_Failure, CPLE_AppDefined,
3230 : "Overflow occurred in ComputeTileAndPixelShifts()");
3231 1 : return CE_Failure;
3232 : }
3233 :
3234 184 : if (!AllocCachedTiles())
3235 : {
3236 0 : return CE_Failure;
3237 : }
3238 :
3239 184 : double dfGDALMinX = m_gt[0];
3240 184 : double dfGDALMinY = m_gt[3] + nRasterYSize * m_gt[5];
3241 184 : double dfGDALMaxX = m_gt[0] + nRasterXSize * m_gt[1];
3242 184 : double dfGDALMaxY = m_gt[3];
3243 :
3244 184 : if (SoftStartTransaction() != OGRERR_NONE)
3245 0 : return CE_Failure;
3246 :
3247 : const char *pszCurrentDate =
3248 184 : CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
3249 : CPLString osInsertGpkgContentsFormatting(
3250 : "INSERT INTO gpkg_contents "
3251 : "(table_name,data_type,identifier,description,min_x,min_y,max_x,max_y,"
3252 : "last_change,srs_id) VALUES "
3253 368 : "('%q','%q','%q','%q',%.17g,%.17g,%.17g,%.17g,");
3254 184 : osInsertGpkgContentsFormatting += (pszCurrentDate) ? "'%q'" : "%s";
3255 184 : osInsertGpkgContentsFormatting += ",%d)";
3256 368 : char *pszSQL = sqlite3_mprintf(
3257 : osInsertGpkgContentsFormatting.c_str(), m_osRasterTable.c_str(),
3258 184 : (m_eDT == GDT_Byte) ? "tiles" : "2d-gridded-coverage",
3259 : m_osIdentifier.c_str(), m_osDescription.c_str(), dfGDALMinX, dfGDALMinY,
3260 : dfGDALMaxX, dfGDALMaxY,
3261 : pszCurrentDate ? pszCurrentDate
3262 : : "strftime('%Y-%m-%dT%H:%M:%fZ','now')",
3263 : m_nSRID);
3264 :
3265 184 : eErr = SQLCommand(hDB, pszSQL);
3266 184 : sqlite3_free(pszSQL);
3267 184 : if (eErr != OGRERR_NONE)
3268 : {
3269 8 : SoftRollbackTransaction();
3270 8 : return CE_Failure;
3271 : }
3272 :
3273 176 : double dfTMSMaxX = m_dfTMSMinX + nTileXCountZoomLevel0 * nTileWidth *
3274 : dfPixelXSizeZoomLevel0;
3275 176 : double dfTMSMinY = m_dfTMSMaxY - nTileYCountZoomLevel0 * nTileHeight *
3276 : dfPixelYSizeZoomLevel0;
3277 :
3278 : pszSQL =
3279 176 : sqlite3_mprintf("INSERT INTO gpkg_tile_matrix_set "
3280 : "(table_name,srs_id,min_x,min_y,max_x,max_y) VALUES "
3281 : "('%q',%d,%.17g,%.17g,%.17g,%.17g)",
3282 : m_osRasterTable.c_str(), m_nSRID, m_dfTMSMinX,
3283 : dfTMSMinY, dfTMSMaxX, m_dfTMSMaxY);
3284 176 : eErr = SQLCommand(hDB, pszSQL);
3285 176 : sqlite3_free(pszSQL);
3286 176 : if (eErr != OGRERR_NONE)
3287 : {
3288 0 : SoftRollbackTransaction();
3289 0 : return CE_Failure;
3290 : }
3291 :
3292 176 : m_apoOverviewDS.resize(m_nZoomLevel);
3293 :
3294 587 : for (int i = 0; i <= m_nZoomLevel; i++)
3295 : {
3296 411 : double dfPixelXSizeZoomLevel = 0.0;
3297 411 : double dfPixelYSizeZoomLevel = 0.0;
3298 411 : int nTileMatrixWidth = 0;
3299 411 : int nTileMatrixHeight = 0;
3300 411 : if (EQUAL(m_osTilingScheme, "CUSTOM"))
3301 : {
3302 230 : dfPixelXSizeZoomLevel = m_gt[1] * (1 << (m_nZoomLevel - i));
3303 230 : dfPixelYSizeZoomLevel = fabs(m_gt[5]) * (1 << (m_nZoomLevel - i));
3304 : }
3305 : else
3306 : {
3307 181 : dfPixelXSizeZoomLevel = dfPixelXSizeZoomLevel0 / (1 << i);
3308 181 : dfPixelYSizeZoomLevel = dfPixelYSizeZoomLevel0 / (1 << i);
3309 : }
3310 411 : nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << i);
3311 411 : nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << i);
3312 :
3313 411 : pszSQL = sqlite3_mprintf(
3314 : "INSERT INTO gpkg_tile_matrix "
3315 : "(table_name,zoom_level,matrix_width,matrix_height,tile_width,tile_"
3316 : "height,pixel_x_size,pixel_y_size) VALUES "
3317 : "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
3318 : m_osRasterTable.c_str(), i, nTileMatrixWidth, nTileMatrixHeight,
3319 : nTileWidth, nTileHeight, dfPixelXSizeZoomLevel,
3320 : dfPixelYSizeZoomLevel);
3321 411 : eErr = SQLCommand(hDB, pszSQL);
3322 411 : sqlite3_free(pszSQL);
3323 411 : if (eErr != OGRERR_NONE)
3324 : {
3325 0 : SoftRollbackTransaction();
3326 0 : return CE_Failure;
3327 : }
3328 :
3329 411 : if (i < m_nZoomLevel)
3330 : {
3331 470 : auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
3332 235 : poOvrDS->ShareLockWithParentDataset(this);
3333 235 : poOvrDS->InitRaster(this, m_osRasterTable, i, nBands, m_dfTMSMinX,
3334 : m_dfTMSMaxY, dfPixelXSizeZoomLevel,
3335 : dfPixelYSizeZoomLevel, nTileWidth, nTileHeight,
3336 : nTileMatrixWidth, nTileMatrixHeight, dfGDALMinX,
3337 : dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
3338 :
3339 235 : m_apoOverviewDS[m_nZoomLevel - 1 - i] = std::move(poOvrDS);
3340 : }
3341 : }
3342 :
3343 176 : if (!m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.empty())
3344 : {
3345 40 : eErr = SQLCommand(
3346 : hDB, m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.c_str());
3347 40 : m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.clear();
3348 40 : if (eErr != OGRERR_NONE)
3349 : {
3350 0 : SoftRollbackTransaction();
3351 0 : return CE_Failure;
3352 : }
3353 : }
3354 :
3355 176 : SoftCommitTransaction();
3356 :
3357 176 : m_apoOverviewDS.resize(m_nZoomLevel);
3358 176 : m_bRecordInsertedInGPKGContent = true;
3359 :
3360 176 : return CE_None;
3361 : }
3362 :
3363 : /************************************************************************/
3364 : /* FlushCache() */
3365 : /************************************************************************/
3366 :
3367 2611 : CPLErr GDALGeoPackageDataset::FlushCache(bool bAtClosing)
3368 : {
3369 2611 : if (m_bInFlushCache)
3370 0 : return CE_None;
3371 :
3372 2611 : if (eAccess == GA_Update || !m_bMetadataDirty)
3373 : {
3374 2608 : SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
3375 : }
3376 :
3377 2611 : if (m_bRemoveOGREmptyTable)
3378 : {
3379 656 : m_bRemoveOGREmptyTable = false;
3380 656 : RemoveOGREmptyTable();
3381 : }
3382 :
3383 2611 : CPLErr eErr = IFlushCacheWithErrCode(bAtClosing);
3384 :
3385 2611 : FlushMetadata();
3386 :
3387 2611 : if (eAccess == GA_Update || !m_bMetadataDirty)
3388 : {
3389 : // Needed again as above IFlushCacheWithErrCode()
3390 : // may have call GDALGeoPackageRasterBand::InvalidateStatistics()
3391 : // which modifies metadata
3392 2611 : SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
3393 : }
3394 :
3395 2611 : return eErr;
3396 : }
3397 :
3398 4846 : CPLErr GDALGeoPackageDataset::IFlushCacheWithErrCode(bool bAtClosing)
3399 :
3400 : {
3401 4846 : if (m_bInFlushCache)
3402 2168 : return CE_None;
3403 2678 : m_bInFlushCache = true;
3404 2678 : if (hDB && eAccess == GA_ReadOnly && bAtClosing)
3405 : {
3406 : // Clean-up metadata that will go to PAM by removing items that
3407 : // are reconstructed.
3408 1950 : CPLStringList aosMD;
3409 1597 : for (CSLConstList papszIter = GetMetadata(); papszIter && *papszIter;
3410 : ++papszIter)
3411 : {
3412 622 : char *pszKey = nullptr;
3413 622 : CPLParseNameValue(*papszIter, &pszKey);
3414 1244 : if (pszKey &&
3415 622 : (EQUAL(pszKey, "AREA_OR_POINT") ||
3416 477 : EQUAL(pszKey, "IDENTIFIER") || EQUAL(pszKey, "DESCRIPTION") ||
3417 256 : EQUAL(pszKey, "ZOOM_LEVEL") ||
3418 652 : STARTS_WITH(pszKey, "GPKG_METADATA_ITEM_")))
3419 : {
3420 : // remove it
3421 : }
3422 : else
3423 : {
3424 30 : aosMD.AddString(*papszIter);
3425 : }
3426 622 : CPLFree(pszKey);
3427 : }
3428 975 : oMDMD.SetMetadata(aosMD.List());
3429 975 : oMDMD.SetMetadata(nullptr, "IMAGE_STRUCTURE");
3430 :
3431 1950 : GDALPamDataset::FlushCache(bAtClosing);
3432 : }
3433 : else
3434 : {
3435 : // Short circuit GDALPamDataset to avoid serialization to .aux.xml
3436 1703 : GDALDataset::FlushCache(bAtClosing);
3437 : }
3438 :
3439 6617 : for (auto &poLayer : m_apoLayers)
3440 : {
3441 3939 : poLayer->RunDeferredCreationIfNecessary();
3442 3939 : poLayer->CreateSpatialIndexIfNecessary();
3443 : }
3444 :
3445 : // Update raster table last_change column in gpkg_contents if needed
3446 2678 : if (m_bHasModifiedTiles)
3447 : {
3448 536 : for (int i = 1; i <= nBands; ++i)
3449 : {
3450 : auto poBand =
3451 357 : cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
3452 357 : if (!poBand->HaveStatsMetadataBeenSetInThisSession())
3453 : {
3454 344 : poBand->InvalidateStatistics();
3455 344 : if (psPam && psPam->pszPamFilename)
3456 344 : VSIUnlink(psPam->pszPamFilename);
3457 : }
3458 : }
3459 :
3460 179 : UpdateGpkgContentsLastChange(m_osRasterTable);
3461 :
3462 179 : m_bHasModifiedTiles = false;
3463 : }
3464 :
3465 2678 : CPLErr eErr = FlushTiles();
3466 :
3467 2678 : m_bInFlushCache = false;
3468 2678 : return eErr;
3469 : }
3470 :
3471 : /************************************************************************/
3472 : /* GetCurrentDateEscapedSQL() */
3473 : /************************************************************************/
3474 :
3475 1901 : std::string GDALGeoPackageDataset::GetCurrentDateEscapedSQL()
3476 : {
3477 : const char *pszCurrentDate =
3478 1901 : CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
3479 1901 : if (pszCurrentDate)
3480 10 : return '\'' + SQLEscapeLiteral(pszCurrentDate) + '\'';
3481 1896 : return "strftime('%Y-%m-%dT%H:%M:%fZ','now')";
3482 : }
3483 :
3484 : /************************************************************************/
3485 : /* UpdateGpkgContentsLastChange() */
3486 : /************************************************************************/
3487 :
3488 : OGRErr
3489 820 : GDALGeoPackageDataset::UpdateGpkgContentsLastChange(const char *pszTableName)
3490 : {
3491 : char *pszSQL =
3492 820 : sqlite3_mprintf("UPDATE gpkg_contents SET "
3493 : "last_change = %s "
3494 : "WHERE lower(table_name) = lower('%q')",
3495 1640 : GetCurrentDateEscapedSQL().c_str(), pszTableName);
3496 820 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3497 820 : sqlite3_free(pszSQL);
3498 820 : return eErr;
3499 : }
3500 :
3501 : /************************************************************************/
3502 : /* IBuildOverviews() */
3503 : /************************************************************************/
3504 :
3505 20 : CPLErr GDALGeoPackageDataset::IBuildOverviews(
3506 : const char *pszResampling, int nOverviews, const int *panOverviewList,
3507 : int nBandsIn, const int * /*panBandList*/, GDALProgressFunc pfnProgress,
3508 : void *pProgressData, CSLConstList papszOptions)
3509 : {
3510 20 : if (GetAccess() != GA_Update)
3511 : {
3512 1 : CPLError(CE_Failure, CPLE_NotSupported,
3513 : "Overview building not supported on a database opened in "
3514 : "read-only mode");
3515 1 : return CE_Failure;
3516 : }
3517 19 : if (m_poParentDS != nullptr)
3518 : {
3519 1 : CPLError(CE_Failure, CPLE_NotSupported,
3520 : "Overview building not supported on overview dataset");
3521 1 : return CE_Failure;
3522 : }
3523 :
3524 18 : if (nOverviews == 0)
3525 : {
3526 5 : for (auto &poOvrDS : m_apoOverviewDS)
3527 3 : poOvrDS->FlushCache(false);
3528 :
3529 2 : SoftStartTransaction();
3530 :
3531 2 : if (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
3532 : {
3533 1 : char *pszSQL = sqlite3_mprintf(
3534 : "DELETE FROM gpkg_2d_gridded_tile_ancillary WHERE id IN "
3535 : "(SELECT y.id FROM \"%w\" x "
3536 : "JOIN gpkg_2d_gridded_tile_ancillary y "
3537 : "ON x.id = y.tpudt_id AND y.tpudt_name = '%q' AND "
3538 : "x.zoom_level < %d)",
3539 : m_osRasterTable.c_str(), m_osRasterTable.c_str(), m_nZoomLevel);
3540 1 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3541 1 : sqlite3_free(pszSQL);
3542 1 : if (eErr != OGRERR_NONE)
3543 : {
3544 0 : SoftRollbackTransaction();
3545 0 : return CE_Failure;
3546 : }
3547 : }
3548 :
3549 : char *pszSQL =
3550 2 : sqlite3_mprintf("DELETE FROM \"%w\" WHERE zoom_level < %d",
3551 : m_osRasterTable.c_str(), m_nZoomLevel);
3552 2 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3553 2 : sqlite3_free(pszSQL);
3554 2 : if (eErr != OGRERR_NONE)
3555 : {
3556 0 : SoftRollbackTransaction();
3557 0 : return CE_Failure;
3558 : }
3559 :
3560 2 : SoftCommitTransaction();
3561 :
3562 2 : return CE_None;
3563 : }
3564 :
3565 16 : if (nBandsIn != nBands)
3566 : {
3567 0 : CPLError(CE_Failure, CPLE_NotSupported,
3568 : "Generation of overviews in GPKG only"
3569 : "supported when operating on all bands.");
3570 0 : return CE_Failure;
3571 : }
3572 :
3573 16 : if (m_apoOverviewDS.empty())
3574 : {
3575 0 : CPLError(CE_Failure, CPLE_AppDefined,
3576 : "Image too small to support overviews");
3577 0 : return CE_Failure;
3578 : }
3579 :
3580 16 : FlushCache(false);
3581 60 : for (int i = 0; i < nOverviews; i++)
3582 : {
3583 47 : if (panOverviewList[i] < 2)
3584 : {
3585 1 : CPLError(CE_Failure, CPLE_IllegalArg,
3586 : "Overview factor must be >= 2");
3587 1 : return CE_Failure;
3588 : }
3589 :
3590 46 : bool bFound = false;
3591 46 : int jCandidate = -1;
3592 46 : int nMaxOvFactor = 0;
3593 196 : for (int j = 0; j < static_cast<int>(m_apoOverviewDS.size()); j++)
3594 : {
3595 190 : const auto poODS = m_apoOverviewDS[j].get();
3596 : const int nOvFactor =
3597 190 : static_cast<int>(0.5 + poODS->m_gt[1] / m_gt[1]);
3598 :
3599 190 : nMaxOvFactor = nOvFactor;
3600 :
3601 190 : if (nOvFactor == panOverviewList[i])
3602 : {
3603 40 : bFound = true;
3604 40 : break;
3605 : }
3606 :
3607 150 : if (jCandidate < 0 && nOvFactor > panOverviewList[i])
3608 1 : jCandidate = j;
3609 : }
3610 :
3611 46 : if (!bFound)
3612 : {
3613 : /* Mostly for debug */
3614 6 : if (!CPLTestBool(CPLGetConfigOption(
3615 : "ALLOW_GPKG_ZOOM_OTHER_EXTENSION", "YES")))
3616 : {
3617 2 : CPLString osOvrList;
3618 4 : for (const auto &poODS : m_apoOverviewDS)
3619 : {
3620 : const int nOvFactor =
3621 2 : static_cast<int>(0.5 + poODS->m_gt[1] / m_gt[1]);
3622 :
3623 2 : if (!osOvrList.empty())
3624 0 : osOvrList += ' ';
3625 2 : osOvrList += CPLSPrintf("%d", nOvFactor);
3626 : }
3627 2 : CPLError(CE_Failure, CPLE_NotSupported,
3628 : "Only overviews %s can be computed",
3629 : osOvrList.c_str());
3630 2 : return CE_Failure;
3631 : }
3632 : else
3633 : {
3634 4 : int nOvFactor = panOverviewList[i];
3635 4 : if (jCandidate < 0)
3636 3 : jCandidate = static_cast<int>(m_apoOverviewDS.size());
3637 :
3638 4 : int nOvXSize = std::max(1, GetRasterXSize() / nOvFactor);
3639 4 : int nOvYSize = std::max(1, GetRasterYSize() / nOvFactor);
3640 4 : if (!(jCandidate == static_cast<int>(m_apoOverviewDS.size()) &&
3641 5 : nOvFactor == 2 * nMaxOvFactor) &&
3642 1 : !m_bZoomOther)
3643 : {
3644 1 : CPLError(CE_Warning, CPLE_AppDefined,
3645 : "Use of overview factor %d causes gpkg_zoom_other "
3646 : "extension to be needed",
3647 : nOvFactor);
3648 1 : RegisterZoomOtherExtension();
3649 1 : m_bZoomOther = true;
3650 : }
3651 :
3652 4 : SoftStartTransaction();
3653 :
3654 4 : CPLAssert(jCandidate > 0);
3655 : const int nNewZoomLevel =
3656 4 : m_apoOverviewDS[jCandidate - 1]->m_nZoomLevel;
3657 :
3658 : char *pszSQL;
3659 : OGRErr eErr;
3660 24 : for (int k = 0; k <= jCandidate; k++)
3661 : {
3662 60 : pszSQL = sqlite3_mprintf(
3663 : "UPDATE gpkg_tile_matrix SET zoom_level = %d "
3664 : "WHERE lower(table_name) = lower('%q') AND zoom_level "
3665 : "= %d",
3666 20 : m_nZoomLevel - k + 1, m_osRasterTable.c_str(),
3667 20 : m_nZoomLevel - k);
3668 20 : eErr = SQLCommand(hDB, pszSQL);
3669 20 : sqlite3_free(pszSQL);
3670 20 : if (eErr != OGRERR_NONE)
3671 : {
3672 0 : SoftRollbackTransaction();
3673 0 : return CE_Failure;
3674 : }
3675 :
3676 : pszSQL =
3677 20 : sqlite3_mprintf("UPDATE \"%w\" SET zoom_level = %d "
3678 : "WHERE zoom_level = %d",
3679 : m_osRasterTable.c_str(),
3680 20 : m_nZoomLevel - k + 1, m_nZoomLevel - k);
3681 20 : eErr = SQLCommand(hDB, pszSQL);
3682 20 : sqlite3_free(pszSQL);
3683 20 : if (eErr != OGRERR_NONE)
3684 : {
3685 0 : SoftRollbackTransaction();
3686 0 : return CE_Failure;
3687 : }
3688 : }
3689 :
3690 4 : double dfGDALMinX = m_gt[0];
3691 4 : double dfGDALMinY = m_gt[3] + nRasterYSize * m_gt[5];
3692 4 : double dfGDALMaxX = m_gt[0] + nRasterXSize * m_gt[1];
3693 4 : double dfGDALMaxY = m_gt[3];
3694 4 : double dfPixelXSizeZoomLevel = m_gt[1] * nOvFactor;
3695 4 : double dfPixelYSizeZoomLevel = fabs(m_gt[5]) * nOvFactor;
3696 : int nTileWidth, nTileHeight;
3697 4 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3698 4 : int nTileMatrixWidth = DIV_ROUND_UP(nOvXSize, nTileWidth);
3699 4 : int nTileMatrixHeight = DIV_ROUND_UP(nOvYSize, nTileHeight);
3700 4 : pszSQL = sqlite3_mprintf(
3701 : "INSERT INTO gpkg_tile_matrix "
3702 : "(table_name,zoom_level,matrix_width,matrix_height,tile_"
3703 : "width,tile_height,pixel_x_size,pixel_y_size) VALUES "
3704 : "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
3705 : m_osRasterTable.c_str(), nNewZoomLevel, nTileMatrixWidth,
3706 : nTileMatrixHeight, nTileWidth, nTileHeight,
3707 : dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel);
3708 4 : eErr = SQLCommand(hDB, pszSQL);
3709 4 : sqlite3_free(pszSQL);
3710 4 : if (eErr != OGRERR_NONE)
3711 : {
3712 0 : SoftRollbackTransaction();
3713 0 : return CE_Failure;
3714 : }
3715 :
3716 4 : SoftCommitTransaction();
3717 :
3718 4 : m_nZoomLevel++; /* this change our zoom level as well as
3719 : previous overviews */
3720 20 : for (int k = 0; k < jCandidate; k++)
3721 16 : m_apoOverviewDS[k]->m_nZoomLevel++;
3722 :
3723 4 : auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
3724 4 : poOvrDS->ShareLockWithParentDataset(this);
3725 4 : poOvrDS->InitRaster(
3726 : this, m_osRasterTable, nNewZoomLevel, nBands, m_dfTMSMinX,
3727 : m_dfTMSMaxY, dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel,
3728 : nTileWidth, nTileHeight, nTileMatrixWidth,
3729 : nTileMatrixHeight, dfGDALMinX, dfGDALMinY, dfGDALMaxX,
3730 : dfGDALMaxY);
3731 4 : m_apoOverviewDS.insert(m_apoOverviewDS.begin() + jCandidate,
3732 8 : std::move(poOvrDS));
3733 : }
3734 : }
3735 : }
3736 :
3737 : GDALRasterBand ***papapoOverviewBands = static_cast<GDALRasterBand ***>(
3738 13 : CPLCalloc(sizeof(GDALRasterBand **), nBands));
3739 13 : CPLErr eErr = CE_None;
3740 49 : for (int iBand = 0; eErr == CE_None && iBand < nBands; iBand++)
3741 : {
3742 72 : papapoOverviewBands[iBand] = static_cast<GDALRasterBand **>(
3743 36 : CPLCalloc(sizeof(GDALRasterBand *), nOverviews));
3744 36 : int iCurOverview = 0;
3745 185 : for (int i = 0; i < nOverviews; i++)
3746 : {
3747 149 : bool bFound = false;
3748 724 : for (const auto &poODS : m_apoOverviewDS)
3749 : {
3750 : const int nOvFactor =
3751 724 : static_cast<int>(0.5 + poODS->m_gt[1] / m_gt[1]);
3752 :
3753 724 : if (nOvFactor == panOverviewList[i])
3754 : {
3755 298 : papapoOverviewBands[iBand][iCurOverview] =
3756 149 : poODS->GetRasterBand(iBand + 1);
3757 149 : iCurOverview++;
3758 149 : bFound = true;
3759 149 : break;
3760 : }
3761 : }
3762 149 : if (!bFound)
3763 : {
3764 0 : CPLError(CE_Failure, CPLE_AppDefined,
3765 : "Could not find dataset corresponding to ov factor %d",
3766 0 : panOverviewList[i]);
3767 0 : eErr = CE_Failure;
3768 : }
3769 : }
3770 36 : if (eErr == CE_None)
3771 : {
3772 36 : CPLAssert(iCurOverview == nOverviews);
3773 : }
3774 : }
3775 :
3776 13 : if (eErr == CE_None)
3777 13 : eErr = GDALRegenerateOverviewsMultiBand(
3778 13 : nBands, papoBands, nOverviews, papapoOverviewBands, pszResampling,
3779 : pfnProgress, pProgressData, papszOptions);
3780 :
3781 49 : for (int iBand = 0; iBand < nBands; iBand++)
3782 : {
3783 36 : CPLFree(papapoOverviewBands[iBand]);
3784 : }
3785 13 : CPLFree(papapoOverviewBands);
3786 :
3787 13 : return eErr;
3788 : }
3789 :
3790 : /************************************************************************/
3791 : /* GetFileList() */
3792 : /************************************************************************/
3793 :
3794 36 : char **GDALGeoPackageDataset::GetFileList()
3795 : {
3796 36 : TryLoadXML();
3797 36 : return GDALPamDataset::GetFileList();
3798 : }
3799 :
3800 : /************************************************************************/
3801 : /* GetMetadataDomainList() */
3802 : /************************************************************************/
3803 :
3804 47 : char **GDALGeoPackageDataset::GetMetadataDomainList()
3805 : {
3806 47 : GetMetadata();
3807 47 : if (!m_osRasterTable.empty())
3808 5 : GetMetadata("GEOPACKAGE");
3809 47 : return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
3810 47 : TRUE, "SUBDATASETS", nullptr);
3811 : }
3812 :
3813 : /************************************************************************/
3814 : /* CheckMetadataDomain() */
3815 : /************************************************************************/
3816 :
3817 5240 : const char *GDALGeoPackageDataset::CheckMetadataDomain(const char *pszDomain)
3818 : {
3819 5423 : if (pszDomain != nullptr && EQUAL(pszDomain, "GEOPACKAGE") &&
3820 183 : m_osRasterTable.empty())
3821 : {
3822 4 : CPLError(
3823 : CE_Warning, CPLE_IllegalArg,
3824 : "Using GEOPACKAGE for a non-raster geopackage is not supported. "
3825 : "Using default domain instead");
3826 4 : return nullptr;
3827 : }
3828 5236 : return pszDomain;
3829 : }
3830 :
3831 : /************************************************************************/
3832 : /* HasMetadataTables() */
3833 : /************************************************************************/
3834 :
3835 5329 : bool GDALGeoPackageDataset::HasMetadataTables() const
3836 : {
3837 5329 : if (m_nHasMetadataTables < 0)
3838 : {
3839 : const int nCount =
3840 1980 : SQLGetInteger(hDB,
3841 : "SELECT COUNT(*) FROM sqlite_master WHERE name IN "
3842 : "('gpkg_metadata', 'gpkg_metadata_reference') "
3843 : "AND type IN ('table', 'view')",
3844 : nullptr);
3845 1980 : m_nHasMetadataTables = nCount == 2;
3846 : }
3847 5329 : return CPL_TO_BOOL(m_nHasMetadataTables);
3848 : }
3849 :
3850 : /************************************************************************/
3851 : /* HasDataColumnsTable() */
3852 : /************************************************************************/
3853 :
3854 1180 : bool GDALGeoPackageDataset::HasDataColumnsTable() const
3855 : {
3856 2360 : const int nCount = SQLGetInteger(
3857 1180 : hDB,
3858 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_data_columns'"
3859 : "AND type IN ('table', 'view')",
3860 : nullptr);
3861 1180 : return nCount == 1;
3862 : }
3863 :
3864 : /************************************************************************/
3865 : /* HasDataColumnConstraintsTable() */
3866 : /************************************************************************/
3867 :
3868 120 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTable() const
3869 : {
3870 120 : const int nCount = SQLGetInteger(hDB,
3871 : "SELECT 1 FROM sqlite_master WHERE name = "
3872 : "'gpkg_data_column_constraints'"
3873 : "AND type IN ('table', 'view')",
3874 : nullptr);
3875 120 : return nCount == 1;
3876 : }
3877 :
3878 : /************************************************************************/
3879 : /* HasDataColumnConstraintsTableGPKG_1_0() */
3880 : /************************************************************************/
3881 :
3882 73 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTableGPKG_1_0() const
3883 : {
3884 73 : if (m_nApplicationId != GP10_APPLICATION_ID)
3885 71 : return false;
3886 : // In GPKG 1.0, the columns were named minIsInclusive, maxIsInclusive
3887 : // They were changed in 1.1 to min_is_inclusive, max_is_inclusive
3888 2 : bool bRet = false;
3889 2 : sqlite3_stmt *hSQLStmt = nullptr;
3890 2 : int rc = sqlite3_prepare_v2(hDB,
3891 : "SELECT minIsInclusive, maxIsInclusive FROM "
3892 : "gpkg_data_column_constraints",
3893 : -1, &hSQLStmt, nullptr);
3894 2 : if (rc == SQLITE_OK)
3895 : {
3896 2 : bRet = true;
3897 2 : sqlite3_finalize(hSQLStmt);
3898 : }
3899 2 : return bRet;
3900 : }
3901 :
3902 : /************************************************************************/
3903 : /* CreateColumnsTableAndColumnConstraintsTablesIfNecessary() */
3904 : /************************************************************************/
3905 :
3906 49 : bool GDALGeoPackageDataset::
3907 : CreateColumnsTableAndColumnConstraintsTablesIfNecessary()
3908 : {
3909 49 : if (!HasDataColumnsTable())
3910 : {
3911 : // Geopackage < 1.3 had
3912 : // CONSTRAINT fk_gdc_tn FOREIGN KEY (table_name) REFERENCES
3913 : // gpkg_contents(table_name) instead of the unique constraint.
3914 10 : if (OGRERR_NONE !=
3915 10 : SQLCommand(
3916 : GetDB(),
3917 : "CREATE TABLE gpkg_data_columns ("
3918 : "table_name TEXT NOT NULL,"
3919 : "column_name TEXT NOT NULL,"
3920 : "name TEXT,"
3921 : "title TEXT,"
3922 : "description TEXT,"
3923 : "mime_type TEXT,"
3924 : "constraint_name TEXT,"
3925 : "CONSTRAINT pk_gdc PRIMARY KEY (table_name, column_name),"
3926 : "CONSTRAINT gdc_tn UNIQUE (table_name, name));"))
3927 : {
3928 0 : return false;
3929 : }
3930 : }
3931 49 : if (!HasDataColumnConstraintsTable())
3932 : {
3933 22 : const char *min_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
3934 11 : ? "min_is_inclusive"
3935 : : "minIsInclusive";
3936 22 : const char *max_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
3937 11 : ? "max_is_inclusive"
3938 : : "maxIsInclusive";
3939 :
3940 : const std::string osSQL(
3941 : CPLSPrintf("CREATE TABLE gpkg_data_column_constraints ("
3942 : "constraint_name TEXT NOT NULL,"
3943 : "constraint_type TEXT NOT NULL,"
3944 : "value TEXT,"
3945 : "min NUMERIC,"
3946 : "%s BOOLEAN,"
3947 : "max NUMERIC,"
3948 : "%s BOOLEAN,"
3949 : "description TEXT,"
3950 : "CONSTRAINT gdcc_ntv UNIQUE (constraint_name, "
3951 : "constraint_type, value));",
3952 11 : min_is_inclusive, max_is_inclusive));
3953 11 : if (OGRERR_NONE != SQLCommand(GetDB(), osSQL.c_str()))
3954 : {
3955 0 : return false;
3956 : }
3957 : }
3958 49 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
3959 : {
3960 0 : return false;
3961 : }
3962 49 : if (SQLGetInteger(GetDB(),
3963 : "SELECT 1 FROM gpkg_extensions WHERE "
3964 : "table_name = 'gpkg_data_columns'",
3965 49 : nullptr) != 1)
3966 : {
3967 11 : if (OGRERR_NONE !=
3968 11 : SQLCommand(
3969 : GetDB(),
3970 : "INSERT INTO gpkg_extensions "
3971 : "(table_name,column_name,extension_name,definition,scope) "
3972 : "VALUES ('gpkg_data_columns', NULL, 'gpkg_schema', "
3973 : "'http://www.geopackage.org/spec121/#extension_schema', "
3974 : "'read-write')"))
3975 : {
3976 0 : return false;
3977 : }
3978 : }
3979 49 : if (SQLGetInteger(GetDB(),
3980 : "SELECT 1 FROM gpkg_extensions WHERE "
3981 : "table_name = 'gpkg_data_column_constraints'",
3982 49 : nullptr) != 1)
3983 : {
3984 11 : if (OGRERR_NONE !=
3985 11 : SQLCommand(
3986 : GetDB(),
3987 : "INSERT INTO gpkg_extensions "
3988 : "(table_name,column_name,extension_name,definition,scope) "
3989 : "VALUES ('gpkg_data_column_constraints', NULL, 'gpkg_schema', "
3990 : "'http://www.geopackage.org/spec121/#extension_schema', "
3991 : "'read-write')"))
3992 : {
3993 0 : return false;
3994 : }
3995 : }
3996 :
3997 49 : return true;
3998 : }
3999 :
4000 : /************************************************************************/
4001 : /* HasGpkgextRelationsTable() */
4002 : /************************************************************************/
4003 :
4004 1176 : bool GDALGeoPackageDataset::HasGpkgextRelationsTable() const
4005 : {
4006 2352 : const int nCount = SQLGetInteger(
4007 1176 : hDB,
4008 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkgext_relations'"
4009 : "AND type IN ('table', 'view')",
4010 : nullptr);
4011 1176 : return nCount == 1;
4012 : }
4013 :
4014 : /************************************************************************/
4015 : /* CreateRelationsTableIfNecessary() */
4016 : /************************************************************************/
4017 :
4018 9 : bool GDALGeoPackageDataset::CreateRelationsTableIfNecessary()
4019 : {
4020 9 : if (HasGpkgextRelationsTable())
4021 : {
4022 5 : return true;
4023 : }
4024 :
4025 4 : if (OGRERR_NONE !=
4026 4 : SQLCommand(GetDB(), "CREATE TABLE gpkgext_relations ("
4027 : "id INTEGER PRIMARY KEY AUTOINCREMENT,"
4028 : "base_table_name TEXT NOT NULL,"
4029 : "base_primary_column TEXT NOT NULL DEFAULT 'id',"
4030 : "related_table_name TEXT NOT NULL,"
4031 : "related_primary_column TEXT NOT NULL DEFAULT 'id',"
4032 : "relation_name TEXT NOT NULL,"
4033 : "mapping_table_name TEXT NOT NULL UNIQUE);"))
4034 : {
4035 0 : return false;
4036 : }
4037 :
4038 4 : return true;
4039 : }
4040 :
4041 : /************************************************************************/
4042 : /* HasQGISLayerStyles() */
4043 : /************************************************************************/
4044 :
4045 11 : bool GDALGeoPackageDataset::HasQGISLayerStyles() const
4046 : {
4047 : // QGIS layer_styles extension:
4048 : // https://github.com/pka/qgpkg/blob/master/qgis_geopackage_extension.md
4049 11 : bool bRet = false;
4050 : const int nCount =
4051 11 : SQLGetInteger(hDB,
4052 : "SELECT 1 FROM sqlite_master WHERE name = 'layer_styles'"
4053 : "AND type = 'table'",
4054 : nullptr);
4055 11 : if (nCount == 1)
4056 : {
4057 1 : sqlite3_stmt *hSQLStmt = nullptr;
4058 2 : int rc = sqlite3_prepare_v2(
4059 1 : hDB, "SELECT f_table_name, f_geometry_column FROM layer_styles", -1,
4060 : &hSQLStmt, nullptr);
4061 1 : if (rc == SQLITE_OK)
4062 : {
4063 1 : bRet = true;
4064 1 : sqlite3_finalize(hSQLStmt);
4065 : }
4066 : }
4067 11 : return bRet;
4068 : }
4069 :
4070 : /************************************************************************/
4071 : /* GetMetadata() */
4072 : /************************************************************************/
4073 :
4074 3523 : char **GDALGeoPackageDataset::GetMetadata(const char *pszDomain)
4075 :
4076 : {
4077 3523 : pszDomain = CheckMetadataDomain(pszDomain);
4078 3523 : if (pszDomain != nullptr && EQUAL(pszDomain, "SUBDATASETS"))
4079 67 : return m_aosSubDatasets.List();
4080 :
4081 3456 : if (m_bHasReadMetadataFromStorage)
4082 1532 : return GDALPamDataset::GetMetadata(pszDomain);
4083 :
4084 1924 : m_bHasReadMetadataFromStorage = true;
4085 :
4086 1924 : TryLoadXML();
4087 :
4088 1924 : if (!HasMetadataTables())
4089 1418 : return GDALPamDataset::GetMetadata(pszDomain);
4090 :
4091 506 : char *pszSQL = nullptr;
4092 506 : if (!m_osRasterTable.empty())
4093 : {
4094 169 : pszSQL = sqlite3_mprintf(
4095 : "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
4096 : "mdr.reference_scope FROM gpkg_metadata md "
4097 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4098 : "WHERE "
4099 : "(mdr.reference_scope = 'geopackage' OR "
4100 : "(mdr.reference_scope = 'table' AND lower(mdr.table_name) = "
4101 : "lower('%q'))) ORDER BY md.id "
4102 : "LIMIT 1000", // to avoid denial of service
4103 : m_osRasterTable.c_str());
4104 : }
4105 : else
4106 : {
4107 337 : pszSQL = sqlite3_mprintf(
4108 : "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
4109 : "mdr.reference_scope FROM gpkg_metadata md "
4110 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4111 : "WHERE "
4112 : "mdr.reference_scope = 'geopackage' ORDER BY md.id "
4113 : "LIMIT 1000" // to avoid denial of service
4114 : );
4115 : }
4116 :
4117 1012 : auto oResult = SQLQuery(hDB, pszSQL);
4118 506 : sqlite3_free(pszSQL);
4119 506 : if (!oResult)
4120 : {
4121 0 : return GDALPamDataset::GetMetadata(pszDomain);
4122 : }
4123 :
4124 506 : char **papszMetadata = CSLDuplicate(GDALPamDataset::GetMetadata());
4125 :
4126 : /* GDAL metadata */
4127 695 : for (int i = 0; i < oResult->RowCount(); i++)
4128 : {
4129 189 : const char *pszMetadata = oResult->GetValue(0, i);
4130 189 : const char *pszMDStandardURI = oResult->GetValue(1, i);
4131 189 : const char *pszMimeType = oResult->GetValue(2, i);
4132 189 : const char *pszReferenceScope = oResult->GetValue(3, i);
4133 189 : if (pszMetadata && pszMDStandardURI && pszMimeType &&
4134 189 : pszReferenceScope && EQUAL(pszMDStandardURI, "http://gdal.org") &&
4135 173 : EQUAL(pszMimeType, "text/xml"))
4136 : {
4137 173 : CPLXMLNode *psXMLNode = CPLParseXMLString(pszMetadata);
4138 173 : if (psXMLNode)
4139 : {
4140 346 : GDALMultiDomainMetadata oLocalMDMD;
4141 173 : oLocalMDMD.XMLInit(psXMLNode, FALSE);
4142 331 : if (!m_osRasterTable.empty() &&
4143 158 : EQUAL(pszReferenceScope, "geopackage"))
4144 : {
4145 6 : oMDMD.SetMetadata(oLocalMDMD.GetMetadata(), "GEOPACKAGE");
4146 : }
4147 : else
4148 : {
4149 : papszMetadata =
4150 167 : CSLMerge(papszMetadata, oLocalMDMD.GetMetadata());
4151 167 : CSLConstList papszDomainList = oLocalMDMD.GetDomainList();
4152 167 : CSLConstList papszIter = papszDomainList;
4153 444 : while (papszIter && *papszIter)
4154 : {
4155 277 : if (EQUAL(*papszIter, "IMAGE_STRUCTURE"))
4156 : {
4157 : CSLConstList papszMD =
4158 125 : oLocalMDMD.GetMetadata(*papszIter);
4159 : const char *pszBAND_COUNT =
4160 125 : CSLFetchNameValue(papszMD, "BAND_COUNT");
4161 125 : if (pszBAND_COUNT)
4162 123 : m_nBandCountFromMetadata = atoi(pszBAND_COUNT);
4163 :
4164 : const char *pszCOLOR_TABLE =
4165 125 : CSLFetchNameValue(papszMD, "COLOR_TABLE");
4166 125 : if (pszCOLOR_TABLE)
4167 : {
4168 : const CPLStringList aosTokens(
4169 : CSLTokenizeString2(pszCOLOR_TABLE, "{,",
4170 26 : 0));
4171 13 : if ((aosTokens.size() % 4) == 0)
4172 : {
4173 13 : const int nColors = aosTokens.size() / 4;
4174 : m_poCTFromMetadata =
4175 13 : std::make_unique<GDALColorTable>();
4176 3341 : for (int iColor = 0; iColor < nColors;
4177 : ++iColor)
4178 : {
4179 : GDALColorEntry sEntry;
4180 3328 : sEntry.c1 = static_cast<short>(
4181 3328 : atoi(aosTokens[4 * iColor + 0]));
4182 3328 : sEntry.c2 = static_cast<short>(
4183 3328 : atoi(aosTokens[4 * iColor + 1]));
4184 3328 : sEntry.c3 = static_cast<short>(
4185 3328 : atoi(aosTokens[4 * iColor + 2]));
4186 3328 : sEntry.c4 = static_cast<short>(
4187 3328 : atoi(aosTokens[4 * iColor + 3]));
4188 3328 : m_poCTFromMetadata->SetColorEntry(
4189 : iColor, &sEntry);
4190 : }
4191 : }
4192 : }
4193 :
4194 : const char *pszTILE_FORMAT =
4195 125 : CSLFetchNameValue(papszMD, "TILE_FORMAT");
4196 125 : if (pszTILE_FORMAT)
4197 : {
4198 8 : m_osTFFromMetadata = pszTILE_FORMAT;
4199 8 : oMDMD.SetMetadataItem("TILE_FORMAT",
4200 : pszTILE_FORMAT,
4201 : "IMAGE_STRUCTURE");
4202 : }
4203 :
4204 : const char *pszNodataValue =
4205 125 : CSLFetchNameValue(papszMD, "NODATA_VALUE");
4206 125 : if (pszNodataValue)
4207 : {
4208 2 : m_osNodataValueFromMetadata = pszNodataValue;
4209 : }
4210 : }
4211 :
4212 152 : else if (!EQUAL(*papszIter, "") &&
4213 16 : !STARTS_WITH(*papszIter, "BAND_"))
4214 : {
4215 12 : oMDMD.SetMetadata(
4216 6 : oLocalMDMD.GetMetadata(*papszIter), *papszIter);
4217 : }
4218 277 : papszIter++;
4219 : }
4220 : }
4221 173 : CPLDestroyXMLNode(psXMLNode);
4222 : }
4223 : }
4224 : }
4225 :
4226 506 : GDALPamDataset::SetMetadata(papszMetadata);
4227 506 : CSLDestroy(papszMetadata);
4228 506 : papszMetadata = nullptr;
4229 :
4230 : /* Add non-GDAL metadata now */
4231 506 : int nNonGDALMDILocal = 1;
4232 506 : int nNonGDALMDIGeopackage = 1;
4233 695 : for (int i = 0; i < oResult->RowCount(); i++)
4234 : {
4235 189 : const char *pszMetadata = oResult->GetValue(0, i);
4236 189 : const char *pszMDStandardURI = oResult->GetValue(1, i);
4237 189 : const char *pszMimeType = oResult->GetValue(2, i);
4238 189 : const char *pszReferenceScope = oResult->GetValue(3, i);
4239 189 : if (pszMetadata == nullptr || pszMDStandardURI == nullptr ||
4240 189 : pszMimeType == nullptr || pszReferenceScope == nullptr)
4241 : {
4242 : // should not happen as there are NOT NULL constraints
4243 : // But a database could lack such NOT NULL constraints or have
4244 : // large values that would cause a memory allocation failure.
4245 0 : continue;
4246 : }
4247 189 : int bIsGPKGScope = EQUAL(pszReferenceScope, "geopackage");
4248 189 : if (EQUAL(pszMDStandardURI, "http://gdal.org") &&
4249 173 : EQUAL(pszMimeType, "text/xml"))
4250 173 : continue;
4251 :
4252 16 : if (!m_osRasterTable.empty() && bIsGPKGScope)
4253 : {
4254 8 : oMDMD.SetMetadataItem(
4255 : CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDIGeopackage),
4256 : pszMetadata, "GEOPACKAGE");
4257 8 : nNonGDALMDIGeopackage++;
4258 : }
4259 : /*else if( strcmp( pszMDStandardURI, "http://www.isotc211.org/2005/gmd"
4260 : ) == 0 && strcmp( pszMimeType, "text/xml" ) == 0 )
4261 : {
4262 : char* apszMD[2];
4263 : apszMD[0] = (char*)pszMetadata;
4264 : apszMD[1] = NULL;
4265 : oMDMD.SetMetadata(apszMD, "xml:MD_Metadata");
4266 : }*/
4267 : else
4268 : {
4269 8 : oMDMD.SetMetadataItem(
4270 : CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDILocal),
4271 : pszMetadata);
4272 8 : nNonGDALMDILocal++;
4273 : }
4274 : }
4275 :
4276 506 : return GDALPamDataset::GetMetadata(pszDomain);
4277 : }
4278 :
4279 : /************************************************************************/
4280 : /* WriteMetadata() */
4281 : /************************************************************************/
4282 :
4283 744 : void GDALGeoPackageDataset::WriteMetadata(
4284 : CPLXMLNode *psXMLNode, /* will be destroyed by the method */
4285 : const char *pszTableName)
4286 : {
4287 744 : const bool bIsEmpty = (psXMLNode == nullptr);
4288 744 : if (!HasMetadataTables())
4289 : {
4290 536 : if (bIsEmpty || !CreateMetadataTables())
4291 : {
4292 255 : CPLDestroyXMLNode(psXMLNode);
4293 255 : return;
4294 : }
4295 : }
4296 :
4297 489 : char *pszXML = nullptr;
4298 489 : if (!bIsEmpty)
4299 : {
4300 : CPLXMLNode *psMasterXMLNode =
4301 328 : CPLCreateXMLNode(nullptr, CXT_Element, "GDALMultiDomainMetadata");
4302 328 : psMasterXMLNode->psChild = psXMLNode;
4303 328 : pszXML = CPLSerializeXMLTree(psMasterXMLNode);
4304 328 : CPLDestroyXMLNode(psMasterXMLNode);
4305 : }
4306 : // cppcheck-suppress uselessAssignmentPtrArg
4307 489 : psXMLNode = nullptr;
4308 :
4309 489 : char *pszSQL = nullptr;
4310 489 : if (pszTableName && pszTableName[0] != '\0')
4311 : {
4312 341 : pszSQL = sqlite3_mprintf(
4313 : "SELECT md.id FROM gpkg_metadata md "
4314 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4315 : "WHERE md.md_scope = 'dataset' AND "
4316 : "md.md_standard_uri='http://gdal.org' "
4317 : "AND md.mime_type='text/xml' AND mdr.reference_scope = 'table' AND "
4318 : "lower(mdr.table_name) = lower('%q')",
4319 : pszTableName);
4320 : }
4321 : else
4322 : {
4323 148 : pszSQL = sqlite3_mprintf(
4324 : "SELECT md.id FROM gpkg_metadata md "
4325 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4326 : "WHERE md.md_scope = 'dataset' AND "
4327 : "md.md_standard_uri='http://gdal.org' "
4328 : "AND md.mime_type='text/xml' AND mdr.reference_scope = "
4329 : "'geopackage'");
4330 : }
4331 : OGRErr err;
4332 489 : int mdId = SQLGetInteger(hDB, pszSQL, &err);
4333 489 : if (err != OGRERR_NONE)
4334 457 : mdId = -1;
4335 489 : sqlite3_free(pszSQL);
4336 :
4337 489 : if (bIsEmpty)
4338 : {
4339 161 : if (mdId >= 0)
4340 : {
4341 6 : SQLCommand(
4342 : hDB,
4343 : CPLSPrintf(
4344 : "DELETE FROM gpkg_metadata_reference WHERE md_file_id = %d",
4345 : mdId));
4346 6 : SQLCommand(
4347 : hDB,
4348 : CPLSPrintf("DELETE FROM gpkg_metadata WHERE id = %d", mdId));
4349 : }
4350 : }
4351 : else
4352 : {
4353 328 : if (mdId >= 0)
4354 : {
4355 26 : pszSQL = sqlite3_mprintf(
4356 : "UPDATE gpkg_metadata SET metadata = '%q' WHERE id = %d",
4357 : pszXML, mdId);
4358 : }
4359 : else
4360 : {
4361 : pszSQL =
4362 302 : sqlite3_mprintf("INSERT INTO gpkg_metadata (md_scope, "
4363 : "md_standard_uri, mime_type, metadata) VALUES "
4364 : "('dataset','http://gdal.org','text/xml','%q')",
4365 : pszXML);
4366 : }
4367 328 : SQLCommand(hDB, pszSQL);
4368 328 : sqlite3_free(pszSQL);
4369 :
4370 328 : CPLFree(pszXML);
4371 :
4372 328 : if (mdId < 0)
4373 : {
4374 302 : const sqlite_int64 nFID = sqlite3_last_insert_rowid(hDB);
4375 302 : if (pszTableName != nullptr && pszTableName[0] != '\0')
4376 : {
4377 290 : pszSQL = sqlite3_mprintf(
4378 : "INSERT INTO gpkg_metadata_reference (reference_scope, "
4379 : "table_name, timestamp, md_file_id) VALUES "
4380 : "('table', '%q', %s, %d)",
4381 580 : pszTableName, GetCurrentDateEscapedSQL().c_str(),
4382 : static_cast<int>(nFID));
4383 : }
4384 : else
4385 : {
4386 12 : pszSQL = sqlite3_mprintf(
4387 : "INSERT INTO gpkg_metadata_reference (reference_scope, "
4388 : "timestamp, md_file_id) VALUES "
4389 : "('geopackage', %s, %d)",
4390 24 : GetCurrentDateEscapedSQL().c_str(), static_cast<int>(nFID));
4391 : }
4392 : }
4393 : else
4394 : {
4395 26 : pszSQL = sqlite3_mprintf("UPDATE gpkg_metadata_reference SET "
4396 : "timestamp = %s WHERE md_file_id = %d",
4397 52 : GetCurrentDateEscapedSQL().c_str(), mdId);
4398 : }
4399 328 : SQLCommand(hDB, pszSQL);
4400 328 : sqlite3_free(pszSQL);
4401 : }
4402 : }
4403 :
4404 : /************************************************************************/
4405 : /* CreateMetadataTables() */
4406 : /************************************************************************/
4407 :
4408 299 : bool GDALGeoPackageDataset::CreateMetadataTables()
4409 : {
4410 : const bool bCreateTriggers =
4411 299 : CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "NO"));
4412 :
4413 : /* From C.10. gpkg_metadata Table 35. gpkg_metadata Table Definition SQL */
4414 : CPLString osSQL = "CREATE TABLE gpkg_metadata ("
4415 : "id INTEGER CONSTRAINT m_pk PRIMARY KEY ASC NOT NULL,"
4416 : "md_scope TEXT NOT NULL DEFAULT 'dataset',"
4417 : "md_standard_uri TEXT NOT NULL,"
4418 : "mime_type TEXT NOT NULL DEFAULT 'text/xml',"
4419 : "metadata TEXT NOT NULL DEFAULT ''"
4420 598 : ")";
4421 :
4422 : /* From D.2. metadata Table 40. metadata Trigger Definition SQL */
4423 299 : const char *pszMetadataTriggers =
4424 : "CREATE TRIGGER 'gpkg_metadata_md_scope_insert' "
4425 : "BEFORE INSERT ON 'gpkg_metadata' "
4426 : "FOR EACH ROW BEGIN "
4427 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata violates "
4428 : "constraint: md_scope must be one of undefined | fieldSession | "
4429 : "collectionSession | series | dataset | featureType | feature | "
4430 : "attributeType | attribute | tile | model | catalogue | schema | "
4431 : "taxonomy software | service | collectionHardware | "
4432 : "nonGeographicDataset | dimensionGroup') "
4433 : "WHERE NOT(NEW.md_scope IN "
4434 : "('undefined','fieldSession','collectionSession','series','dataset', "
4435 : "'featureType','feature','attributeType','attribute','tile','model', "
4436 : "'catalogue','schema','taxonomy','software','service', "
4437 : "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
4438 : "END; "
4439 : "CREATE TRIGGER 'gpkg_metadata_md_scope_update' "
4440 : "BEFORE UPDATE OF 'md_scope' ON 'gpkg_metadata' "
4441 : "FOR EACH ROW BEGIN "
4442 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata violates "
4443 : "constraint: md_scope must be one of undefined | fieldSession | "
4444 : "collectionSession | series | dataset | featureType | feature | "
4445 : "attributeType | attribute | tile | model | catalogue | schema | "
4446 : "taxonomy software | service | collectionHardware | "
4447 : "nonGeographicDataset | dimensionGroup') "
4448 : "WHERE NOT(NEW.md_scope IN "
4449 : "('undefined','fieldSession','collectionSession','series','dataset', "
4450 : "'featureType','feature','attributeType','attribute','tile','model', "
4451 : "'catalogue','schema','taxonomy','software','service', "
4452 : "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
4453 : "END";
4454 299 : if (bCreateTriggers)
4455 : {
4456 0 : osSQL += ";";
4457 0 : osSQL += pszMetadataTriggers;
4458 : }
4459 :
4460 : /* From C.11. gpkg_metadata_reference Table 36. gpkg_metadata_reference
4461 : * Table Definition SQL */
4462 : osSQL += ";"
4463 : "CREATE TABLE gpkg_metadata_reference ("
4464 : "reference_scope TEXT NOT NULL,"
4465 : "table_name TEXT,"
4466 : "column_name TEXT,"
4467 : "row_id_value INTEGER,"
4468 : "timestamp DATETIME NOT NULL DEFAULT "
4469 : "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
4470 : "md_file_id INTEGER NOT NULL,"
4471 : "md_parent_id INTEGER,"
4472 : "CONSTRAINT crmr_mfi_fk FOREIGN KEY (md_file_id) REFERENCES "
4473 : "gpkg_metadata(id),"
4474 : "CONSTRAINT crmr_mpi_fk FOREIGN KEY (md_parent_id) REFERENCES "
4475 : "gpkg_metadata(id)"
4476 299 : ")";
4477 :
4478 : /* From D.3. metadata_reference Table 41. gpkg_metadata_reference Trigger
4479 : * Definition SQL */
4480 299 : const char *pszMetadataReferenceTriggers =
4481 : "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_insert' "
4482 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4483 : "FOR EACH ROW BEGIN "
4484 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4485 : "violates constraint: reference_scope must be one of \"geopackage\", "
4486 : "table\", \"column\", \"row\", \"row/col\"') "
4487 : "WHERE NOT NEW.reference_scope IN "
4488 : "('geopackage','table','column','row','row/col'); "
4489 : "END; "
4490 : "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_update' "
4491 : "BEFORE UPDATE OF 'reference_scope' ON 'gpkg_metadata_reference' "
4492 : "FOR EACH ROW BEGIN "
4493 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4494 : "violates constraint: reference_scope must be one of \"geopackage\", "
4495 : "\"table\", \"column\", \"row\", \"row/col\"') "
4496 : "WHERE NOT NEW.reference_scope IN "
4497 : "('geopackage','table','column','row','row/col'); "
4498 : "END; "
4499 : "CREATE TRIGGER 'gpkg_metadata_reference_column_name_insert' "
4500 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4501 : "FOR EACH ROW BEGIN "
4502 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4503 : "violates constraint: column name must be NULL when reference_scope "
4504 : "is \"geopackage\", \"table\" or \"row\"') "
4505 : "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
4506 : "AND NEW.column_name IS NOT NULL); "
4507 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4508 : "violates constraint: column name must be defined for the specified "
4509 : "table when reference_scope is \"column\" or \"row/col\"') "
4510 : "WHERE (NEW.reference_scope IN ('column','row/col') "
4511 : "AND NOT NEW.table_name IN ( "
4512 : "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
4513 : "AND name = NEW.table_name "
4514 : "AND sql LIKE ('%' || NEW.column_name || '%'))); "
4515 : "END; "
4516 : "CREATE TRIGGER 'gpkg_metadata_reference_column_name_update' "
4517 : "BEFORE UPDATE OF column_name ON 'gpkg_metadata_reference' "
4518 : "FOR EACH ROW BEGIN "
4519 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4520 : "violates constraint: column name must be NULL when reference_scope "
4521 : "is \"geopackage\", \"table\" or \"row\"') "
4522 : "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
4523 : "AND NEW.column_name IS NOT NULL); "
4524 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4525 : "violates constraint: column name must be defined for the specified "
4526 : "table when reference_scope is \"column\" or \"row/col\"') "
4527 : "WHERE (NEW.reference_scope IN ('column','row/col') "
4528 : "AND NOT NEW.table_name IN ( "
4529 : "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
4530 : "AND name = NEW.table_name "
4531 : "AND sql LIKE ('%' || NEW.column_name || '%'))); "
4532 : "END; "
4533 : "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_insert' "
4534 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4535 : "FOR EACH ROW BEGIN "
4536 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4537 : "violates constraint: row_id_value must be NULL when reference_scope "
4538 : "is \"geopackage\", \"table\" or \"column\"') "
4539 : "WHERE NEW.reference_scope IN ('geopackage','table','column') "
4540 : "AND NEW.row_id_value IS NOT NULL; "
4541 : "END; "
4542 : "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_update' "
4543 : "BEFORE UPDATE OF 'row_id_value' ON 'gpkg_metadata_reference' "
4544 : "FOR EACH ROW BEGIN "
4545 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4546 : "violates constraint: row_id_value must be NULL when reference_scope "
4547 : "is \"geopackage\", \"table\" or \"column\"') "
4548 : "WHERE NEW.reference_scope IN ('geopackage','table','column') "
4549 : "AND NEW.row_id_value IS NOT NULL; "
4550 : "END; "
4551 : "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_insert' "
4552 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4553 : "FOR EACH ROW BEGIN "
4554 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4555 : "violates constraint: timestamp must be a valid time in ISO 8601 "
4556 : "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
4557 : "WHERE NOT (NEW.timestamp GLOB "
4558 : "'[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-"
4559 : "5][0-9].[0-9][0-9][0-9]Z' "
4560 : "AND strftime('%s',NEW.timestamp) NOT NULL); "
4561 : "END; "
4562 : "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_update' "
4563 : "BEFORE UPDATE OF 'timestamp' ON 'gpkg_metadata_reference' "
4564 : "FOR EACH ROW BEGIN "
4565 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4566 : "violates constraint: timestamp must be a valid time in ISO 8601 "
4567 : "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
4568 : "WHERE NOT (NEW.timestamp GLOB "
4569 : "'[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-"
4570 : "5][0-9].[0-9][0-9][0-9]Z' "
4571 : "AND strftime('%s',NEW.timestamp) NOT NULL); "
4572 : "END";
4573 299 : if (bCreateTriggers)
4574 : {
4575 0 : osSQL += ";";
4576 0 : osSQL += pszMetadataReferenceTriggers;
4577 : }
4578 :
4579 299 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
4580 2 : return false;
4581 :
4582 297 : osSQL += ";";
4583 : osSQL += "INSERT INTO gpkg_extensions "
4584 : "(table_name, column_name, extension_name, definition, scope) "
4585 : "VALUES "
4586 : "('gpkg_metadata', NULL, 'gpkg_metadata', "
4587 : "'http://www.geopackage.org/spec120/#extension_metadata', "
4588 297 : "'read-write')";
4589 :
4590 297 : osSQL += ";";
4591 : osSQL += "INSERT INTO gpkg_extensions "
4592 : "(table_name, column_name, extension_name, definition, scope) "
4593 : "VALUES "
4594 : "('gpkg_metadata_reference', NULL, 'gpkg_metadata', "
4595 : "'http://www.geopackage.org/spec120/#extension_metadata', "
4596 297 : "'read-write')";
4597 :
4598 297 : const bool bOK = SQLCommand(hDB, osSQL) == OGRERR_NONE;
4599 297 : m_nHasMetadataTables = bOK;
4600 297 : return bOK;
4601 : }
4602 :
4603 : /************************************************************************/
4604 : /* FlushMetadata() */
4605 : /************************************************************************/
4606 :
4607 8502 : void GDALGeoPackageDataset::FlushMetadata()
4608 : {
4609 8502 : if (!m_bMetadataDirty || m_poParentDS != nullptr ||
4610 374 : m_nCreateMetadataTables == FALSE)
4611 8134 : return;
4612 368 : m_bMetadataDirty = false;
4613 :
4614 368 : if (eAccess == GA_ReadOnly)
4615 : {
4616 3 : return;
4617 : }
4618 :
4619 365 : bool bCanWriteAreaOrPoint =
4620 728 : !m_bGridCellEncodingAsCO &&
4621 363 : (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT);
4622 365 : if (!m_osRasterTable.empty())
4623 : {
4624 : const char *pszIdentifier =
4625 142 : GDALGeoPackageDataset::GetMetadataItem("IDENTIFIER");
4626 : const char *pszDescription =
4627 142 : GDALGeoPackageDataset::GetMetadataItem("DESCRIPTION");
4628 171 : if (!m_bIdentifierAsCO && pszIdentifier != nullptr &&
4629 29 : pszIdentifier != m_osIdentifier)
4630 : {
4631 14 : m_osIdentifier = pszIdentifier;
4632 : char *pszSQL =
4633 14 : sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
4634 : "WHERE lower(table_name) = lower('%q')",
4635 : pszIdentifier, m_osRasterTable.c_str());
4636 14 : SQLCommand(hDB, pszSQL);
4637 14 : sqlite3_free(pszSQL);
4638 : }
4639 149 : if (!m_bDescriptionAsCO && pszDescription != nullptr &&
4640 7 : pszDescription != m_osDescription)
4641 : {
4642 7 : m_osDescription = pszDescription;
4643 : char *pszSQL =
4644 7 : sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
4645 : "WHERE lower(table_name) = lower('%q')",
4646 : pszDescription, m_osRasterTable.c_str());
4647 7 : SQLCommand(hDB, pszSQL);
4648 7 : sqlite3_free(pszSQL);
4649 : }
4650 142 : if (bCanWriteAreaOrPoint)
4651 : {
4652 : const char *pszAreaOrPoint =
4653 28 : GDALGeoPackageDataset::GetMetadataItem(GDALMD_AREA_OR_POINT);
4654 28 : if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_AREA))
4655 : {
4656 23 : bCanWriteAreaOrPoint = false;
4657 23 : char *pszSQL = sqlite3_mprintf(
4658 : "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
4659 : "grid_cell_encoding = 'grid-value-is-area' WHERE "
4660 : "lower(tile_matrix_set_name) = lower('%q')",
4661 : m_osRasterTable.c_str());
4662 23 : SQLCommand(hDB, pszSQL);
4663 23 : sqlite3_free(pszSQL);
4664 : }
4665 5 : else if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_POINT))
4666 : {
4667 1 : bCanWriteAreaOrPoint = false;
4668 1 : char *pszSQL = sqlite3_mprintf(
4669 : "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
4670 : "grid_cell_encoding = 'grid-value-is-center' WHERE "
4671 : "lower(tile_matrix_set_name) = lower('%q')",
4672 : m_osRasterTable.c_str());
4673 1 : SQLCommand(hDB, pszSQL);
4674 1 : sqlite3_free(pszSQL);
4675 : }
4676 : }
4677 : }
4678 :
4679 365 : char **papszMDDup = nullptr;
4680 568 : for (char **papszIter = GDALGeoPackageDataset::GetMetadata();
4681 568 : papszIter && *papszIter; ++papszIter)
4682 : {
4683 203 : if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
4684 29 : continue;
4685 174 : if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
4686 8 : continue;
4687 166 : if (STARTS_WITH_CI(*papszIter, "ZOOM_LEVEL="))
4688 14 : continue;
4689 152 : if (STARTS_WITH_CI(*papszIter, "GPKG_METADATA_ITEM_"))
4690 4 : continue;
4691 148 : if ((m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT) &&
4692 29 : !bCanWriteAreaOrPoint &&
4693 26 : STARTS_WITH_CI(*papszIter, GDALMD_AREA_OR_POINT))
4694 : {
4695 26 : continue;
4696 : }
4697 122 : papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
4698 : }
4699 :
4700 365 : CPLXMLNode *psXMLNode = nullptr;
4701 : {
4702 365 : GDALMultiDomainMetadata oLocalMDMD;
4703 365 : CSLConstList papszDomainList = oMDMD.GetDomainList();
4704 365 : CSLConstList papszIter = papszDomainList;
4705 365 : oLocalMDMD.SetMetadata(papszMDDup);
4706 705 : while (papszIter && *papszIter)
4707 : {
4708 340 : if (!EQUAL(*papszIter, "") &&
4709 172 : !EQUAL(*papszIter, "IMAGE_STRUCTURE") &&
4710 15 : !EQUAL(*papszIter, "GEOPACKAGE"))
4711 : {
4712 8 : oLocalMDMD.SetMetadata(oMDMD.GetMetadata(*papszIter),
4713 : *papszIter);
4714 : }
4715 340 : papszIter++;
4716 : }
4717 365 : if (m_nBandCountFromMetadata > 0)
4718 : {
4719 72 : oLocalMDMD.SetMetadataItem(
4720 : "BAND_COUNT", CPLSPrintf("%d", m_nBandCountFromMetadata),
4721 : "IMAGE_STRUCTURE");
4722 72 : if (nBands == 1)
4723 : {
4724 48 : const auto poCT = GetRasterBand(1)->GetColorTable();
4725 48 : if (poCT)
4726 : {
4727 16 : std::string osVal("{");
4728 8 : const int nColorCount = poCT->GetColorEntryCount();
4729 2056 : for (int i = 0; i < nColorCount; ++i)
4730 : {
4731 2048 : if (i > 0)
4732 2040 : osVal += ',';
4733 2048 : const GDALColorEntry *psEntry = poCT->GetColorEntry(i);
4734 : osVal +=
4735 2048 : CPLSPrintf("{%d,%d,%d,%d}", psEntry->c1,
4736 2048 : psEntry->c2, psEntry->c3, psEntry->c4);
4737 : }
4738 8 : osVal += '}';
4739 8 : oLocalMDMD.SetMetadataItem("COLOR_TABLE", osVal.c_str(),
4740 : "IMAGE_STRUCTURE");
4741 : }
4742 : }
4743 72 : if (nBands == 1)
4744 : {
4745 48 : const char *pszTILE_FORMAT = nullptr;
4746 48 : switch (m_eTF)
4747 : {
4748 0 : case GPKG_TF_PNG_JPEG:
4749 0 : pszTILE_FORMAT = "JPEG_PNG";
4750 0 : break;
4751 42 : case GPKG_TF_PNG:
4752 42 : break;
4753 0 : case GPKG_TF_PNG8:
4754 0 : pszTILE_FORMAT = "PNG8";
4755 0 : break;
4756 3 : case GPKG_TF_JPEG:
4757 3 : pszTILE_FORMAT = "JPEG";
4758 3 : break;
4759 3 : case GPKG_TF_WEBP:
4760 3 : pszTILE_FORMAT = "WEBP";
4761 3 : break;
4762 0 : case GPKG_TF_PNG_16BIT:
4763 0 : break;
4764 0 : case GPKG_TF_TIFF_32BIT_FLOAT:
4765 0 : break;
4766 : }
4767 48 : if (pszTILE_FORMAT)
4768 6 : oLocalMDMD.SetMetadataItem("TILE_FORMAT", pszTILE_FORMAT,
4769 : "IMAGE_STRUCTURE");
4770 : }
4771 : }
4772 507 : if (GetRasterCount() > 0 &&
4773 142 : GetRasterBand(1)->GetRasterDataType() == GDT_Byte)
4774 : {
4775 112 : int bHasNoData = FALSE;
4776 : const double dfNoDataValue =
4777 112 : GetRasterBand(1)->GetNoDataValue(&bHasNoData);
4778 112 : if (bHasNoData)
4779 : {
4780 3 : oLocalMDMD.SetMetadataItem("NODATA_VALUE",
4781 : CPLSPrintf("%.17g", dfNoDataValue),
4782 : "IMAGE_STRUCTURE");
4783 : }
4784 : }
4785 612 : for (int i = 1; i <= GetRasterCount(); ++i)
4786 : {
4787 : auto poBand =
4788 247 : cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
4789 247 : poBand->AddImplicitStatistics(false);
4790 247 : char **papszMD = GetRasterBand(i)->GetMetadata();
4791 247 : poBand->AddImplicitStatistics(true);
4792 247 : if (papszMD)
4793 : {
4794 14 : oLocalMDMD.SetMetadata(papszMD, CPLSPrintf("BAND_%d", i));
4795 : }
4796 : }
4797 365 : psXMLNode = oLocalMDMD.Serialize();
4798 : }
4799 :
4800 365 : CSLDestroy(papszMDDup);
4801 365 : papszMDDup = nullptr;
4802 :
4803 365 : WriteMetadata(psXMLNode, m_osRasterTable.c_str());
4804 :
4805 365 : if (!m_osRasterTable.empty())
4806 : {
4807 : char **papszGeopackageMD =
4808 142 : GDALGeoPackageDataset::GetMetadata("GEOPACKAGE");
4809 :
4810 142 : papszMDDup = nullptr;
4811 151 : for (char **papszIter = papszGeopackageMD; papszIter && *papszIter;
4812 : ++papszIter)
4813 : {
4814 9 : papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
4815 : }
4816 :
4817 284 : GDALMultiDomainMetadata oLocalMDMD;
4818 142 : oLocalMDMD.SetMetadata(papszMDDup);
4819 142 : CSLDestroy(papszMDDup);
4820 142 : papszMDDup = nullptr;
4821 142 : psXMLNode = oLocalMDMD.Serialize();
4822 :
4823 142 : WriteMetadata(psXMLNode, nullptr);
4824 : }
4825 :
4826 602 : for (auto &poLayer : m_apoLayers)
4827 : {
4828 237 : const char *pszIdentifier = poLayer->GetMetadataItem("IDENTIFIER");
4829 237 : const char *pszDescription = poLayer->GetMetadataItem("DESCRIPTION");
4830 237 : if (pszIdentifier != nullptr)
4831 : {
4832 : char *pszSQL =
4833 3 : sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
4834 : "WHERE lower(table_name) = lower('%q')",
4835 : pszIdentifier, poLayer->GetName());
4836 3 : SQLCommand(hDB, pszSQL);
4837 3 : sqlite3_free(pszSQL);
4838 : }
4839 237 : if (pszDescription != nullptr)
4840 : {
4841 : char *pszSQL =
4842 3 : sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
4843 : "WHERE lower(table_name) = lower('%q')",
4844 : pszDescription, poLayer->GetName());
4845 3 : SQLCommand(hDB, pszSQL);
4846 3 : sqlite3_free(pszSQL);
4847 : }
4848 :
4849 237 : papszMDDup = nullptr;
4850 625 : for (char **papszIter = poLayer->GetMetadata(); papszIter && *papszIter;
4851 : ++papszIter)
4852 : {
4853 388 : if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
4854 3 : continue;
4855 385 : if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
4856 3 : continue;
4857 382 : if (STARTS_WITH_CI(*papszIter, "OLMD_FID64="))
4858 0 : continue;
4859 382 : papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
4860 : }
4861 :
4862 : {
4863 237 : GDALMultiDomainMetadata oLocalMDMD;
4864 237 : char **papszDomainList = poLayer->GetMetadataDomainList();
4865 237 : char **papszIter = papszDomainList;
4866 237 : oLocalMDMD.SetMetadata(papszMDDup);
4867 513 : while (papszIter && *papszIter)
4868 : {
4869 276 : if (!EQUAL(*papszIter, ""))
4870 60 : oLocalMDMD.SetMetadata(poLayer->GetMetadata(*papszIter),
4871 : *papszIter);
4872 276 : papszIter++;
4873 : }
4874 237 : CSLDestroy(papszDomainList);
4875 237 : psXMLNode = oLocalMDMD.Serialize();
4876 : }
4877 :
4878 237 : CSLDestroy(papszMDDup);
4879 237 : papszMDDup = nullptr;
4880 :
4881 237 : WriteMetadata(psXMLNode, poLayer->GetName());
4882 : }
4883 : }
4884 :
4885 : /************************************************************************/
4886 : /* GetMetadataItem() */
4887 : /************************************************************************/
4888 :
4889 1550 : const char *GDALGeoPackageDataset::GetMetadataItem(const char *pszName,
4890 : const char *pszDomain)
4891 : {
4892 1550 : pszDomain = CheckMetadataDomain(pszDomain);
4893 1550 : return CSLFetchNameValue(GetMetadata(pszDomain), pszName);
4894 : }
4895 :
4896 : /************************************************************************/
4897 : /* SetMetadata() */
4898 : /************************************************************************/
4899 :
4900 146 : CPLErr GDALGeoPackageDataset::SetMetadata(char **papszMetadata,
4901 : const char *pszDomain)
4902 : {
4903 146 : pszDomain = CheckMetadataDomain(pszDomain);
4904 146 : m_bMetadataDirty = true;
4905 146 : GetMetadata(); /* force loading from storage if needed */
4906 146 : return GDALPamDataset::SetMetadata(papszMetadata, pszDomain);
4907 : }
4908 :
4909 : /************************************************************************/
4910 : /* SetMetadataItem() */
4911 : /************************************************************************/
4912 :
4913 21 : CPLErr GDALGeoPackageDataset::SetMetadataItem(const char *pszName,
4914 : const char *pszValue,
4915 : const char *pszDomain)
4916 : {
4917 21 : pszDomain = CheckMetadataDomain(pszDomain);
4918 21 : m_bMetadataDirty = true;
4919 21 : GetMetadata(); /* force loading from storage if needed */
4920 21 : return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
4921 : }
4922 :
4923 : /************************************************************************/
4924 : /* Create() */
4925 : /************************************************************************/
4926 :
4927 892 : int GDALGeoPackageDataset::Create(const char *pszFilename, int nXSize,
4928 : int nYSize, int nBandsIn, GDALDataType eDT,
4929 : char **papszOptions)
4930 : {
4931 1784 : CPLString osCommand;
4932 :
4933 : /* First, ensure there isn't any such file yet. */
4934 : VSIStatBufL sStatBuf;
4935 :
4936 892 : if (nBandsIn != 0)
4937 : {
4938 224 : if (eDT == GDT_Byte)
4939 : {
4940 154 : if (nBandsIn != 1 && nBandsIn != 2 && nBandsIn != 3 &&
4941 : nBandsIn != 4)
4942 : {
4943 1 : CPLError(CE_Failure, CPLE_NotSupported,
4944 : "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), "
4945 : "3 (RGB) or 4 (RGBA) band dataset supported for "
4946 : "Byte datatype");
4947 1 : return FALSE;
4948 : }
4949 : }
4950 70 : else if (eDT == GDT_Int16 || eDT == GDT_UInt16 || eDT == GDT_Float32)
4951 : {
4952 43 : if (nBandsIn != 1)
4953 : {
4954 3 : CPLError(CE_Failure, CPLE_NotSupported,
4955 : "Only single band dataset supported for non Byte "
4956 : "datatype");
4957 3 : return FALSE;
4958 : }
4959 : }
4960 : else
4961 : {
4962 27 : CPLError(CE_Failure, CPLE_NotSupported,
4963 : "Only Byte, Int16, UInt16 or Float32 supported");
4964 27 : return FALSE;
4965 : }
4966 : }
4967 :
4968 861 : const size_t nFilenameLen = strlen(pszFilename);
4969 861 : const bool bGpkgZip =
4970 856 : (nFilenameLen > strlen(".gpkg.zip") &&
4971 1717 : !STARTS_WITH(pszFilename, "/vsizip/") &&
4972 856 : EQUAL(pszFilename + nFilenameLen - strlen(".gpkg.zip"), ".gpkg.zip"));
4973 :
4974 : const bool bUseTempFile =
4975 862 : bGpkgZip || (CPLTestBool(CPLGetConfigOption(
4976 1 : "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", "NO")) &&
4977 1 : (VSIHasOptimizedReadMultiRange(pszFilename) != FALSE ||
4978 1 : EQUAL(CPLGetConfigOption(
4979 : "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", ""),
4980 861 : "FORCED")));
4981 :
4982 861 : bool bFileExists = false;
4983 861 : if (VSIStatL(pszFilename, &sStatBuf) == 0)
4984 : {
4985 10 : bFileExists = true;
4986 20 : if (nBandsIn == 0 || bUseTempFile ||
4987 10 : !CPLTestBool(
4988 : CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")))
4989 : {
4990 0 : CPLError(CE_Failure, CPLE_AppDefined,
4991 : "A file system object called '%s' already exists.",
4992 : pszFilename);
4993 :
4994 0 : return FALSE;
4995 : }
4996 : }
4997 :
4998 861 : if (bUseTempFile)
4999 : {
5000 3 : if (bGpkgZip)
5001 : {
5002 2 : std::string osFilenameInZip(CPLGetFilename(pszFilename));
5003 2 : osFilenameInZip.resize(osFilenameInZip.size() - strlen(".zip"));
5004 : m_osFinalFilename =
5005 2 : std::string("/vsizip/{") + pszFilename + "}/" + osFilenameInZip;
5006 : }
5007 : else
5008 : {
5009 1 : m_osFinalFilename = pszFilename;
5010 : }
5011 3 : m_pszFilename = CPLStrdup(
5012 6 : CPLGenerateTempFilenameSafe(CPLGetFilename(pszFilename)).c_str());
5013 3 : CPLDebug("GPKG", "Creating temporary file %s", m_pszFilename);
5014 : }
5015 : else
5016 : {
5017 858 : m_pszFilename = CPLStrdup(pszFilename);
5018 : }
5019 861 : m_bNew = true;
5020 861 : eAccess = GA_Update;
5021 861 : m_bDateTimeWithTZ =
5022 861 : EQUAL(CSLFetchNameValueDef(papszOptions, "DATETIME_FORMAT", "WITH_TZ"),
5023 : "WITH_TZ");
5024 :
5025 : // for test/debug purposes only. true is the nominal value
5026 861 : m_bPNGSupports2Bands =
5027 861 : CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_2BANDS", "TRUE"));
5028 861 : m_bPNGSupportsCT =
5029 861 : CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_CT", "TRUE"));
5030 :
5031 861 : if (!OpenOrCreateDB(bFileExists
5032 : ? SQLITE_OPEN_READWRITE
5033 : : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE))
5034 7 : return FALSE;
5035 :
5036 : /* Default to synchronous=off for performance for new file */
5037 1698 : if (!bFileExists &&
5038 844 : CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
5039 : {
5040 345 : SQLCommand(hDB, "PRAGMA synchronous = OFF");
5041 : }
5042 :
5043 : /* OGR UTF-8 support. If we set the UTF-8 Pragma early on, it */
5044 : /* will be written into the main file and supported henceforth */
5045 854 : SQLCommand(hDB, "PRAGMA encoding = \"UTF-8\"");
5046 :
5047 854 : if (bFileExists)
5048 : {
5049 10 : VSILFILE *fp = VSIFOpenL(pszFilename, "rb");
5050 10 : if (fp)
5051 : {
5052 : GByte abyHeader[100];
5053 10 : VSIFReadL(abyHeader, 1, sizeof(abyHeader), fp);
5054 10 : VSIFCloseL(fp);
5055 :
5056 10 : memcpy(&m_nApplicationId, abyHeader + knApplicationIdPos, 4);
5057 10 : m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
5058 10 : memcpy(&m_nUserVersion, abyHeader + knUserVersionPos, 4);
5059 10 : m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
5060 :
5061 10 : if (m_nApplicationId == GP10_APPLICATION_ID)
5062 : {
5063 0 : CPLDebug("GPKG", "GeoPackage v1.0");
5064 : }
5065 10 : else if (m_nApplicationId == GP11_APPLICATION_ID)
5066 : {
5067 0 : CPLDebug("GPKG", "GeoPackage v1.1");
5068 : }
5069 10 : else if (m_nApplicationId == GPKG_APPLICATION_ID &&
5070 10 : m_nUserVersion >= GPKG_1_2_VERSION)
5071 : {
5072 10 : CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
5073 10 : (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
5074 : }
5075 : }
5076 :
5077 10 : DetectSpatialRefSysColumns();
5078 : }
5079 :
5080 854 : const char *pszVersion = CSLFetchNameValue(papszOptions, "VERSION");
5081 854 : if (pszVersion && !EQUAL(pszVersion, "AUTO"))
5082 : {
5083 40 : if (EQUAL(pszVersion, "1.0"))
5084 : {
5085 2 : m_nApplicationId = GP10_APPLICATION_ID;
5086 2 : m_nUserVersion = 0;
5087 : }
5088 38 : else if (EQUAL(pszVersion, "1.1"))
5089 : {
5090 1 : m_nApplicationId = GP11_APPLICATION_ID;
5091 1 : m_nUserVersion = 0;
5092 : }
5093 37 : else if (EQUAL(pszVersion, "1.2"))
5094 : {
5095 15 : m_nApplicationId = GPKG_APPLICATION_ID;
5096 15 : m_nUserVersion = GPKG_1_2_VERSION;
5097 : }
5098 22 : else if (EQUAL(pszVersion, "1.3"))
5099 : {
5100 3 : m_nApplicationId = GPKG_APPLICATION_ID;
5101 3 : m_nUserVersion = GPKG_1_3_VERSION;
5102 : }
5103 19 : else if (EQUAL(pszVersion, "1.4"))
5104 : {
5105 19 : m_nApplicationId = GPKG_APPLICATION_ID;
5106 19 : m_nUserVersion = GPKG_1_4_VERSION;
5107 : }
5108 : }
5109 :
5110 854 : SoftStartTransaction();
5111 :
5112 1708 : CPLString osSQL;
5113 854 : if (!bFileExists)
5114 : {
5115 : /* Requirement 10: A GeoPackage SHALL include a gpkg_spatial_ref_sys
5116 : * table */
5117 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5118 : osSQL = "CREATE TABLE gpkg_spatial_ref_sys ("
5119 : "srs_name TEXT NOT NULL,"
5120 : "srs_id INTEGER NOT NULL PRIMARY KEY,"
5121 : "organization TEXT NOT NULL,"
5122 : "organization_coordsys_id INTEGER NOT NULL,"
5123 : "definition TEXT NOT NULL,"
5124 844 : "description TEXT";
5125 844 : if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "CRS_WKT_EXTENSION",
5126 1023 : "NO")) ||
5127 179 : (nBandsIn != 0 && eDT != GDT_Byte))
5128 : {
5129 42 : m_bHasDefinition12_063 = true;
5130 42 : osSQL += ", definition_12_063 TEXT NOT NULL";
5131 42 : if (m_nUserVersion >= GPKG_1_4_VERSION)
5132 : {
5133 40 : osSQL += ", epoch DOUBLE";
5134 40 : m_bHasEpochColumn = true;
5135 : }
5136 : }
5137 : osSQL += ")"
5138 : ";"
5139 : /* Requirement 11: The gpkg_spatial_ref_sys table in a
5140 : GeoPackage SHALL */
5141 : /* contain a record for EPSG:4326, the geodetic WGS84 SRS */
5142 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5143 :
5144 : "INSERT INTO gpkg_spatial_ref_sys ("
5145 : "srs_name, srs_id, organization, organization_coordsys_id, "
5146 844 : "definition, description";
5147 844 : if (m_bHasDefinition12_063)
5148 42 : osSQL += ", definition_12_063";
5149 : osSQL +=
5150 : ") VALUES ("
5151 : "'WGS 84 geodetic', 4326, 'EPSG', 4326, '"
5152 : "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS "
5153 : "84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],"
5154 : "AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY["
5155 : "\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY["
5156 : "\"EPSG\",\"9122\"]],AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\","
5157 : "EAST],AUTHORITY[\"EPSG\",\"4326\"]]"
5158 : "', 'longitude/latitude coordinates in decimal degrees on the WGS "
5159 844 : "84 spheroid'";
5160 844 : if (m_bHasDefinition12_063)
5161 : osSQL +=
5162 : ", 'GEODCRS[\"WGS 84\", DATUM[\"World Geodetic System 1984\", "
5163 : "ELLIPSOID[\"WGS 84\",6378137, 298.257223563, "
5164 : "LENGTHUNIT[\"metre\", 1.0]]], PRIMEM[\"Greenwich\", 0.0, "
5165 : "ANGLEUNIT[\"degree\",0.0174532925199433]], CS[ellipsoidal, "
5166 : "2], AXIS[\"latitude\", north, ORDER[1]], AXIS[\"longitude\", "
5167 : "east, ORDER[2]], ANGLEUNIT[\"degree\", 0.0174532925199433], "
5168 42 : "ID[\"EPSG\", 4326]]'";
5169 : osSQL +=
5170 : ")"
5171 : ";"
5172 : /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
5173 : SHALL */
5174 : /* contain a record with an srs_id of -1, an organization of “NONE”,
5175 : */
5176 : /* an organization_coordsys_id of -1, and definition “undefined” */
5177 : /* for undefined Cartesian coordinate reference systems */
5178 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5179 : "INSERT INTO gpkg_spatial_ref_sys ("
5180 : "srs_name, srs_id, organization, organization_coordsys_id, "
5181 844 : "definition, description";
5182 844 : if (m_bHasDefinition12_063)
5183 42 : osSQL += ", definition_12_063";
5184 : osSQL += ") VALUES ("
5185 : "'Undefined Cartesian SRS', -1, 'NONE', -1, 'undefined', "
5186 844 : "'undefined Cartesian coordinate reference system'";
5187 844 : if (m_bHasDefinition12_063)
5188 42 : osSQL += ", 'undefined'";
5189 : osSQL +=
5190 : ")"
5191 : ";"
5192 : /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
5193 : SHALL */
5194 : /* contain a record with an srs_id of 0, an organization of “NONE”,
5195 : */
5196 : /* an organization_coordsys_id of 0, and definition “undefined” */
5197 : /* for undefined geographic coordinate reference systems */
5198 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5199 : "INSERT INTO gpkg_spatial_ref_sys ("
5200 : "srs_name, srs_id, organization, organization_coordsys_id, "
5201 844 : "definition, description";
5202 844 : if (m_bHasDefinition12_063)
5203 42 : osSQL += ", definition_12_063";
5204 : osSQL += ") VALUES ("
5205 : "'Undefined geographic SRS', 0, 'NONE', 0, 'undefined', "
5206 844 : "'undefined geographic coordinate reference system'";
5207 844 : if (m_bHasDefinition12_063)
5208 42 : osSQL += ", 'undefined'";
5209 : osSQL += ")"
5210 : ";"
5211 : /* Requirement 13: A GeoPackage file SHALL include a
5212 : gpkg_contents table */
5213 : /* http://opengis.github.io/geopackage/#_contents */
5214 : "CREATE TABLE gpkg_contents ("
5215 : "table_name TEXT NOT NULL PRIMARY KEY,"
5216 : "data_type TEXT NOT NULL,"
5217 : "identifier TEXT UNIQUE,"
5218 : "description TEXT DEFAULT '',"
5219 : "last_change DATETIME NOT NULL DEFAULT "
5220 : "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
5221 : "min_x DOUBLE, min_y DOUBLE,"
5222 : "max_x DOUBLE, max_y DOUBLE,"
5223 : "srs_id INTEGER,"
5224 : "CONSTRAINT fk_gc_r_srs_id FOREIGN KEY (srs_id) REFERENCES "
5225 : "gpkg_spatial_ref_sys(srs_id)"
5226 844 : ")";
5227 :
5228 : #ifdef ENABLE_GPKG_OGR_CONTENTS
5229 844 : if (CPLFetchBool(papszOptions, "ADD_GPKG_OGR_CONTENTS", true))
5230 : {
5231 839 : m_bHasGPKGOGRContents = true;
5232 : osSQL += ";"
5233 : "CREATE TABLE gpkg_ogr_contents("
5234 : "table_name TEXT NOT NULL PRIMARY KEY,"
5235 : "feature_count INTEGER DEFAULT NULL"
5236 839 : ")";
5237 : }
5238 : #endif
5239 :
5240 : /* Requirement 21: A GeoPackage with a gpkg_contents table row with a
5241 : * “features” */
5242 : /* data_type SHALL contain a gpkg_geometry_columns table or updateable
5243 : * view */
5244 : /* http://opengis.github.io/geopackage/#_geometry_columns */
5245 : const bool bCreateGeometryColumns =
5246 844 : CPLTestBool(CPLGetConfigOption("CREATE_GEOMETRY_COLUMNS", "YES"));
5247 844 : if (bCreateGeometryColumns)
5248 : {
5249 843 : m_bHasGPKGGeometryColumns = true;
5250 843 : osSQL += ";";
5251 843 : osSQL += pszCREATE_GPKG_GEOMETRY_COLUMNS;
5252 : }
5253 : }
5254 :
5255 : const bool bCreateTriggers =
5256 854 : CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "YES"));
5257 10 : if ((bFileExists && nBandsIn != 0 &&
5258 10 : SQLGetInteger(
5259 : hDB,
5260 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_tile_matrix_set' "
5261 : "AND type in ('table', 'view')",
5262 1708 : nullptr) == 0) ||
5263 853 : (!bFileExists &&
5264 844 : CPLTestBool(CPLGetConfigOption("CREATE_RASTER_TABLES", "YES"))))
5265 : {
5266 844 : if (!osSQL.empty())
5267 843 : osSQL += ";";
5268 :
5269 : /* From C.5. gpkg_tile_matrix_set Table 28. gpkg_tile_matrix_set Table
5270 : * Creation SQL */
5271 : osSQL += "CREATE TABLE gpkg_tile_matrix_set ("
5272 : "table_name TEXT NOT NULL PRIMARY KEY,"
5273 : "srs_id INTEGER NOT NULL,"
5274 : "min_x DOUBLE NOT NULL,"
5275 : "min_y DOUBLE NOT NULL,"
5276 : "max_x DOUBLE NOT NULL,"
5277 : "max_y DOUBLE NOT NULL,"
5278 : "CONSTRAINT fk_gtms_table_name FOREIGN KEY (table_name) "
5279 : "REFERENCES gpkg_contents(table_name),"
5280 : "CONSTRAINT fk_gtms_srs FOREIGN KEY (srs_id) REFERENCES "
5281 : "gpkg_spatial_ref_sys (srs_id)"
5282 : ")"
5283 : ";"
5284 :
5285 : /* From C.6. gpkg_tile_matrix Table 29. gpkg_tile_matrix Table
5286 : Creation SQL */
5287 : "CREATE TABLE gpkg_tile_matrix ("
5288 : "table_name TEXT NOT NULL,"
5289 : "zoom_level INTEGER NOT NULL,"
5290 : "matrix_width INTEGER NOT NULL,"
5291 : "matrix_height INTEGER NOT NULL,"
5292 : "tile_width INTEGER NOT NULL,"
5293 : "tile_height INTEGER NOT NULL,"
5294 : "pixel_x_size DOUBLE NOT NULL,"
5295 : "pixel_y_size DOUBLE NOT NULL,"
5296 : "CONSTRAINT pk_ttm PRIMARY KEY (table_name, zoom_level),"
5297 : "CONSTRAINT fk_tmm_table_name FOREIGN KEY (table_name) "
5298 : "REFERENCES gpkg_contents(table_name)"
5299 844 : ")";
5300 :
5301 844 : if (bCreateTriggers)
5302 : {
5303 : /* From D.1. gpkg_tile_matrix Table 39. gpkg_tile_matrix Trigger
5304 : * Definition SQL */
5305 844 : const char *pszTileMatrixTrigger =
5306 : "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_insert' "
5307 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5308 : "FOR EACH ROW BEGIN "
5309 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5310 : "violates constraint: zoom_level cannot be less than 0') "
5311 : "WHERE (NEW.zoom_level < 0); "
5312 : "END; "
5313 : "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_update' "
5314 : "BEFORE UPDATE of zoom_level ON 'gpkg_tile_matrix' "
5315 : "FOR EACH ROW BEGIN "
5316 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5317 : "violates constraint: zoom_level cannot be less than 0') "
5318 : "WHERE (NEW.zoom_level < 0); "
5319 : "END; "
5320 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_insert' "
5321 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5322 : "FOR EACH ROW BEGIN "
5323 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5324 : "violates constraint: matrix_width cannot be less than 1') "
5325 : "WHERE (NEW.matrix_width < 1); "
5326 : "END; "
5327 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_update' "
5328 : "BEFORE UPDATE OF matrix_width ON 'gpkg_tile_matrix' "
5329 : "FOR EACH ROW BEGIN "
5330 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5331 : "violates constraint: matrix_width cannot be less than 1') "
5332 : "WHERE (NEW.matrix_width < 1); "
5333 : "END; "
5334 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_insert' "
5335 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5336 : "FOR EACH ROW BEGIN "
5337 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5338 : "violates constraint: matrix_height cannot be less than 1') "
5339 : "WHERE (NEW.matrix_height < 1); "
5340 : "END; "
5341 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_update' "
5342 : "BEFORE UPDATE OF matrix_height ON 'gpkg_tile_matrix' "
5343 : "FOR EACH ROW BEGIN "
5344 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5345 : "violates constraint: matrix_height cannot be less than 1') "
5346 : "WHERE (NEW.matrix_height < 1); "
5347 : "END; "
5348 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_insert' "
5349 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5350 : "FOR EACH ROW BEGIN "
5351 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5352 : "violates constraint: pixel_x_size must be greater than 0') "
5353 : "WHERE NOT (NEW.pixel_x_size > 0); "
5354 : "END; "
5355 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_update' "
5356 : "BEFORE UPDATE OF pixel_x_size ON 'gpkg_tile_matrix' "
5357 : "FOR EACH ROW BEGIN "
5358 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5359 : "violates constraint: pixel_x_size must be greater than 0') "
5360 : "WHERE NOT (NEW.pixel_x_size > 0); "
5361 : "END; "
5362 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_insert' "
5363 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5364 : "FOR EACH ROW BEGIN "
5365 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5366 : "violates constraint: pixel_y_size must be greater than 0') "
5367 : "WHERE NOT (NEW.pixel_y_size > 0); "
5368 : "END; "
5369 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_update' "
5370 : "BEFORE UPDATE OF pixel_y_size ON 'gpkg_tile_matrix' "
5371 : "FOR EACH ROW BEGIN "
5372 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5373 : "violates constraint: pixel_y_size must be greater than 0') "
5374 : "WHERE NOT (NEW.pixel_y_size > 0); "
5375 : "END;";
5376 844 : osSQL += ";";
5377 844 : osSQL += pszTileMatrixTrigger;
5378 : }
5379 : }
5380 :
5381 854 : if (!osSQL.empty() && OGRERR_NONE != SQLCommand(hDB, osSQL))
5382 1 : return FALSE;
5383 :
5384 853 : if (!bFileExists)
5385 : {
5386 : const char *pszMetadataTables =
5387 843 : CSLFetchNameValue(papszOptions, "METADATA_TABLES");
5388 843 : if (pszMetadataTables)
5389 9 : m_nCreateMetadataTables = int(CPLTestBool(pszMetadataTables));
5390 :
5391 843 : if (m_nCreateMetadataTables == TRUE && !CreateMetadataTables())
5392 0 : return FALSE;
5393 :
5394 843 : if (m_bHasDefinition12_063)
5395 : {
5396 84 : if (OGRERR_NONE != CreateExtensionsTableIfNecessary() ||
5397 : OGRERR_NONE !=
5398 42 : SQLCommand(hDB, "INSERT INTO gpkg_extensions "
5399 : "(table_name, column_name, extension_name, "
5400 : "definition, scope) "
5401 : "VALUES "
5402 : "('gpkg_spatial_ref_sys', "
5403 : "'definition_12_063', 'gpkg_crs_wkt', "
5404 : "'http://www.geopackage.org/spec120/"
5405 : "#extension_crs_wkt', 'read-write')"))
5406 : {
5407 0 : return FALSE;
5408 : }
5409 42 : if (m_bHasEpochColumn)
5410 : {
5411 40 : if (OGRERR_NONE !=
5412 40 : SQLCommand(
5413 : hDB, "UPDATE gpkg_extensions SET extension_name = "
5414 : "'gpkg_crs_wkt_1_1' "
5415 80 : "WHERE extension_name = 'gpkg_crs_wkt'") ||
5416 : OGRERR_NONE !=
5417 40 : SQLCommand(hDB, "INSERT INTO gpkg_extensions "
5418 : "(table_name, column_name, "
5419 : "extension_name, definition, scope) "
5420 : "VALUES "
5421 : "('gpkg_spatial_ref_sys', 'epoch', "
5422 : "'gpkg_crs_wkt_1_1', "
5423 : "'http://www.geopackage.org/spec/"
5424 : "#extension_crs_wkt', "
5425 : "'read-write')"))
5426 : {
5427 0 : return FALSE;
5428 : }
5429 : }
5430 : }
5431 : }
5432 :
5433 853 : if (nBandsIn != 0)
5434 : {
5435 188 : const std::string osTableName = CPLGetBasenameSafe(m_pszFilename);
5436 : m_osRasterTable = CSLFetchNameValueDef(papszOptions, "RASTER_TABLE",
5437 188 : osTableName.c_str());
5438 188 : if (m_osRasterTable.empty())
5439 : {
5440 0 : CPLError(CE_Failure, CPLE_AppDefined,
5441 : "RASTER_TABLE must be set to a non empty value");
5442 0 : return FALSE;
5443 : }
5444 188 : m_bIdentifierAsCO =
5445 188 : CSLFetchNameValue(papszOptions, "RASTER_IDENTIFIER") != nullptr;
5446 : m_osIdentifier = CSLFetchNameValueDef(papszOptions, "RASTER_IDENTIFIER",
5447 188 : m_osRasterTable);
5448 188 : m_bDescriptionAsCO =
5449 188 : CSLFetchNameValue(papszOptions, "RASTER_DESCRIPTION") != nullptr;
5450 : m_osDescription =
5451 188 : CSLFetchNameValueDef(papszOptions, "RASTER_DESCRIPTION", "");
5452 188 : SetDataType(eDT);
5453 188 : if (eDT == GDT_Int16)
5454 16 : SetGlobalOffsetScale(-32768.0, 1.0);
5455 :
5456 : /* From C.7. sample_tile_pyramid (Informative) Table 31. EXAMPLE: tiles
5457 : * table Create Table SQL (Informative) */
5458 : char *pszSQL =
5459 188 : sqlite3_mprintf("CREATE TABLE \"%w\" ("
5460 : "id INTEGER PRIMARY KEY AUTOINCREMENT,"
5461 : "zoom_level INTEGER NOT NULL,"
5462 : "tile_column INTEGER NOT NULL,"
5463 : "tile_row INTEGER NOT NULL,"
5464 : "tile_data BLOB NOT NULL,"
5465 : "UNIQUE (zoom_level, tile_column, tile_row)"
5466 : ")",
5467 : m_osRasterTable.c_str());
5468 188 : osSQL = pszSQL;
5469 188 : sqlite3_free(pszSQL);
5470 :
5471 188 : if (bCreateTriggers)
5472 : {
5473 188 : osSQL += ";";
5474 188 : osSQL += CreateRasterTriggersSQL(m_osRasterTable);
5475 : }
5476 :
5477 188 : OGRErr eErr = SQLCommand(hDB, osSQL);
5478 188 : if (OGRERR_NONE != eErr)
5479 0 : return FALSE;
5480 :
5481 188 : const char *pszTF = CSLFetchNameValue(papszOptions, "TILE_FORMAT");
5482 188 : if (eDT == GDT_Int16 || eDT == GDT_UInt16)
5483 : {
5484 27 : m_eTF = GPKG_TF_PNG_16BIT;
5485 27 : if (pszTF)
5486 : {
5487 1 : if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "PNG"))
5488 : {
5489 0 : CPLError(CE_Warning, CPLE_NotSupported,
5490 : "Only AUTO or PNG supported "
5491 : "as tile format for Int16 / UInt16");
5492 : }
5493 : }
5494 : }
5495 161 : else if (eDT == GDT_Float32)
5496 : {
5497 13 : m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
5498 13 : if (pszTF)
5499 : {
5500 5 : if (EQUAL(pszTF, "PNG"))
5501 5 : m_eTF = GPKG_TF_PNG_16BIT;
5502 0 : else if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "TIFF"))
5503 : {
5504 0 : CPLError(CE_Warning, CPLE_NotSupported,
5505 : "Only AUTO, PNG or TIFF supported "
5506 : "as tile format for Float32");
5507 : }
5508 : }
5509 : }
5510 : else
5511 : {
5512 148 : if (pszTF)
5513 : {
5514 71 : m_eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
5515 71 : if (nBandsIn == 1 && m_eTF != GPKG_TF_PNG)
5516 7 : m_bMetadataDirty = true;
5517 : }
5518 77 : else if (nBandsIn == 1)
5519 66 : m_eTF = GPKG_TF_PNG;
5520 : }
5521 :
5522 188 : if (eDT != GDT_Byte)
5523 : {
5524 40 : if (!CreateTileGriddedTable(papszOptions))
5525 0 : return FALSE;
5526 : }
5527 :
5528 188 : nRasterXSize = nXSize;
5529 188 : nRasterYSize = nYSize;
5530 :
5531 : const char *pszTileSize =
5532 188 : CSLFetchNameValueDef(papszOptions, "BLOCKSIZE", "256");
5533 : const char *pszTileWidth =
5534 188 : CSLFetchNameValueDef(papszOptions, "BLOCKXSIZE", pszTileSize);
5535 : const char *pszTileHeight =
5536 188 : CSLFetchNameValueDef(papszOptions, "BLOCKYSIZE", pszTileSize);
5537 188 : int nTileWidth = atoi(pszTileWidth);
5538 188 : int nTileHeight = atoi(pszTileHeight);
5539 188 : if ((nTileWidth < 8 || nTileWidth > 4096 || nTileHeight < 8 ||
5540 376 : nTileHeight > 4096) &&
5541 1 : !CPLTestBool(CPLGetConfigOption("GPKG_ALLOW_CRAZY_SETTINGS", "NO")))
5542 : {
5543 0 : CPLError(CE_Failure, CPLE_AppDefined,
5544 : "Invalid block dimensions: %dx%d", nTileWidth,
5545 : nTileHeight);
5546 0 : return FALSE;
5547 : }
5548 :
5549 509 : for (int i = 1; i <= nBandsIn; i++)
5550 : {
5551 321 : SetBand(i, std::make_unique<GDALGeoPackageRasterBand>(
5552 : this, nTileWidth, nTileHeight));
5553 : }
5554 :
5555 188 : GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL",
5556 : "IMAGE_STRUCTURE");
5557 188 : GDALPamDataset::SetMetadataItem("IDENTIFIER", m_osIdentifier);
5558 188 : if (!m_osDescription.empty())
5559 1 : GDALPamDataset::SetMetadataItem("DESCRIPTION", m_osDescription);
5560 :
5561 188 : ParseCompressionOptions(papszOptions);
5562 :
5563 188 : if (m_eTF == GPKG_TF_WEBP)
5564 : {
5565 10 : if (!RegisterWebPExtension())
5566 0 : return FALSE;
5567 : }
5568 :
5569 : m_osTilingScheme =
5570 188 : CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
5571 188 : if (!EQUAL(m_osTilingScheme, "CUSTOM"))
5572 : {
5573 22 : const auto poTS = GetTilingScheme(m_osTilingScheme);
5574 22 : if (!poTS)
5575 0 : return FALSE;
5576 :
5577 43 : if (nTileWidth != poTS->nTileWidth ||
5578 21 : nTileHeight != poTS->nTileHeight)
5579 : {
5580 2 : CPLError(CE_Failure, CPLE_NotSupported,
5581 : "Tile dimension should be %dx%d for %s tiling scheme",
5582 1 : poTS->nTileWidth, poTS->nTileHeight,
5583 : m_osTilingScheme.c_str());
5584 1 : return FALSE;
5585 : }
5586 :
5587 : const char *pszZoomLevel =
5588 21 : CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
5589 21 : if (pszZoomLevel)
5590 : {
5591 1 : m_nZoomLevel = atoi(pszZoomLevel);
5592 1 : int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
5593 1 : while ((1 << nMaxZoomLevelForThisTM) >
5594 2 : INT_MAX / poTS->nTileXCountZoomLevel0 ||
5595 1 : (1 << nMaxZoomLevelForThisTM) >
5596 1 : INT_MAX / poTS->nTileYCountZoomLevel0)
5597 : {
5598 0 : --nMaxZoomLevelForThisTM;
5599 : }
5600 :
5601 1 : if (m_nZoomLevel < 0 || m_nZoomLevel > nMaxZoomLevelForThisTM)
5602 : {
5603 0 : CPLError(CE_Failure, CPLE_AppDefined,
5604 : "ZOOM_LEVEL = %s is invalid. It should be in "
5605 : "[0,%d] range",
5606 : pszZoomLevel, nMaxZoomLevelForThisTM);
5607 0 : return FALSE;
5608 : }
5609 : }
5610 :
5611 : // Implicitly sets SRS.
5612 21 : OGRSpatialReference oSRS;
5613 21 : if (oSRS.importFromEPSG(poTS->nEPSGCode) != OGRERR_NONE)
5614 0 : return FALSE;
5615 21 : char *pszWKT = nullptr;
5616 21 : oSRS.exportToWkt(&pszWKT);
5617 21 : SetProjection(pszWKT);
5618 21 : CPLFree(pszWKT);
5619 : }
5620 : else
5621 : {
5622 166 : if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
5623 : {
5624 0 : CPLError(
5625 : CE_Failure, CPLE_NotSupported,
5626 : "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
5627 0 : return false;
5628 : }
5629 : }
5630 : }
5631 :
5632 852 : if (bFileExists && nBandsIn > 0 && eDT == GDT_Byte)
5633 : {
5634 : // If there was an ogr_empty_table table, we can remove it
5635 9 : RemoveOGREmptyTable();
5636 : }
5637 :
5638 852 : SoftCommitTransaction();
5639 :
5640 : /* Requirement 2 */
5641 : /* We have to do this after there's some content so the database file */
5642 : /* is not zero length */
5643 852 : SetApplicationAndUserVersionId();
5644 :
5645 : /* Default to synchronous=off for performance for new file */
5646 1694 : if (!bFileExists &&
5647 842 : CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
5648 : {
5649 345 : SQLCommand(hDB, "PRAGMA synchronous = OFF");
5650 : }
5651 :
5652 852 : return TRUE;
5653 : }
5654 :
5655 : /************************************************************************/
5656 : /* RemoveOGREmptyTable() */
5657 : /************************************************************************/
5658 :
5659 665 : void GDALGeoPackageDataset::RemoveOGREmptyTable()
5660 : {
5661 : // Run with sqlite3_exec since we don't want errors to be emitted
5662 665 : sqlite3_exec(hDB, "DROP TABLE IF EXISTS ogr_empty_table", nullptr, nullptr,
5663 : nullptr);
5664 665 : sqlite3_exec(
5665 : hDB, "DELETE FROM gpkg_contents WHERE table_name = 'ogr_empty_table'",
5666 : nullptr, nullptr, nullptr);
5667 : #ifdef ENABLE_GPKG_OGR_CONTENTS
5668 665 : if (m_bHasGPKGOGRContents)
5669 : {
5670 651 : sqlite3_exec(hDB,
5671 : "DELETE FROM gpkg_ogr_contents WHERE "
5672 : "table_name = 'ogr_empty_table'",
5673 : nullptr, nullptr, nullptr);
5674 : }
5675 : #endif
5676 665 : sqlite3_exec(hDB,
5677 : "DELETE FROM gpkg_geometry_columns WHERE "
5678 : "table_name = 'ogr_empty_table'",
5679 : nullptr, nullptr, nullptr);
5680 665 : }
5681 :
5682 : /************************************************************************/
5683 : /* CreateTileGriddedTable() */
5684 : /************************************************************************/
5685 :
5686 40 : bool GDALGeoPackageDataset::CreateTileGriddedTable(char **papszOptions)
5687 : {
5688 80 : CPLString osSQL;
5689 40 : if (!HasGriddedCoverageAncillaryTable())
5690 : {
5691 : // It doesn't exist. So create gpkg_extensions table if necessary, and
5692 : // gpkg_2d_gridded_coverage_ancillary & gpkg_2d_gridded_tile_ancillary,
5693 : // and register them as extensions.
5694 40 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
5695 0 : return false;
5696 :
5697 : // Req 1 /table-defs/coverage-ancillary
5698 : osSQL = "CREATE TABLE gpkg_2d_gridded_coverage_ancillary ("
5699 : "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
5700 : "tile_matrix_set_name TEXT NOT NULL UNIQUE,"
5701 : "datatype TEXT NOT NULL DEFAULT 'integer',"
5702 : "scale REAL NOT NULL DEFAULT 1.0,"
5703 : "offset REAL NOT NULL DEFAULT 0.0,"
5704 : "precision REAL DEFAULT 1.0,"
5705 : "data_null REAL,"
5706 : "grid_cell_encoding TEXT DEFAULT 'grid-value-is-center',"
5707 : "uom TEXT,"
5708 : "field_name TEXT DEFAULT 'Height',"
5709 : "quantity_definition TEXT DEFAULT 'Height',"
5710 : "CONSTRAINT fk_g2dgtct_name FOREIGN KEY(tile_matrix_set_name) "
5711 : "REFERENCES gpkg_tile_matrix_set ( table_name ) "
5712 : "CHECK (datatype in ('integer','float')))"
5713 : ";"
5714 : // Requirement 2 /table-defs/tile-ancillary
5715 : "CREATE TABLE gpkg_2d_gridded_tile_ancillary ("
5716 : "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
5717 : "tpudt_name TEXT NOT NULL,"
5718 : "tpudt_id INTEGER NOT NULL,"
5719 : "scale REAL NOT NULL DEFAULT 1.0,"
5720 : "offset REAL NOT NULL DEFAULT 0.0,"
5721 : "min REAL DEFAULT NULL,"
5722 : "max REAL DEFAULT NULL,"
5723 : "mean REAL DEFAULT NULL,"
5724 : "std_dev REAL DEFAULT NULL,"
5725 : "CONSTRAINT fk_g2dgtat_name FOREIGN KEY (tpudt_name) "
5726 : "REFERENCES gpkg_contents(table_name),"
5727 : "UNIQUE (tpudt_name, tpudt_id))"
5728 : ";"
5729 : // Requirement 6 /gpkg-extensions
5730 : "INSERT INTO gpkg_extensions "
5731 : "(table_name, column_name, extension_name, definition, scope) "
5732 : "VALUES ('gpkg_2d_gridded_coverage_ancillary', NULL, "
5733 : "'gpkg_2d_gridded_coverage', "
5734 : "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5735 : "'read-write')"
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_tile_ancillary', NULL, "
5741 : "'gpkg_2d_gridded_coverage', "
5742 : "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5743 : "'read-write')"
5744 40 : ";";
5745 : }
5746 :
5747 : // Requirement 6 /gpkg-extensions
5748 40 : char *pszSQL = sqlite3_mprintf(
5749 : "INSERT INTO gpkg_extensions "
5750 : "(table_name, column_name, extension_name, definition, scope) "
5751 : "VALUES ('%q', 'tile_data', "
5752 : "'gpkg_2d_gridded_coverage', "
5753 : "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5754 : "'read-write')",
5755 : m_osRasterTable.c_str());
5756 40 : osSQL += pszSQL;
5757 40 : osSQL += ";";
5758 40 : sqlite3_free(pszSQL);
5759 :
5760 : // Requirement 7 /gpkg-2d-gridded-coverage-ancillary
5761 : // Requirement 8 /gpkg-2d-gridded-coverage-ancillary-set-name
5762 : // Requirement 9 /gpkg-2d-gridded-coverage-ancillary-datatype
5763 40 : m_dfPrecision =
5764 40 : CPLAtof(CSLFetchNameValueDef(papszOptions, "PRECISION", "1"));
5765 : CPLString osGridCellEncoding(CSLFetchNameValueDef(
5766 80 : papszOptions, "GRID_CELL_ENCODING", "grid-value-is-center"));
5767 40 : m_bGridCellEncodingAsCO =
5768 40 : CSLFetchNameValue(papszOptions, "GRID_CELL_ENCODING") != nullptr;
5769 80 : CPLString osUom(CSLFetchNameValueDef(papszOptions, "UOM", ""));
5770 : CPLString osFieldName(
5771 80 : CSLFetchNameValueDef(papszOptions, "FIELD_NAME", "Height"));
5772 : CPLString osQuantityDefinition(
5773 80 : CSLFetchNameValueDef(papszOptions, "QUANTITY_DEFINITION", "Height"));
5774 :
5775 121 : pszSQL = sqlite3_mprintf(
5776 : "INSERT INTO gpkg_2d_gridded_coverage_ancillary "
5777 : "(tile_matrix_set_name, datatype, scale, offset, precision, "
5778 : "grid_cell_encoding, uom, field_name, quantity_definition) "
5779 : "VALUES (%Q, '%s', %.17g, %.17g, %.17g, %Q, %Q, %Q, %Q)",
5780 : m_osRasterTable.c_str(),
5781 40 : (m_eTF == GPKG_TF_PNG_16BIT) ? "integer" : "float", m_dfScale,
5782 : m_dfOffset, m_dfPrecision, osGridCellEncoding.c_str(),
5783 41 : osUom.empty() ? nullptr : osUom.c_str(), osFieldName.c_str(),
5784 : osQuantityDefinition.c_str());
5785 40 : m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary = pszSQL;
5786 40 : sqlite3_free(pszSQL);
5787 :
5788 : // Requirement 3 /gpkg-spatial-ref-sys-row
5789 : auto oResultTable = SQLQuery(
5790 80 : hDB, "SELECT * FROM gpkg_spatial_ref_sys WHERE srs_id = 4979 LIMIT 2");
5791 40 : bool bHasEPSG4979 = (oResultTable && oResultTable->RowCount() == 1);
5792 40 : if (!bHasEPSG4979)
5793 : {
5794 41 : if (!m_bHasDefinition12_063 &&
5795 1 : !ConvertGpkgSpatialRefSysToExtensionWkt2(/*bForceEpoch=*/false))
5796 : {
5797 0 : return false;
5798 : }
5799 :
5800 : // This is WKT 2...
5801 40 : const char *pszWKT =
5802 : "GEODCRS[\"WGS 84\","
5803 : "DATUM[\"World Geodetic System 1984\","
5804 : " ELLIPSOID[\"WGS 84\",6378137,298.257223563,"
5805 : "LENGTHUNIT[\"metre\",1.0]]],"
5806 : "CS[ellipsoidal,3],"
5807 : " AXIS[\"latitude\",north,ORDER[1],ANGLEUNIT[\"degree\","
5808 : "0.0174532925199433]],"
5809 : " AXIS[\"longitude\",east,ORDER[2],ANGLEUNIT[\"degree\","
5810 : "0.0174532925199433]],"
5811 : " AXIS[\"ellipsoidal height\",up,ORDER[3],"
5812 : "LENGTHUNIT[\"metre\",1.0]],"
5813 : "ID[\"EPSG\",4979]]";
5814 :
5815 40 : pszSQL = sqlite3_mprintf(
5816 : "INSERT INTO gpkg_spatial_ref_sys "
5817 : "(srs_name,srs_id,organization,organization_coordsys_id,"
5818 : "definition,definition_12_063) VALUES "
5819 : "('WGS 84 3D', 4979, 'EPSG', 4979, 'undefined', '%q')",
5820 : pszWKT);
5821 40 : osSQL += ";";
5822 40 : osSQL += pszSQL;
5823 40 : sqlite3_free(pszSQL);
5824 : }
5825 :
5826 40 : return SQLCommand(hDB, osSQL) == OGRERR_NONE;
5827 : }
5828 :
5829 : /************************************************************************/
5830 : /* HasGriddedCoverageAncillaryTable() */
5831 : /************************************************************************/
5832 :
5833 44 : bool GDALGeoPackageDataset::HasGriddedCoverageAncillaryTable()
5834 : {
5835 : auto oResultTable = SQLQuery(
5836 : hDB, "SELECT * FROM sqlite_master WHERE type IN ('table', 'view') AND "
5837 44 : "name = 'gpkg_2d_gridded_coverage_ancillary'");
5838 44 : bool bHasTable = (oResultTable && oResultTable->RowCount() == 1);
5839 88 : return bHasTable;
5840 : }
5841 :
5842 : /************************************************************************/
5843 : /* GetUnderlyingDataset() */
5844 : /************************************************************************/
5845 :
5846 3 : static GDALDataset *GetUnderlyingDataset(GDALDataset *poSrcDS)
5847 : {
5848 3 : if (auto poVRTDS = dynamic_cast<VRTDataset *>(poSrcDS))
5849 : {
5850 0 : auto poTmpDS = poVRTDS->GetSingleSimpleSource();
5851 0 : if (poTmpDS)
5852 0 : return poTmpDS;
5853 : }
5854 :
5855 3 : return poSrcDS;
5856 : }
5857 :
5858 : /************************************************************************/
5859 : /* CreateCopy() */
5860 : /************************************************************************/
5861 :
5862 : typedef struct
5863 : {
5864 : const char *pszName;
5865 : GDALResampleAlg eResampleAlg;
5866 : } WarpResamplingAlg;
5867 :
5868 : static const WarpResamplingAlg asResamplingAlg[] = {
5869 : {"NEAREST", GRA_NearestNeighbour},
5870 : {"BILINEAR", GRA_Bilinear},
5871 : {"CUBIC", GRA_Cubic},
5872 : {"CUBICSPLINE", GRA_CubicSpline},
5873 : {"LANCZOS", GRA_Lanczos},
5874 : {"MODE", GRA_Mode},
5875 : {"AVERAGE", GRA_Average},
5876 : {"RMS", GRA_RMS},
5877 : };
5878 :
5879 160 : GDALDataset *GDALGeoPackageDataset::CreateCopy(const char *pszFilename,
5880 : GDALDataset *poSrcDS,
5881 : int bStrict, char **papszOptions,
5882 : GDALProgressFunc pfnProgress,
5883 : void *pProgressData)
5884 : {
5885 160 : const int nBands = poSrcDS->GetRasterCount();
5886 160 : if (nBands == 0)
5887 : {
5888 2 : GDALDataset *poDS = nullptr;
5889 : GDALDriver *poThisDriver =
5890 2 : GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
5891 2 : if (poThisDriver != nullptr)
5892 : {
5893 2 : poDS = poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS,
5894 : bStrict, papszOptions,
5895 : pfnProgress, pProgressData);
5896 : }
5897 2 : return poDS;
5898 : }
5899 :
5900 : const char *pszTilingScheme =
5901 158 : CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
5902 :
5903 316 : CPLStringList apszUpdatedOptions(CSLDuplicate(papszOptions));
5904 158 : if (CPLTestBool(
5905 164 : CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")) &&
5906 6 : CSLFetchNameValue(papszOptions, "RASTER_TABLE") == nullptr)
5907 : {
5908 : const std::string osBasename(CPLGetBasenameSafe(
5909 6 : GetUnderlyingDataset(poSrcDS)->GetDescription()));
5910 3 : apszUpdatedOptions.SetNameValue("RASTER_TABLE", osBasename.c_str());
5911 : }
5912 :
5913 158 : if (nBands != 1 && nBands != 2 && nBands != 3 && nBands != 4)
5914 : {
5915 1 : CPLError(CE_Failure, CPLE_NotSupported,
5916 : "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), 3 (RGB) or "
5917 : "4 (RGBA) band dataset supported");
5918 1 : return nullptr;
5919 : }
5920 :
5921 157 : const char *pszUnitType = poSrcDS->GetRasterBand(1)->GetUnitType();
5922 314 : if (CSLFetchNameValue(papszOptions, "UOM") == nullptr && pszUnitType &&
5923 157 : !EQUAL(pszUnitType, ""))
5924 : {
5925 1 : apszUpdatedOptions.SetNameValue("UOM", pszUnitType);
5926 : }
5927 :
5928 157 : if (EQUAL(pszTilingScheme, "CUSTOM"))
5929 : {
5930 133 : if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
5931 : {
5932 0 : CPLError(CE_Failure, CPLE_NotSupported,
5933 : "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
5934 0 : return nullptr;
5935 : }
5936 :
5937 133 : GDALGeoPackageDataset *poDS = nullptr;
5938 : GDALDriver *poThisDriver =
5939 133 : GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
5940 133 : if (poThisDriver != nullptr)
5941 : {
5942 133 : apszUpdatedOptions.SetNameValue("SKIP_HOLES", "YES");
5943 133 : poDS = cpl::down_cast<GDALGeoPackageDataset *>(
5944 : poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
5945 : apszUpdatedOptions, pfnProgress,
5946 133 : pProgressData));
5947 :
5948 246 : if (poDS != nullptr &&
5949 133 : poSrcDS->GetRasterBand(1)->GetRasterDataType() == GDT_Byte &&
5950 : nBands <= 3)
5951 : {
5952 73 : poDS->m_nBandCountFromMetadata = nBands;
5953 73 : poDS->m_bMetadataDirty = true;
5954 : }
5955 : }
5956 133 : if (poDS)
5957 113 : poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
5958 133 : return poDS;
5959 : }
5960 :
5961 48 : const auto poTS = GetTilingScheme(pszTilingScheme);
5962 24 : if (!poTS)
5963 : {
5964 2 : return nullptr;
5965 : }
5966 22 : const int nEPSGCode = poTS->nEPSGCode;
5967 :
5968 44 : OGRSpatialReference oSRS;
5969 22 : if (oSRS.importFromEPSG(nEPSGCode) != OGRERR_NONE)
5970 : {
5971 0 : return nullptr;
5972 : }
5973 22 : char *pszWKT = nullptr;
5974 22 : oSRS.exportToWkt(&pszWKT);
5975 22 : char **papszTO = CSLSetNameValue(nullptr, "DST_SRS", pszWKT);
5976 :
5977 22 : void *hTransformArg = nullptr;
5978 :
5979 : // Hack to compensate for GDALSuggestedWarpOutput2() failure (or not
5980 : // ideal suggestion with PROJ 8) when reprojecting latitude = +/- 90 to
5981 : // EPSG:3857.
5982 22 : GDALGeoTransform srcGT;
5983 22 : std::unique_ptr<GDALDataset> poTmpDS;
5984 22 : bool bEPSG3857Adjust = false;
5985 8 : if (nEPSGCode == 3857 && poSrcDS->GetGeoTransform(srcGT) == CE_None &&
5986 30 : srcGT[2] == 0 && srcGT[4] == 0 && srcGT[5] < 0)
5987 : {
5988 8 : const auto poSrcSRS = poSrcDS->GetSpatialRef();
5989 8 : if (poSrcSRS && poSrcSRS->IsGeographic())
5990 : {
5991 2 : double maxLat = srcGT[3];
5992 2 : double minLat = srcGT[3] + poSrcDS->GetRasterYSize() * srcGT[5];
5993 : // Corresponds to the latitude of below MAX_GM
5994 2 : constexpr double MAX_LAT = 85.0511287798066;
5995 2 : bool bModified = false;
5996 2 : if (maxLat > MAX_LAT)
5997 : {
5998 2 : maxLat = MAX_LAT;
5999 2 : bModified = true;
6000 : }
6001 2 : if (minLat < -MAX_LAT)
6002 : {
6003 2 : minLat = -MAX_LAT;
6004 2 : bModified = true;
6005 : }
6006 2 : if (bModified)
6007 : {
6008 4 : CPLStringList aosOptions;
6009 2 : aosOptions.AddString("-of");
6010 2 : aosOptions.AddString("VRT");
6011 2 : aosOptions.AddString("-projwin");
6012 2 : aosOptions.AddString(CPLSPrintf("%.17g", srcGT[0]));
6013 2 : aosOptions.AddString(CPLSPrintf("%.17g", maxLat));
6014 : aosOptions.AddString(CPLSPrintf(
6015 2 : "%.17g", srcGT[0] + poSrcDS->GetRasterXSize() * srcGT[1]));
6016 2 : aosOptions.AddString(CPLSPrintf("%.17g", minLat));
6017 : auto psOptions =
6018 2 : GDALTranslateOptionsNew(aosOptions.List(), nullptr);
6019 2 : poTmpDS.reset(GDALDataset::FromHandle(GDALTranslate(
6020 : "", GDALDataset::ToHandle(poSrcDS), psOptions, nullptr)));
6021 2 : GDALTranslateOptionsFree(psOptions);
6022 2 : if (poTmpDS)
6023 : {
6024 2 : bEPSG3857Adjust = true;
6025 2 : hTransformArg = GDALCreateGenImgProjTransformer2(
6026 2 : GDALDataset::FromHandle(poTmpDS.get()), nullptr,
6027 : papszTO);
6028 : }
6029 : }
6030 : }
6031 : }
6032 22 : if (hTransformArg == nullptr)
6033 : {
6034 : hTransformArg =
6035 20 : GDALCreateGenImgProjTransformer2(poSrcDS, nullptr, papszTO);
6036 : }
6037 :
6038 22 : if (hTransformArg == nullptr)
6039 : {
6040 1 : CPLFree(pszWKT);
6041 1 : CSLDestroy(papszTO);
6042 1 : return nullptr;
6043 : }
6044 :
6045 21 : GDALTransformerInfo *psInfo =
6046 : static_cast<GDALTransformerInfo *>(hTransformArg);
6047 21 : GDALGeoTransform gt;
6048 : double adfExtent[4];
6049 : int nXSize, nYSize;
6050 :
6051 21 : if (GDALSuggestedWarpOutput2(poSrcDS, psInfo->pfnTransform, hTransformArg,
6052 : gt.data(), &nXSize, &nYSize, adfExtent,
6053 21 : 0) != CE_None)
6054 : {
6055 0 : CPLFree(pszWKT);
6056 0 : CSLDestroy(papszTO);
6057 0 : GDALDestroyGenImgProjTransformer(hTransformArg);
6058 0 : return nullptr;
6059 : }
6060 :
6061 21 : GDALDestroyGenImgProjTransformer(hTransformArg);
6062 21 : hTransformArg = nullptr;
6063 21 : poTmpDS.reset();
6064 :
6065 21 : if (bEPSG3857Adjust)
6066 : {
6067 2 : constexpr double SPHERICAL_RADIUS = 6378137.0;
6068 2 : constexpr double MAX_GM =
6069 : SPHERICAL_RADIUS * M_PI; // 20037508.342789244
6070 2 : double maxNorthing = gt[3];
6071 2 : double minNorthing = gt[3] + gt[5] * nYSize;
6072 2 : bool bChanged = false;
6073 2 : if (maxNorthing > MAX_GM)
6074 : {
6075 2 : bChanged = true;
6076 2 : maxNorthing = MAX_GM;
6077 : }
6078 2 : if (minNorthing < -MAX_GM)
6079 : {
6080 2 : bChanged = true;
6081 2 : minNorthing = -MAX_GM;
6082 : }
6083 2 : if (bChanged)
6084 : {
6085 2 : gt[3] = maxNorthing;
6086 2 : nYSize = int((maxNorthing - minNorthing) / (-gt[5]) + 0.5);
6087 2 : adfExtent[1] = maxNorthing + nYSize * gt[5];
6088 2 : adfExtent[3] = maxNorthing;
6089 : }
6090 : }
6091 :
6092 21 : double dfComputedRes = gt[1];
6093 21 : double dfPrevRes = 0.0;
6094 21 : double dfRes = 0.0;
6095 21 : int nZoomLevel = 0; // Used after for.
6096 21 : const char *pszZoomLevel = CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
6097 21 : if (pszZoomLevel)
6098 : {
6099 2 : nZoomLevel = atoi(pszZoomLevel);
6100 :
6101 2 : int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
6102 2 : while ((1 << nMaxZoomLevelForThisTM) >
6103 4 : INT_MAX / poTS->nTileXCountZoomLevel0 ||
6104 2 : (1 << nMaxZoomLevelForThisTM) >
6105 2 : INT_MAX / poTS->nTileYCountZoomLevel0)
6106 : {
6107 0 : --nMaxZoomLevelForThisTM;
6108 : }
6109 :
6110 2 : if (nZoomLevel < 0 || nZoomLevel > nMaxZoomLevelForThisTM)
6111 : {
6112 1 : CPLError(CE_Failure, CPLE_AppDefined,
6113 : "ZOOM_LEVEL = %s is invalid. It should be in [0,%d] range",
6114 : pszZoomLevel, nMaxZoomLevelForThisTM);
6115 1 : CPLFree(pszWKT);
6116 1 : CSLDestroy(papszTO);
6117 1 : return nullptr;
6118 : }
6119 : }
6120 : else
6121 : {
6122 171 : for (; nZoomLevel < MAX_ZOOM_LEVEL; nZoomLevel++)
6123 : {
6124 171 : dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
6125 171 : if (dfComputedRes > dfRes ||
6126 152 : fabs(dfComputedRes - dfRes) / dfRes <= 1e-8)
6127 : break;
6128 152 : dfPrevRes = dfRes;
6129 : }
6130 38 : if (nZoomLevel == MAX_ZOOM_LEVEL ||
6131 38 : (1 << nZoomLevel) > INT_MAX / poTS->nTileXCountZoomLevel0 ||
6132 19 : (1 << nZoomLevel) > INT_MAX / poTS->nTileYCountZoomLevel0)
6133 : {
6134 0 : CPLError(CE_Failure, CPLE_AppDefined,
6135 : "Could not find an appropriate zoom level");
6136 0 : CPLFree(pszWKT);
6137 0 : CSLDestroy(papszTO);
6138 0 : return nullptr;
6139 : }
6140 :
6141 19 : if (nZoomLevel > 0 && fabs(dfComputedRes - dfRes) / dfRes > 1e-8)
6142 : {
6143 17 : const char *pszZoomLevelStrategy = CSLFetchNameValueDef(
6144 : papszOptions, "ZOOM_LEVEL_STRATEGY", "AUTO");
6145 17 : if (EQUAL(pszZoomLevelStrategy, "LOWER"))
6146 : {
6147 1 : nZoomLevel--;
6148 : }
6149 16 : else if (EQUAL(pszZoomLevelStrategy, "UPPER"))
6150 : {
6151 : /* do nothing */
6152 : }
6153 : else
6154 : {
6155 15 : if (dfPrevRes / dfComputedRes < dfComputedRes / dfRes)
6156 13 : nZoomLevel--;
6157 : }
6158 : }
6159 : }
6160 :
6161 20 : dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
6162 :
6163 20 : double dfMinX = adfExtent[0];
6164 20 : double dfMinY = adfExtent[1];
6165 20 : double dfMaxX = adfExtent[2];
6166 20 : double dfMaxY = adfExtent[3];
6167 :
6168 20 : nXSize = static_cast<int>(0.5 + (dfMaxX - dfMinX) / dfRes);
6169 20 : nYSize = static_cast<int>(0.5 + (dfMaxY - dfMinY) / dfRes);
6170 20 : gt[1] = dfRes;
6171 20 : gt[5] = -dfRes;
6172 :
6173 20 : const GDALDataType eDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
6174 20 : int nTargetBands = nBands;
6175 : /* For grey level or RGB, if there's reprojection involved, add an alpha */
6176 : /* channel */
6177 37 : if (eDT == GDT_Byte &&
6178 13 : ((nBands == 1 &&
6179 17 : poSrcDS->GetRasterBand(1)->GetColorTable() == nullptr) ||
6180 : nBands == 3))
6181 : {
6182 30 : OGRSpatialReference oSrcSRS;
6183 15 : oSrcSRS.SetFromUserInput(poSrcDS->GetProjectionRef());
6184 15 : oSrcSRS.AutoIdentifyEPSG();
6185 30 : if (oSrcSRS.GetAuthorityCode(nullptr) == nullptr ||
6186 15 : atoi(oSrcSRS.GetAuthorityCode(nullptr)) != nEPSGCode)
6187 : {
6188 13 : nTargetBands++;
6189 : }
6190 : }
6191 :
6192 20 : GDALResampleAlg eResampleAlg = GRA_Bilinear;
6193 20 : const char *pszResampling = CSLFetchNameValue(papszOptions, "RESAMPLING");
6194 20 : if (pszResampling)
6195 : {
6196 6 : for (size_t iAlg = 0;
6197 6 : iAlg < sizeof(asResamplingAlg) / sizeof(asResamplingAlg[0]);
6198 : iAlg++)
6199 : {
6200 6 : if (EQUAL(pszResampling, asResamplingAlg[iAlg].pszName))
6201 : {
6202 3 : eResampleAlg = asResamplingAlg[iAlg].eResampleAlg;
6203 3 : break;
6204 : }
6205 : }
6206 : }
6207 :
6208 16 : if (nBands == 1 && poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr &&
6209 36 : eResampleAlg != GRA_NearestNeighbour && eResampleAlg != GRA_Mode)
6210 : {
6211 0 : CPLError(
6212 : CE_Warning, CPLE_AppDefined,
6213 : "Input dataset has a color table, which will likely lead to "
6214 : "bad results when using a resampling method other than "
6215 : "nearest neighbour or mode. Converting the dataset to 24/32 bit "
6216 : "(e.g. with gdal_translate -expand rgb/rgba) is advised.");
6217 : }
6218 :
6219 40 : auto poDS = std::make_unique<GDALGeoPackageDataset>();
6220 20 : if (!(poDS->Create(pszFilename, nXSize, nYSize, nTargetBands, eDT,
6221 : apszUpdatedOptions)))
6222 : {
6223 1 : CPLFree(pszWKT);
6224 1 : CSLDestroy(papszTO);
6225 1 : return nullptr;
6226 : }
6227 :
6228 : // Assign nodata values before the SetGeoTransform call.
6229 : // SetGeoTransform will trigger creation of the overview datasets for each
6230 : // zoom level and at that point the nodata value needs to be known.
6231 19 : int bHasNoData = FALSE;
6232 : double dfNoDataValue =
6233 19 : poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
6234 19 : if (eDT != GDT_Byte && bHasNoData)
6235 : {
6236 3 : poDS->GetRasterBand(1)->SetNoDataValue(dfNoDataValue);
6237 : }
6238 :
6239 19 : poDS->SetGeoTransform(gt);
6240 19 : poDS->SetProjection(pszWKT);
6241 19 : CPLFree(pszWKT);
6242 19 : pszWKT = nullptr;
6243 24 : if (nTargetBands == 1 && nBands == 1 &&
6244 5 : poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr)
6245 : {
6246 2 : poDS->GetRasterBand(1)->SetColorTable(
6247 1 : poSrcDS->GetRasterBand(1)->GetColorTable());
6248 : }
6249 :
6250 : hTransformArg =
6251 19 : GDALCreateGenImgProjTransformer2(poSrcDS, poDS.get(), papszTO);
6252 19 : CSLDestroy(papszTO);
6253 19 : if (hTransformArg == nullptr)
6254 : {
6255 0 : return nullptr;
6256 : }
6257 :
6258 19 : poDS->SetMetadata(poSrcDS->GetMetadata());
6259 :
6260 : /* -------------------------------------------------------------------- */
6261 : /* Warp the transformer with a linear approximator */
6262 : /* -------------------------------------------------------------------- */
6263 19 : hTransformArg = GDALCreateApproxTransformer(GDALGenImgProjTransform,
6264 : hTransformArg, 0.125);
6265 19 : GDALApproxTransformerOwnsSubtransformer(hTransformArg, TRUE);
6266 :
6267 : /* -------------------------------------------------------------------- */
6268 : /* Setup warp options. */
6269 : /* -------------------------------------------------------------------- */
6270 19 : GDALWarpOptions *psWO = GDALCreateWarpOptions();
6271 :
6272 19 : psWO->papszWarpOptions = CSLSetNameValue(nullptr, "OPTIMIZE_SIZE", "YES");
6273 19 : psWO->papszWarpOptions =
6274 19 : CSLSetNameValue(psWO->papszWarpOptions, "SAMPLE_GRID", "YES");
6275 19 : if (bHasNoData)
6276 : {
6277 3 : if (dfNoDataValue == 0.0)
6278 : {
6279 : // Do not initialize in the case where nodata != 0, since we
6280 : // want the GeoPackage driver to return empty tiles at the nodata
6281 : // value instead of 0 as GDAL core would
6282 0 : psWO->papszWarpOptions =
6283 0 : CSLSetNameValue(psWO->papszWarpOptions, "INIT_DEST", "0");
6284 : }
6285 :
6286 3 : psWO->padfSrcNoDataReal =
6287 3 : static_cast<double *>(CPLMalloc(sizeof(double)));
6288 3 : psWO->padfSrcNoDataReal[0] = dfNoDataValue;
6289 :
6290 3 : psWO->padfDstNoDataReal =
6291 3 : static_cast<double *>(CPLMalloc(sizeof(double)));
6292 3 : psWO->padfDstNoDataReal[0] = dfNoDataValue;
6293 : }
6294 19 : psWO->eWorkingDataType = eDT;
6295 19 : psWO->eResampleAlg = eResampleAlg;
6296 :
6297 19 : psWO->hSrcDS = poSrcDS;
6298 19 : psWO->hDstDS = poDS.get();
6299 :
6300 19 : psWO->pfnTransformer = GDALApproxTransform;
6301 19 : psWO->pTransformerArg = hTransformArg;
6302 :
6303 19 : psWO->pfnProgress = pfnProgress;
6304 19 : psWO->pProgressArg = pProgressData;
6305 :
6306 : /* -------------------------------------------------------------------- */
6307 : /* Setup band mapping. */
6308 : /* -------------------------------------------------------------------- */
6309 :
6310 19 : if (nBands == 2 || nBands == 4)
6311 1 : psWO->nBandCount = nBands - 1;
6312 : else
6313 18 : psWO->nBandCount = nBands;
6314 :
6315 19 : psWO->panSrcBands =
6316 19 : static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
6317 19 : psWO->panDstBands =
6318 19 : static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
6319 :
6320 46 : for (int i = 0; i < psWO->nBandCount; i++)
6321 : {
6322 27 : psWO->panSrcBands[i] = i + 1;
6323 27 : psWO->panDstBands[i] = i + 1;
6324 : }
6325 :
6326 19 : if (nBands == 2 || nBands == 4)
6327 : {
6328 1 : psWO->nSrcAlphaBand = nBands;
6329 : }
6330 19 : if (nTargetBands == 2 || nTargetBands == 4)
6331 : {
6332 13 : psWO->nDstAlphaBand = nTargetBands;
6333 : }
6334 :
6335 : /* -------------------------------------------------------------------- */
6336 : /* Initialize and execute the warp. */
6337 : /* -------------------------------------------------------------------- */
6338 38 : GDALWarpOperation oWO;
6339 :
6340 19 : CPLErr eErr = oWO.Initialize(psWO);
6341 19 : if (eErr == CE_None)
6342 : {
6343 : /*if( bMulti )
6344 : eErr = oWO.ChunkAndWarpMulti( 0, 0, nXSize, nYSize );
6345 : else*/
6346 19 : eErr = oWO.ChunkAndWarpImage(0, 0, nXSize, nYSize);
6347 : }
6348 19 : if (eErr != CE_None)
6349 : {
6350 0 : poDS.reset();
6351 : }
6352 :
6353 19 : GDALDestroyTransformer(hTransformArg);
6354 19 : GDALDestroyWarpOptions(psWO);
6355 :
6356 19 : if (poDS)
6357 19 : poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
6358 :
6359 19 : return poDS.release();
6360 : }
6361 :
6362 : /************************************************************************/
6363 : /* ParseCompressionOptions() */
6364 : /************************************************************************/
6365 :
6366 457 : void GDALGeoPackageDataset::ParseCompressionOptions(char **papszOptions)
6367 : {
6368 457 : const char *pszZLevel = CSLFetchNameValue(papszOptions, "ZLEVEL");
6369 457 : if (pszZLevel)
6370 0 : m_nZLevel = atoi(pszZLevel);
6371 :
6372 457 : const char *pszQuality = CSLFetchNameValue(papszOptions, "QUALITY");
6373 457 : if (pszQuality)
6374 0 : m_nQuality = atoi(pszQuality);
6375 :
6376 457 : const char *pszDither = CSLFetchNameValue(papszOptions, "DITHER");
6377 457 : if (pszDither)
6378 0 : m_bDither = CPLTestBool(pszDither);
6379 457 : }
6380 :
6381 : /************************************************************************/
6382 : /* RegisterWebPExtension() */
6383 : /************************************************************************/
6384 :
6385 11 : bool GDALGeoPackageDataset::RegisterWebPExtension()
6386 : {
6387 11 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
6388 0 : return false;
6389 :
6390 11 : char *pszSQL = sqlite3_mprintf(
6391 : "INSERT INTO gpkg_extensions "
6392 : "(table_name, column_name, extension_name, definition, scope) "
6393 : "VALUES "
6394 : "('%q', 'tile_data', 'gpkg_webp', "
6395 : "'http://www.geopackage.org/spec120/#extension_tiles_webp', "
6396 : "'read-write')",
6397 : m_osRasterTable.c_str());
6398 11 : const OGRErr eErr = SQLCommand(hDB, pszSQL);
6399 11 : sqlite3_free(pszSQL);
6400 :
6401 11 : return OGRERR_NONE == eErr;
6402 : }
6403 :
6404 : /************************************************************************/
6405 : /* RegisterZoomOtherExtension() */
6406 : /************************************************************************/
6407 :
6408 1 : bool GDALGeoPackageDataset::RegisterZoomOtherExtension()
6409 : {
6410 1 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
6411 0 : return false;
6412 :
6413 1 : char *pszSQL = sqlite3_mprintf(
6414 : "INSERT INTO gpkg_extensions "
6415 : "(table_name, column_name, extension_name, definition, scope) "
6416 : "VALUES "
6417 : "('%q', 'tile_data', 'gpkg_zoom_other', "
6418 : "'http://www.geopackage.org/spec120/#extension_zoom_other_intervals', "
6419 : "'read-write')",
6420 : m_osRasterTable.c_str());
6421 1 : const OGRErr eErr = SQLCommand(hDB, pszSQL);
6422 1 : sqlite3_free(pszSQL);
6423 1 : return OGRERR_NONE == eErr;
6424 : }
6425 :
6426 : /************************************************************************/
6427 : /* GetLayer() */
6428 : /************************************************************************/
6429 :
6430 15430 : OGRLayer *GDALGeoPackageDataset::GetLayer(int iLayer)
6431 :
6432 : {
6433 15430 : if (iLayer < 0 || iLayer >= static_cast<int>(m_apoLayers.size()))
6434 6 : return nullptr;
6435 : else
6436 15424 : return m_apoLayers[iLayer].get();
6437 : }
6438 :
6439 : /************************************************************************/
6440 : /* LaunderName() */
6441 : /************************************************************************/
6442 :
6443 : /** Launder identifiers (table, column names) according to guidance at
6444 : * https://www.geopackage.org/guidance/getting-started.html:
6445 : * "For maximum interoperability, start your database identifiers (table names,
6446 : * column names, etc.) with a lowercase character and only use lowercase
6447 : * characters, numbers 0-9, and underscores (_)."
6448 : */
6449 :
6450 : /* static */
6451 5 : std::string GDALGeoPackageDataset::LaunderName(const std::string &osStr)
6452 : {
6453 5 : char *pszASCII = CPLUTF8ForceToASCII(osStr.c_str(), '_');
6454 10 : const std::string osStrASCII(pszASCII);
6455 5 : CPLFree(pszASCII);
6456 :
6457 10 : std::string osRet;
6458 5 : osRet.reserve(osStrASCII.size());
6459 :
6460 29 : for (size_t i = 0; i < osStrASCII.size(); ++i)
6461 : {
6462 24 : if (osRet.empty())
6463 : {
6464 5 : if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
6465 : {
6466 2 : osRet += (osStrASCII[i] - 'A' + 'a');
6467 : }
6468 3 : else if (osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z')
6469 : {
6470 2 : osRet += osStrASCII[i];
6471 : }
6472 : else
6473 : {
6474 1 : continue;
6475 : }
6476 : }
6477 19 : else if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
6478 : {
6479 11 : osRet += (osStrASCII[i] - 'A' + 'a');
6480 : }
6481 9 : else if ((osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z') ||
6482 14 : (osStrASCII[i] >= '0' && osStrASCII[i] <= '9') ||
6483 5 : osStrASCII[i] == '_')
6484 : {
6485 7 : osRet += osStrASCII[i];
6486 : }
6487 : else
6488 : {
6489 1 : osRet += '_';
6490 : }
6491 : }
6492 :
6493 5 : if (osRet.empty() && !osStrASCII.empty())
6494 2 : return LaunderName(std::string("x").append(osStrASCII));
6495 :
6496 4 : if (osRet != osStr)
6497 : {
6498 3 : CPLDebug("PG", "LaunderName('%s') -> '%s'", osStr.c_str(),
6499 : osRet.c_str());
6500 : }
6501 :
6502 4 : return osRet;
6503 : }
6504 :
6505 : /************************************************************************/
6506 : /* ICreateLayer() */
6507 : /************************************************************************/
6508 :
6509 : OGRLayer *
6510 757 : GDALGeoPackageDataset::ICreateLayer(const char *pszLayerName,
6511 : const OGRGeomFieldDefn *poSrcGeomFieldDefn,
6512 : CSLConstList papszOptions)
6513 : {
6514 : /* -------------------------------------------------------------------- */
6515 : /* Verify we are in update mode. */
6516 : /* -------------------------------------------------------------------- */
6517 757 : if (!GetUpdate())
6518 : {
6519 0 : CPLError(CE_Failure, CPLE_NoWriteAccess,
6520 : "Data source %s opened read-only.\n"
6521 : "New layer %s cannot be created.\n",
6522 : m_pszFilename, pszLayerName);
6523 :
6524 0 : return nullptr;
6525 : }
6526 :
6527 : const bool bLaunder =
6528 757 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "LAUNDER", "NO"));
6529 : const std::string osTableName(bLaunder ? LaunderName(pszLayerName)
6530 2271 : : std::string(pszLayerName));
6531 :
6532 : const auto eGType =
6533 757 : poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone;
6534 : const auto poSpatialRef =
6535 757 : poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr;
6536 :
6537 757 : if (!m_bHasGPKGGeometryColumns)
6538 : {
6539 1 : if (SQLCommand(hDB, pszCREATE_GPKG_GEOMETRY_COLUMNS) != OGRERR_NONE)
6540 : {
6541 0 : return nullptr;
6542 : }
6543 1 : m_bHasGPKGGeometryColumns = true;
6544 : }
6545 :
6546 : // Check identifier unicity
6547 757 : const char *pszIdentifier = CSLFetchNameValue(papszOptions, "IDENTIFIER");
6548 757 : if (pszIdentifier != nullptr && pszIdentifier[0] == '\0')
6549 0 : pszIdentifier = nullptr;
6550 757 : if (pszIdentifier != nullptr)
6551 : {
6552 13 : for (auto &poLayer : m_apoLayers)
6553 : {
6554 : const char *pszOtherIdentifier =
6555 9 : poLayer->GetMetadataItem("IDENTIFIER");
6556 9 : if (pszOtherIdentifier == nullptr)
6557 6 : pszOtherIdentifier = poLayer->GetName();
6558 18 : if (pszOtherIdentifier != nullptr &&
6559 12 : EQUAL(pszOtherIdentifier, pszIdentifier) &&
6560 3 : !EQUAL(poLayer->GetName(), osTableName.c_str()))
6561 : {
6562 2 : CPLError(CE_Failure, CPLE_AppDefined,
6563 : "Identifier %s is already used by table %s",
6564 : pszIdentifier, poLayer->GetName());
6565 2 : return nullptr;
6566 : }
6567 : }
6568 :
6569 : // In case there would be table in gpkg_contents not listed as a
6570 : // vector layer
6571 4 : char *pszSQL = sqlite3_mprintf(
6572 : "SELECT table_name FROM gpkg_contents WHERE identifier = '%q' "
6573 : "LIMIT 2",
6574 : pszIdentifier);
6575 4 : auto oResult = SQLQuery(hDB, pszSQL);
6576 4 : sqlite3_free(pszSQL);
6577 8 : if (oResult && oResult->RowCount() > 0 &&
6578 9 : oResult->GetValue(0, 0) != nullptr &&
6579 1 : !EQUAL(oResult->GetValue(0, 0), osTableName.c_str()))
6580 : {
6581 1 : CPLError(CE_Failure, CPLE_AppDefined,
6582 : "Identifier %s is already used by table %s", pszIdentifier,
6583 : oResult->GetValue(0, 0));
6584 1 : return nullptr;
6585 : }
6586 : }
6587 :
6588 : /* Read GEOMETRY_NAME option */
6589 : const char *pszGeomColumnName =
6590 754 : CSLFetchNameValue(papszOptions, "GEOMETRY_NAME");
6591 754 : if (pszGeomColumnName == nullptr) /* deprecated name */
6592 673 : pszGeomColumnName = CSLFetchNameValue(papszOptions, "GEOMETRY_COLUMN");
6593 754 : if (pszGeomColumnName == nullptr && poSrcGeomFieldDefn)
6594 : {
6595 621 : pszGeomColumnName = poSrcGeomFieldDefn->GetNameRef();
6596 621 : if (pszGeomColumnName && pszGeomColumnName[0] == 0)
6597 617 : pszGeomColumnName = nullptr;
6598 : }
6599 754 : if (pszGeomColumnName == nullptr)
6600 669 : pszGeomColumnName = "geom";
6601 : const bool bGeomNullable =
6602 754 : CPLFetchBool(papszOptions, "GEOMETRY_NULLABLE", true);
6603 :
6604 : /* Read FID option */
6605 754 : const char *pszFIDColumnName = CSLFetchNameValue(papszOptions, "FID");
6606 754 : if (pszFIDColumnName == nullptr)
6607 718 : pszFIDColumnName = "fid";
6608 :
6609 754 : if (CPLTestBool(CPLGetConfigOption("GPKG_NAME_CHECK", "YES")))
6610 : {
6611 754 : if (strspn(pszFIDColumnName, "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") > 0)
6612 : {
6613 1 : CPLError(CE_Failure, CPLE_AppDefined,
6614 : "The primary key (%s) name may not contain special "
6615 : "characters or spaces",
6616 : pszFIDColumnName);
6617 1 : return nullptr;
6618 : }
6619 :
6620 : /* Avoiding gpkg prefixes is not an official requirement, but seems wise
6621 : */
6622 753 : if (STARTS_WITH(osTableName.c_str(), "gpkg"))
6623 : {
6624 0 : CPLError(CE_Failure, CPLE_AppDefined,
6625 : "The layer name may not begin with 'gpkg' as it is a "
6626 : "reserved geopackage prefix");
6627 0 : return nullptr;
6628 : }
6629 :
6630 : /* Preemptively try and avoid sqlite3 syntax errors due to */
6631 : /* illegal characters. */
6632 753 : if (strspn(osTableName.c_str(), "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") >
6633 : 0)
6634 : {
6635 0 : CPLError(
6636 : CE_Failure, CPLE_AppDefined,
6637 : "The layer name may not contain special characters or spaces");
6638 0 : return nullptr;
6639 : }
6640 : }
6641 :
6642 : /* Check for any existing layers that already use this name */
6643 957 : for (int iLayer = 0; iLayer < static_cast<int>(m_apoLayers.size());
6644 : iLayer++)
6645 : {
6646 205 : if (EQUAL(osTableName.c_str(), m_apoLayers[iLayer]->GetName()))
6647 : {
6648 : const char *pszOverwrite =
6649 2 : CSLFetchNameValue(papszOptions, "OVERWRITE");
6650 2 : if (pszOverwrite != nullptr && CPLTestBool(pszOverwrite))
6651 : {
6652 1 : DeleteLayer(iLayer);
6653 : }
6654 : else
6655 : {
6656 1 : CPLError(CE_Failure, CPLE_AppDefined,
6657 : "Layer %s already exists, CreateLayer failed.\n"
6658 : "Use the layer creation option OVERWRITE=YES to "
6659 : "replace it.",
6660 : osTableName.c_str());
6661 1 : return nullptr;
6662 : }
6663 : }
6664 : }
6665 :
6666 752 : if (m_apoLayers.size() == 1)
6667 : {
6668 : // Async RTree building doesn't play well with multiple layer:
6669 : // SQLite3 locks being hold for a long time, random failed commits,
6670 : // etc.
6671 78 : m_apoLayers[0]->FinishOrDisableThreadedRTree();
6672 : }
6673 :
6674 : /* Create a blank layer. */
6675 : auto poLayer =
6676 1504 : std::make_unique<OGRGeoPackageTableLayer>(this, osTableName.c_str());
6677 :
6678 752 : OGRSpatialReference *poSRS = nullptr;
6679 752 : if (poSpatialRef)
6680 : {
6681 241 : poSRS = poSpatialRef->Clone();
6682 241 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
6683 : }
6684 1505 : poLayer->SetCreationParameters(
6685 : eGType,
6686 753 : bLaunder ? LaunderName(pszGeomColumnName).c_str() : pszGeomColumnName,
6687 : bGeomNullable, poSRS, CSLFetchNameValue(papszOptions, "SRID"),
6688 1504 : poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetCoordinatePrecision()
6689 : : OGRGeomCoordinatePrecision(),
6690 752 : CPLTestBool(
6691 : CSLFetchNameValueDef(papszOptions, "DISCARD_COORD_LSB", "NO")),
6692 752 : CPLTestBool(CSLFetchNameValueDef(
6693 : papszOptions, "UNDO_DISCARD_COORD_LSB_ON_READING", "NO")),
6694 753 : bLaunder ? LaunderName(pszFIDColumnName).c_str() : pszFIDColumnName,
6695 : pszIdentifier, CSLFetchNameValue(papszOptions, "DESCRIPTION"));
6696 752 : if (poSRS)
6697 : {
6698 241 : poSRS->Release();
6699 : }
6700 :
6701 752 : poLayer->SetLaunder(bLaunder);
6702 :
6703 : /* Should we create a spatial index ? */
6704 752 : const char *pszSI = CSLFetchNameValue(papszOptions, "SPATIAL_INDEX");
6705 752 : int bCreateSpatialIndex = (pszSI == nullptr || CPLTestBool(pszSI));
6706 752 : if (eGType != wkbNone && bCreateSpatialIndex)
6707 : {
6708 678 : poLayer->SetDeferredSpatialIndexCreation(true);
6709 : }
6710 :
6711 752 : poLayer->SetPrecisionFlag(CPLFetchBool(papszOptions, "PRECISION", true));
6712 752 : poLayer->SetTruncateFieldsFlag(
6713 752 : CPLFetchBool(papszOptions, "TRUNCATE_FIELDS", false));
6714 752 : if (eGType == wkbNone)
6715 : {
6716 52 : const char *pszASpatialVariant = CSLFetchNameValueDef(
6717 : papszOptions, "ASPATIAL_VARIANT",
6718 52 : m_bNonSpatialTablesNonRegisteredInGpkgContentsFound
6719 : ? "NOT_REGISTERED"
6720 : : "GPKG_ATTRIBUTES");
6721 52 : GPKGASpatialVariant eASpatialVariant = GPKG_ATTRIBUTES;
6722 52 : if (EQUAL(pszASpatialVariant, "GPKG_ATTRIBUTES"))
6723 40 : eASpatialVariant = GPKG_ATTRIBUTES;
6724 12 : else if (EQUAL(pszASpatialVariant, "OGR_ASPATIAL"))
6725 : {
6726 0 : CPLError(CE_Failure, CPLE_NotSupported,
6727 : "ASPATIAL_VARIANT=OGR_ASPATIAL is no longer supported");
6728 0 : return nullptr;
6729 : }
6730 12 : else if (EQUAL(pszASpatialVariant, "NOT_REGISTERED"))
6731 12 : eASpatialVariant = NOT_REGISTERED;
6732 : else
6733 : {
6734 0 : CPLError(CE_Failure, CPLE_NotSupported,
6735 : "Unsupported value for ASPATIAL_VARIANT: %s",
6736 : pszASpatialVariant);
6737 0 : return nullptr;
6738 : }
6739 52 : poLayer->SetASpatialVariant(eASpatialVariant);
6740 : }
6741 :
6742 : const char *pszDateTimePrecision =
6743 752 : CSLFetchNameValueDef(papszOptions, "DATETIME_PRECISION", "AUTO");
6744 752 : if (EQUAL(pszDateTimePrecision, "MILLISECOND"))
6745 : {
6746 2 : poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
6747 : }
6748 750 : else if (EQUAL(pszDateTimePrecision, "SECOND"))
6749 : {
6750 1 : if (m_nUserVersion < GPKG_1_4_VERSION)
6751 0 : CPLError(
6752 : CE_Warning, CPLE_AppDefined,
6753 : "DATETIME_PRECISION=SECOND is only valid since GeoPackage 1.4");
6754 1 : poLayer->SetDateTimePrecision(OGRISO8601Precision::SECOND);
6755 : }
6756 749 : else if (EQUAL(pszDateTimePrecision, "MINUTE"))
6757 : {
6758 1 : if (m_nUserVersion < GPKG_1_4_VERSION)
6759 0 : CPLError(
6760 : CE_Warning, CPLE_AppDefined,
6761 : "DATETIME_PRECISION=MINUTE is only valid since GeoPackage 1.4");
6762 1 : poLayer->SetDateTimePrecision(OGRISO8601Precision::MINUTE);
6763 : }
6764 748 : else if (EQUAL(pszDateTimePrecision, "AUTO"))
6765 : {
6766 747 : if (m_nUserVersion < GPKG_1_4_VERSION)
6767 13 : poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
6768 : }
6769 : else
6770 : {
6771 1 : CPLError(CE_Failure, CPLE_NotSupported,
6772 : "Unsupported value for DATETIME_PRECISION: %s",
6773 : pszDateTimePrecision);
6774 1 : return nullptr;
6775 : }
6776 :
6777 : // If there was an ogr_empty_table table, we can remove it
6778 : // But do it at dataset closing, otherwise locking performance issues
6779 : // can arise (probably when transactions are used).
6780 751 : m_bRemoveOGREmptyTable = true;
6781 :
6782 751 : m_apoLayers.emplace_back(std::move(poLayer));
6783 751 : return m_apoLayers.back().get();
6784 : }
6785 :
6786 : /************************************************************************/
6787 : /* FindLayerIndex() */
6788 : /************************************************************************/
6789 :
6790 27 : int GDALGeoPackageDataset::FindLayerIndex(const char *pszLayerName)
6791 :
6792 : {
6793 42 : for (int iLayer = 0; iLayer < static_cast<int>(m_apoLayers.size());
6794 : iLayer++)
6795 : {
6796 28 : if (EQUAL(pszLayerName, m_apoLayers[iLayer]->GetName()))
6797 13 : return iLayer;
6798 : }
6799 14 : return -1;
6800 : }
6801 :
6802 : /************************************************************************/
6803 : /* DeleteLayerCommon() */
6804 : /************************************************************************/
6805 :
6806 40 : OGRErr GDALGeoPackageDataset::DeleteLayerCommon(const char *pszLayerName)
6807 : {
6808 : // Temporary remove foreign key checks
6809 : const GPKGTemporaryForeignKeyCheckDisabler
6810 40 : oGPKGTemporaryForeignKeyCheckDisabler(this);
6811 :
6812 40 : char *pszSQL = sqlite3_mprintf(
6813 : "DELETE FROM gpkg_contents WHERE lower(table_name) = lower('%q')",
6814 : pszLayerName);
6815 40 : OGRErr eErr = SQLCommand(hDB, pszSQL);
6816 40 : sqlite3_free(pszSQL);
6817 :
6818 40 : if (eErr == OGRERR_NONE && HasExtensionsTable())
6819 : {
6820 38 : pszSQL = sqlite3_mprintf(
6821 : "DELETE FROM gpkg_extensions WHERE lower(table_name) = lower('%q')",
6822 : pszLayerName);
6823 38 : eErr = SQLCommand(hDB, pszSQL);
6824 38 : sqlite3_free(pszSQL);
6825 : }
6826 :
6827 40 : if (eErr == OGRERR_NONE && HasMetadataTables())
6828 : {
6829 : // Delete from gpkg_metadata metadata records that are only referenced
6830 : // by the table we are about to drop
6831 11 : pszSQL = sqlite3_mprintf(
6832 : "DELETE FROM gpkg_metadata WHERE id IN ("
6833 : "SELECT DISTINCT md_file_id FROM "
6834 : "gpkg_metadata_reference WHERE "
6835 : "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
6836 : "AND id NOT IN ("
6837 : "SELECT DISTINCT md_file_id FROM gpkg_metadata_reference WHERE "
6838 : "md_file_id IN (SELECT DISTINCT md_file_id FROM "
6839 : "gpkg_metadata_reference WHERE "
6840 : "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
6841 : "AND lower(table_name) <> lower('%q'))",
6842 : pszLayerName, pszLayerName, pszLayerName);
6843 11 : eErr = SQLCommand(hDB, pszSQL);
6844 11 : sqlite3_free(pszSQL);
6845 :
6846 11 : if (eErr == OGRERR_NONE)
6847 : {
6848 : pszSQL =
6849 11 : sqlite3_mprintf("DELETE FROM gpkg_metadata_reference WHERE "
6850 : "lower(table_name) = lower('%q')",
6851 : pszLayerName);
6852 11 : eErr = SQLCommand(hDB, pszSQL);
6853 11 : sqlite3_free(pszSQL);
6854 : }
6855 : }
6856 :
6857 40 : if (eErr == OGRERR_NONE && HasGpkgextRelationsTable())
6858 : {
6859 : // Remove reference to potential corresponding mapping table in
6860 : // gpkg_extensions
6861 4 : pszSQL = sqlite3_mprintf(
6862 : "DELETE FROM gpkg_extensions WHERE "
6863 : "extension_name IN ('related_tables', "
6864 : "'gpkg_related_tables') AND lower(table_name) = "
6865 : "(SELECT lower(mapping_table_name) FROM gpkgext_relations WHERE "
6866 : "lower(base_table_name) = lower('%q') OR "
6867 : "lower(related_table_name) = lower('%q') OR "
6868 : "lower(mapping_table_name) = lower('%q'))",
6869 : pszLayerName, pszLayerName, pszLayerName);
6870 4 : eErr = SQLCommand(hDB, pszSQL);
6871 4 : sqlite3_free(pszSQL);
6872 :
6873 4 : if (eErr == OGRERR_NONE)
6874 : {
6875 : // Remove reference to potential corresponding mapping table in
6876 : // gpkgext_relations
6877 : pszSQL =
6878 4 : sqlite3_mprintf("DELETE FROM gpkgext_relations WHERE "
6879 : "lower(base_table_name) = lower('%q') OR "
6880 : "lower(related_table_name) = lower('%q') OR "
6881 : "lower(mapping_table_name) = lower('%q')",
6882 : pszLayerName, pszLayerName, pszLayerName);
6883 4 : eErr = SQLCommand(hDB, pszSQL);
6884 4 : sqlite3_free(pszSQL);
6885 : }
6886 :
6887 4 : if (eErr == OGRERR_NONE && HasExtensionsTable())
6888 : {
6889 : // If there is no longer any mapping table, then completely
6890 : // remove any reference to the extension in gpkg_extensions
6891 : // as mandated per the related table specification.
6892 : OGRErr err;
6893 4 : if (SQLGetInteger(hDB,
6894 : "SELECT COUNT(*) FROM gpkg_extensions WHERE "
6895 : "extension_name IN ('related_tables', "
6896 : "'gpkg_related_tables') AND "
6897 : "lower(table_name) != 'gpkgext_relations'",
6898 4 : &err) == 0)
6899 : {
6900 2 : eErr = SQLCommand(hDB, "DELETE FROM gpkg_extensions WHERE "
6901 : "extension_name IN ('related_tables', "
6902 : "'gpkg_related_tables')");
6903 : }
6904 :
6905 4 : ClearCachedRelationships();
6906 : }
6907 : }
6908 :
6909 40 : if (eErr == OGRERR_NONE)
6910 : {
6911 40 : pszSQL = sqlite3_mprintf("DROP TABLE \"%w\"", pszLayerName);
6912 40 : eErr = SQLCommand(hDB, pszSQL);
6913 40 : sqlite3_free(pszSQL);
6914 : }
6915 :
6916 : // Check foreign key integrity
6917 40 : if (eErr == OGRERR_NONE)
6918 : {
6919 40 : eErr = PragmaCheck("foreign_key_check", "", 0);
6920 : }
6921 :
6922 80 : return eErr;
6923 : }
6924 :
6925 : /************************************************************************/
6926 : /* DeleteLayer() */
6927 : /************************************************************************/
6928 :
6929 37 : OGRErr GDALGeoPackageDataset::DeleteLayer(int iLayer)
6930 : {
6931 73 : if (!GetUpdate() || iLayer < 0 ||
6932 36 : iLayer >= static_cast<int>(m_apoLayers.size()))
6933 2 : return OGRERR_FAILURE;
6934 :
6935 35 : m_apoLayers[iLayer]->ResetReading();
6936 35 : m_apoLayers[iLayer]->SyncToDisk();
6937 :
6938 70 : CPLString osLayerName = m_apoLayers[iLayer]->GetName();
6939 :
6940 35 : CPLDebug("GPKG", "DeleteLayer(%s)", osLayerName.c_str());
6941 :
6942 : // Temporary remove foreign key checks
6943 : const GPKGTemporaryForeignKeyCheckDisabler
6944 35 : oGPKGTemporaryForeignKeyCheckDisabler(this);
6945 :
6946 35 : OGRErr eErr = SoftStartTransaction();
6947 :
6948 35 : if (eErr == OGRERR_NONE)
6949 : {
6950 35 : if (m_apoLayers[iLayer]->HasSpatialIndex())
6951 32 : m_apoLayers[iLayer]->DropSpatialIndex();
6952 :
6953 : char *pszSQL =
6954 35 : sqlite3_mprintf("DELETE FROM gpkg_geometry_columns WHERE "
6955 : "lower(table_name) = lower('%q')",
6956 : osLayerName.c_str());
6957 35 : eErr = SQLCommand(hDB, pszSQL);
6958 35 : sqlite3_free(pszSQL);
6959 : }
6960 :
6961 35 : if (eErr == OGRERR_NONE && HasDataColumnsTable())
6962 : {
6963 1 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_data_columns WHERE "
6964 : "lower(table_name) = lower('%q')",
6965 : osLayerName.c_str());
6966 1 : eErr = SQLCommand(hDB, pszSQL);
6967 1 : sqlite3_free(pszSQL);
6968 : }
6969 :
6970 : #ifdef ENABLE_GPKG_OGR_CONTENTS
6971 35 : if (eErr == OGRERR_NONE && m_bHasGPKGOGRContents)
6972 : {
6973 35 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_ogr_contents WHERE "
6974 : "lower(table_name) = lower('%q')",
6975 : osLayerName.c_str());
6976 35 : eErr = SQLCommand(hDB, pszSQL);
6977 35 : sqlite3_free(pszSQL);
6978 : }
6979 : #endif
6980 :
6981 35 : if (eErr == OGRERR_NONE)
6982 : {
6983 35 : eErr = DeleteLayerCommon(osLayerName.c_str());
6984 : }
6985 :
6986 35 : if (eErr == OGRERR_NONE)
6987 : {
6988 35 : eErr = SoftCommitTransaction();
6989 35 : if (eErr == OGRERR_NONE)
6990 : {
6991 : /* Delete the layer object */
6992 35 : m_apoLayers.erase(m_apoLayers.begin() + iLayer);
6993 : }
6994 : }
6995 : else
6996 : {
6997 0 : SoftRollbackTransaction();
6998 : }
6999 :
7000 35 : return eErr;
7001 : }
7002 :
7003 : /************************************************************************/
7004 : /* DeleteRasterLayer() */
7005 : /************************************************************************/
7006 :
7007 2 : OGRErr GDALGeoPackageDataset::DeleteRasterLayer(const char *pszLayerName)
7008 : {
7009 : // Temporary remove foreign key checks
7010 : const GPKGTemporaryForeignKeyCheckDisabler
7011 2 : oGPKGTemporaryForeignKeyCheckDisabler(this);
7012 :
7013 2 : OGRErr eErr = SoftStartTransaction();
7014 :
7015 2 : if (eErr == OGRERR_NONE)
7016 : {
7017 2 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix WHERE "
7018 : "lower(table_name) = lower('%q')",
7019 : pszLayerName);
7020 2 : eErr = SQLCommand(hDB, pszSQL);
7021 2 : sqlite3_free(pszSQL);
7022 : }
7023 :
7024 2 : if (eErr == OGRERR_NONE)
7025 : {
7026 2 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix_set WHERE "
7027 : "lower(table_name) = lower('%q')",
7028 : pszLayerName);
7029 2 : eErr = SQLCommand(hDB, pszSQL);
7030 2 : sqlite3_free(pszSQL);
7031 : }
7032 :
7033 2 : if (eErr == OGRERR_NONE && HasGriddedCoverageAncillaryTable())
7034 : {
7035 : char *pszSQL =
7036 1 : sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_coverage_ancillary "
7037 : "WHERE lower(tile_matrix_set_name) = lower('%q')",
7038 : pszLayerName);
7039 1 : eErr = SQLCommand(hDB, pszSQL);
7040 1 : sqlite3_free(pszSQL);
7041 :
7042 1 : if (eErr == OGRERR_NONE)
7043 : {
7044 : pszSQL =
7045 1 : sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_tile_ancillary "
7046 : "WHERE lower(tpudt_name) = lower('%q')",
7047 : pszLayerName);
7048 1 : eErr = SQLCommand(hDB, pszSQL);
7049 1 : sqlite3_free(pszSQL);
7050 : }
7051 : }
7052 :
7053 2 : if (eErr == OGRERR_NONE)
7054 : {
7055 2 : eErr = DeleteLayerCommon(pszLayerName);
7056 : }
7057 :
7058 2 : if (eErr == OGRERR_NONE)
7059 : {
7060 2 : eErr = SoftCommitTransaction();
7061 : }
7062 : else
7063 : {
7064 0 : SoftRollbackTransaction();
7065 : }
7066 :
7067 4 : return eErr;
7068 : }
7069 :
7070 : /************************************************************************/
7071 : /* DeleteVectorOrRasterLayer() */
7072 : /************************************************************************/
7073 :
7074 13 : bool GDALGeoPackageDataset::DeleteVectorOrRasterLayer(const char *pszLayerName)
7075 : {
7076 :
7077 13 : int idx = FindLayerIndex(pszLayerName);
7078 13 : if (idx >= 0)
7079 : {
7080 5 : DeleteLayer(idx);
7081 5 : return true;
7082 : }
7083 :
7084 : char *pszSQL =
7085 8 : sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
7086 : "lower(table_name) = lower('%q') "
7087 : "AND data_type IN ('tiles', '2d-gridded-coverage')",
7088 : pszLayerName);
7089 8 : bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
7090 8 : sqlite3_free(pszSQL);
7091 8 : if (bIsRasterTable)
7092 : {
7093 2 : DeleteRasterLayer(pszLayerName);
7094 2 : return true;
7095 : }
7096 6 : return false;
7097 : }
7098 :
7099 7 : bool GDALGeoPackageDataset::RenameVectorOrRasterLayer(
7100 : const char *pszLayerName, const char *pszNewLayerName)
7101 : {
7102 7 : int idx = FindLayerIndex(pszLayerName);
7103 7 : if (idx >= 0)
7104 : {
7105 4 : m_apoLayers[idx]->Rename(pszNewLayerName);
7106 4 : return true;
7107 : }
7108 :
7109 : char *pszSQL =
7110 3 : sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
7111 : "lower(table_name) = lower('%q') "
7112 : "AND data_type IN ('tiles', '2d-gridded-coverage')",
7113 : pszLayerName);
7114 3 : const bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
7115 3 : sqlite3_free(pszSQL);
7116 :
7117 3 : if (bIsRasterTable)
7118 : {
7119 2 : return RenameRasterLayer(pszLayerName, pszNewLayerName);
7120 : }
7121 :
7122 1 : return false;
7123 : }
7124 :
7125 2 : bool GDALGeoPackageDataset::RenameRasterLayer(const char *pszLayerName,
7126 : const char *pszNewLayerName)
7127 : {
7128 4 : std::string osSQL;
7129 :
7130 2 : char *pszSQL = sqlite3_mprintf(
7131 : "SELECT 1 FROM sqlite_master WHERE lower(name) = lower('%q') "
7132 : "AND type IN ('table', 'view')",
7133 : pszNewLayerName);
7134 2 : const bool bAlreadyExists = SQLGetInteger(GetDB(), pszSQL, nullptr) == 1;
7135 2 : sqlite3_free(pszSQL);
7136 2 : if (bAlreadyExists)
7137 : {
7138 0 : CPLError(CE_Failure, CPLE_AppDefined, "Table %s already exists",
7139 : pszNewLayerName);
7140 0 : return false;
7141 : }
7142 :
7143 : // Temporary remove foreign key checks
7144 : const GPKGTemporaryForeignKeyCheckDisabler
7145 4 : oGPKGTemporaryForeignKeyCheckDisabler(this);
7146 :
7147 2 : if (SoftStartTransaction() != OGRERR_NONE)
7148 : {
7149 0 : return false;
7150 : }
7151 :
7152 2 : pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET table_name = '%q' WHERE "
7153 : "lower(table_name) = lower('%q');",
7154 : pszNewLayerName, pszLayerName);
7155 2 : osSQL = pszSQL;
7156 2 : sqlite3_free(pszSQL);
7157 :
7158 2 : pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' WHERE "
7159 : "lower(identifier) = lower('%q');",
7160 : pszNewLayerName, pszLayerName);
7161 2 : osSQL += pszSQL;
7162 2 : sqlite3_free(pszSQL);
7163 :
7164 : pszSQL =
7165 2 : sqlite3_mprintf("UPDATE gpkg_tile_matrix SET table_name = '%q' WHERE "
7166 : "lower(table_name) = lower('%q');",
7167 : pszNewLayerName, pszLayerName);
7168 2 : osSQL += pszSQL;
7169 2 : sqlite3_free(pszSQL);
7170 :
7171 2 : pszSQL = sqlite3_mprintf(
7172 : "UPDATE gpkg_tile_matrix_set SET table_name = '%q' WHERE "
7173 : "lower(table_name) = lower('%q');",
7174 : pszNewLayerName, pszLayerName);
7175 2 : osSQL += pszSQL;
7176 2 : sqlite3_free(pszSQL);
7177 :
7178 2 : if (HasGriddedCoverageAncillaryTable())
7179 : {
7180 1 : pszSQL = sqlite3_mprintf("UPDATE gpkg_2d_gridded_coverage_ancillary "
7181 : "SET tile_matrix_set_name = '%q' WHERE "
7182 : "lower(tile_matrix_set_name) = lower('%q');",
7183 : pszNewLayerName, pszLayerName);
7184 1 : osSQL += pszSQL;
7185 1 : sqlite3_free(pszSQL);
7186 :
7187 1 : pszSQL = sqlite3_mprintf(
7188 : "UPDATE gpkg_2d_gridded_tile_ancillary SET tpudt_name = '%q' WHERE "
7189 : "lower(tpudt_name) = lower('%q');",
7190 : pszNewLayerName, pszLayerName);
7191 1 : osSQL += pszSQL;
7192 1 : sqlite3_free(pszSQL);
7193 : }
7194 :
7195 2 : if (HasExtensionsTable())
7196 : {
7197 2 : pszSQL = sqlite3_mprintf(
7198 : "UPDATE gpkg_extensions SET table_name = '%q' WHERE "
7199 : "lower(table_name) = lower('%q');",
7200 : pszNewLayerName, pszLayerName);
7201 2 : osSQL += pszSQL;
7202 2 : sqlite3_free(pszSQL);
7203 : }
7204 :
7205 2 : if (HasMetadataTables())
7206 : {
7207 1 : pszSQL = sqlite3_mprintf(
7208 : "UPDATE gpkg_metadata_reference SET table_name = '%q' WHERE "
7209 : "lower(table_name) = lower('%q');",
7210 : pszNewLayerName, pszLayerName);
7211 1 : osSQL += pszSQL;
7212 1 : sqlite3_free(pszSQL);
7213 : }
7214 :
7215 2 : if (HasDataColumnsTable())
7216 : {
7217 0 : pszSQL = sqlite3_mprintf(
7218 : "UPDATE gpkg_data_columns SET table_name = '%q' WHERE "
7219 : "lower(table_name) = lower('%q');",
7220 : pszNewLayerName, pszLayerName);
7221 0 : osSQL += pszSQL;
7222 0 : sqlite3_free(pszSQL);
7223 : }
7224 :
7225 2 : if (HasQGISLayerStyles())
7226 : {
7227 : // Update QGIS styles
7228 : pszSQL =
7229 0 : sqlite3_mprintf("UPDATE layer_styles SET f_table_name = '%q' WHERE "
7230 : "lower(f_table_name) = lower('%q');",
7231 : pszNewLayerName, pszLayerName);
7232 0 : osSQL += pszSQL;
7233 0 : sqlite3_free(pszSQL);
7234 : }
7235 :
7236 : #ifdef ENABLE_GPKG_OGR_CONTENTS
7237 2 : if (m_bHasGPKGOGRContents)
7238 : {
7239 2 : pszSQL = sqlite3_mprintf(
7240 : "UPDATE gpkg_ogr_contents SET table_name = '%q' WHERE "
7241 : "lower(table_name) = lower('%q');",
7242 : pszNewLayerName, pszLayerName);
7243 2 : osSQL += pszSQL;
7244 2 : sqlite3_free(pszSQL);
7245 : }
7246 : #endif
7247 :
7248 2 : if (HasGpkgextRelationsTable())
7249 : {
7250 0 : pszSQL = sqlite3_mprintf(
7251 : "UPDATE gpkgext_relations SET base_table_name = '%q' WHERE "
7252 : "lower(base_table_name) = lower('%q');",
7253 : pszNewLayerName, pszLayerName);
7254 0 : osSQL += pszSQL;
7255 0 : sqlite3_free(pszSQL);
7256 :
7257 0 : pszSQL = sqlite3_mprintf(
7258 : "UPDATE gpkgext_relations SET related_table_name = '%q' WHERE "
7259 : "lower(related_table_name) = lower('%q');",
7260 : pszNewLayerName, pszLayerName);
7261 0 : osSQL += pszSQL;
7262 0 : sqlite3_free(pszSQL);
7263 :
7264 0 : pszSQL = sqlite3_mprintf(
7265 : "UPDATE gpkgext_relations SET mapping_table_name = '%q' WHERE "
7266 : "lower(mapping_table_name) = lower('%q');",
7267 : pszNewLayerName, pszLayerName);
7268 0 : osSQL += pszSQL;
7269 0 : sqlite3_free(pszSQL);
7270 : }
7271 :
7272 : // Drop all triggers for the layer
7273 2 : pszSQL = sqlite3_mprintf("SELECT name FROM sqlite_master WHERE type = "
7274 : "'trigger' AND tbl_name = '%q'",
7275 : pszLayerName);
7276 2 : auto oTriggerResult = SQLQuery(GetDB(), pszSQL);
7277 2 : sqlite3_free(pszSQL);
7278 2 : if (oTriggerResult)
7279 : {
7280 14 : for (int i = 0; i < oTriggerResult->RowCount(); i++)
7281 : {
7282 12 : const char *pszTriggerName = oTriggerResult->GetValue(0, i);
7283 12 : pszSQL = sqlite3_mprintf("DROP TRIGGER IF EXISTS \"%w\";",
7284 : pszTriggerName);
7285 12 : osSQL += pszSQL;
7286 12 : sqlite3_free(pszSQL);
7287 : }
7288 : }
7289 :
7290 2 : pszSQL = sqlite3_mprintf("ALTER TABLE \"%w\" RENAME TO \"%w\";",
7291 : pszLayerName, pszNewLayerName);
7292 2 : osSQL += pszSQL;
7293 2 : sqlite3_free(pszSQL);
7294 :
7295 : // Recreate all zoom/tile triggers
7296 2 : if (oTriggerResult)
7297 : {
7298 2 : osSQL += CreateRasterTriggersSQL(pszNewLayerName);
7299 : }
7300 :
7301 2 : OGRErr eErr = SQLCommand(GetDB(), osSQL.c_str());
7302 :
7303 : // Check foreign key integrity
7304 2 : if (eErr == OGRERR_NONE)
7305 : {
7306 2 : eErr = PragmaCheck("foreign_key_check", "", 0);
7307 : }
7308 :
7309 2 : if (eErr == OGRERR_NONE)
7310 : {
7311 2 : eErr = SoftCommitTransaction();
7312 : }
7313 : else
7314 : {
7315 0 : SoftRollbackTransaction();
7316 : }
7317 :
7318 2 : return eErr == OGRERR_NONE;
7319 : }
7320 :
7321 : /************************************************************************/
7322 : /* TestCapability() */
7323 : /************************************************************************/
7324 :
7325 449 : int GDALGeoPackageDataset::TestCapability(const char *pszCap)
7326 : {
7327 449 : if (EQUAL(pszCap, ODsCCreateLayer) || EQUAL(pszCap, ODsCDeleteLayer) ||
7328 284 : EQUAL(pszCap, "RenameLayer"))
7329 : {
7330 165 : return GetUpdate();
7331 : }
7332 284 : else if (EQUAL(pszCap, ODsCCurveGeometries))
7333 12 : return TRUE;
7334 272 : else if (EQUAL(pszCap, ODsCMeasuredGeometries))
7335 8 : return TRUE;
7336 264 : else if (EQUAL(pszCap, ODsCZGeometries))
7337 8 : return TRUE;
7338 256 : else if (EQUAL(pszCap, ODsCRandomLayerWrite) ||
7339 256 : EQUAL(pszCap, GDsCAddRelationship) ||
7340 256 : EQUAL(pszCap, GDsCDeleteRelationship) ||
7341 256 : EQUAL(pszCap, GDsCUpdateRelationship) ||
7342 256 : EQUAL(pszCap, ODsCAddFieldDomain))
7343 1 : return GetUpdate();
7344 :
7345 255 : return OGRSQLiteBaseDataSource::TestCapability(pszCap);
7346 : }
7347 :
7348 : /************************************************************************/
7349 : /* ResetReadingAllLayers() */
7350 : /************************************************************************/
7351 :
7352 204 : void GDALGeoPackageDataset::ResetReadingAllLayers()
7353 : {
7354 413 : for (auto &poLayer : m_apoLayers)
7355 : {
7356 209 : poLayer->ResetReading();
7357 : }
7358 204 : }
7359 :
7360 : /************************************************************************/
7361 : /* ExecuteSQL() */
7362 : /************************************************************************/
7363 :
7364 : static const char *const apszFuncsWithSideEffects[] = {
7365 : "CreateSpatialIndex",
7366 : "DisableSpatialIndex",
7367 : "HasSpatialIndex",
7368 : "RegisterGeometryExtension",
7369 : };
7370 :
7371 5650 : OGRLayer *GDALGeoPackageDataset::ExecuteSQL(const char *pszSQLCommand,
7372 : OGRGeometry *poSpatialFilter,
7373 : const char *pszDialect)
7374 :
7375 : {
7376 5650 : m_bHasReadMetadataFromStorage = false;
7377 :
7378 5650 : FlushMetadata();
7379 :
7380 5668 : while (*pszSQLCommand != '\0' &&
7381 5668 : isspace(static_cast<unsigned char>(*pszSQLCommand)))
7382 18 : pszSQLCommand++;
7383 :
7384 11300 : CPLString osSQLCommand(pszSQLCommand);
7385 5650 : if (!osSQLCommand.empty() && osSQLCommand.back() == ';')
7386 48 : osSQLCommand.pop_back();
7387 :
7388 5650 : if (pszDialect == nullptr || !EQUAL(pszDialect, "DEBUG"))
7389 : {
7390 : // Some SQL commands will influence the feature count behind our
7391 : // back, so disable it in that case.
7392 : #ifdef ENABLE_GPKG_OGR_CONTENTS
7393 : const bool bInsertOrDelete =
7394 5581 : osSQLCommand.ifind("insert into ") != std::string::npos ||
7395 2460 : osSQLCommand.ifind("insert or replace into ") !=
7396 8041 : std::string::npos ||
7397 2423 : osSQLCommand.ifind("delete from ") != std::string::npos;
7398 : const bool bRollback =
7399 5581 : osSQLCommand.ifind("rollback ") != std::string::npos;
7400 : #endif
7401 :
7402 7410 : for (auto &poLayer : m_apoLayers)
7403 : {
7404 1829 : if (poLayer->SyncToDisk() != OGRERR_NONE)
7405 0 : return nullptr;
7406 : #ifdef ENABLE_GPKG_OGR_CONTENTS
7407 2034 : if (bRollback ||
7408 205 : (bInsertOrDelete &&
7409 205 : osSQLCommand.ifind(poLayer->GetName()) != std::string::npos))
7410 : {
7411 203 : poLayer->DisableFeatureCount();
7412 : }
7413 : #endif
7414 : }
7415 : }
7416 :
7417 5650 : if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 0") ||
7418 5649 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=0") ||
7419 5649 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =0") ||
7420 5649 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 0"))
7421 : {
7422 1 : OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, false);
7423 : }
7424 5649 : else if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 1") ||
7425 5648 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=1") ||
7426 5648 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =1") ||
7427 5648 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 1"))
7428 : {
7429 1 : OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, true);
7430 : }
7431 :
7432 : /* -------------------------------------------------------------------- */
7433 : /* DEBUG "SELECT nolock" command. */
7434 : /* -------------------------------------------------------------------- */
7435 5719 : if (pszDialect != nullptr && EQUAL(pszDialect, "DEBUG") &&
7436 69 : EQUAL(osSQLCommand, "SELECT nolock"))
7437 : {
7438 3 : return new OGRSQLiteSingleFeatureLayer(osSQLCommand, m_bNoLock ? 1 : 0);
7439 : }
7440 :
7441 : /* -------------------------------------------------------------------- */
7442 : /* Special case DELLAYER: command. */
7443 : /* -------------------------------------------------------------------- */
7444 5647 : if (STARTS_WITH_CI(osSQLCommand, "DELLAYER:"))
7445 : {
7446 4 : const char *pszLayerName = osSQLCommand.c_str() + strlen("DELLAYER:");
7447 :
7448 4 : while (*pszLayerName == ' ')
7449 0 : pszLayerName++;
7450 :
7451 4 : if (!DeleteVectorOrRasterLayer(pszLayerName))
7452 : {
7453 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
7454 : pszLayerName);
7455 : }
7456 4 : return nullptr;
7457 : }
7458 :
7459 : /* -------------------------------------------------------------------- */
7460 : /* Special case RECOMPUTE EXTENT ON command. */
7461 : /* -------------------------------------------------------------------- */
7462 5643 : if (STARTS_WITH_CI(osSQLCommand, "RECOMPUTE EXTENT ON "))
7463 : {
7464 : const char *pszLayerName =
7465 4 : osSQLCommand.c_str() + strlen("RECOMPUTE EXTENT ON ");
7466 :
7467 4 : while (*pszLayerName == ' ')
7468 0 : pszLayerName++;
7469 :
7470 4 : int idx = FindLayerIndex(pszLayerName);
7471 4 : if (idx >= 0)
7472 : {
7473 4 : m_apoLayers[idx]->RecomputeExtent();
7474 : }
7475 : else
7476 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
7477 : pszLayerName);
7478 4 : return nullptr;
7479 : }
7480 :
7481 : /* -------------------------------------------------------------------- */
7482 : /* Intercept DROP TABLE */
7483 : /* -------------------------------------------------------------------- */
7484 5639 : if (STARTS_WITH_CI(osSQLCommand, "DROP TABLE "))
7485 : {
7486 9 : const char *pszLayerName = osSQLCommand.c_str() + strlen("DROP TABLE ");
7487 :
7488 9 : while (*pszLayerName == ' ')
7489 0 : pszLayerName++;
7490 :
7491 9 : if (DeleteVectorOrRasterLayer(SQLUnescape(pszLayerName)))
7492 4 : return nullptr;
7493 : }
7494 :
7495 : /* -------------------------------------------------------------------- */
7496 : /* Intercept ALTER TABLE src_table RENAME TO dst_table */
7497 : /* and ALTER TABLE table RENAME COLUMN src_name TO dst_name */
7498 : /* and ALTER TABLE table DROP COLUMN col_name */
7499 : /* */
7500 : /* We do this because SQLite mechanisms can't deal with updating */
7501 : /* literal values in gpkg_ tables that refer to table and column */
7502 : /* names. */
7503 : /* -------------------------------------------------------------------- */
7504 5635 : if (STARTS_WITH_CI(osSQLCommand, "ALTER TABLE "))
7505 : {
7506 9 : char **papszTokens = SQLTokenize(osSQLCommand);
7507 : /* ALTER TABLE src_table RENAME TO dst_table */
7508 16 : if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "RENAME") &&
7509 7 : EQUAL(papszTokens[4], "TO"))
7510 : {
7511 7 : const char *pszSrcTableName = papszTokens[2];
7512 7 : const char *pszDstTableName = papszTokens[5];
7513 7 : if (RenameVectorOrRasterLayer(SQLUnescape(pszSrcTableName),
7514 14 : SQLUnescape(pszDstTableName)))
7515 : {
7516 6 : CSLDestroy(papszTokens);
7517 6 : return nullptr;
7518 : }
7519 : }
7520 : /* ALTER TABLE table RENAME COLUMN src_name TO dst_name */
7521 2 : else if (CSLCount(papszTokens) == 8 &&
7522 1 : EQUAL(papszTokens[3], "RENAME") &&
7523 3 : EQUAL(papszTokens[4], "COLUMN") && EQUAL(papszTokens[6], "TO"))
7524 : {
7525 1 : const char *pszTableName = papszTokens[2];
7526 1 : const char *pszSrcColumn = papszTokens[5];
7527 1 : const char *pszDstColumn = papszTokens[7];
7528 : OGRGeoPackageTableLayer *poLayer =
7529 0 : dynamic_cast<OGRGeoPackageTableLayer *>(
7530 1 : GetLayerByName(SQLUnescape(pszTableName)));
7531 1 : if (poLayer)
7532 : {
7533 2 : int nSrcFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
7534 2 : SQLUnescape(pszSrcColumn));
7535 1 : if (nSrcFieldIdx >= 0)
7536 : {
7537 : // OFTString or any type will do as we just alter the name
7538 : // so it will be ignored.
7539 1 : OGRFieldDefn oFieldDefn(SQLUnescape(pszDstColumn),
7540 1 : OFTString);
7541 1 : poLayer->AlterFieldDefn(nSrcFieldIdx, &oFieldDefn,
7542 : ALTER_NAME_FLAG);
7543 1 : CSLDestroy(papszTokens);
7544 1 : return nullptr;
7545 : }
7546 : }
7547 : }
7548 : /* ALTER TABLE table DROP COLUMN col_name */
7549 2 : else if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "DROP") &&
7550 1 : EQUAL(papszTokens[4], "COLUMN"))
7551 : {
7552 1 : const char *pszTableName = papszTokens[2];
7553 1 : const char *pszColumnName = papszTokens[5];
7554 : OGRGeoPackageTableLayer *poLayer =
7555 0 : dynamic_cast<OGRGeoPackageTableLayer *>(
7556 1 : GetLayerByName(SQLUnescape(pszTableName)));
7557 1 : if (poLayer)
7558 : {
7559 2 : int nFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
7560 2 : SQLUnescape(pszColumnName));
7561 1 : if (nFieldIdx >= 0)
7562 : {
7563 1 : poLayer->DeleteField(nFieldIdx);
7564 1 : CSLDestroy(papszTokens);
7565 1 : return nullptr;
7566 : }
7567 : }
7568 : }
7569 1 : CSLDestroy(papszTokens);
7570 : }
7571 :
7572 5627 : if (ProcessTransactionSQL(osSQLCommand))
7573 : {
7574 253 : return nullptr;
7575 : }
7576 :
7577 5374 : if (EQUAL(osSQLCommand, "VACUUM"))
7578 : {
7579 13 : ResetReadingAllLayers();
7580 : }
7581 5361 : else if (STARTS_WITH_CI(osSQLCommand, "DELETE FROM "))
7582 : {
7583 : // Optimize truncation of a table, especially if it has a spatial
7584 : // index.
7585 24 : const CPLStringList aosTokens(SQLTokenize(osSQLCommand));
7586 24 : if (aosTokens.size() == 3)
7587 : {
7588 16 : const char *pszTableName = aosTokens[2];
7589 : OGRGeoPackageTableLayer *poLayer =
7590 8 : dynamic_cast<OGRGeoPackageTableLayer *>(
7591 24 : GetLayerByName(SQLUnescape(pszTableName)));
7592 16 : if (poLayer)
7593 : {
7594 8 : poLayer->Truncate();
7595 8 : return nullptr;
7596 : }
7597 : }
7598 : }
7599 5337 : else if (pszDialect != nullptr && EQUAL(pszDialect, "INDIRECT_SQLITE"))
7600 1 : return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter, "SQLITE");
7601 5336 : else if (pszDialect != nullptr && !EQUAL(pszDialect, "") &&
7602 67 : !EQUAL(pszDialect, "NATIVE") && !EQUAL(pszDialect, "SQLITE") &&
7603 67 : !EQUAL(pszDialect, "DEBUG"))
7604 1 : return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter,
7605 1 : pszDialect);
7606 :
7607 : /* -------------------------------------------------------------------- */
7608 : /* Prepare statement. */
7609 : /* -------------------------------------------------------------------- */
7610 5364 : sqlite3_stmt *hSQLStmt = nullptr;
7611 :
7612 : /* This will speed-up layer creation */
7613 : /* ORDER BY are costly to evaluate and are not necessary to establish */
7614 : /* the layer definition. */
7615 5364 : bool bUseStatementForGetNextFeature = true;
7616 5364 : bool bEmptyLayer = false;
7617 10728 : CPLString osSQLCommandTruncated(osSQLCommand);
7618 :
7619 17698 : if (osSQLCommand.ifind("SELECT ") == 0 &&
7620 6167 : CPLString(osSQLCommand.substr(1)).ifind("SELECT ") ==
7621 769 : std::string::npos &&
7622 769 : osSQLCommand.ifind(" UNION ") == std::string::npos &&
7623 6936 : osSQLCommand.ifind(" INTERSECT ") == std::string::npos &&
7624 769 : osSQLCommand.ifind(" EXCEPT ") == std::string::npos)
7625 : {
7626 769 : size_t nOrderByPos = osSQLCommand.ifind(" ORDER BY ");
7627 769 : if (nOrderByPos != std::string::npos)
7628 : {
7629 9 : osSQLCommandTruncated.resize(nOrderByPos);
7630 9 : bUseStatementForGetNextFeature = false;
7631 : }
7632 : }
7633 :
7634 5364 : int rc = prepareSql(hDB, osSQLCommandTruncated.c_str(),
7635 5364 : static_cast<int>(osSQLCommandTruncated.size()),
7636 : &hSQLStmt, nullptr);
7637 :
7638 5364 : if (rc != SQLITE_OK)
7639 : {
7640 9 : CPLError(CE_Failure, CPLE_AppDefined,
7641 : "In ExecuteSQL(): sqlite3_prepare_v2(%s): %s",
7642 : osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
7643 :
7644 9 : if (hSQLStmt != nullptr)
7645 : {
7646 0 : sqlite3_finalize(hSQLStmt);
7647 : }
7648 :
7649 9 : return nullptr;
7650 : }
7651 :
7652 : /* -------------------------------------------------------------------- */
7653 : /* Do we get a resultset? */
7654 : /* -------------------------------------------------------------------- */
7655 5355 : rc = sqlite3_step(hSQLStmt);
7656 :
7657 6949 : for (auto &poLayer : m_apoLayers)
7658 : {
7659 1594 : poLayer->RunDeferredDropRTreeTableIfNecessary();
7660 : }
7661 :
7662 5355 : if (rc != SQLITE_ROW)
7663 : {
7664 4633 : if (rc != SQLITE_DONE)
7665 : {
7666 7 : CPLError(CE_Failure, CPLE_AppDefined,
7667 : "In ExecuteSQL(): sqlite3_step(%s):\n %s",
7668 : osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
7669 :
7670 7 : sqlite3_finalize(hSQLStmt);
7671 7 : return nullptr;
7672 : }
7673 :
7674 4626 : if (EQUAL(osSQLCommand, "VACUUM"))
7675 : {
7676 13 : sqlite3_finalize(hSQLStmt);
7677 : /* VACUUM rewrites the DB, so we need to reset the application id */
7678 13 : SetApplicationAndUserVersionId();
7679 13 : return nullptr;
7680 : }
7681 :
7682 4613 : if (!STARTS_WITH_CI(osSQLCommand, "SELECT "))
7683 : {
7684 4488 : sqlite3_finalize(hSQLStmt);
7685 4488 : return nullptr;
7686 : }
7687 :
7688 125 : bUseStatementForGetNextFeature = false;
7689 125 : bEmptyLayer = true;
7690 : }
7691 :
7692 : /* -------------------------------------------------------------------- */
7693 : /* Special case for some functions which must be run */
7694 : /* only once */
7695 : /* -------------------------------------------------------------------- */
7696 847 : if (STARTS_WITH_CI(osSQLCommand, "SELECT "))
7697 : {
7698 3859 : for (unsigned int i = 0; i < sizeof(apszFuncsWithSideEffects) /
7699 : sizeof(apszFuncsWithSideEffects[0]);
7700 : i++)
7701 : {
7702 3113 : if (EQUALN(apszFuncsWithSideEffects[i], osSQLCommand.c_str() + 7,
7703 : strlen(apszFuncsWithSideEffects[i])))
7704 : {
7705 112 : if (sqlite3_column_count(hSQLStmt) == 1 &&
7706 56 : sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
7707 : {
7708 56 : int ret = sqlite3_column_int(hSQLStmt, 0);
7709 :
7710 56 : sqlite3_finalize(hSQLStmt);
7711 :
7712 : return new OGRSQLiteSingleFeatureLayer(
7713 56 : apszFuncsWithSideEffects[i], ret);
7714 : }
7715 : }
7716 : }
7717 : }
7718 45 : else if (STARTS_WITH_CI(osSQLCommand, "PRAGMA "))
7719 : {
7720 63 : if (sqlite3_column_count(hSQLStmt) == 1 &&
7721 18 : sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
7722 : {
7723 15 : int ret = sqlite3_column_int(hSQLStmt, 0);
7724 :
7725 15 : sqlite3_finalize(hSQLStmt);
7726 :
7727 15 : return new OGRSQLiteSingleFeatureLayer(osSQLCommand.c_str() + 7,
7728 15 : ret);
7729 : }
7730 33 : else if (sqlite3_column_count(hSQLStmt) == 1 &&
7731 3 : sqlite3_column_type(hSQLStmt, 0) == SQLITE_TEXT)
7732 : {
7733 : const char *pszRet = reinterpret_cast<const char *>(
7734 3 : sqlite3_column_text(hSQLStmt, 0));
7735 :
7736 : OGRLayer *poRet = new OGRSQLiteSingleFeatureLayer(
7737 3 : osSQLCommand.c_str() + 7, pszRet);
7738 :
7739 3 : sqlite3_finalize(hSQLStmt);
7740 :
7741 3 : return poRet;
7742 : }
7743 : }
7744 :
7745 : /* -------------------------------------------------------------------- */
7746 : /* Create layer. */
7747 : /* -------------------------------------------------------------------- */
7748 :
7749 : auto poLayer = std::make_unique<OGRGeoPackageSelectLayer>(
7750 : this, osSQLCommand, hSQLStmt, bUseStatementForGetNextFeature,
7751 1546 : bEmptyLayer);
7752 :
7753 776 : if (poSpatialFilter != nullptr &&
7754 3 : poLayer->GetLayerDefn()->GetGeomFieldCount() > 0)
7755 3 : poLayer->SetSpatialFilter(0, poSpatialFilter);
7756 :
7757 773 : return poLayer.release();
7758 : }
7759 :
7760 : /************************************************************************/
7761 : /* ReleaseResultSet() */
7762 : /************************************************************************/
7763 :
7764 806 : void GDALGeoPackageDataset::ReleaseResultSet(OGRLayer *poLayer)
7765 :
7766 : {
7767 806 : delete poLayer;
7768 806 : }
7769 :
7770 : /************************************************************************/
7771 : /* HasExtensionsTable() */
7772 : /************************************************************************/
7773 :
7774 6511 : bool GDALGeoPackageDataset::HasExtensionsTable()
7775 : {
7776 6511 : return SQLGetInteger(
7777 : hDB,
7778 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_extensions' "
7779 : "AND type IN ('table', 'view')",
7780 6511 : nullptr) == 1;
7781 : }
7782 :
7783 : /************************************************************************/
7784 : /* CheckUnknownExtensions() */
7785 : /************************************************************************/
7786 :
7787 1477 : void GDALGeoPackageDataset::CheckUnknownExtensions(bool bCheckRasterTable)
7788 : {
7789 1477 : if (!HasExtensionsTable())
7790 197 : return;
7791 :
7792 1280 : char *pszSQL = nullptr;
7793 1280 : if (!bCheckRasterTable)
7794 1071 : pszSQL = sqlite3_mprintf(
7795 : "SELECT extension_name, definition, scope FROM gpkg_extensions "
7796 : "WHERE (table_name IS NULL "
7797 : "AND extension_name IS NOT NULL "
7798 : "AND definition IS NOT NULL "
7799 : "AND scope IS NOT NULL "
7800 : "AND extension_name NOT IN ("
7801 : "'gdal_aspatial', "
7802 : "'gpkg_elevation_tiles', " // Old name before GPKG 1.2 approval
7803 : "'2d_gridded_coverage', " // Old name after GPKG 1.2 and before OGC
7804 : // 17-066r1 finalization
7805 : "'gpkg_2d_gridded_coverage', " // Name in OGC 17-066r1 final
7806 : "'gpkg_metadata', "
7807 : "'gpkg_schema', "
7808 : "'gpkg_crs_wkt', "
7809 : "'gpkg_crs_wkt_1_1', "
7810 : "'related_tables', 'gpkg_related_tables')) "
7811 : #ifdef WORKAROUND_SQLITE3_BUGS
7812 : "OR 0 "
7813 : #endif
7814 : "LIMIT 1000");
7815 : else
7816 209 : pszSQL = sqlite3_mprintf(
7817 : "SELECT extension_name, definition, scope FROM gpkg_extensions "
7818 : "WHERE (lower(table_name) = lower('%q') "
7819 : "AND extension_name IS NOT NULL "
7820 : "AND definition IS NOT NULL "
7821 : "AND scope IS NOT NULL "
7822 : "AND extension_name NOT IN ("
7823 : "'gpkg_elevation_tiles', " // Old name before GPKG 1.2 approval
7824 : "'2d_gridded_coverage', " // Old name after GPKG 1.2 and before OGC
7825 : // 17-066r1 finalization
7826 : "'gpkg_2d_gridded_coverage', " // Name in OGC 17-066r1 final
7827 : "'gpkg_metadata', "
7828 : "'gpkg_schema', "
7829 : "'gpkg_crs_wkt', "
7830 : "'gpkg_crs_wkt_1_1', "
7831 : "'related_tables', 'gpkg_related_tables')) "
7832 : #ifdef WORKAROUND_SQLITE3_BUGS
7833 : "OR 0 "
7834 : #endif
7835 : "LIMIT 1000",
7836 : m_osRasterTable.c_str());
7837 :
7838 2560 : auto oResultTable = SQLQuery(GetDB(), pszSQL);
7839 1280 : sqlite3_free(pszSQL);
7840 1280 : if (oResultTable && oResultTable->RowCount() > 0)
7841 : {
7842 44 : for (int i = 0; i < oResultTable->RowCount(); i++)
7843 : {
7844 22 : const char *pszExtName = oResultTable->GetValue(0, i);
7845 22 : const char *pszDefinition = oResultTable->GetValue(1, i);
7846 22 : const char *pszScope = oResultTable->GetValue(2, i);
7847 22 : if (pszExtName == nullptr || pszDefinition == nullptr ||
7848 : pszScope == nullptr)
7849 : {
7850 0 : continue;
7851 : }
7852 :
7853 22 : if (EQUAL(pszExtName, "gpkg_webp"))
7854 : {
7855 16 : if (GDALGetDriverByName("WEBP") == nullptr)
7856 : {
7857 1 : CPLError(
7858 : CE_Warning, CPLE_AppDefined,
7859 : "Table %s contains WEBP tiles, but GDAL configured "
7860 : "without WEBP support. Data will be missing",
7861 : m_osRasterTable.c_str());
7862 : }
7863 16 : m_eTF = GPKG_TF_WEBP;
7864 16 : continue;
7865 : }
7866 6 : if (EQUAL(pszExtName, "gpkg_zoom_other"))
7867 : {
7868 2 : m_bZoomOther = true;
7869 2 : continue;
7870 : }
7871 :
7872 4 : if (GetUpdate() && EQUAL(pszScope, "write-only"))
7873 : {
7874 1 : CPLError(
7875 : CE_Warning, CPLE_AppDefined,
7876 : "Database relies on the '%s' (%s) extension that should "
7877 : "be implemented for safe write-support, but is not "
7878 : "currently. "
7879 : "Update of that database are strongly discouraged to avoid "
7880 : "corruption.",
7881 : pszExtName, pszDefinition);
7882 : }
7883 3 : else if (GetUpdate() && EQUAL(pszScope, "read-write"))
7884 : {
7885 1 : CPLError(
7886 : CE_Warning, CPLE_AppDefined,
7887 : "Database relies on the '%s' (%s) extension that should "
7888 : "be implemented in order to read/write it safely, but is "
7889 : "not currently. "
7890 : "Some data may be missing while reading that database, and "
7891 : "updates are strongly discouraged.",
7892 : pszExtName, pszDefinition);
7893 : }
7894 2 : else if (EQUAL(pszScope, "read-write") &&
7895 : // None of the NGA extensions at
7896 : // http://ngageoint.github.io/GeoPackage/docs/extensions/
7897 : // affect read-only scenarios
7898 1 : !STARTS_WITH(pszExtName, "nga_"))
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 it safely, but is not "
7904 : "currently. "
7905 : "Some data may be missing while reading that database.",
7906 : pszExtName, pszDefinition);
7907 : }
7908 : }
7909 : }
7910 : }
7911 :
7912 : /************************************************************************/
7913 : /* HasGDALAspatialExtension() */
7914 : /************************************************************************/
7915 :
7916 1020 : bool GDALGeoPackageDataset::HasGDALAspatialExtension()
7917 : {
7918 1020 : if (!HasExtensionsTable())
7919 90 : return false;
7920 :
7921 : auto oResultTable = SQLQuery(hDB, "SELECT * FROM gpkg_extensions "
7922 : "WHERE (extension_name = 'gdal_aspatial' "
7923 : "AND table_name IS NULL "
7924 : "AND column_name IS NULL)"
7925 : #ifdef WORKAROUND_SQLITE3_BUGS
7926 : " OR 0"
7927 : #endif
7928 930 : );
7929 930 : bool bHasExtension = (oResultTable && oResultTable->RowCount() == 1);
7930 930 : return bHasExtension;
7931 : }
7932 :
7933 : std::string
7934 190 : GDALGeoPackageDataset::CreateRasterTriggersSQL(const std::string &osTableName)
7935 : {
7936 : char *pszSQL;
7937 190 : std::string osSQL;
7938 : /* From D.5. sample_tile_pyramid Table 43. tiles table Trigger
7939 : * Definition SQL */
7940 190 : pszSQL = sqlite3_mprintf(
7941 : "CREATE TRIGGER \"%w_zoom_insert\" "
7942 : "BEFORE INSERT ON \"%w\" "
7943 : "FOR EACH ROW BEGIN "
7944 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
7945 : "constraint: zoom_level not specified for table in "
7946 : "gpkg_tile_matrix') "
7947 : "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
7948 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
7949 : "END; "
7950 : "CREATE TRIGGER \"%w_zoom_update\" "
7951 : "BEFORE UPDATE OF zoom_level ON \"%w\" "
7952 : "FOR EACH ROW BEGIN "
7953 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
7954 : "constraint: zoom_level not specified for table in "
7955 : "gpkg_tile_matrix') "
7956 : "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
7957 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
7958 : "END; "
7959 : "CREATE TRIGGER \"%w_tile_column_insert\" "
7960 : "BEFORE INSERT ON \"%w\" "
7961 : "FOR EACH ROW BEGIN "
7962 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
7963 : "constraint: tile_column cannot be < 0') "
7964 : "WHERE (NEW.tile_column < 0) ; "
7965 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
7966 : "constraint: tile_column must by < matrix_width specified for "
7967 : "table and zoom level in gpkg_tile_matrix') "
7968 : "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
7969 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
7970 : "zoom_level = NEW.zoom_level)); "
7971 : "END; "
7972 : "CREATE TRIGGER \"%w_tile_column_update\" "
7973 : "BEFORE UPDATE OF tile_column ON \"%w\" "
7974 : "FOR EACH ROW BEGIN "
7975 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
7976 : "constraint: tile_column cannot be < 0') "
7977 : "WHERE (NEW.tile_column < 0) ; "
7978 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
7979 : "constraint: tile_column must by < matrix_width specified for "
7980 : "table and zoom level in gpkg_tile_matrix') "
7981 : "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
7982 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
7983 : "zoom_level = NEW.zoom_level)); "
7984 : "END; "
7985 : "CREATE TRIGGER \"%w_tile_row_insert\" "
7986 : "BEFORE INSERT ON \"%w\" "
7987 : "FOR EACH ROW BEGIN "
7988 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
7989 : "constraint: tile_row cannot be < 0') "
7990 : "WHERE (NEW.tile_row < 0) ; "
7991 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
7992 : "constraint: tile_row must by < matrix_height specified for "
7993 : "table and zoom level in gpkg_tile_matrix') "
7994 : "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
7995 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
7996 : "zoom_level = NEW.zoom_level)); "
7997 : "END; "
7998 : "CREATE TRIGGER \"%w_tile_row_update\" "
7999 : "BEFORE UPDATE OF tile_row ON \"%w\" "
8000 : "FOR EACH ROW BEGIN "
8001 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8002 : "constraint: tile_row cannot be < 0') "
8003 : "WHERE (NEW.tile_row < 0) ; "
8004 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8005 : "constraint: tile_row must by < matrix_height specified for "
8006 : "table and zoom level in gpkg_tile_matrix') "
8007 : "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
8008 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8009 : "zoom_level = NEW.zoom_level)); "
8010 : "END; ",
8011 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8012 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8013 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8014 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8015 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8016 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8017 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8018 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8019 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8020 : osTableName.c_str());
8021 190 : osSQL = pszSQL;
8022 190 : sqlite3_free(pszSQL);
8023 190 : return osSQL;
8024 : }
8025 :
8026 : /************************************************************************/
8027 : /* CreateExtensionsTableIfNecessary() */
8028 : /************************************************************************/
8029 :
8030 1159 : OGRErr GDALGeoPackageDataset::CreateExtensionsTableIfNecessary()
8031 : {
8032 : /* Check if the table gpkg_extensions exists */
8033 1159 : if (HasExtensionsTable())
8034 410 : return OGRERR_NONE;
8035 :
8036 : /* Requirement 79 : Every extension of a GeoPackage SHALL be registered */
8037 : /* in a corresponding row in the gpkg_extensions table. The absence of a */
8038 : /* gpkg_extensions table or the absence of rows in gpkg_extensions table */
8039 : /* SHALL both indicate the absence of extensions to a GeoPackage. */
8040 749 : const char *pszCreateGpkgExtensions =
8041 : "CREATE TABLE gpkg_extensions ("
8042 : "table_name TEXT,"
8043 : "column_name TEXT,"
8044 : "extension_name TEXT NOT NULL,"
8045 : "definition TEXT NOT NULL,"
8046 : "scope TEXT NOT NULL,"
8047 : "CONSTRAINT ge_tce UNIQUE (table_name, column_name, extension_name)"
8048 : ")";
8049 :
8050 749 : return SQLCommand(hDB, pszCreateGpkgExtensions);
8051 : }
8052 :
8053 : /************************************************************************/
8054 : /* OGR_GPKG_Intersects_Spatial_Filter() */
8055 : /************************************************************************/
8056 :
8057 23135 : void OGR_GPKG_Intersects_Spatial_Filter(sqlite3_context *pContext, int argc,
8058 : sqlite3_value **argv)
8059 : {
8060 23135 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8061 : {
8062 0 : sqlite3_result_int(pContext, 0);
8063 23125 : return;
8064 : }
8065 :
8066 : auto poLayer =
8067 23135 : static_cast<OGRGeoPackageTableLayer *>(sqlite3_user_data(pContext));
8068 :
8069 23135 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8070 : const GByte *pabyBLOB =
8071 23135 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8072 :
8073 : GPkgHeader sHeader;
8074 46270 : if (poLayer->m_bFilterIsEnvelope &&
8075 23135 : OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false, 0))
8076 : {
8077 23135 : if (sHeader.bExtentHasXY)
8078 : {
8079 95 : OGREnvelope sEnvelope;
8080 95 : sEnvelope.MinX = sHeader.MinX;
8081 95 : sEnvelope.MinY = sHeader.MinY;
8082 95 : sEnvelope.MaxX = sHeader.MaxX;
8083 95 : sEnvelope.MaxY = sHeader.MaxY;
8084 95 : if (poLayer->m_sFilterEnvelope.Contains(sEnvelope))
8085 : {
8086 31 : sqlite3_result_int(pContext, 1);
8087 31 : return;
8088 : }
8089 : }
8090 :
8091 : // Check if at least one point falls into the layer filter envelope
8092 : // nHeaderLen is > 0 for GeoPackage geometries
8093 46208 : if (sHeader.nHeaderLen > 0 &&
8094 23104 : OGRWKBIntersectsPessimistic(pabyBLOB + sHeader.nHeaderLen,
8095 23104 : nBLOBLen - sHeader.nHeaderLen,
8096 23104 : poLayer->m_sFilterEnvelope))
8097 : {
8098 23094 : sqlite3_result_int(pContext, 1);
8099 23094 : return;
8100 : }
8101 : }
8102 :
8103 : auto poGeom = std::unique_ptr<OGRGeometry>(
8104 10 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8105 10 : if (poGeom == nullptr)
8106 : {
8107 : // Try also spatialite geometry blobs
8108 0 : OGRGeometry *poGeomSpatialite = nullptr;
8109 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8110 0 : &poGeomSpatialite) != OGRERR_NONE)
8111 : {
8112 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8113 0 : sqlite3_result_int(pContext, 0);
8114 0 : return;
8115 : }
8116 0 : poGeom.reset(poGeomSpatialite);
8117 : }
8118 :
8119 10 : sqlite3_result_int(pContext, poLayer->FilterGeometry(poGeom.get()));
8120 : }
8121 :
8122 : /************************************************************************/
8123 : /* OGRGeoPackageSTMinX() */
8124 : /************************************************************************/
8125 :
8126 243781 : static void OGRGeoPackageSTMinX(sqlite3_context *pContext, int argc,
8127 : sqlite3_value **argv)
8128 : {
8129 : GPkgHeader sHeader;
8130 243781 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8131 : {
8132 3 : sqlite3_result_null(pContext);
8133 3 : return;
8134 : }
8135 243778 : sqlite3_result_double(pContext, sHeader.MinX);
8136 : }
8137 :
8138 : /************************************************************************/
8139 : /* OGRGeoPackageSTMinY() */
8140 : /************************************************************************/
8141 :
8142 243779 : static void OGRGeoPackageSTMinY(sqlite3_context *pContext, int argc,
8143 : sqlite3_value **argv)
8144 : {
8145 : GPkgHeader sHeader;
8146 243779 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8147 : {
8148 1 : sqlite3_result_null(pContext);
8149 1 : return;
8150 : }
8151 243778 : sqlite3_result_double(pContext, sHeader.MinY);
8152 : }
8153 :
8154 : /************************************************************************/
8155 : /* OGRGeoPackageSTMaxX() */
8156 : /************************************************************************/
8157 :
8158 243779 : static void OGRGeoPackageSTMaxX(sqlite3_context *pContext, int argc,
8159 : sqlite3_value **argv)
8160 : {
8161 : GPkgHeader sHeader;
8162 243779 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8163 : {
8164 1 : sqlite3_result_null(pContext);
8165 1 : return;
8166 : }
8167 243778 : sqlite3_result_double(pContext, sHeader.MaxX);
8168 : }
8169 :
8170 : /************************************************************************/
8171 : /* OGRGeoPackageSTMaxY() */
8172 : /************************************************************************/
8173 :
8174 243779 : static void OGRGeoPackageSTMaxY(sqlite3_context *pContext, int argc,
8175 : sqlite3_value **argv)
8176 : {
8177 : GPkgHeader sHeader;
8178 243779 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8179 : {
8180 1 : sqlite3_result_null(pContext);
8181 1 : return;
8182 : }
8183 243778 : sqlite3_result_double(pContext, sHeader.MaxY);
8184 : }
8185 :
8186 : /************************************************************************/
8187 : /* OGRGeoPackageSTIsEmpty() */
8188 : /************************************************************************/
8189 :
8190 245184 : static void OGRGeoPackageSTIsEmpty(sqlite3_context *pContext, int argc,
8191 : sqlite3_value **argv)
8192 : {
8193 : GPkgHeader sHeader;
8194 245184 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8195 : {
8196 2 : sqlite3_result_null(pContext);
8197 2 : return;
8198 : }
8199 245182 : sqlite3_result_int(pContext, sHeader.bEmpty);
8200 : }
8201 :
8202 : /************************************************************************/
8203 : /* OGRGeoPackageSTGeometryType() */
8204 : /************************************************************************/
8205 :
8206 7 : static void OGRGeoPackageSTGeometryType(sqlite3_context *pContext, int /*argc*/,
8207 : sqlite3_value **argv)
8208 : {
8209 : GPkgHeader sHeader;
8210 :
8211 7 : int nBLOBLen = sqlite3_value_bytes(argv[0]);
8212 : const GByte *pabyBLOB =
8213 7 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8214 : OGRwkbGeometryType eGeometryType;
8215 :
8216 13 : if (nBLOBLen < 8 ||
8217 6 : GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
8218 : {
8219 2 : if (OGRSQLiteGetSpatialiteGeometryHeader(
8220 : pabyBLOB, nBLOBLen, nullptr, &eGeometryType, nullptr, nullptr,
8221 2 : nullptr, nullptr, nullptr) == OGRERR_NONE)
8222 : {
8223 1 : sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
8224 : SQLITE_TRANSIENT);
8225 4 : return;
8226 : }
8227 : else
8228 : {
8229 1 : sqlite3_result_null(pContext);
8230 1 : return;
8231 : }
8232 : }
8233 :
8234 5 : if (static_cast<size_t>(nBLOBLen) < sHeader.nHeaderLen + 5)
8235 : {
8236 2 : sqlite3_result_null(pContext);
8237 2 : return;
8238 : }
8239 :
8240 3 : OGRErr err = OGRReadWKBGeometryType(pabyBLOB + sHeader.nHeaderLen,
8241 : wkbVariantIso, &eGeometryType);
8242 3 : if (err != OGRERR_NONE)
8243 1 : sqlite3_result_null(pContext);
8244 : else
8245 2 : sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
8246 : SQLITE_TRANSIENT);
8247 : }
8248 :
8249 : /************************************************************************/
8250 : /* OGRGeoPackageSTEnvelopesIntersects() */
8251 : /************************************************************************/
8252 :
8253 118 : static void OGRGeoPackageSTEnvelopesIntersects(sqlite3_context *pContext,
8254 : int argc, sqlite3_value **argv)
8255 : {
8256 : GPkgHeader sHeader;
8257 118 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8258 : {
8259 2 : sqlite3_result_int(pContext, FALSE);
8260 107 : return;
8261 : }
8262 116 : const double dfMinX = sqlite3_value_double(argv[1]);
8263 116 : if (sHeader.MaxX < dfMinX)
8264 : {
8265 93 : sqlite3_result_int(pContext, FALSE);
8266 93 : return;
8267 : }
8268 23 : const double dfMinY = sqlite3_value_double(argv[2]);
8269 23 : if (sHeader.MaxY < dfMinY)
8270 : {
8271 11 : sqlite3_result_int(pContext, FALSE);
8272 11 : return;
8273 : }
8274 12 : const double dfMaxX = sqlite3_value_double(argv[3]);
8275 12 : if (sHeader.MinX > dfMaxX)
8276 : {
8277 1 : sqlite3_result_int(pContext, FALSE);
8278 1 : return;
8279 : }
8280 11 : const double dfMaxY = sqlite3_value_double(argv[4]);
8281 11 : sqlite3_result_int(pContext, sHeader.MinY <= dfMaxY);
8282 : }
8283 :
8284 : /************************************************************************/
8285 : /* OGRGeoPackageSTEnvelopesIntersectsTwoParams() */
8286 : /************************************************************************/
8287 :
8288 : static void
8289 3 : OGRGeoPackageSTEnvelopesIntersectsTwoParams(sqlite3_context *pContext, int argc,
8290 : sqlite3_value **argv)
8291 : {
8292 : GPkgHeader sHeader;
8293 3 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false, 0))
8294 : {
8295 0 : sqlite3_result_int(pContext, FALSE);
8296 2 : return;
8297 : }
8298 : GPkgHeader sHeader2;
8299 3 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader2, true, false,
8300 : 1))
8301 : {
8302 0 : sqlite3_result_int(pContext, FALSE);
8303 0 : return;
8304 : }
8305 3 : if (sHeader.MaxX < sHeader2.MinX)
8306 : {
8307 1 : sqlite3_result_int(pContext, FALSE);
8308 1 : return;
8309 : }
8310 2 : if (sHeader.MaxY < sHeader2.MinY)
8311 : {
8312 0 : sqlite3_result_int(pContext, FALSE);
8313 0 : return;
8314 : }
8315 2 : if (sHeader.MinX > sHeader2.MaxX)
8316 : {
8317 1 : sqlite3_result_int(pContext, FALSE);
8318 1 : return;
8319 : }
8320 1 : sqlite3_result_int(pContext, sHeader.MinY <= sHeader2.MaxY);
8321 : }
8322 :
8323 : /************************************************************************/
8324 : /* OGRGeoPackageGPKGIsAssignable() */
8325 : /************************************************************************/
8326 :
8327 8 : static void OGRGeoPackageGPKGIsAssignable(sqlite3_context *pContext,
8328 : int /*argc*/, sqlite3_value **argv)
8329 : {
8330 15 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8331 7 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
8332 : {
8333 2 : sqlite3_result_int(pContext, 0);
8334 2 : return;
8335 : }
8336 :
8337 : const char *pszExpected =
8338 6 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8339 : const char *pszActual =
8340 6 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8341 6 : int bIsAssignable = OGR_GT_IsSubClassOf(OGRFromOGCGeomType(pszActual),
8342 : OGRFromOGCGeomType(pszExpected));
8343 6 : sqlite3_result_int(pContext, bIsAssignable);
8344 : }
8345 :
8346 : /************************************************************************/
8347 : /* OGRGeoPackageSTSRID() */
8348 : /************************************************************************/
8349 :
8350 12 : static void OGRGeoPackageSTSRID(sqlite3_context *pContext, int argc,
8351 : sqlite3_value **argv)
8352 : {
8353 : GPkgHeader sHeader;
8354 12 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8355 : {
8356 2 : sqlite3_result_null(pContext);
8357 2 : return;
8358 : }
8359 10 : sqlite3_result_int(pContext, sHeader.iSrsId);
8360 : }
8361 :
8362 : /************************************************************************/
8363 : /* OGRGeoPackageSetSRID() */
8364 : /************************************************************************/
8365 :
8366 28 : static void OGRGeoPackageSetSRID(sqlite3_context *pContext, int /* argc */,
8367 : sqlite3_value **argv)
8368 : {
8369 28 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8370 : {
8371 1 : sqlite3_result_null(pContext);
8372 1 : return;
8373 : }
8374 27 : const int nDestSRID = sqlite3_value_int(argv[1]);
8375 : GPkgHeader sHeader;
8376 27 : int nBLOBLen = sqlite3_value_bytes(argv[0]);
8377 : const GByte *pabyBLOB =
8378 27 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8379 :
8380 54 : if (nBLOBLen < 8 ||
8381 27 : GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
8382 : {
8383 : // Try also spatialite geometry blobs
8384 0 : OGRGeometry *poGeom = nullptr;
8385 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeom) !=
8386 : OGRERR_NONE)
8387 : {
8388 0 : sqlite3_result_null(pContext);
8389 0 : return;
8390 : }
8391 0 : size_t nBLOBDestLen = 0;
8392 : GByte *pabyDestBLOB =
8393 0 : GPkgGeometryFromOGR(poGeom, nDestSRID, nullptr, &nBLOBDestLen);
8394 0 : if (!pabyDestBLOB)
8395 : {
8396 0 : sqlite3_result_null(pContext);
8397 0 : return;
8398 : }
8399 0 : sqlite3_result_blob(pContext, pabyDestBLOB,
8400 : static_cast<int>(nBLOBDestLen), VSIFree);
8401 0 : return;
8402 : }
8403 :
8404 27 : GByte *pabyDestBLOB = static_cast<GByte *>(CPLMalloc(nBLOBLen));
8405 27 : memcpy(pabyDestBLOB, pabyBLOB, nBLOBLen);
8406 27 : int32_t nSRIDToSerialize = nDestSRID;
8407 27 : if (OGR_SWAP(sHeader.eByteOrder))
8408 0 : nSRIDToSerialize = CPL_SWAP32(nSRIDToSerialize);
8409 27 : memcpy(pabyDestBLOB + 4, &nSRIDToSerialize, 4);
8410 27 : sqlite3_result_blob(pContext, pabyDestBLOB, nBLOBLen, VSIFree);
8411 : }
8412 :
8413 : /************************************************************************/
8414 : /* OGRGeoPackageSTMakeValid() */
8415 : /************************************************************************/
8416 :
8417 3 : static void OGRGeoPackageSTMakeValid(sqlite3_context *pContext, int argc,
8418 : sqlite3_value **argv)
8419 : {
8420 3 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8421 : {
8422 2 : sqlite3_result_null(pContext);
8423 2 : return;
8424 : }
8425 1 : int nBLOBLen = sqlite3_value_bytes(argv[0]);
8426 : const GByte *pabyBLOB =
8427 1 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8428 :
8429 : GPkgHeader sHeader;
8430 1 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8431 : {
8432 0 : sqlite3_result_null(pContext);
8433 0 : return;
8434 : }
8435 :
8436 : auto poGeom = std::unique_ptr<OGRGeometry>(
8437 1 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8438 1 : if (poGeom == nullptr)
8439 : {
8440 : // Try also spatialite geometry blobs
8441 0 : OGRGeometry *poGeomPtr = nullptr;
8442 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
8443 : OGRERR_NONE)
8444 : {
8445 0 : sqlite3_result_null(pContext);
8446 0 : return;
8447 : }
8448 0 : poGeom.reset(poGeomPtr);
8449 : }
8450 1 : auto poValid = std::unique_ptr<OGRGeometry>(poGeom->MakeValid());
8451 1 : if (poValid == nullptr)
8452 : {
8453 0 : sqlite3_result_null(pContext);
8454 0 : return;
8455 : }
8456 :
8457 1 : size_t nBLOBDestLen = 0;
8458 1 : GByte *pabyDestBLOB = GPkgGeometryFromOGR(poValid.get(), sHeader.iSrsId,
8459 : nullptr, &nBLOBDestLen);
8460 1 : if (!pabyDestBLOB)
8461 : {
8462 0 : sqlite3_result_null(pContext);
8463 0 : return;
8464 : }
8465 1 : sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
8466 : VSIFree);
8467 : }
8468 :
8469 : /************************************************************************/
8470 : /* OGRGeoPackageSTArea() */
8471 : /************************************************************************/
8472 :
8473 19 : static void OGRGeoPackageSTArea(sqlite3_context *pContext, int /*argc*/,
8474 : sqlite3_value **argv)
8475 : {
8476 19 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8477 : {
8478 1 : sqlite3_result_null(pContext);
8479 15 : return;
8480 : }
8481 18 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8482 : const GByte *pabyBLOB =
8483 18 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8484 :
8485 : GPkgHeader sHeader;
8486 0 : std::unique_ptr<OGRGeometry> poGeom;
8487 18 : if (GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) == OGRERR_NONE)
8488 : {
8489 16 : if (sHeader.bEmpty)
8490 : {
8491 3 : sqlite3_result_double(pContext, 0);
8492 13 : return;
8493 : }
8494 13 : const GByte *pabyWkb = pabyBLOB + sHeader.nHeaderLen;
8495 13 : size_t nWKBSize = nBLOBLen - sHeader.nHeaderLen;
8496 : bool bNeedSwap;
8497 : uint32_t nType;
8498 13 : if (OGRWKBGetGeomType(pabyWkb, nWKBSize, bNeedSwap, nType))
8499 : {
8500 13 : if (nType == wkbPolygon || nType == wkbPolygon25D ||
8501 11 : nType == wkbPolygon + 1000 || // wkbPolygonZ
8502 10 : nType == wkbPolygonM || nType == wkbPolygonZM)
8503 : {
8504 : double dfArea;
8505 5 : if (OGRWKBPolygonGetArea(pabyWkb, nWKBSize, dfArea))
8506 : {
8507 5 : sqlite3_result_double(pContext, dfArea);
8508 5 : return;
8509 0 : }
8510 : }
8511 8 : else if (nType == wkbMultiPolygon || nType == wkbMultiPolygon25D ||
8512 6 : nType == wkbMultiPolygon + 1000 || // wkbMultiPolygonZ
8513 5 : nType == wkbMultiPolygonM || nType == wkbMultiPolygonZM)
8514 : {
8515 : double dfArea;
8516 5 : if (OGRWKBMultiPolygonGetArea(pabyWkb, nWKBSize, dfArea))
8517 : {
8518 5 : sqlite3_result_double(pContext, dfArea);
8519 5 : return;
8520 : }
8521 : }
8522 : }
8523 :
8524 : // For curve geometries, fallback to OGRGeometry methods
8525 3 : poGeom.reset(GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8526 : }
8527 : else
8528 : {
8529 : // Try also spatialite geometry blobs
8530 2 : OGRGeometry *poGeomPtr = nullptr;
8531 2 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
8532 : OGRERR_NONE)
8533 : {
8534 1 : sqlite3_result_null(pContext);
8535 1 : return;
8536 : }
8537 1 : poGeom.reset(poGeomPtr);
8538 : }
8539 4 : auto poSurface = dynamic_cast<OGRSurface *>(poGeom.get());
8540 4 : if (poSurface == nullptr)
8541 : {
8542 2 : auto poMultiSurface = dynamic_cast<OGRMultiSurface *>(poGeom.get());
8543 2 : if (poMultiSurface == nullptr)
8544 : {
8545 1 : sqlite3_result_double(pContext, 0);
8546 : }
8547 : else
8548 : {
8549 1 : sqlite3_result_double(pContext, poMultiSurface->get_Area());
8550 : }
8551 : }
8552 : else
8553 : {
8554 2 : sqlite3_result_double(pContext, poSurface->get_Area());
8555 : }
8556 : }
8557 :
8558 : /************************************************************************/
8559 : /* OGRGeoPackageGeodesicArea() */
8560 : /************************************************************************/
8561 :
8562 5 : static void OGRGeoPackageGeodesicArea(sqlite3_context *pContext, int argc,
8563 : sqlite3_value **argv)
8564 : {
8565 5 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8566 : {
8567 1 : sqlite3_result_null(pContext);
8568 3 : return;
8569 : }
8570 4 : if (sqlite3_value_int(argv[1]) != 1)
8571 : {
8572 2 : CPLError(CE_Warning, CPLE_NotSupported,
8573 : "ST_Area(geom, use_ellipsoid) is only supported for "
8574 : "use_ellipsoid = 1");
8575 : }
8576 :
8577 4 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8578 : const GByte *pabyBLOB =
8579 4 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8580 : GPkgHeader sHeader;
8581 4 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8582 : {
8583 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8584 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8585 1 : return;
8586 : }
8587 :
8588 : GDALGeoPackageDataset *poDS =
8589 3 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8590 :
8591 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSrcSRS(
8592 3 : poDS->GetSpatialRef(sHeader.iSrsId, true));
8593 3 : if (poSrcSRS == nullptr)
8594 : {
8595 1 : CPLError(CE_Failure, CPLE_AppDefined,
8596 : "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8597 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8598 1 : return;
8599 : }
8600 :
8601 : auto poGeom = std::unique_ptr<OGRGeometry>(
8602 2 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8603 2 : if (poGeom == nullptr)
8604 : {
8605 : // Try also spatialite geometry blobs
8606 0 : OGRGeometry *poGeomSpatialite = nullptr;
8607 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8608 0 : &poGeomSpatialite) != OGRERR_NONE)
8609 : {
8610 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8611 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8612 0 : return;
8613 : }
8614 0 : poGeom.reset(poGeomSpatialite);
8615 : }
8616 :
8617 2 : poGeom->assignSpatialReference(poSrcSRS.get());
8618 2 : sqlite3_result_double(
8619 : pContext, OGR_G_GeodesicArea(OGRGeometry::ToHandle(poGeom.get())));
8620 : }
8621 :
8622 : /************************************************************************/
8623 : /* OGRGeoPackageLengthOrGeodesicLength() */
8624 : /************************************************************************/
8625 :
8626 8 : static void OGRGeoPackageLengthOrGeodesicLength(sqlite3_context *pContext,
8627 : int argc, sqlite3_value **argv)
8628 : {
8629 8 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8630 : {
8631 2 : sqlite3_result_null(pContext);
8632 5 : return;
8633 : }
8634 6 : if (argc == 2 && sqlite3_value_int(argv[1]) != 1)
8635 : {
8636 2 : CPLError(CE_Warning, CPLE_NotSupported,
8637 : "ST_Length(geom, use_ellipsoid) is only supported for "
8638 : "use_ellipsoid = 1");
8639 : }
8640 :
8641 6 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8642 : const GByte *pabyBLOB =
8643 6 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8644 : GPkgHeader sHeader;
8645 6 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8646 : {
8647 2 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8648 2 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8649 2 : return;
8650 : }
8651 :
8652 : GDALGeoPackageDataset *poDS =
8653 4 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8654 :
8655 0 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSrcSRS;
8656 4 : if (argc == 2)
8657 : {
8658 3 : poSrcSRS = poDS->GetSpatialRef(sHeader.iSrsId, true);
8659 3 : if (!poSrcSRS)
8660 : {
8661 1 : CPLError(CE_Failure, CPLE_AppDefined,
8662 : "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8663 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8664 1 : return;
8665 : }
8666 : }
8667 :
8668 : auto poGeom = std::unique_ptr<OGRGeometry>(
8669 3 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8670 3 : if (poGeom == nullptr)
8671 : {
8672 : // Try also spatialite geometry blobs
8673 0 : OGRGeometry *poGeomSpatialite = nullptr;
8674 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8675 0 : &poGeomSpatialite) != OGRERR_NONE)
8676 : {
8677 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8678 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8679 0 : return;
8680 : }
8681 0 : poGeom.reset(poGeomSpatialite);
8682 : }
8683 :
8684 3 : if (argc == 2)
8685 2 : poGeom->assignSpatialReference(poSrcSRS.get());
8686 :
8687 6 : sqlite3_result_double(
8688 : pContext,
8689 1 : argc == 1 ? OGR_G_Length(OGRGeometry::ToHandle(poGeom.get()))
8690 2 : : OGR_G_GeodesicLength(OGRGeometry::ToHandle(poGeom.get())));
8691 : }
8692 :
8693 : /************************************************************************/
8694 : /* OGRGeoPackageTransform() */
8695 : /************************************************************************/
8696 :
8697 : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
8698 : sqlite3_value **argv);
8699 :
8700 32 : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
8701 : sqlite3_value **argv)
8702 : {
8703 63 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB ||
8704 31 : sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
8705 : {
8706 2 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8707 32 : return;
8708 : }
8709 :
8710 30 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8711 : const GByte *pabyBLOB =
8712 30 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8713 : GPkgHeader sHeader;
8714 30 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8715 : {
8716 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8717 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8718 1 : return;
8719 : }
8720 :
8721 29 : const int nDestSRID = sqlite3_value_int(argv[1]);
8722 29 : if (sHeader.iSrsId == nDestSRID)
8723 : {
8724 : // Return blob unmodified
8725 3 : sqlite3_result_blob(pContext, pabyBLOB, nBLOBLen, SQLITE_TRANSIENT);
8726 3 : return;
8727 : }
8728 :
8729 : GDALGeoPackageDataset *poDS =
8730 26 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8731 :
8732 : // Try to get the cached coordinate transformation
8733 : OGRCoordinateTransformation *poCT;
8734 26 : if (poDS->m_nLastCachedCTSrcSRId == sHeader.iSrsId &&
8735 20 : poDS->m_nLastCachedCTDstSRId == nDestSRID)
8736 : {
8737 20 : poCT = poDS->m_poLastCachedCT.get();
8738 : }
8739 : else
8740 : {
8741 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
8742 6 : poSrcSRS(poDS->GetSpatialRef(sHeader.iSrsId, true));
8743 6 : if (poSrcSRS == nullptr)
8744 : {
8745 0 : CPLError(CE_Failure, CPLE_AppDefined,
8746 : "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8747 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8748 0 : return;
8749 : }
8750 :
8751 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
8752 6 : poDstSRS(poDS->GetSpatialRef(nDestSRID, true));
8753 6 : if (poDstSRS == nullptr)
8754 : {
8755 0 : CPLError(CE_Failure, CPLE_AppDefined, "Target SRID (%d) is invalid",
8756 : nDestSRID);
8757 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8758 0 : return;
8759 : }
8760 : poCT =
8761 6 : OGRCreateCoordinateTransformation(poSrcSRS.get(), poDstSRS.get());
8762 6 : if (poCT == nullptr)
8763 : {
8764 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8765 0 : return;
8766 : }
8767 :
8768 : // Cache coordinate transformation for potential later reuse
8769 6 : poDS->m_nLastCachedCTSrcSRId = sHeader.iSrsId;
8770 6 : poDS->m_nLastCachedCTDstSRId = nDestSRID;
8771 6 : poDS->m_poLastCachedCT.reset(poCT);
8772 6 : poCT = poDS->m_poLastCachedCT.get();
8773 : }
8774 :
8775 26 : if (sHeader.nHeaderLen >= 8)
8776 : {
8777 26 : std::vector<GByte> &abyNewBLOB = poDS->m_abyWKBTransformCache;
8778 26 : abyNewBLOB.resize(nBLOBLen);
8779 26 : memcpy(abyNewBLOB.data(), pabyBLOB, nBLOBLen);
8780 :
8781 26 : OGREnvelope3D oEnv3d;
8782 26 : if (!OGRWKBTransform(abyNewBLOB.data() + sHeader.nHeaderLen,
8783 26 : nBLOBLen - sHeader.nHeaderLen, poCT,
8784 78 : poDS->m_oWKBTransformCache, oEnv3d) ||
8785 26 : !GPkgUpdateHeader(abyNewBLOB.data(), nBLOBLen, nDestSRID,
8786 : oEnv3d.MinX, oEnv3d.MaxX, oEnv3d.MinY,
8787 : oEnv3d.MaxY, oEnv3d.MinZ, oEnv3d.MaxZ))
8788 : {
8789 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8790 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8791 0 : return;
8792 : }
8793 :
8794 26 : sqlite3_result_blob(pContext, abyNewBLOB.data(), nBLOBLen,
8795 : SQLITE_TRANSIENT);
8796 26 : return;
8797 : }
8798 :
8799 : // Try also spatialite geometry blobs
8800 0 : OGRGeometry *poGeomSpatialite = nullptr;
8801 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8802 0 : &poGeomSpatialite) != OGRERR_NONE)
8803 : {
8804 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8805 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8806 0 : return;
8807 : }
8808 0 : auto poGeom = std::unique_ptr<OGRGeometry>(poGeomSpatialite);
8809 :
8810 0 : if (poGeom->transform(poCT) != OGRERR_NONE)
8811 : {
8812 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8813 0 : return;
8814 : }
8815 :
8816 0 : size_t nBLOBDestLen = 0;
8817 : GByte *pabyDestBLOB =
8818 0 : GPkgGeometryFromOGR(poGeom.get(), nDestSRID, nullptr, &nBLOBDestLen);
8819 0 : if (!pabyDestBLOB)
8820 : {
8821 0 : sqlite3_result_null(pContext);
8822 0 : return;
8823 : }
8824 0 : sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
8825 : VSIFree);
8826 : }
8827 :
8828 : /************************************************************************/
8829 : /* OGRGeoPackageSridFromAuthCRS() */
8830 : /************************************************************************/
8831 :
8832 4 : static void OGRGeoPackageSridFromAuthCRS(sqlite3_context *pContext,
8833 : int /*argc*/, sqlite3_value **argv)
8834 : {
8835 7 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8836 3 : sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
8837 : {
8838 2 : sqlite3_result_int(pContext, -1);
8839 2 : return;
8840 : }
8841 :
8842 : GDALGeoPackageDataset *poDS =
8843 2 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8844 :
8845 2 : char *pszSQL = sqlite3_mprintf(
8846 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
8847 : "lower(organization) = lower('%q') AND organization_coordsys_id = %d",
8848 2 : sqlite3_value_text(argv[0]), sqlite3_value_int(argv[1]));
8849 2 : OGRErr err = OGRERR_NONE;
8850 2 : int nSRSId = SQLGetInteger(poDS->GetDB(), pszSQL, &err);
8851 2 : sqlite3_free(pszSQL);
8852 2 : if (err != OGRERR_NONE)
8853 1 : nSRSId = -1;
8854 2 : sqlite3_result_int(pContext, nSRSId);
8855 : }
8856 :
8857 : /************************************************************************/
8858 : /* OGRGeoPackageImportFromEPSG() */
8859 : /************************************************************************/
8860 :
8861 4 : static void OGRGeoPackageImportFromEPSG(sqlite3_context *pContext, int /*argc*/,
8862 : sqlite3_value **argv)
8863 : {
8864 4 : if (sqlite3_value_type(argv[0]) != SQLITE_INTEGER)
8865 : {
8866 1 : sqlite3_result_int(pContext, -1);
8867 2 : return;
8868 : }
8869 :
8870 : GDALGeoPackageDataset *poDS =
8871 3 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8872 3 : OGRSpatialReference oSRS;
8873 3 : if (oSRS.importFromEPSG(sqlite3_value_int(argv[0])) != OGRERR_NONE)
8874 : {
8875 1 : sqlite3_result_int(pContext, -1);
8876 1 : return;
8877 : }
8878 :
8879 2 : sqlite3_result_int(pContext, poDS->GetSrsId(&oSRS));
8880 : }
8881 :
8882 : /************************************************************************/
8883 : /* OGRGeoPackageRegisterGeometryExtension() */
8884 : /************************************************************************/
8885 :
8886 1 : static void OGRGeoPackageRegisterGeometryExtension(sqlite3_context *pContext,
8887 : int /*argc*/,
8888 : sqlite3_value **argv)
8889 : {
8890 1 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8891 2 : sqlite3_value_type(argv[1]) != SQLITE_TEXT ||
8892 1 : sqlite3_value_type(argv[2]) != SQLITE_TEXT)
8893 : {
8894 0 : sqlite3_result_int(pContext, 0);
8895 0 : return;
8896 : }
8897 :
8898 : const char *pszTableName =
8899 1 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8900 : const char *pszGeomName =
8901 1 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8902 : const char *pszGeomType =
8903 1 : reinterpret_cast<const char *>(sqlite3_value_text(argv[2]));
8904 :
8905 : GDALGeoPackageDataset *poDS =
8906 1 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8907 :
8908 1 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
8909 1 : poDS->GetLayerByName(pszTableName));
8910 1 : if (poLyr == nullptr)
8911 : {
8912 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
8913 0 : sqlite3_result_int(pContext, 0);
8914 0 : return;
8915 : }
8916 1 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
8917 : {
8918 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
8919 0 : sqlite3_result_int(pContext, 0);
8920 0 : return;
8921 : }
8922 1 : const OGRwkbGeometryType eGeomType = OGRFromOGCGeomType(pszGeomType);
8923 1 : if (eGeomType == wkbUnknown)
8924 : {
8925 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry type name");
8926 0 : sqlite3_result_int(pContext, 0);
8927 0 : return;
8928 : }
8929 :
8930 1 : sqlite3_result_int(
8931 : pContext,
8932 1 : static_cast<int>(poLyr->CreateGeometryExtensionIfNecessary(eGeomType)));
8933 : }
8934 :
8935 : /************************************************************************/
8936 : /* OGRGeoPackageCreateSpatialIndex() */
8937 : /************************************************************************/
8938 :
8939 14 : static void OGRGeoPackageCreateSpatialIndex(sqlite3_context *pContext,
8940 : int /*argc*/, sqlite3_value **argv)
8941 : {
8942 27 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8943 13 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
8944 : {
8945 2 : sqlite3_result_int(pContext, 0);
8946 2 : return;
8947 : }
8948 :
8949 : const char *pszTableName =
8950 12 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8951 : const char *pszGeomName =
8952 12 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8953 : GDALGeoPackageDataset *poDS =
8954 12 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8955 :
8956 12 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
8957 12 : poDS->GetLayerByName(pszTableName));
8958 12 : if (poLyr == nullptr)
8959 : {
8960 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
8961 1 : sqlite3_result_int(pContext, 0);
8962 1 : return;
8963 : }
8964 11 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
8965 : {
8966 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
8967 1 : sqlite3_result_int(pContext, 0);
8968 1 : return;
8969 : }
8970 :
8971 10 : sqlite3_result_int(pContext, poLyr->CreateSpatialIndex());
8972 : }
8973 :
8974 : /************************************************************************/
8975 : /* OGRGeoPackageDisableSpatialIndex() */
8976 : /************************************************************************/
8977 :
8978 12 : static void OGRGeoPackageDisableSpatialIndex(sqlite3_context *pContext,
8979 : int /*argc*/, sqlite3_value **argv)
8980 : {
8981 23 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8982 11 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
8983 : {
8984 2 : sqlite3_result_int(pContext, 0);
8985 2 : return;
8986 : }
8987 :
8988 : const char *pszTableName =
8989 10 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8990 : const char *pszGeomName =
8991 10 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8992 : GDALGeoPackageDataset *poDS =
8993 10 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8994 :
8995 10 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
8996 10 : poDS->GetLayerByName(pszTableName));
8997 10 : if (poLyr == nullptr)
8998 : {
8999 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
9000 1 : sqlite3_result_int(pContext, 0);
9001 1 : return;
9002 : }
9003 9 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
9004 : {
9005 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
9006 1 : sqlite3_result_int(pContext, 0);
9007 1 : return;
9008 : }
9009 :
9010 8 : sqlite3_result_int(pContext, poLyr->DropSpatialIndex(true));
9011 : }
9012 :
9013 : /************************************************************************/
9014 : /* OGRGeoPackageHasSpatialIndex() */
9015 : /************************************************************************/
9016 :
9017 29 : static void OGRGeoPackageHasSpatialIndex(sqlite3_context *pContext,
9018 : int /*argc*/, sqlite3_value **argv)
9019 : {
9020 57 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9021 28 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9022 : {
9023 2 : sqlite3_result_int(pContext, 0);
9024 2 : return;
9025 : }
9026 :
9027 : const char *pszTableName =
9028 27 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9029 : const char *pszGeomName =
9030 27 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9031 : GDALGeoPackageDataset *poDS =
9032 27 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9033 :
9034 27 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
9035 27 : poDS->GetLayerByName(pszTableName));
9036 27 : if (poLyr == nullptr)
9037 : {
9038 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
9039 1 : sqlite3_result_int(pContext, 0);
9040 1 : return;
9041 : }
9042 26 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
9043 : {
9044 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
9045 1 : sqlite3_result_int(pContext, 0);
9046 1 : return;
9047 : }
9048 :
9049 25 : poLyr->RunDeferredCreationIfNecessary();
9050 25 : poLyr->CreateSpatialIndexIfNecessary();
9051 :
9052 25 : sqlite3_result_int(pContext, poLyr->HasSpatialIndex());
9053 : }
9054 :
9055 : /************************************************************************/
9056 : /* GPKG_hstore_get_value() */
9057 : /************************************************************************/
9058 :
9059 4 : static void GPKG_hstore_get_value(sqlite3_context *pContext, int /*argc*/,
9060 : sqlite3_value **argv)
9061 : {
9062 7 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9063 3 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9064 : {
9065 2 : sqlite3_result_null(pContext);
9066 2 : return;
9067 : }
9068 :
9069 : const char *pszHStore =
9070 2 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9071 : const char *pszSearchedKey =
9072 2 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9073 2 : char *pszValue = OGRHStoreGetValue(pszHStore, pszSearchedKey);
9074 2 : if (pszValue != nullptr)
9075 1 : sqlite3_result_text(pContext, pszValue, -1, CPLFree);
9076 : else
9077 1 : sqlite3_result_null(pContext);
9078 : }
9079 :
9080 : /************************************************************************/
9081 : /* GPKG_GDAL_GetMemFileFromBlob() */
9082 : /************************************************************************/
9083 :
9084 105 : static CPLString GPKG_GDAL_GetMemFileFromBlob(sqlite3_value **argv)
9085 : {
9086 105 : int nBytes = sqlite3_value_bytes(argv[0]);
9087 : const GByte *pabyBLOB =
9088 105 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
9089 : CPLString osMemFileName(
9090 105 : VSIMemGenerateHiddenFilename("GPKG_GDAL_GetMemFileFromBlob"));
9091 105 : VSILFILE *fp = VSIFileFromMemBuffer(
9092 : osMemFileName.c_str(), const_cast<GByte *>(pabyBLOB), nBytes, FALSE);
9093 105 : VSIFCloseL(fp);
9094 105 : return osMemFileName;
9095 : }
9096 :
9097 : /************************************************************************/
9098 : /* GPKG_GDAL_GetMimeType() */
9099 : /************************************************************************/
9100 :
9101 35 : static void GPKG_GDAL_GetMimeType(sqlite3_context *pContext, int /*argc*/,
9102 : sqlite3_value **argv)
9103 : {
9104 35 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
9105 : {
9106 0 : sqlite3_result_null(pContext);
9107 0 : return;
9108 : }
9109 :
9110 70 : CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
9111 : GDALDriver *poDriver =
9112 35 : GDALDriver::FromHandle(GDALIdentifyDriver(osMemFileName, nullptr));
9113 35 : if (poDriver != nullptr)
9114 : {
9115 35 : const char *pszRes = nullptr;
9116 35 : if (EQUAL(poDriver->GetDescription(), "PNG"))
9117 23 : pszRes = "image/png";
9118 12 : else if (EQUAL(poDriver->GetDescription(), "JPEG"))
9119 6 : pszRes = "image/jpeg";
9120 6 : else if (EQUAL(poDriver->GetDescription(), "WEBP"))
9121 6 : pszRes = "image/x-webp";
9122 0 : else if (EQUAL(poDriver->GetDescription(), "GTIFF"))
9123 0 : pszRes = "image/tiff";
9124 : else
9125 0 : pszRes = CPLSPrintf("gdal/%s", poDriver->GetDescription());
9126 35 : sqlite3_result_text(pContext, pszRes, -1, SQLITE_TRANSIENT);
9127 : }
9128 : else
9129 0 : sqlite3_result_null(pContext);
9130 35 : VSIUnlink(osMemFileName);
9131 : }
9132 :
9133 : /************************************************************************/
9134 : /* GPKG_GDAL_GetBandCount() */
9135 : /************************************************************************/
9136 :
9137 35 : static void GPKG_GDAL_GetBandCount(sqlite3_context *pContext, int /*argc*/,
9138 : sqlite3_value **argv)
9139 : {
9140 35 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
9141 : {
9142 0 : sqlite3_result_null(pContext);
9143 0 : return;
9144 : }
9145 :
9146 70 : CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
9147 : auto poDS = std::unique_ptr<GDALDataset>(
9148 : GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
9149 70 : nullptr, nullptr, nullptr));
9150 35 : if (poDS != nullptr)
9151 : {
9152 35 : sqlite3_result_int(pContext, poDS->GetRasterCount());
9153 : }
9154 : else
9155 0 : sqlite3_result_null(pContext);
9156 35 : VSIUnlink(osMemFileName);
9157 : }
9158 :
9159 : /************************************************************************/
9160 : /* GPKG_GDAL_HasColorTable() */
9161 : /************************************************************************/
9162 :
9163 35 : static void GPKG_GDAL_HasColorTable(sqlite3_context *pContext, int /*argc*/,
9164 : sqlite3_value **argv)
9165 : {
9166 35 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
9167 : {
9168 0 : sqlite3_result_null(pContext);
9169 0 : return;
9170 : }
9171 :
9172 70 : CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
9173 : auto poDS = std::unique_ptr<GDALDataset>(
9174 : GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
9175 70 : nullptr, nullptr, nullptr));
9176 35 : if (poDS != nullptr)
9177 : {
9178 35 : sqlite3_result_int(
9179 46 : pContext, poDS->GetRasterCount() == 1 &&
9180 11 : poDS->GetRasterBand(1)->GetColorTable() != nullptr);
9181 : }
9182 : else
9183 0 : sqlite3_result_null(pContext);
9184 35 : VSIUnlink(osMemFileName);
9185 : }
9186 :
9187 : /************************************************************************/
9188 : /* GetRasterLayerDataset() */
9189 : /************************************************************************/
9190 :
9191 : GDALDataset *
9192 12 : GDALGeoPackageDataset::GetRasterLayerDataset(const char *pszLayerName)
9193 : {
9194 12 : const auto oIter = m_oCachedRasterDS.find(pszLayerName);
9195 12 : if (oIter != m_oCachedRasterDS.end())
9196 10 : return oIter->second.get();
9197 :
9198 : auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
9199 4 : (std::string("GPKG:\"") + m_pszFilename + "\":" + pszLayerName).c_str(),
9200 4 : GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
9201 2 : if (!poDS)
9202 : {
9203 0 : return nullptr;
9204 : }
9205 2 : m_oCachedRasterDS[pszLayerName] = std::move(poDS);
9206 2 : return m_oCachedRasterDS[pszLayerName].get();
9207 : }
9208 :
9209 : /************************************************************************/
9210 : /* GPKG_gdal_get_layer_pixel_value() */
9211 : /************************************************************************/
9212 :
9213 : // NOTE: keep in sync implementations in ogrsqlitesqlfunctionscommon.cpp
9214 : // and ogrgeopackagedatasource.cpp
9215 13 : static void GPKG_gdal_get_layer_pixel_value(sqlite3_context *pContext, int argc,
9216 : sqlite3_value **argv)
9217 : {
9218 13 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
9219 : {
9220 1 : CPLError(CE_Failure, CPLE_AppDefined,
9221 : "Invalid arguments to gdal_get_layer_pixel_value()");
9222 1 : sqlite3_result_null(pContext);
9223 1 : return;
9224 : }
9225 :
9226 : const char *pszLayerName =
9227 12 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9228 :
9229 : GDALGeoPackageDataset *poGlobalDS =
9230 12 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9231 12 : auto poDS = poGlobalDS->GetRasterLayerDataset(pszLayerName);
9232 12 : if (!poDS)
9233 : {
9234 0 : sqlite3_result_null(pContext);
9235 0 : return;
9236 : }
9237 :
9238 12 : OGRSQLite_gdal_get_pixel_value_common("gdal_get_layer_pixel_value",
9239 : pContext, argc, argv, poDS);
9240 : }
9241 :
9242 : /************************************************************************/
9243 : /* GPKG_ogr_layer_Extent() */
9244 : /************************************************************************/
9245 :
9246 3 : static void GPKG_ogr_layer_Extent(sqlite3_context *pContext, int /*argc*/,
9247 : sqlite3_value **argv)
9248 : {
9249 3 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
9250 : {
9251 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s: Invalid argument type",
9252 : "ogr_layer_Extent");
9253 1 : sqlite3_result_null(pContext);
9254 2 : return;
9255 : }
9256 :
9257 : const char *pszLayerName =
9258 2 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9259 : GDALGeoPackageDataset *poDS =
9260 2 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9261 2 : OGRLayer *poLayer = poDS->GetLayerByName(pszLayerName);
9262 2 : if (!poLayer)
9263 : {
9264 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s: unknown layer",
9265 : "ogr_layer_Extent");
9266 1 : sqlite3_result_null(pContext);
9267 1 : return;
9268 : }
9269 :
9270 1 : if (poLayer->GetGeomType() == wkbNone)
9271 : {
9272 0 : sqlite3_result_null(pContext);
9273 0 : return;
9274 : }
9275 :
9276 1 : OGREnvelope sExtent;
9277 1 : if (poLayer->GetExtent(&sExtent) != OGRERR_NONE)
9278 : {
9279 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s: Cannot fetch layer extent",
9280 : "ogr_layer_Extent");
9281 0 : sqlite3_result_null(pContext);
9282 0 : return;
9283 : }
9284 :
9285 1 : OGRPolygon oPoly;
9286 1 : auto poRing = std::make_unique<OGRLinearRing>();
9287 1 : poRing->addPoint(sExtent.MinX, sExtent.MinY);
9288 1 : poRing->addPoint(sExtent.MaxX, sExtent.MinY);
9289 1 : poRing->addPoint(sExtent.MaxX, sExtent.MaxY);
9290 1 : poRing->addPoint(sExtent.MinX, sExtent.MaxY);
9291 1 : poRing->addPoint(sExtent.MinX, sExtent.MinY);
9292 1 : oPoly.addRing(std::move(poRing));
9293 :
9294 1 : const auto poSRS = poLayer->GetSpatialRef();
9295 1 : const int nSRID = poDS->GetSrsId(poSRS);
9296 1 : size_t nBLOBDestLen = 0;
9297 : GByte *pabyDestBLOB =
9298 1 : GPkgGeometryFromOGR(&oPoly, nSRID, nullptr, &nBLOBDestLen);
9299 1 : if (!pabyDestBLOB)
9300 : {
9301 0 : sqlite3_result_null(pContext);
9302 0 : return;
9303 : }
9304 1 : sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
9305 : VSIFree);
9306 : }
9307 :
9308 : /************************************************************************/
9309 : /* InstallSQLFunctions() */
9310 : /************************************************************************/
9311 :
9312 : #ifndef SQLITE_DETERMINISTIC
9313 : #define SQLITE_DETERMINISTIC 0
9314 : #endif
9315 :
9316 : #ifndef SQLITE_INNOCUOUS
9317 : #define SQLITE_INNOCUOUS 0
9318 : #endif
9319 :
9320 : #ifndef UTF8_INNOCUOUS
9321 : #define UTF8_INNOCUOUS (SQLITE_UTF8 | SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS)
9322 : #endif
9323 :
9324 2073 : void GDALGeoPackageDataset::InstallSQLFunctions()
9325 : {
9326 2073 : InitSpatialite();
9327 :
9328 : // Enable SpatiaLite 4.3 "amphibious" mode, i.e. that SpatiaLite functions
9329 : // that take geometries will accept GPKG encoded geometries without
9330 : // explicit conversion.
9331 : // Use sqlite3_exec() instead of SQLCommand() since we don't want verbose
9332 : // error.
9333 2073 : sqlite3_exec(hDB, "SELECT EnableGpkgAmphibiousMode()", nullptr, nullptr,
9334 : nullptr);
9335 :
9336 : /* Used by RTree Spatial Index Extension */
9337 2073 : sqlite3_create_function(hDB, "ST_MinX", 1, UTF8_INNOCUOUS, nullptr,
9338 : OGRGeoPackageSTMinX, nullptr, nullptr);
9339 2073 : sqlite3_create_function(hDB, "ST_MinY", 1, UTF8_INNOCUOUS, nullptr,
9340 : OGRGeoPackageSTMinY, nullptr, nullptr);
9341 2073 : sqlite3_create_function(hDB, "ST_MaxX", 1, UTF8_INNOCUOUS, nullptr,
9342 : OGRGeoPackageSTMaxX, nullptr, nullptr);
9343 2073 : sqlite3_create_function(hDB, "ST_MaxY", 1, UTF8_INNOCUOUS, nullptr,
9344 : OGRGeoPackageSTMaxY, nullptr, nullptr);
9345 2073 : sqlite3_create_function(hDB, "ST_IsEmpty", 1, UTF8_INNOCUOUS, nullptr,
9346 : OGRGeoPackageSTIsEmpty, nullptr, nullptr);
9347 :
9348 : /* Used by Geometry Type Triggers Extension */
9349 2073 : sqlite3_create_function(hDB, "ST_GeometryType", 1, UTF8_INNOCUOUS, nullptr,
9350 : OGRGeoPackageSTGeometryType, nullptr, nullptr);
9351 2073 : sqlite3_create_function(hDB, "GPKG_IsAssignable", 2, UTF8_INNOCUOUS,
9352 : nullptr, OGRGeoPackageGPKGIsAssignable, nullptr,
9353 : nullptr);
9354 :
9355 : /* Used by Geometry SRS ID Triggers Extension */
9356 2073 : sqlite3_create_function(hDB, "ST_SRID", 1, UTF8_INNOCUOUS, nullptr,
9357 : OGRGeoPackageSTSRID, nullptr, nullptr);
9358 :
9359 : /* Spatialite-like functions */
9360 2073 : sqlite3_create_function(hDB, "CreateSpatialIndex", 2, SQLITE_UTF8, this,
9361 : OGRGeoPackageCreateSpatialIndex, nullptr, nullptr);
9362 2073 : sqlite3_create_function(hDB, "DisableSpatialIndex", 2, SQLITE_UTF8, this,
9363 : OGRGeoPackageDisableSpatialIndex, nullptr, nullptr);
9364 2073 : sqlite3_create_function(hDB, "HasSpatialIndex", 2, SQLITE_UTF8, this,
9365 : OGRGeoPackageHasSpatialIndex, nullptr, nullptr);
9366 :
9367 : // HSTORE functions
9368 2073 : sqlite3_create_function(hDB, "hstore_get_value", 2, UTF8_INNOCUOUS, nullptr,
9369 : GPKG_hstore_get_value, nullptr, nullptr);
9370 :
9371 : // Override a few Spatialite functions to work with gpkg_spatial_ref_sys
9372 2073 : sqlite3_create_function(hDB, "ST_Transform", 2, UTF8_INNOCUOUS, this,
9373 : OGRGeoPackageTransform, nullptr, nullptr);
9374 2073 : sqlite3_create_function(hDB, "Transform", 2, UTF8_INNOCUOUS, this,
9375 : OGRGeoPackageTransform, nullptr, nullptr);
9376 2073 : sqlite3_create_function(hDB, "SridFromAuthCRS", 2, SQLITE_UTF8, this,
9377 : OGRGeoPackageSridFromAuthCRS, nullptr, nullptr);
9378 :
9379 2073 : sqlite3_create_function(hDB, "ST_EnvIntersects", 2, UTF8_INNOCUOUS, nullptr,
9380 : OGRGeoPackageSTEnvelopesIntersectsTwoParams,
9381 : nullptr, nullptr);
9382 2073 : sqlite3_create_function(
9383 : hDB, "ST_EnvelopesIntersects", 2, UTF8_INNOCUOUS, nullptr,
9384 : OGRGeoPackageSTEnvelopesIntersectsTwoParams, nullptr, nullptr);
9385 :
9386 2073 : sqlite3_create_function(hDB, "ST_EnvIntersects", 5, UTF8_INNOCUOUS, nullptr,
9387 : OGRGeoPackageSTEnvelopesIntersects, nullptr,
9388 : nullptr);
9389 2073 : sqlite3_create_function(hDB, "ST_EnvelopesIntersects", 5, UTF8_INNOCUOUS,
9390 : nullptr, OGRGeoPackageSTEnvelopesIntersects,
9391 : nullptr, nullptr);
9392 :
9393 : // Implementation that directly hacks the GeoPackage geometry blob header
9394 2073 : sqlite3_create_function(hDB, "SetSRID", 2, UTF8_INNOCUOUS, nullptr,
9395 : OGRGeoPackageSetSRID, nullptr, nullptr);
9396 :
9397 : // GDAL specific function
9398 2073 : sqlite3_create_function(hDB, "ImportFromEPSG", 1, SQLITE_UTF8, this,
9399 : OGRGeoPackageImportFromEPSG, nullptr, nullptr);
9400 :
9401 : // May be used by ogrmerge.py
9402 2073 : sqlite3_create_function(hDB, "RegisterGeometryExtension", 3, SQLITE_UTF8,
9403 : this, OGRGeoPackageRegisterGeometryExtension,
9404 : nullptr, nullptr);
9405 :
9406 2073 : if (OGRGeometryFactory::haveGEOS())
9407 : {
9408 2073 : sqlite3_create_function(hDB, "ST_MakeValid", 1, UTF8_INNOCUOUS, nullptr,
9409 : OGRGeoPackageSTMakeValid, nullptr, nullptr);
9410 : }
9411 :
9412 2073 : sqlite3_create_function(hDB, "ST_Length", 1, UTF8_INNOCUOUS, nullptr,
9413 : OGRGeoPackageLengthOrGeodesicLength, nullptr,
9414 : nullptr);
9415 2073 : sqlite3_create_function(hDB, "ST_Length", 2, UTF8_INNOCUOUS, this,
9416 : OGRGeoPackageLengthOrGeodesicLength, nullptr,
9417 : nullptr);
9418 :
9419 2073 : sqlite3_create_function(hDB, "ST_Area", 1, UTF8_INNOCUOUS, nullptr,
9420 : OGRGeoPackageSTArea, nullptr, nullptr);
9421 2073 : sqlite3_create_function(hDB, "ST_Area", 2, UTF8_INNOCUOUS, this,
9422 : OGRGeoPackageGeodesicArea, nullptr, nullptr);
9423 :
9424 : // Debug functions
9425 2073 : if (CPLTestBool(CPLGetConfigOption("GPKG_DEBUG", "FALSE")))
9426 : {
9427 423 : sqlite3_create_function(hDB, "GDAL_GetMimeType", 1,
9428 : SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9429 : GPKG_GDAL_GetMimeType, nullptr, nullptr);
9430 423 : sqlite3_create_function(hDB, "GDAL_GetBandCount", 1,
9431 : SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9432 : GPKG_GDAL_GetBandCount, nullptr, nullptr);
9433 423 : sqlite3_create_function(hDB, "GDAL_HasColorTable", 1,
9434 : SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9435 : GPKG_GDAL_HasColorTable, nullptr, nullptr);
9436 : }
9437 :
9438 2073 : sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 5, SQLITE_UTF8,
9439 : this, GPKG_gdal_get_layer_pixel_value, nullptr,
9440 : nullptr);
9441 2073 : sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 6, SQLITE_UTF8,
9442 : this, GPKG_gdal_get_layer_pixel_value, nullptr,
9443 : nullptr);
9444 :
9445 : // Function from VirtualOGR
9446 2073 : sqlite3_create_function(hDB, "ogr_layer_Extent", 1, SQLITE_UTF8, this,
9447 : GPKG_ogr_layer_Extent, nullptr, nullptr);
9448 :
9449 2073 : m_pSQLFunctionData = OGRSQLiteRegisterSQLFunctionsCommon(hDB);
9450 2073 : }
9451 :
9452 : /************************************************************************/
9453 : /* OpenOrCreateDB() */
9454 : /************************************************************************/
9455 :
9456 2077 : bool GDALGeoPackageDataset::OpenOrCreateDB(int flags)
9457 : {
9458 2077 : const bool bSuccess = OGRSQLiteBaseDataSource::OpenOrCreateDB(
9459 : flags, /*bRegisterOGR2SQLiteExtensions=*/false,
9460 : /*bLoadExtensions=*/true);
9461 2077 : if (!bSuccess)
9462 9 : return false;
9463 :
9464 : // Turning on recursive_triggers is needed so that DELETE triggers fire
9465 : // in a INSERT OR REPLACE statement. In particular this is needed to
9466 : // make sure gpkg_ogr_contents.feature_count is properly updated.
9467 2068 : SQLCommand(hDB, "PRAGMA recursive_triggers = 1");
9468 :
9469 2068 : InstallSQLFunctions();
9470 :
9471 : const char *pszSqlitePragma =
9472 2068 : CPLGetConfigOption("OGR_SQLITE_PRAGMA", nullptr);
9473 2068 : OGRErr eErr = OGRERR_NONE;
9474 6 : if ((!pszSqlitePragma || !strstr(pszSqlitePragma, "trusted_schema")) &&
9475 : // Older sqlite versions don't have this pragma
9476 4142 : SQLGetInteger(hDB, "PRAGMA trusted_schema", &eErr) == 0 &&
9477 2068 : eErr == OGRERR_NONE)
9478 : {
9479 2068 : bool bNeedsTrustedSchema = false;
9480 :
9481 : // Current SQLite versions require PRAGMA trusted_schema = 1 to be
9482 : // able to use the RTree from triggers, which is only needed when
9483 : // modifying the RTree.
9484 5094 : if (((flags & SQLITE_OPEN_READWRITE) != 0 ||
9485 3178 : (flags & SQLITE_OPEN_CREATE) != 0) &&
9486 1110 : OGRSQLiteRTreeRequiresTrustedSchemaOn())
9487 : {
9488 1110 : bNeedsTrustedSchema = true;
9489 : }
9490 :
9491 : #ifdef HAVE_SPATIALITE
9492 : // Spatialite <= 5.1.0 doesn't declare its functions as SQLITE_INNOCUOUS
9493 958 : if (!bNeedsTrustedSchema && HasExtensionsTable() &&
9494 877 : SQLGetInteger(
9495 : hDB,
9496 : "SELECT 1 FROM gpkg_extensions WHERE "
9497 : "extension_name ='gdal_spatialite_computed_geom_column'",
9498 1 : nullptr) == 1 &&
9499 3026 : SpatialiteRequiresTrustedSchemaOn() && AreSpatialiteTriggersSafe())
9500 : {
9501 1 : bNeedsTrustedSchema = true;
9502 : }
9503 : #endif
9504 :
9505 2068 : if (bNeedsTrustedSchema)
9506 : {
9507 1111 : CPLDebug("GPKG", "Setting PRAGMA trusted_schema = 1");
9508 1111 : SQLCommand(hDB, "PRAGMA trusted_schema = 1");
9509 : }
9510 : }
9511 :
9512 : const char *pszPreludeStatements =
9513 2068 : CSLFetchNameValue(papszOpenOptions, "PRELUDE_STATEMENTS");
9514 2068 : if (pszPreludeStatements)
9515 : {
9516 2 : if (SQLCommand(hDB, pszPreludeStatements) != OGRERR_NONE)
9517 0 : return false;
9518 : }
9519 :
9520 2068 : return true;
9521 : }
9522 :
9523 : /************************************************************************/
9524 : /* GetLayerWithGetSpatialWhereByName() */
9525 : /************************************************************************/
9526 :
9527 : std::pair<OGRLayer *, IOGRSQLiteGetSpatialWhere *>
9528 90 : GDALGeoPackageDataset::GetLayerWithGetSpatialWhereByName(const char *pszName)
9529 : {
9530 : OGRGeoPackageLayer *poRet =
9531 90 : cpl::down_cast<OGRGeoPackageLayer *>(GetLayerByName(pszName));
9532 90 : return std::pair(poRet, poRet);
9533 : }
9534 :
9535 : /************************************************************************/
9536 : /* CommitTransaction() */
9537 : /************************************************************************/
9538 :
9539 208 : OGRErr GDALGeoPackageDataset::CommitTransaction()
9540 :
9541 : {
9542 208 : if (m_nSoftTransactionLevel == 1)
9543 : {
9544 207 : FlushMetadata();
9545 463 : for (auto &poLayer : m_apoLayers)
9546 : {
9547 256 : poLayer->DoJobAtTransactionCommit();
9548 : }
9549 : }
9550 :
9551 208 : return OGRSQLiteBaseDataSource::CommitTransaction();
9552 : }
9553 :
9554 : /************************************************************************/
9555 : /* RollbackTransaction() */
9556 : /************************************************************************/
9557 :
9558 35 : OGRErr GDALGeoPackageDataset::RollbackTransaction()
9559 :
9560 : {
9561 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9562 70 : std::vector<bool> abAddTriggers;
9563 35 : std::vector<bool> abTriggersDeletedInTransaction;
9564 : #endif
9565 35 : if (m_nSoftTransactionLevel == 1)
9566 : {
9567 34 : FlushMetadata();
9568 70 : for (auto &poLayer : m_apoLayers)
9569 : {
9570 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9571 36 : abAddTriggers.push_back(poLayer->GetAddOGRFeatureCountTriggers());
9572 36 : abTriggersDeletedInTransaction.push_back(
9573 36 : poLayer->GetOGRFeatureCountTriggersDeletedInTransaction());
9574 36 : poLayer->SetAddOGRFeatureCountTriggers(false);
9575 : #endif
9576 36 : poLayer->DoJobAtTransactionRollback();
9577 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9578 36 : poLayer->DisableFeatureCount();
9579 : #endif
9580 : }
9581 : }
9582 :
9583 35 : const OGRErr eErr = OGRSQLiteBaseDataSource::RollbackTransaction();
9584 :
9585 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9586 35 : if (!abAddTriggers.empty())
9587 : {
9588 68 : for (size_t i = 0; i < m_apoLayers.size(); ++i)
9589 : {
9590 36 : auto &poLayer = m_apoLayers[i];
9591 36 : if (abTriggersDeletedInTransaction[i])
9592 : {
9593 7 : poLayer->SetOGRFeatureCountTriggersEnabled(true);
9594 : }
9595 : else
9596 : {
9597 29 : poLayer->SetAddOGRFeatureCountTriggers(abAddTriggers[i]);
9598 : }
9599 : }
9600 : }
9601 : #endif
9602 70 : return eErr;
9603 : }
9604 :
9605 : /************************************************************************/
9606 : /* GetGeometryTypeString() */
9607 : /************************************************************************/
9608 :
9609 : const char *
9610 1447 : GDALGeoPackageDataset::GetGeometryTypeString(OGRwkbGeometryType eType)
9611 : {
9612 1447 : const char *pszGPKGGeomType = OGRToOGCGeomType(eType);
9613 1459 : if (EQUAL(pszGPKGGeomType, "GEOMETRYCOLLECTION") &&
9614 12 : CPLTestBool(CPLGetConfigOption("OGR_GPKG_GEOMCOLLECTION", "NO")))
9615 : {
9616 0 : pszGPKGGeomType = "GEOMCOLLECTION";
9617 : }
9618 1447 : return pszGPKGGeomType;
9619 : }
9620 :
9621 : /************************************************************************/
9622 : /* GetFieldDomainNames() */
9623 : /************************************************************************/
9624 :
9625 : std::vector<std::string>
9626 11 : GDALGeoPackageDataset::GetFieldDomainNames(CSLConstList) const
9627 : {
9628 11 : if (!HasDataColumnConstraintsTable())
9629 4 : return std::vector<std::string>();
9630 :
9631 14 : std::vector<std::string> oDomainNamesList;
9632 :
9633 7 : std::unique_ptr<SQLResult> oResultTable;
9634 : {
9635 : std::string osSQL =
9636 : "SELECT DISTINCT constraint_name "
9637 : "FROM gpkg_data_column_constraints "
9638 : "WHERE constraint_name NOT LIKE '_%_domain_description' "
9639 : "ORDER BY constraint_name "
9640 7 : "LIMIT 10000" // to avoid denial of service
9641 : ;
9642 7 : oResultTable = SQLQuery(hDB, osSQL.c_str());
9643 7 : if (!oResultTable)
9644 0 : return oDomainNamesList;
9645 : }
9646 :
9647 7 : if (oResultTable->RowCount() == 10000)
9648 : {
9649 0 : CPLError(CE_Warning, CPLE_AppDefined,
9650 : "Number of rows returned for field domain names has been "
9651 : "truncated.");
9652 : }
9653 7 : else if (oResultTable->RowCount() > 0)
9654 : {
9655 7 : oDomainNamesList.reserve(oResultTable->RowCount());
9656 89 : for (int i = 0; i < oResultTable->RowCount(); i++)
9657 : {
9658 82 : const char *pszConstraintName = oResultTable->GetValue(0, i);
9659 82 : if (!pszConstraintName)
9660 0 : continue;
9661 :
9662 82 : oDomainNamesList.emplace_back(pszConstraintName);
9663 : }
9664 : }
9665 :
9666 7 : return oDomainNamesList;
9667 : }
9668 :
9669 : /************************************************************************/
9670 : /* GetFieldDomain() */
9671 : /************************************************************************/
9672 :
9673 : const OGRFieldDomain *
9674 102 : GDALGeoPackageDataset::GetFieldDomain(const std::string &name) const
9675 : {
9676 102 : const auto baseRet = GDALDataset::GetFieldDomain(name);
9677 102 : if (baseRet)
9678 42 : return baseRet;
9679 :
9680 60 : if (!HasDataColumnConstraintsTable())
9681 4 : return nullptr;
9682 :
9683 56 : const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
9684 56 : const char *min_is_inclusive =
9685 56 : bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
9686 56 : const char *max_is_inclusive =
9687 56 : bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
9688 :
9689 56 : std::unique_ptr<SQLResult> oResultTable;
9690 : // Note: for coded domains, we use a little trick by using a dummy
9691 : // _{domainname}_domain_description enum that has a single entry whose
9692 : // description is the description of the main domain.
9693 : {
9694 56 : char *pszSQL = sqlite3_mprintf(
9695 : "SELECT constraint_type, value, min, %s, "
9696 : "max, %s, description, constraint_name "
9697 : "FROM gpkg_data_column_constraints "
9698 : "WHERE constraint_name IN ('%q', "
9699 : "'_%q_domain_description') "
9700 : "AND length(constraint_type) < 100 " // to
9701 : // avoid
9702 : // denial
9703 : // of
9704 : // service
9705 : "AND (value IS NULL OR length(value) < "
9706 : "10000) " // to avoid denial
9707 : // of service
9708 : "AND (description IS NULL OR "
9709 : "length(description) < 10000) " // to
9710 : // avoid
9711 : // denial
9712 : // of
9713 : // service
9714 : "ORDER BY value "
9715 : "LIMIT 10000", // to avoid denial of
9716 : // service
9717 : min_is_inclusive, max_is_inclusive, name.c_str(), name.c_str());
9718 56 : oResultTable = SQLQuery(hDB, pszSQL);
9719 56 : sqlite3_free(pszSQL);
9720 56 : if (!oResultTable)
9721 0 : return nullptr;
9722 : }
9723 56 : if (oResultTable->RowCount() == 0)
9724 : {
9725 15 : return nullptr;
9726 : }
9727 41 : if (oResultTable->RowCount() == 10000)
9728 : {
9729 0 : CPLError(CE_Warning, CPLE_AppDefined,
9730 : "Number of rows returned for field domain %s has been "
9731 : "truncated.",
9732 : name.c_str());
9733 : }
9734 :
9735 : // Try to find the field domain data type from fields that implement it
9736 41 : int nFieldType = -1;
9737 41 : OGRFieldSubType eSubType = OFSTNone;
9738 41 : if (HasDataColumnsTable())
9739 : {
9740 36 : char *pszSQL = sqlite3_mprintf(
9741 : "SELECT table_name, column_name FROM gpkg_data_columns WHERE "
9742 : "constraint_name = '%q' LIMIT 10",
9743 : name.c_str());
9744 72 : auto oResultTable2 = SQLQuery(hDB, pszSQL);
9745 36 : sqlite3_free(pszSQL);
9746 36 : if (oResultTable2 && oResultTable2->RowCount() >= 1)
9747 : {
9748 46 : for (int iRecord = 0; iRecord < oResultTable2->RowCount();
9749 : iRecord++)
9750 : {
9751 23 : const char *pszTableName = oResultTable2->GetValue(0, iRecord);
9752 23 : const char *pszColumnName = oResultTable2->GetValue(1, iRecord);
9753 23 : if (pszTableName == nullptr || pszColumnName == nullptr)
9754 0 : continue;
9755 : OGRLayer *poLayer =
9756 46 : const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
9757 23 : pszTableName);
9758 23 : if (poLayer)
9759 : {
9760 23 : const auto poFDefn = poLayer->GetLayerDefn();
9761 23 : int nIdx = poFDefn->GetFieldIndex(pszColumnName);
9762 23 : if (nIdx >= 0)
9763 : {
9764 23 : const auto poFieldDefn = poFDefn->GetFieldDefn(nIdx);
9765 23 : const auto eType = poFieldDefn->GetType();
9766 23 : if (nFieldType < 0)
9767 : {
9768 23 : nFieldType = eType;
9769 23 : eSubType = poFieldDefn->GetSubType();
9770 : }
9771 0 : else if ((eType == OFTInteger64 || eType == OFTReal) &&
9772 : nFieldType == OFTInteger)
9773 : {
9774 : // ok
9775 : }
9776 0 : else if (eType == OFTInteger &&
9777 0 : (nFieldType == OFTInteger64 ||
9778 : nFieldType == OFTReal))
9779 : {
9780 0 : nFieldType = OFTInteger;
9781 0 : eSubType = OFSTNone;
9782 : }
9783 0 : else if (nFieldType != eType)
9784 : {
9785 0 : nFieldType = -1;
9786 0 : eSubType = OFSTNone;
9787 0 : break;
9788 : }
9789 : }
9790 : }
9791 : }
9792 : }
9793 : }
9794 :
9795 41 : std::unique_ptr<OGRFieldDomain> poDomain;
9796 82 : std::vector<OGRCodedValue> asValues;
9797 41 : bool error = false;
9798 82 : CPLString osLastConstraintType;
9799 41 : int nFieldTypeFromEnumCode = -1;
9800 82 : std::string osConstraintDescription;
9801 82 : std::string osDescrConstraintName("_");
9802 41 : osDescrConstraintName += name;
9803 41 : osDescrConstraintName += "_domain_description";
9804 100 : for (int iRecord = 0; iRecord < oResultTable->RowCount(); iRecord++)
9805 : {
9806 63 : const char *pszConstraintType = oResultTable->GetValue(0, iRecord);
9807 63 : if (pszConstraintType == nullptr)
9808 1 : continue;
9809 63 : const char *pszValue = oResultTable->GetValue(1, iRecord);
9810 63 : const char *pszMin = oResultTable->GetValue(2, iRecord);
9811 : const bool bIsMinIncluded =
9812 63 : oResultTable->GetValueAsInteger(3, iRecord) == 1;
9813 63 : const char *pszMax = oResultTable->GetValue(4, iRecord);
9814 : const bool bIsMaxIncluded =
9815 63 : oResultTable->GetValueAsInteger(5, iRecord) == 1;
9816 63 : const char *pszDescription = oResultTable->GetValue(6, iRecord);
9817 63 : const char *pszConstraintName = oResultTable->GetValue(7, iRecord);
9818 :
9819 63 : if (!osLastConstraintType.empty() && osLastConstraintType != "enum")
9820 : {
9821 1 : CPLError(CE_Failure, CPLE_AppDefined,
9822 : "Only constraint of type 'enum' can have multiple rows");
9823 1 : error = true;
9824 4 : break;
9825 : }
9826 :
9827 62 : if (strcmp(pszConstraintType, "enum") == 0)
9828 : {
9829 42 : if (pszValue == nullptr)
9830 : {
9831 1 : CPLError(CE_Failure, CPLE_AppDefined,
9832 : "NULL in 'value' column of enumeration");
9833 1 : error = true;
9834 1 : break;
9835 : }
9836 41 : if (osDescrConstraintName == pszConstraintName)
9837 : {
9838 1 : if (pszDescription)
9839 : {
9840 1 : osConstraintDescription = pszDescription;
9841 : }
9842 1 : continue;
9843 : }
9844 40 : if (asValues.empty())
9845 : {
9846 20 : asValues.reserve(oResultTable->RowCount() + 1);
9847 : }
9848 : OGRCodedValue cv;
9849 : // intended: the 'value' column in GPKG is actually the code
9850 40 : cv.pszCode = VSI_STRDUP_VERBOSE(pszValue);
9851 40 : if (cv.pszCode == nullptr)
9852 : {
9853 0 : error = true;
9854 0 : break;
9855 : }
9856 40 : if (pszDescription)
9857 : {
9858 29 : cv.pszValue = VSI_STRDUP_VERBOSE(pszDescription);
9859 29 : if (cv.pszValue == nullptr)
9860 : {
9861 0 : VSIFree(cv.pszCode);
9862 0 : error = true;
9863 0 : break;
9864 : }
9865 : }
9866 : else
9867 : {
9868 11 : cv.pszValue = nullptr;
9869 : }
9870 :
9871 : // If we can't get the data type from field definition, guess it
9872 : // from code.
9873 40 : if (nFieldType < 0 && nFieldTypeFromEnumCode != OFTString)
9874 : {
9875 18 : switch (CPLGetValueType(cv.pszCode))
9876 : {
9877 13 : case CPL_VALUE_INTEGER:
9878 : {
9879 13 : if (nFieldTypeFromEnumCode != OFTReal &&
9880 : nFieldTypeFromEnumCode != OFTInteger64)
9881 : {
9882 9 : const auto nVal = CPLAtoGIntBig(cv.pszCode);
9883 17 : if (nVal < std::numeric_limits<int>::min() ||
9884 8 : nVal > std::numeric_limits<int>::max())
9885 : {
9886 3 : nFieldTypeFromEnumCode = OFTInteger64;
9887 : }
9888 : else
9889 : {
9890 6 : nFieldTypeFromEnumCode = OFTInteger;
9891 : }
9892 : }
9893 13 : break;
9894 : }
9895 :
9896 3 : case CPL_VALUE_REAL:
9897 3 : nFieldTypeFromEnumCode = OFTReal;
9898 3 : break;
9899 :
9900 2 : case CPL_VALUE_STRING:
9901 2 : nFieldTypeFromEnumCode = OFTString;
9902 2 : break;
9903 : }
9904 : }
9905 :
9906 40 : asValues.emplace_back(cv);
9907 : }
9908 20 : else if (strcmp(pszConstraintType, "range") == 0)
9909 : {
9910 : OGRField sMin;
9911 : OGRField sMax;
9912 14 : OGR_RawField_SetUnset(&sMin);
9913 14 : OGR_RawField_SetUnset(&sMax);
9914 14 : if (nFieldType != OFTInteger && nFieldType != OFTInteger64)
9915 8 : nFieldType = OFTReal;
9916 27 : if (pszMin != nullptr &&
9917 13 : CPLAtof(pszMin) != -std::numeric_limits<double>::infinity())
9918 : {
9919 10 : if (nFieldType == OFTInteger)
9920 3 : sMin.Integer = atoi(pszMin);
9921 7 : else if (nFieldType == OFTInteger64)
9922 3 : sMin.Integer64 = CPLAtoGIntBig(pszMin);
9923 : else /* if( nFieldType == OFTReal ) */
9924 4 : sMin.Real = CPLAtof(pszMin);
9925 : }
9926 27 : if (pszMax != nullptr &&
9927 13 : CPLAtof(pszMax) != std::numeric_limits<double>::infinity())
9928 : {
9929 10 : if (nFieldType == OFTInteger)
9930 3 : sMax.Integer = atoi(pszMax);
9931 7 : else if (nFieldType == OFTInteger64)
9932 3 : sMax.Integer64 = CPLAtoGIntBig(pszMax);
9933 : else /* if( nFieldType == OFTReal ) */
9934 4 : sMax.Real = CPLAtof(pszMax);
9935 : }
9936 14 : poDomain = std::make_unique<OGRRangeFieldDomain>(
9937 14 : name, pszDescription ? pszDescription : "",
9938 28 : static_cast<OGRFieldType>(nFieldType), eSubType, sMin,
9939 14 : bIsMinIncluded, sMax, bIsMaxIncluded);
9940 : }
9941 6 : else if (strcmp(pszConstraintType, "glob") == 0)
9942 : {
9943 5 : if (pszValue == nullptr)
9944 : {
9945 1 : CPLError(CE_Failure, CPLE_AppDefined,
9946 : "NULL in 'value' column of glob");
9947 1 : error = true;
9948 1 : break;
9949 : }
9950 4 : if (nFieldType < 0)
9951 1 : nFieldType = OFTString;
9952 4 : poDomain = std::make_unique<OGRGlobFieldDomain>(
9953 4 : name, pszDescription ? pszDescription : "",
9954 12 : static_cast<OGRFieldType>(nFieldType), eSubType, pszValue);
9955 : }
9956 : else
9957 : {
9958 1 : CPLError(CE_Failure, CPLE_AppDefined,
9959 : "Unhandled constraint_type: %s", pszConstraintType);
9960 1 : error = true;
9961 1 : break;
9962 : }
9963 :
9964 58 : osLastConstraintType = pszConstraintType;
9965 : }
9966 :
9967 41 : if (!asValues.empty())
9968 : {
9969 20 : if (nFieldType < 0)
9970 9 : nFieldType = nFieldTypeFromEnumCode;
9971 20 : poDomain = std::make_unique<OGRCodedFieldDomain>(
9972 : name, osConstraintDescription,
9973 40 : static_cast<OGRFieldType>(nFieldType), eSubType,
9974 40 : std::move(asValues));
9975 : }
9976 :
9977 41 : if (error)
9978 : {
9979 4 : return nullptr;
9980 : }
9981 :
9982 37 : m_oMapFieldDomains[name] = std::move(poDomain);
9983 37 : return GDALDataset::GetFieldDomain(name);
9984 : }
9985 :
9986 : /************************************************************************/
9987 : /* AddFieldDomain() */
9988 : /************************************************************************/
9989 :
9990 18 : bool GDALGeoPackageDataset::AddFieldDomain(
9991 : std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
9992 : {
9993 36 : const std::string domainName(domain->GetName());
9994 18 : if (!GetUpdate())
9995 : {
9996 0 : CPLError(CE_Failure, CPLE_NotSupported,
9997 : "AddFieldDomain() not supported on read-only dataset");
9998 0 : return false;
9999 : }
10000 18 : if (GetFieldDomain(domainName) != nullptr)
10001 : {
10002 1 : failureReason = "A domain of identical name already exists";
10003 1 : return false;
10004 : }
10005 17 : if (!CreateColumnsTableAndColumnConstraintsTablesIfNecessary())
10006 0 : return false;
10007 :
10008 17 : const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
10009 17 : const char *min_is_inclusive =
10010 17 : bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
10011 17 : const char *max_is_inclusive =
10012 17 : bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
10013 :
10014 17 : const auto &osDescription = domain->GetDescription();
10015 17 : switch (domain->GetDomainType())
10016 : {
10017 11 : case OFDT_CODED:
10018 : {
10019 : const auto poCodedDomain =
10020 11 : cpl::down_cast<const OGRCodedFieldDomain *>(domain.get());
10021 11 : if (!osDescription.empty())
10022 : {
10023 : // We use a little trick by using a dummy
10024 : // _{domainname}_domain_description enum that has a single
10025 : // entry whose description is the description of the main
10026 : // domain.
10027 1 : char *pszSQL = sqlite3_mprintf(
10028 : "INSERT INTO gpkg_data_column_constraints ("
10029 : "constraint_name, constraint_type, value, "
10030 : "min, %s, max, %s, "
10031 : "description) VALUES ("
10032 : "'_%q_domain_description', 'enum', '', NULL, NULL, NULL, "
10033 : "NULL, %Q)",
10034 : min_is_inclusive, max_is_inclusive, domainName.c_str(),
10035 : osDescription.c_str());
10036 1 : CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
10037 1 : sqlite3_free(pszSQL);
10038 : }
10039 11 : const auto &enumeration = poCodedDomain->GetEnumeration();
10040 33 : for (int i = 0; enumeration[i].pszCode != nullptr; ++i)
10041 : {
10042 22 : 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', 'enum', '%q', NULL, NULL, NULL, NULL, %Q)",
10048 : min_is_inclusive, max_is_inclusive, domainName.c_str(),
10049 22 : enumeration[i].pszCode, enumeration[i].pszValue);
10050 22 : bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
10051 22 : sqlite3_free(pszSQL);
10052 22 : if (!ok)
10053 0 : return false;
10054 : }
10055 11 : break;
10056 : }
10057 :
10058 5 : case OFDT_RANGE:
10059 : {
10060 : const auto poRangeDomain =
10061 5 : cpl::down_cast<const OGRRangeFieldDomain *>(domain.get());
10062 5 : const auto eFieldType = poRangeDomain->GetFieldType();
10063 5 : if (eFieldType != OFTInteger && eFieldType != OFTInteger64 &&
10064 : eFieldType != OFTReal)
10065 : {
10066 : failureReason = "Only range domains of numeric type are "
10067 0 : "supported in GeoPackage";
10068 0 : return false;
10069 : }
10070 :
10071 5 : double dfMin = -std::numeric_limits<double>::infinity();
10072 5 : double dfMax = std::numeric_limits<double>::infinity();
10073 5 : bool bMinIsInclusive = true;
10074 5 : const auto &sMin = poRangeDomain->GetMin(bMinIsInclusive);
10075 5 : bool bMaxIsInclusive = true;
10076 5 : const auto &sMax = poRangeDomain->GetMax(bMaxIsInclusive);
10077 5 : if (eFieldType == OFTInteger)
10078 : {
10079 1 : if (!OGR_RawField_IsUnset(&sMin))
10080 1 : dfMin = sMin.Integer;
10081 1 : if (!OGR_RawField_IsUnset(&sMax))
10082 1 : dfMax = sMax.Integer;
10083 : }
10084 4 : else if (eFieldType == OFTInteger64)
10085 : {
10086 1 : if (!OGR_RawField_IsUnset(&sMin))
10087 1 : dfMin = static_cast<double>(sMin.Integer64);
10088 1 : if (!OGR_RawField_IsUnset(&sMax))
10089 1 : dfMax = static_cast<double>(sMax.Integer64);
10090 : }
10091 : else /* if( eFieldType == OFTReal ) */
10092 : {
10093 3 : if (!OGR_RawField_IsUnset(&sMin))
10094 3 : dfMin = sMin.Real;
10095 3 : if (!OGR_RawField_IsUnset(&sMax))
10096 3 : dfMax = sMax.Real;
10097 : }
10098 :
10099 5 : sqlite3_stmt *hInsertStmt = nullptr;
10100 : const char *pszSQL =
10101 5 : CPLSPrintf("INSERT INTO gpkg_data_column_constraints ("
10102 : "constraint_name, constraint_type, value, "
10103 : "min, %s, max, %s, "
10104 : "description) VALUES ("
10105 : "?, 'range', NULL, ?, ?, ?, ?, ?)",
10106 : min_is_inclusive, max_is_inclusive);
10107 5 : if (SQLPrepareWithError(hDB, pszSQL, -1, &hInsertStmt, nullptr) !=
10108 : SQLITE_OK)
10109 : {
10110 0 : return false;
10111 : }
10112 5 : sqlite3_bind_text(hInsertStmt, 1, domainName.c_str(),
10113 5 : static_cast<int>(domainName.size()),
10114 : SQLITE_TRANSIENT);
10115 5 : sqlite3_bind_double(hInsertStmt, 2, dfMin);
10116 5 : sqlite3_bind_int(hInsertStmt, 3, bMinIsInclusive ? 1 : 0);
10117 5 : sqlite3_bind_double(hInsertStmt, 4, dfMax);
10118 5 : sqlite3_bind_int(hInsertStmt, 5, bMaxIsInclusive ? 1 : 0);
10119 5 : if (osDescription.empty())
10120 : {
10121 3 : sqlite3_bind_null(hInsertStmt, 6);
10122 : }
10123 : else
10124 : {
10125 2 : sqlite3_bind_text(hInsertStmt, 6, osDescription.c_str(),
10126 2 : static_cast<int>(osDescription.size()),
10127 : SQLITE_TRANSIENT);
10128 : }
10129 5 : const int sqlite_err = sqlite3_step(hInsertStmt);
10130 5 : sqlite3_finalize(hInsertStmt);
10131 5 : if (sqlite_err != SQLITE_OK && sqlite_err != SQLITE_DONE)
10132 : {
10133 0 : CPLError(CE_Failure, CPLE_AppDefined,
10134 : "failed to execute insertion '%s': %s", pszSQL,
10135 : sqlite3_errmsg(hDB));
10136 0 : return false;
10137 : }
10138 :
10139 5 : break;
10140 : }
10141 :
10142 1 : case OFDT_GLOB:
10143 : {
10144 : const auto poGlobDomain =
10145 1 : cpl::down_cast<const OGRGlobFieldDomain *>(domain.get());
10146 2 : char *pszSQL = sqlite3_mprintf(
10147 : "INSERT INTO gpkg_data_column_constraints ("
10148 : "constraint_name, constraint_type, value, "
10149 : "min, %s, max, %s, "
10150 : "description) VALUES ("
10151 : "'%q', 'glob', '%q', NULL, NULL, NULL, NULL, %Q)",
10152 : min_is_inclusive, max_is_inclusive, domainName.c_str(),
10153 1 : poGlobDomain->GetGlob().c_str(),
10154 2 : osDescription.empty() ? nullptr : osDescription.c_str());
10155 1 : bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
10156 1 : sqlite3_free(pszSQL);
10157 1 : if (!ok)
10158 0 : return false;
10159 :
10160 1 : break;
10161 : }
10162 : }
10163 :
10164 17 : m_oMapFieldDomains[domainName] = std::move(domain);
10165 17 : return true;
10166 : }
10167 :
10168 : /************************************************************************/
10169 : /* AddRelationship() */
10170 : /************************************************************************/
10171 :
10172 24 : bool GDALGeoPackageDataset::AddRelationship(
10173 : std::unique_ptr<GDALRelationship> &&relationship,
10174 : std::string &failureReason)
10175 : {
10176 24 : if (!GetUpdate())
10177 : {
10178 0 : CPLError(CE_Failure, CPLE_NotSupported,
10179 : "AddRelationship() not supported on read-only dataset");
10180 0 : return false;
10181 : }
10182 :
10183 : const std::string osRelationshipName = GenerateNameForRelationship(
10184 24 : relationship->GetLeftTableName().c_str(),
10185 24 : relationship->GetRightTableName().c_str(),
10186 96 : relationship->GetRelatedTableType().c_str());
10187 : // sanity checks
10188 24 : if (GetRelationship(osRelationshipName) != nullptr)
10189 : {
10190 1 : failureReason = "A relationship of identical name already exists";
10191 1 : return false;
10192 : }
10193 :
10194 23 : if (!ValidateRelationship(relationship.get(), failureReason))
10195 : {
10196 14 : return false;
10197 : }
10198 :
10199 9 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
10200 : {
10201 0 : return false;
10202 : }
10203 9 : if (!CreateRelationsTableIfNecessary())
10204 : {
10205 0 : failureReason = "Could not create gpkgext_relations table";
10206 0 : return false;
10207 : }
10208 9 : if (SQLGetInteger(GetDB(),
10209 : "SELECT 1 FROM gpkg_extensions WHERE "
10210 : "table_name = 'gpkgext_relations'",
10211 9 : nullptr) != 1)
10212 : {
10213 4 : if (OGRERR_NONE !=
10214 4 : SQLCommand(
10215 : GetDB(),
10216 : "INSERT INTO gpkg_extensions "
10217 : "(table_name,column_name,extension_name,definition,scope) "
10218 : "VALUES ('gpkgext_relations', NULL, 'gpkg_related_tables', "
10219 : "'http://www.geopackage.org/18-000.html', "
10220 : "'read-write')"))
10221 : {
10222 : failureReason =
10223 0 : "Could not create gpkg_extensions entry for gpkgext_relations";
10224 0 : return false;
10225 : }
10226 : }
10227 :
10228 9 : const std::string &osLeftTableName = relationship->GetLeftTableName();
10229 9 : const std::string &osRightTableName = relationship->GetRightTableName();
10230 9 : const auto &aosLeftTableFields = relationship->GetLeftTableFields();
10231 9 : const auto &aosRightTableFields = relationship->GetRightTableFields();
10232 :
10233 18 : std::string osRelatedTableType = relationship->GetRelatedTableType();
10234 9 : if (osRelatedTableType.empty())
10235 : {
10236 5 : osRelatedTableType = "features";
10237 : }
10238 :
10239 : // generate mapping table if not set
10240 18 : CPLString osMappingTableName = relationship->GetMappingTableName();
10241 9 : if (osMappingTableName.empty())
10242 : {
10243 3 : int nIndex = 1;
10244 3 : osMappingTableName = osLeftTableName + "_" + osRightTableName;
10245 3 : while (FindLayerIndex(osMappingTableName.c_str()) >= 0)
10246 : {
10247 0 : nIndex += 1;
10248 : osMappingTableName.Printf("%s_%s_%d", osLeftTableName.c_str(),
10249 0 : osRightTableName.c_str(), nIndex);
10250 : }
10251 :
10252 : // determine whether base/related keys are unique
10253 3 : bool bBaseKeyIsUnique = false;
10254 : {
10255 : const std::set<std::string> uniqueBaseFieldsUC =
10256 : SQLGetUniqueFieldUCConstraints(GetDB(),
10257 6 : osLeftTableName.c_str());
10258 6 : if (uniqueBaseFieldsUC.find(
10259 3 : CPLString(aosLeftTableFields[0]).toupper()) !=
10260 6 : uniqueBaseFieldsUC.end())
10261 : {
10262 2 : bBaseKeyIsUnique = true;
10263 : }
10264 : }
10265 3 : bool bRelatedKeyIsUnique = false;
10266 : {
10267 : const std::set<std::string> uniqueRelatedFieldsUC =
10268 : SQLGetUniqueFieldUCConstraints(GetDB(),
10269 6 : osRightTableName.c_str());
10270 6 : if (uniqueRelatedFieldsUC.find(
10271 3 : CPLString(aosRightTableFields[0]).toupper()) !=
10272 6 : uniqueRelatedFieldsUC.end())
10273 : {
10274 2 : bRelatedKeyIsUnique = true;
10275 : }
10276 : }
10277 :
10278 : // create mapping table
10279 :
10280 3 : std::string osBaseIdDefinition = "base_id INTEGER";
10281 3 : if (bBaseKeyIsUnique)
10282 : {
10283 2 : char *pszSQL = sqlite3_mprintf(
10284 : " CONSTRAINT 'fk_base_id_%q' REFERENCES \"%w\"(\"%w\") ON "
10285 : "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
10286 : "DEFERRED",
10287 : osMappingTableName.c_str(), osLeftTableName.c_str(),
10288 2 : aosLeftTableFields[0].c_str());
10289 2 : osBaseIdDefinition += pszSQL;
10290 2 : sqlite3_free(pszSQL);
10291 : }
10292 :
10293 3 : std::string osRelatedIdDefinition = "related_id INTEGER";
10294 3 : if (bRelatedKeyIsUnique)
10295 : {
10296 2 : char *pszSQL = sqlite3_mprintf(
10297 : " CONSTRAINT 'fk_related_id_%q' REFERENCES \"%w\"(\"%w\") ON "
10298 : "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
10299 : "DEFERRED",
10300 : osMappingTableName.c_str(), osRightTableName.c_str(),
10301 2 : aosRightTableFields[0].c_str());
10302 2 : osRelatedIdDefinition += pszSQL;
10303 2 : sqlite3_free(pszSQL);
10304 : }
10305 :
10306 3 : char *pszSQL = sqlite3_mprintf("CREATE TABLE \"%w\" ("
10307 : "id INTEGER PRIMARY KEY AUTOINCREMENT, "
10308 : "%s, %s);",
10309 : osMappingTableName.c_str(),
10310 : osBaseIdDefinition.c_str(),
10311 : osRelatedIdDefinition.c_str());
10312 3 : OGRErr eErr = SQLCommand(hDB, pszSQL);
10313 3 : sqlite3_free(pszSQL);
10314 3 : if (eErr != OGRERR_NONE)
10315 : {
10316 : failureReason =
10317 0 : ("Could not create mapping table " + osMappingTableName)
10318 0 : .c_str();
10319 0 : return false;
10320 : }
10321 :
10322 : /*
10323 : * Strictly speaking we should NOT be inserting the mapping table into gpkg_contents.
10324 : * The related tables extension explicitly states that the mapping table should only be
10325 : * in the gpkgext_relations table and not in gpkg_contents. (See also discussion at
10326 : * https://github.com/opengeospatial/geopackage/issues/679).
10327 : *
10328 : * However, if we don't insert the mapping table into gpkg_contents then it is no longer
10329 : * visible to some clients (eg ESRI software only allows opening tables that are present
10330 : * in gpkg_contents). So we'll do this anyway, for maximum compatibility and flexibility.
10331 : *
10332 : * More related discussion is at https://github.com/OSGeo/gdal/pull/9258
10333 : */
10334 3 : pszSQL = sqlite3_mprintf(
10335 : "INSERT INTO gpkg_contents "
10336 : "(table_name,data_type,identifier,description,last_change,srs_id) "
10337 : "VALUES "
10338 : "('%q','attributes','%q','Mapping table for relationship between "
10339 : "%q and %q',%s,0)",
10340 : osMappingTableName.c_str(), /*table_name*/
10341 : osMappingTableName.c_str(), /*identifier*/
10342 : osLeftTableName.c_str(), /*description left table name*/
10343 : osRightTableName.c_str(), /*description right table name*/
10344 6 : GDALGeoPackageDataset::GetCurrentDateEscapedSQL().c_str());
10345 :
10346 : // Note -- we explicitly ignore failures here, because hey, we aren't really
10347 : // supposed to be adding this table to gpkg_contents anyway!
10348 3 : (void)SQLCommand(hDB, pszSQL);
10349 3 : sqlite3_free(pszSQL);
10350 :
10351 3 : pszSQL = sqlite3_mprintf(
10352 : "CREATE INDEX \"idx_%w_base_id\" ON \"%w\" (base_id);",
10353 : osMappingTableName.c_str(), osMappingTableName.c_str());
10354 3 : eErr = SQLCommand(hDB, pszSQL);
10355 3 : sqlite3_free(pszSQL);
10356 3 : if (eErr != OGRERR_NONE)
10357 : {
10358 0 : failureReason = ("Could not create index for " +
10359 0 : osMappingTableName + " (base_id)")
10360 0 : .c_str();
10361 0 : return false;
10362 : }
10363 :
10364 3 : pszSQL = sqlite3_mprintf(
10365 : "CREATE INDEX \"idx_%qw_related_id\" ON \"%w\" (related_id);",
10366 : osMappingTableName.c_str(), osMappingTableName.c_str());
10367 3 : eErr = SQLCommand(hDB, pszSQL);
10368 3 : sqlite3_free(pszSQL);
10369 3 : if (eErr != OGRERR_NONE)
10370 : {
10371 0 : failureReason = ("Could not create index for " +
10372 0 : osMappingTableName + " (related_id)")
10373 0 : .c_str();
10374 0 : return false;
10375 : }
10376 : }
10377 : else
10378 : {
10379 : // validate mapping table structure
10380 6 : if (OGRGeoPackageTableLayer *poLayer =
10381 6 : cpl::down_cast<OGRGeoPackageTableLayer *>(
10382 6 : GetLayerByName(osMappingTableName)))
10383 : {
10384 4 : if (poLayer->GetLayerDefn()->GetFieldIndex("base_id") < 0)
10385 : {
10386 : failureReason =
10387 2 : ("Field base_id must exist in " + osMappingTableName)
10388 1 : .c_str();
10389 1 : return false;
10390 : }
10391 3 : if (poLayer->GetLayerDefn()->GetFieldIndex("related_id") < 0)
10392 : {
10393 : failureReason =
10394 2 : ("Field related_id must exist in " + osMappingTableName)
10395 1 : .c_str();
10396 1 : return false;
10397 : }
10398 : }
10399 : else
10400 : {
10401 : failureReason =
10402 2 : ("Could not retrieve table " + osMappingTableName).c_str();
10403 2 : return false;
10404 : }
10405 : }
10406 :
10407 5 : char *pszSQL = sqlite3_mprintf(
10408 : "INSERT INTO gpkg_extensions "
10409 : "(table_name,column_name,extension_name,definition,scope) "
10410 : "VALUES ('%q', NULL, 'gpkg_related_tables', "
10411 : "'http://www.geopackage.org/18-000.html', "
10412 : "'read-write')",
10413 : osMappingTableName.c_str());
10414 5 : OGRErr eErr = SQLCommand(hDB, pszSQL);
10415 5 : sqlite3_free(pszSQL);
10416 5 : if (eErr != OGRERR_NONE)
10417 : {
10418 0 : failureReason = ("Could not insert mapping table " +
10419 0 : osMappingTableName + " into gpkg_extensions")
10420 0 : .c_str();
10421 0 : return false;
10422 : }
10423 :
10424 15 : pszSQL = sqlite3_mprintf(
10425 : "INSERT INTO gpkgext_relations "
10426 : "(base_table_name,base_primary_column,related_table_name,related_"
10427 : "primary_column,relation_name,mapping_table_name) "
10428 : "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
10429 5 : osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
10430 5 : osRightTableName.c_str(), aosRightTableFields[0].c_str(),
10431 : osRelatedTableType.c_str(), osMappingTableName.c_str());
10432 5 : eErr = SQLCommand(hDB, pszSQL);
10433 5 : sqlite3_free(pszSQL);
10434 5 : if (eErr != OGRERR_NONE)
10435 : {
10436 0 : failureReason = "Could not insert relationship into gpkgext_relations";
10437 0 : return false;
10438 : }
10439 :
10440 5 : ClearCachedRelationships();
10441 5 : LoadRelationships();
10442 5 : return true;
10443 : }
10444 :
10445 : /************************************************************************/
10446 : /* DeleteRelationship() */
10447 : /************************************************************************/
10448 :
10449 4 : bool GDALGeoPackageDataset::DeleteRelationship(const std::string &name,
10450 : std::string &failureReason)
10451 : {
10452 4 : if (eAccess != GA_Update)
10453 : {
10454 0 : CPLError(CE_Failure, CPLE_NotSupported,
10455 : "DeleteRelationship() not supported on read-only dataset");
10456 0 : return false;
10457 : }
10458 :
10459 : // ensure relationships are up to date before we try to remove one
10460 4 : ClearCachedRelationships();
10461 4 : LoadRelationships();
10462 :
10463 8 : std::string osMappingTableName;
10464 : {
10465 4 : const GDALRelationship *poRelationship = GetRelationship(name);
10466 4 : if (poRelationship == nullptr)
10467 : {
10468 1 : failureReason = "Could not find relationship with name " + name;
10469 1 : return false;
10470 : }
10471 :
10472 3 : osMappingTableName = poRelationship->GetMappingTableName();
10473 : }
10474 :
10475 : // DeleteLayerCommon will delete existing relationship objects, so we can't
10476 : // refer to poRelationship or any of its members previously obtained here
10477 3 : if (DeleteLayerCommon(osMappingTableName.c_str()) != OGRERR_NONE)
10478 : {
10479 : failureReason =
10480 0 : "Could not remove mapping layer name " + osMappingTableName;
10481 :
10482 : // relationships may have been left in an inconsistent state -- reload
10483 : // them now
10484 0 : ClearCachedRelationships();
10485 0 : LoadRelationships();
10486 0 : return false;
10487 : }
10488 :
10489 3 : ClearCachedRelationships();
10490 3 : LoadRelationships();
10491 3 : return true;
10492 : }
10493 :
10494 : /************************************************************************/
10495 : /* UpdateRelationship() */
10496 : /************************************************************************/
10497 :
10498 6 : bool GDALGeoPackageDataset::UpdateRelationship(
10499 : std::unique_ptr<GDALRelationship> &&relationship,
10500 : std::string &failureReason)
10501 : {
10502 6 : if (eAccess != GA_Update)
10503 : {
10504 0 : CPLError(CE_Failure, CPLE_NotSupported,
10505 : "UpdateRelationship() not supported on read-only dataset");
10506 0 : return false;
10507 : }
10508 :
10509 : // ensure relationships are up to date before we try to update one
10510 6 : ClearCachedRelationships();
10511 6 : LoadRelationships();
10512 :
10513 6 : const std::string &osRelationshipName = relationship->GetName();
10514 6 : const std::string &osLeftTableName = relationship->GetLeftTableName();
10515 6 : const std::string &osRightTableName = relationship->GetRightTableName();
10516 6 : const std::string &osMappingTableName = relationship->GetMappingTableName();
10517 6 : const auto &aosLeftTableFields = relationship->GetLeftTableFields();
10518 6 : const auto &aosRightTableFields = relationship->GetRightTableFields();
10519 :
10520 : // sanity checks
10521 : {
10522 : const GDALRelationship *poExistingRelationship =
10523 6 : GetRelationship(osRelationshipName);
10524 6 : if (poExistingRelationship == nullptr)
10525 : {
10526 : failureReason =
10527 1 : "The relationship should already exist to be updated";
10528 1 : return false;
10529 : }
10530 :
10531 5 : if (!ValidateRelationship(relationship.get(), failureReason))
10532 : {
10533 2 : return false;
10534 : }
10535 :
10536 : // we don't permit changes to the participating tables
10537 3 : if (osLeftTableName != poExistingRelationship->GetLeftTableName())
10538 : {
10539 0 : failureReason = ("Cannot change base table from " +
10540 0 : poExistingRelationship->GetLeftTableName() +
10541 0 : " to " + osLeftTableName)
10542 0 : .c_str();
10543 0 : return false;
10544 : }
10545 3 : if (osRightTableName != poExistingRelationship->GetRightTableName())
10546 : {
10547 0 : failureReason = ("Cannot change related table from " +
10548 0 : poExistingRelationship->GetRightTableName() +
10549 0 : " to " + osRightTableName)
10550 0 : .c_str();
10551 0 : return false;
10552 : }
10553 3 : if (osMappingTableName != poExistingRelationship->GetMappingTableName())
10554 : {
10555 0 : failureReason = ("Cannot change mapping table from " +
10556 0 : poExistingRelationship->GetMappingTableName() +
10557 0 : " to " + osMappingTableName)
10558 0 : .c_str();
10559 0 : return false;
10560 : }
10561 : }
10562 :
10563 6 : std::string osRelatedTableType = relationship->GetRelatedTableType();
10564 3 : if (osRelatedTableType.empty())
10565 : {
10566 0 : osRelatedTableType = "features";
10567 : }
10568 :
10569 3 : char *pszSQL = sqlite3_mprintf(
10570 : "DELETE FROM gpkgext_relations WHERE mapping_table_name='%q'",
10571 : osMappingTableName.c_str());
10572 3 : OGRErr eErr = SQLCommand(hDB, pszSQL);
10573 3 : sqlite3_free(pszSQL);
10574 3 : if (eErr != OGRERR_NONE)
10575 : {
10576 : failureReason =
10577 0 : "Could not delete old relationship from gpkgext_relations";
10578 0 : return false;
10579 : }
10580 :
10581 9 : pszSQL = sqlite3_mprintf(
10582 : "INSERT INTO gpkgext_relations "
10583 : "(base_table_name,base_primary_column,related_table_name,related_"
10584 : "primary_column,relation_name,mapping_table_name) "
10585 : "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
10586 3 : osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
10587 3 : osRightTableName.c_str(), aosRightTableFields[0].c_str(),
10588 : osRelatedTableType.c_str(), osMappingTableName.c_str());
10589 3 : eErr = SQLCommand(hDB, pszSQL);
10590 3 : sqlite3_free(pszSQL);
10591 3 : if (eErr != OGRERR_NONE)
10592 : {
10593 : failureReason =
10594 0 : "Could not insert updated relationship into gpkgext_relations";
10595 0 : return false;
10596 : }
10597 :
10598 3 : ClearCachedRelationships();
10599 3 : LoadRelationships();
10600 3 : return true;
10601 : }
10602 :
10603 : /************************************************************************/
10604 : /* GetSqliteMasterContent() */
10605 : /************************************************************************/
10606 :
10607 : const std::vector<SQLSqliteMasterContent> &
10608 2 : GDALGeoPackageDataset::GetSqliteMasterContent()
10609 : {
10610 2 : if (m_aoSqliteMasterContent.empty())
10611 : {
10612 : auto oResultTable =
10613 2 : SQLQuery(hDB, "SELECT sql, type, tbl_name FROM sqlite_master");
10614 1 : if (oResultTable)
10615 : {
10616 58 : for (int rowCnt = 0; rowCnt < oResultTable->RowCount(); ++rowCnt)
10617 : {
10618 114 : SQLSqliteMasterContent row;
10619 57 : const char *pszSQL = oResultTable->GetValue(0, rowCnt);
10620 57 : row.osSQL = pszSQL ? pszSQL : "";
10621 57 : const char *pszType = oResultTable->GetValue(1, rowCnt);
10622 57 : row.osType = pszType ? pszType : "";
10623 57 : const char *pszTableName = oResultTable->GetValue(2, rowCnt);
10624 57 : row.osTableName = pszTableName ? pszTableName : "";
10625 57 : m_aoSqliteMasterContent.emplace_back(std::move(row));
10626 : }
10627 : }
10628 : }
10629 2 : return m_aoSqliteMasterContent;
10630 : }
|