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 570 : GetTilingScheme(const char *pszName)
82 : {
83 570 : if (EQUAL(pszName, "CUSTOM"))
84 442 : 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 961 : OGRErr GDALGeoPackageDataset::SetApplicationAndUserVersionId()
191 : {
192 961 : CPLAssert(hDB != nullptr);
193 :
194 961 : const CPLString osPragma(CPLString().Printf("PRAGMA application_id = %u;"
195 : "PRAGMA user_version = %u",
196 : m_nApplicationId,
197 1922 : m_nUserVersion));
198 1922 : return SQLCommand(hDB, osPragma.c_str());
199 : }
200 :
201 2609 : bool GDALGeoPackageDataset::CloseDB()
202 : {
203 2609 : OGRSQLiteUnregisterSQLFunctions(m_pSQLFunctionData);
204 2609 : m_pSQLFunctionData = nullptr;
205 2609 : 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 830 : static OGRErr GDALGPKGImportFromEPSG(OGRSpatialReference *poSpatialRef,
222 : int nEPSGCode)
223 : {
224 830 : CPLPushErrorHandler(CPLQuietErrorHandler);
225 830 : const OGRErr eErr = poSpatialRef->importFromEPSG(nEPSGCode);
226 830 : CPLPopErrorHandler();
227 830 : CPLErrorReset();
228 830 : return eErr;
229 : }
230 :
231 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
232 1271 : GDALGeoPackageDataset::GetSpatialRef(int iSrsId, bool bFallbackToEPSG,
233 : bool bEmitErrorIfNotFound)
234 : {
235 1271 : const auto oIter = m_oMapSrsIdToSrs.find(iSrsId);
236 1271 : if (oIter != m_oMapSrsIdToSrs.end())
237 : {
238 90 : if (oIter->second == nullptr)
239 33 : return nullptr;
240 57 : oIter->second->Reference();
241 : return std::unique_ptr<OGRSpatialReference,
242 57 : OGRSpatialReferenceReleaser>(oIter->second);
243 : }
244 :
245 1181 : if (iSrsId == 0 || iSrsId == -1)
246 : {
247 119 : OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
248 119 : poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
249 :
250 : // See corresponding tests in GDALGeoPackageDataset::GetSrsId
251 119 : if (iSrsId == 0)
252 : {
253 29 : poSpatialRef->SetGeogCS("Undefined geographic SRS", "unknown",
254 : "unknown", SRS_WGS84_SEMIMAJOR,
255 : SRS_WGS84_INVFLATTENING);
256 : }
257 90 : else if (iSrsId == -1)
258 : {
259 90 : poSpatialRef->SetLocalCS("Undefined Cartesian SRS");
260 90 : poSpatialRef->SetLinearUnits(SRS_UL_METER, 1.0);
261 : }
262 :
263 119 : m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
264 119 : poSpatialRef->Reference();
265 : return std::unique_ptr<OGRSpatialReference,
266 119 : OGRSpatialReferenceReleaser>(poSpatialRef);
267 : }
268 :
269 2124 : CPLString oSQL;
270 1062 : 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 1062 : m_bHasDefinition12_063 ? ", definition_12_063" : "",
275 1062 : m_bHasEpochColumn ? ", epoch" : "", iSrsId);
276 :
277 2124 : auto oResult = SQLQuery(hDB, oSQL.c_str());
278 :
279 1062 : 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 1050 : const char *pszName = oResult->GetValue(0, 0);
306 1050 : if (pszName && EQUAL(pszName, "Undefined SRS"))
307 : {
308 456 : m_oMapSrsIdToSrs[iSrsId] = nullptr;
309 456 : return nullptr;
310 : }
311 594 : const char *pszWkt = oResult->GetValue(1, 0);
312 594 : if (pszWkt == nullptr)
313 0 : return nullptr;
314 594 : const char *pszOrganization = oResult->GetValue(2, 0);
315 594 : const char *pszOrganizationCoordsysID = oResult->GetValue(3, 0);
316 : const char *pszWkt2 =
317 594 : m_bHasDefinition12_063 ? oResult->GetValue(4, 0) : nullptr;
318 594 : if (pszWkt2 && !EQUAL(pszWkt2, "undefined"))
319 76 : pszWkt = pszWkt2;
320 : const char *pszCoordinateEpoch =
321 594 : m_bHasEpochColumn ? oResult->GetValue(5, 0) : nullptr;
322 : const double dfCoordinateEpoch =
323 594 : pszCoordinateEpoch ? CPLAtof(pszCoordinateEpoch) : 0.0;
324 :
325 594 : OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
326 594 : poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
327 : // Try to import first from EPSG code, and then from WKT
328 594 : if (!(pszOrganization && pszOrganizationCoordsysID &&
329 594 : EQUAL(pszOrganization, "EPSG") &&
330 574 : (atoi(pszOrganizationCoordsysID) == iSrsId ||
331 4 : (dfCoordinateEpoch > 0 && strstr(pszWkt, "DYNAMIC[") == nullptr)) &&
332 574 : GDALGPKGImportFromEPSG(
333 1188 : 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 594 : poSpatialRef->StripTOWGS84IfKnownDatumAndAllowed();
345 594 : poSpatialRef->SetCoordinateEpoch(dfCoordinateEpoch);
346 594 : m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
347 594 : poSpatialRef->Reference();
348 : return std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>(
349 594 : poSpatialRef);
350 : }
351 :
352 285 : const char *GDALGeoPackageDataset::GetSrsName(const OGRSpatialReference &oSRS)
353 : {
354 285 : const char *pszName = oSRS.GetName();
355 285 : if (pszName)
356 285 : 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 923 : int GDALGeoPackageDataset::GetSrsId(const OGRSpatialReference *poSRSIn)
529 : {
530 923 : const char *pszName = poSRSIn ? poSRSIn->GetName() : nullptr;
531 1333 : if (!poSRSIn || poSRSIn->IsEmpty() ||
532 410 : (pszName && EQUAL(pszName, "Undefined SRS")))
533 : {
534 515 : OGRErr err = OGRERR_NONE;
535 515 : 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 515 : if (err == OGRERR_NONE)
541 57 : 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 458 : 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 457 : 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 458 : if (SQLCommand(hDB, pszSQL) == OGRERR_NONE)
585 458 : return UNDEFINED_CRS_SRS_ID;
586 : #undef UNDEFINED_CRS_SRS_ID
587 : #undef XSTRINGIFY
588 : #undef STRINGIFY
589 0 : return -1;
590 : }
591 :
592 816 : std::unique_ptr<OGRSpatialReference> poSRS(poSRSIn->Clone());
593 :
594 408 : if (poSRS->IsGeographic() || poSRS->IsLocal())
595 : {
596 : // See corresponding tests in GDALGeoPackageDataset::GetSpatialRef
597 140 : if (pszName != nullptr && strlen(pszName) > 0)
598 : {
599 140 : if (EQUAL(pszName, "Undefined geographic SRS"))
600 2 : return 0;
601 :
602 138 : if (EQUAL(pszName, "Undefined Cartesian SRS"))
603 1 : return -1;
604 : }
605 : }
606 :
607 405 : const char *pszAuthorityName = poSRS->GetAuthorityName(nullptr);
608 :
609 405 : 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 405 : char *pszSQL = nullptr;
633 405 : int nSRSId = DEFAULT_SRID;
634 405 : int nAuthorityCode = 0;
635 405 : OGRErr err = OGRERR_NONE;
636 405 : bool bCanUseAuthorityCode = false;
637 405 : const char *const apszIsSameOptions[] = {
638 : "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES",
639 : "IGNORE_COORDINATE_EPOCH=YES", nullptr};
640 405 : if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0)
641 : {
642 379 : const char *pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
643 379 : if (pszAuthorityCode)
644 : {
645 379 : if (CPLGetValueType(pszAuthorityCode) == CPL_VALUE_INTEGER)
646 : {
647 379 : 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 784 : if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
662 379 : poSRSIn->GetCoordinateEpoch() == 0)
663 : {
664 : pszSQL =
665 374 : 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 374 : nSRSId = SQLGetInteger(hDB, pszSQL, &err);
671 374 : sqlite3_free(pszSQL);
672 :
673 : // Got a match? Return it!
674 374 : if (OGRERR_NONE == err)
675 : {
676 116 : auto poRefSRS = GetSpatialRef(nSRSId);
677 : bool bOK =
678 116 : (poRefSRS == nullptr ||
679 117 : poSRS->IsSame(poRefSRS.get(), apszIsSameOptions) ||
680 1 : !CPLTestBool(CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")));
681 116 : if (bOK)
682 : {
683 115 : 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 290 : CPLCharUniquePtr pszWKT1;
701 290 : CPLCharUniquePtr pszWKT2_2015;
702 290 : CPLCharUniquePtr pszWKT2_2019;
703 290 : const char *const apszOptionsWkt1[] = {"FORMAT=WKT1_GDAL", nullptr};
704 290 : const char *const apszOptionsWkt2_2015[] = {"FORMAT=WKT2_2015", nullptr};
705 290 : const char *const apszOptionsWkt2_2019[] = {"FORMAT=WKT2_2019", nullptr};
706 :
707 580 : std::string osEpochTest;
708 290 : if (poSRSIn->GetCoordinateEpoch() > 0 && m_bHasEpochColumn)
709 : {
710 : osEpochTest =
711 3 : CPLSPrintf(" AND epoch = %.17g", poSRSIn->GetCoordinateEpoch());
712 : }
713 :
714 571 : if (!(poSRS->IsGeographic() && poSRS->GetAxesCount() == 3) &&
715 281 : !poSRS->IsDerivedGeographic())
716 : {
717 562 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
718 281 : char *pszTmp = nullptr;
719 281 : poSRS->exportToWkt(&pszTmp, apszOptionsWkt1);
720 281 : pszWKT1.reset(pszTmp);
721 281 : if (pszWKT1 && pszWKT1.get()[0] == '\0')
722 : {
723 0 : pszWKT1.reset();
724 : }
725 : }
726 : {
727 580 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
728 290 : char *pszTmp = nullptr;
729 290 : poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2015);
730 290 : pszWKT2_2015.reset(pszTmp);
731 290 : if (pszWKT2_2015 && pszWKT2_2015.get()[0] == '\0')
732 : {
733 0 : pszWKT2_2015.reset();
734 : }
735 : }
736 : {
737 290 : char *pszTmp = nullptr;
738 290 : poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2019);
739 290 : pszWKT2_2019.reset(pszTmp);
740 290 : if (pszWKT2_2019 && pszWKT2_2019.get()[0] == '\0')
741 : {
742 0 : pszWKT2_2019.reset();
743 : }
744 : }
745 :
746 290 : if (!pszWKT1 && !pszWKT2_2015 && !pszWKT2_2019)
747 : {
748 0 : return DEFAULT_SRID;
749 : }
750 :
751 290 : if (poSRSIn->GetCoordinateEpoch() == 0 || m_bHasEpochColumn)
752 : {
753 : // Search if there is already an existing entry with this WKT
754 287 : if (m_bHasDefinition12_063 && (pszWKT2_2015 || pszWKT2_2019))
755 : {
756 42 : if (pszWKT1)
757 : {
758 144 : pszSQL = sqlite3_mprintf(
759 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
760 : "(definition = '%q' OR definition_12_063 IN ('%q','%q'))%s",
761 : pszWKT1.get(),
762 72 : pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
763 72 : pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
764 : osEpochTest.c_str());
765 : }
766 : else
767 : {
768 24 : pszSQL = sqlite3_mprintf(
769 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
770 : "definition_12_063 IN ('%q', '%q')%s",
771 12 : pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
772 12 : pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
773 : osEpochTest.c_str());
774 : }
775 : }
776 245 : else if (pszWKT1)
777 : {
778 : pszSQL =
779 242 : sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
780 : "definition = '%q'%s",
781 : pszWKT1.get(), osEpochTest.c_str());
782 : }
783 : else
784 : {
785 3 : pszSQL = nullptr;
786 : }
787 287 : if (pszSQL)
788 : {
789 284 : nSRSId = SQLGetInteger(hDB, pszSQL, &err);
790 284 : sqlite3_free(pszSQL);
791 284 : if (OGRERR_NONE == err)
792 : {
793 5 : return nSRSId;
794 : }
795 : }
796 : }
797 :
798 546 : if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
799 261 : poSRSIn->GetCoordinateEpoch() == 0)
800 : {
801 257 : bool bTryToReuseSRSId = true;
802 257 : if (EQUAL(pszAuthorityName, "EPSG"))
803 : {
804 512 : OGRSpatialReference oSRS_EPSG;
805 256 : if (GDALGPKGImportFromEPSG(&oSRS_EPSG, nAuthorityCode) ==
806 : OGRERR_NONE)
807 : {
808 257 : if (!poSRS->IsSame(&oSRS_EPSG, apszIsSameOptions) &&
809 1 : CPLTestBool(
810 : CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")))
811 : {
812 1 : bTryToReuseSRSId = false;
813 1 : CPLError(
814 : CE_Warning, CPLE_AppDefined,
815 : "Passed SRS uses %s:%d identification, but its "
816 : "definition is not compatible with the "
817 : "official definition of the object. "
818 : "Registering it as a non-%s entry into the database.",
819 : pszAuthorityName, nAuthorityCode, pszAuthorityName);
820 1 : pszAuthorityName = nullptr;
821 1 : nAuthorityCode = 0;
822 : }
823 : }
824 : }
825 257 : if (bTryToReuseSRSId)
826 : {
827 : // No match, but maybe we can use the nAuthorityCode as the nSRSId?
828 256 : pszSQL = sqlite3_mprintf(
829 : "SELECT Count(*) FROM gpkg_spatial_ref_sys WHERE "
830 : "srs_id = %d",
831 : nAuthorityCode);
832 :
833 : // Yep, we can!
834 256 : if (SQLGetInteger(hDB, pszSQL, nullptr) == 0)
835 255 : bCanUseAuthorityCode = true;
836 256 : sqlite3_free(pszSQL);
837 : }
838 : }
839 :
840 285 : bool bConvertGpkgSpatialRefSysToExtensionWkt2 = false;
841 285 : bool bForceEpoch = false;
842 288 : if (!m_bHasDefinition12_063 && pszWKT1 == nullptr &&
843 3 : (pszWKT2_2015 != nullptr || pszWKT2_2019 != nullptr))
844 : {
845 3 : bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
846 : }
847 :
848 : // Add epoch column if needed
849 285 : if (poSRSIn->GetCoordinateEpoch() > 0 && !m_bHasEpochColumn)
850 : {
851 3 : if (m_bHasDefinition12_063)
852 : {
853 0 : if (SoftStartTransaction() != OGRERR_NONE)
854 0 : return DEFAULT_SRID;
855 0 : if (SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys "
856 0 : "ADD COLUMN epoch DOUBLE") != OGRERR_NONE ||
857 0 : SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
858 : "'gpkg_crs_wkt_1_1' "
859 : "WHERE extension_name = 'gpkg_crs_wkt'") !=
860 0 : OGRERR_NONE ||
861 0 : SQLCommand(
862 : hDB,
863 : "INSERT INTO gpkg_extensions "
864 : "(table_name, column_name, extension_name, definition, "
865 : "scope) "
866 : "VALUES "
867 : "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
868 : "'http://www.geopackage.org/spec/#extension_crs_wkt', "
869 : "'read-write')") != OGRERR_NONE)
870 : {
871 0 : SoftRollbackTransaction();
872 0 : return DEFAULT_SRID;
873 : }
874 :
875 0 : if (SoftCommitTransaction() != OGRERR_NONE)
876 0 : return DEFAULT_SRID;
877 :
878 0 : m_bHasEpochColumn = true;
879 : }
880 : else
881 : {
882 3 : bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
883 3 : bForceEpoch = true;
884 : }
885 : }
886 :
887 291 : if (bConvertGpkgSpatialRefSysToExtensionWkt2 &&
888 6 : !ConvertGpkgSpatialRefSysToExtensionWkt2(bForceEpoch))
889 : {
890 0 : return DEFAULT_SRID;
891 : }
892 :
893 : // Reuse the authority code number as SRS_ID if we can
894 285 : if (bCanUseAuthorityCode)
895 : {
896 255 : nSRSId = nAuthorityCode;
897 : }
898 : // Otherwise, generate a new SRS_ID number (max + 1)
899 : else
900 : {
901 : // Get the current maximum srid in the srs table.
902 30 : const int nMaxSRSId = SQLGetInteger(
903 : hDB, "SELECT MAX(srs_id) FROM gpkg_spatial_ref_sys", nullptr);
904 30 : nSRSId = std::max(FIRST_CUSTOM_SRSID, nMaxSRSId + 1);
905 : }
906 :
907 570 : std::string osEpochColumn;
908 285 : std::string osEpochVal;
909 285 : if (poSRSIn->GetCoordinateEpoch() > 0)
910 : {
911 5 : osEpochColumn = ", epoch";
912 5 : osEpochVal = CPLSPrintf(", %.17g", poSRSIn->GetCoordinateEpoch());
913 : }
914 :
915 : // Add new SRS row to gpkg_spatial_ref_sys.
916 285 : if (m_bHasDefinition12_063)
917 : {
918 : // Force WKT2_2019 when we have a dynamic CRS and coordinate epoch
919 45 : const char *pszWKT2 = poSRSIn->IsDynamic() &&
920 10 : poSRSIn->GetCoordinateEpoch() > 0 &&
921 1 : pszWKT2_2019
922 1 : ? pszWKT2_2019.get()
923 44 : : pszWKT2_2015 ? pszWKT2_2015.get()
924 97 : : pszWKT2_2019.get();
925 :
926 45 : if (pszAuthorityName != nullptr && nAuthorityCode > 0)
927 : {
928 99 : pszSQL = sqlite3_mprintf(
929 : "INSERT INTO gpkg_spatial_ref_sys "
930 : "(srs_name,srs_id,organization,organization_coordsys_id,"
931 : "definition, definition_12_063%s) VALUES "
932 : "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
933 33 : osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId,
934 : pszAuthorityName, nAuthorityCode,
935 62 : pszWKT1 ? pszWKT1.get() : "undefined",
936 : pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
937 : }
938 : else
939 : {
940 36 : pszSQL = sqlite3_mprintf(
941 : "INSERT INTO gpkg_spatial_ref_sys "
942 : "(srs_name,srs_id,organization,organization_coordsys_id,"
943 : "definition, definition_12_063%s) VALUES "
944 : "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
945 12 : osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId, "NONE",
946 21 : nSRSId, pszWKT1 ? pszWKT1.get() : "undefined",
947 : pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
948 : }
949 : }
950 : else
951 : {
952 240 : if (pszAuthorityName != nullptr && nAuthorityCode > 0)
953 : {
954 454 : pszSQL = sqlite3_mprintf(
955 : "INSERT INTO gpkg_spatial_ref_sys "
956 : "(srs_name,srs_id,organization,organization_coordsys_id,"
957 : "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
958 227 : GetSrsName(*poSRS), nSRSId, pszAuthorityName, nAuthorityCode,
959 454 : pszWKT1 ? pszWKT1.get() : "undefined");
960 : }
961 : else
962 : {
963 26 : pszSQL = sqlite3_mprintf(
964 : "INSERT INTO gpkg_spatial_ref_sys "
965 : "(srs_name,srs_id,organization,organization_coordsys_id,"
966 : "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
967 13 : GetSrsName(*poSRS), nSRSId, "NONE", nSRSId,
968 26 : pszWKT1 ? pszWKT1.get() : "undefined");
969 : }
970 : }
971 :
972 : // Add new row to gpkg_spatial_ref_sys.
973 285 : CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
974 :
975 : // Free everything that was allocated.
976 285 : sqlite3_free(pszSQL);
977 :
978 285 : return nSRSId;
979 : }
980 :
981 : /************************************************************************/
982 : /* ~GDALGeoPackageDataset() */
983 : /************************************************************************/
984 :
985 5196 : GDALGeoPackageDataset::~GDALGeoPackageDataset()
986 : {
987 2598 : GDALGeoPackageDataset::Close();
988 5196 : }
989 :
990 : /************************************************************************/
991 : /* Close() */
992 : /************************************************************************/
993 :
994 4355 : CPLErr GDALGeoPackageDataset::Close()
995 : {
996 4355 : CPLErr eErr = CE_None;
997 4355 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
998 : {
999 1530 : if (eAccess == GA_Update && m_poParentDS == nullptr &&
1000 4128 : !m_osRasterTable.empty() && !m_bGeoTransformValid)
1001 : {
1002 3 : CPLError(CE_Failure, CPLE_AppDefined,
1003 : "Raster table %s not correctly initialized due to missing "
1004 : "call to SetGeoTransform()",
1005 : m_osRasterTable.c_str());
1006 : }
1007 :
1008 5187 : if (!IsMarkedSuppressOnClose() &&
1009 2589 : GDALGeoPackageDataset::FlushCache(true) != CE_None)
1010 : {
1011 7 : eErr = CE_Failure;
1012 : }
1013 :
1014 : // Destroy bands now since we don't want
1015 : // GDALGPKGMBTilesLikeRasterBand::FlushCache() to run after dataset
1016 : // destruction
1017 4415 : for (int i = 0; i < nBands; i++)
1018 1817 : delete papoBands[i];
1019 2598 : nBands = 0;
1020 2598 : CPLFree(papoBands);
1021 2598 : papoBands = nullptr;
1022 :
1023 : // Destroy overviews before cleaning m_hTempDB as they could still
1024 : // need it
1025 2598 : m_apoOverviewDS.clear();
1026 :
1027 2598 : if (m_poParentDS)
1028 : {
1029 325 : hDB = nullptr;
1030 : }
1031 :
1032 2598 : m_apoLayers.clear();
1033 :
1034 : std::map<int, OGRSpatialReference *>::iterator oIter =
1035 2598 : m_oMapSrsIdToSrs.begin();
1036 3769 : for (; oIter != m_oMapSrsIdToSrs.end(); ++oIter)
1037 : {
1038 1171 : OGRSpatialReference *poSRS = oIter->second;
1039 1171 : if (poSRS)
1040 713 : poSRS->Release();
1041 : }
1042 :
1043 2598 : if (!CloseDB())
1044 0 : eErr = CE_Failure;
1045 :
1046 2598 : if (OGRSQLiteBaseDataSource::Close() != CE_None)
1047 0 : eErr = CE_Failure;
1048 : }
1049 4355 : return eErr;
1050 : }
1051 :
1052 : /************************************************************************/
1053 : /* ICanIWriteBlock() */
1054 : /************************************************************************/
1055 :
1056 5696 : bool GDALGeoPackageDataset::ICanIWriteBlock()
1057 : {
1058 5696 : if (!GetUpdate())
1059 : {
1060 0 : CPLError(
1061 : CE_Failure, CPLE_NotSupported,
1062 : "IWriteBlock() not supported on dataset opened in read-only mode");
1063 0 : return false;
1064 : }
1065 :
1066 5696 : if (m_pabyCachedTiles == nullptr)
1067 : {
1068 0 : return false;
1069 : }
1070 :
1071 5696 : if (!m_bGeoTransformValid || m_nSRID == UNKNOWN_SRID)
1072 : {
1073 0 : CPLError(CE_Failure, CPLE_NotSupported,
1074 : "IWriteBlock() not supported if georeferencing not set");
1075 0 : return false;
1076 : }
1077 5696 : return true;
1078 : }
1079 :
1080 : /************************************************************************/
1081 : /* IRasterIO() */
1082 : /************************************************************************/
1083 :
1084 132 : CPLErr GDALGeoPackageDataset::IRasterIO(
1085 : GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
1086 : void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
1087 : int nBandCount, BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
1088 : GSpacing nLineSpace, GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
1089 :
1090 : {
1091 132 : CPLErr eErr = OGRSQLiteBaseDataSource::IRasterIO(
1092 : eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
1093 : eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace, nBandSpace,
1094 : psExtraArg);
1095 :
1096 : // If writing all bands, in non-shifted mode, flush all entirely written
1097 : // tiles This can avoid "stressing" the block cache with too many dirty
1098 : // blocks. Note: this logic would be useless with a per-dataset block cache.
1099 132 : if (eErr == CE_None && eRWFlag == GF_Write && nXSize == nBufXSize &&
1100 123 : nYSize == nBufYSize && nBandCount == nBands &&
1101 120 : m_nShiftXPixelsMod == 0 && m_nShiftYPixelsMod == 0)
1102 : {
1103 : auto poBand =
1104 116 : cpl::down_cast<GDALGPKGMBTilesLikeRasterBand *>(GetRasterBand(1));
1105 : int nBlockXSize, nBlockYSize;
1106 116 : poBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
1107 116 : const int nBlockXStart = DIV_ROUND_UP(nXOff, nBlockXSize);
1108 116 : const int nBlockYStart = DIV_ROUND_UP(nYOff, nBlockYSize);
1109 116 : const int nBlockXEnd = (nXOff + nXSize) / nBlockXSize;
1110 116 : const int nBlockYEnd = (nYOff + nYSize) / nBlockYSize;
1111 270 : for (int nBlockY = nBlockXStart; nBlockY < nBlockYEnd; nBlockY++)
1112 : {
1113 4371 : for (int nBlockX = nBlockYStart; nBlockX < nBlockXEnd; nBlockX++)
1114 : {
1115 : GDALRasterBlock *poBlock =
1116 4217 : poBand->AccessibleTryGetLockedBlockRef(nBlockX, nBlockY);
1117 4217 : if (poBlock)
1118 : {
1119 : // GetDirty() should be true in most situation (otherwise
1120 : // it means the block cache is under extreme pressure!)
1121 4215 : if (poBlock->GetDirty())
1122 : {
1123 : // IWriteBlock() on one band will check the dirty state
1124 : // of the corresponding blocks in other bands, to decide
1125 : // if it can call WriteTile(), so we have only to do
1126 : // that on one of the bands
1127 4215 : if (poBlock->Write() != CE_None)
1128 250 : eErr = CE_Failure;
1129 : }
1130 4215 : poBlock->DropLock();
1131 : }
1132 : }
1133 : }
1134 : }
1135 :
1136 132 : return eErr;
1137 : }
1138 :
1139 : /************************************************************************/
1140 : /* GetOGRTableLimit() */
1141 : /************************************************************************/
1142 :
1143 4240 : static int GetOGRTableLimit()
1144 : {
1145 4240 : return atoi(CPLGetConfigOption("OGR_TABLE_LIMIT", "10000"));
1146 : }
1147 :
1148 : /************************************************************************/
1149 : /* GetNameTypeMapFromSQliteMaster() */
1150 : /************************************************************************/
1151 :
1152 : const std::map<CPLString, CPLString> &
1153 1315 : GDALGeoPackageDataset::GetNameTypeMapFromSQliteMaster()
1154 : {
1155 1315 : if (!m_oMapNameToType.empty())
1156 346 : return m_oMapNameToType;
1157 :
1158 : CPLString osSQL(
1159 : "SELECT name, type FROM sqlite_master WHERE "
1160 : "type IN ('view', 'table') OR "
1161 1938 : "(name LIKE 'trigger_%_feature_count_%' AND type = 'trigger')");
1162 969 : const int nTableLimit = GetOGRTableLimit();
1163 969 : if (nTableLimit > 0)
1164 : {
1165 969 : osSQL += " LIMIT ";
1166 969 : osSQL += CPLSPrintf("%d", 1 + 3 * nTableLimit);
1167 : }
1168 :
1169 969 : auto oResult = SQLQuery(hDB, osSQL);
1170 969 : if (oResult)
1171 : {
1172 16078 : for (int i = 0; i < oResult->RowCount(); i++)
1173 : {
1174 15109 : const char *pszName = oResult->GetValue(0, i);
1175 15109 : const char *pszType = oResult->GetValue(1, i);
1176 15109 : m_oMapNameToType[CPLString(pszName).toupper()] = pszType;
1177 : }
1178 : }
1179 :
1180 969 : return m_oMapNameToType;
1181 : }
1182 :
1183 : /************************************************************************/
1184 : /* RemoveTableFromSQLiteMasterCache() */
1185 : /************************************************************************/
1186 :
1187 56 : void GDALGeoPackageDataset::RemoveTableFromSQLiteMasterCache(
1188 : const char *pszTableName)
1189 : {
1190 56 : m_oMapNameToType.erase(CPLString(pszTableName).toupper());
1191 56 : }
1192 :
1193 : /************************************************************************/
1194 : /* GetUnknownExtensionsTableSpecific() */
1195 : /************************************************************************/
1196 :
1197 : const std::map<CPLString, std::vector<GPKGExtensionDesc>> &
1198 921 : GDALGeoPackageDataset::GetUnknownExtensionsTableSpecific()
1199 : {
1200 921 : if (m_bMapTableToExtensionsBuilt)
1201 92 : return m_oMapTableToExtensions;
1202 829 : m_bMapTableToExtensionsBuilt = true;
1203 :
1204 829 : if (!HasExtensionsTable())
1205 52 : return m_oMapTableToExtensions;
1206 :
1207 : CPLString osSQL(
1208 : "SELECT table_name, extension_name, definition, scope "
1209 : "FROM gpkg_extensions WHERE "
1210 : "table_name IS NOT NULL "
1211 : "AND extension_name IS NOT NULL "
1212 : "AND definition IS NOT NULL "
1213 : "AND scope IS NOT NULL "
1214 : "AND extension_name NOT IN ('gpkg_geom_CIRCULARSTRING', "
1215 : "'gpkg_geom_COMPOUNDCURVE', 'gpkg_geom_CURVEPOLYGON', "
1216 : "'gpkg_geom_MULTICURVE', "
1217 : "'gpkg_geom_MULTISURFACE', 'gpkg_geom_CURVE', 'gpkg_geom_SURFACE', "
1218 : "'gpkg_geom_POLYHEDRALSURFACE', 'gpkg_geom_TIN', 'gpkg_geom_TRIANGLE', "
1219 : "'gpkg_rtree_index', 'gpkg_geometry_type_trigger', "
1220 : "'gpkg_srs_id_trigger', "
1221 : "'gpkg_crs_wkt', 'gpkg_crs_wkt_1_1', 'gpkg_schema', "
1222 : "'gpkg_related_tables', 'related_tables'"
1223 : #ifdef HAVE_SPATIALITE
1224 : ", 'gdal_spatialite_computed_geom_column'"
1225 : #endif
1226 1554 : ")");
1227 777 : const int nTableLimit = GetOGRTableLimit();
1228 777 : if (nTableLimit > 0)
1229 : {
1230 777 : osSQL += " LIMIT ";
1231 777 : osSQL += CPLSPrintf("%d", 1 + 10 * nTableLimit);
1232 : }
1233 :
1234 777 : auto oResult = SQLQuery(hDB, osSQL);
1235 777 : if (oResult)
1236 : {
1237 1440 : for (int i = 0; i < oResult->RowCount(); i++)
1238 : {
1239 663 : const char *pszTableName = oResult->GetValue(0, i);
1240 663 : const char *pszExtensionName = oResult->GetValue(1, i);
1241 663 : const char *pszDefinition = oResult->GetValue(2, i);
1242 663 : const char *pszScope = oResult->GetValue(3, i);
1243 663 : if (pszTableName && pszExtensionName && pszDefinition && pszScope)
1244 : {
1245 663 : GPKGExtensionDesc oDesc;
1246 663 : oDesc.osExtensionName = pszExtensionName;
1247 663 : oDesc.osDefinition = pszDefinition;
1248 663 : oDesc.osScope = pszScope;
1249 1326 : m_oMapTableToExtensions[CPLString(pszTableName).toupper()]
1250 663 : .push_back(std::move(oDesc));
1251 : }
1252 : }
1253 : }
1254 :
1255 777 : return m_oMapTableToExtensions;
1256 : }
1257 :
1258 : /************************************************************************/
1259 : /* GetContents() */
1260 : /************************************************************************/
1261 :
1262 : const std::map<CPLString, GPKGContentsDesc> &
1263 903 : GDALGeoPackageDataset::GetContents()
1264 : {
1265 903 : if (m_bMapTableToContentsBuilt)
1266 76 : return m_oMapTableToContents;
1267 827 : m_bMapTableToContentsBuilt = true;
1268 :
1269 : CPLString osSQL("SELECT table_name, data_type, identifier, "
1270 : "description, min_x, min_y, max_x, max_y "
1271 1654 : "FROM gpkg_contents");
1272 827 : const int nTableLimit = GetOGRTableLimit();
1273 827 : if (nTableLimit > 0)
1274 : {
1275 827 : osSQL += " LIMIT ";
1276 827 : osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1277 : }
1278 :
1279 827 : auto oResult = SQLQuery(hDB, osSQL);
1280 827 : if (oResult)
1281 : {
1282 1775 : for (int i = 0; i < oResult->RowCount(); i++)
1283 : {
1284 948 : const char *pszTableName = oResult->GetValue(0, i);
1285 948 : if (pszTableName == nullptr)
1286 0 : continue;
1287 948 : const char *pszDataType = oResult->GetValue(1, i);
1288 948 : const char *pszIdentifier = oResult->GetValue(2, i);
1289 948 : const char *pszDescription = oResult->GetValue(3, i);
1290 948 : const char *pszMinX = oResult->GetValue(4, i);
1291 948 : const char *pszMinY = oResult->GetValue(5, i);
1292 948 : const char *pszMaxX = oResult->GetValue(6, i);
1293 948 : const char *pszMaxY = oResult->GetValue(7, i);
1294 948 : GPKGContentsDesc oDesc;
1295 948 : if (pszDataType)
1296 948 : oDesc.osDataType = pszDataType;
1297 948 : if (pszIdentifier)
1298 948 : oDesc.osIdentifier = pszIdentifier;
1299 948 : if (pszDescription)
1300 947 : oDesc.osDescription = pszDescription;
1301 948 : if (pszMinX)
1302 635 : oDesc.osMinX = pszMinX;
1303 948 : if (pszMinY)
1304 635 : oDesc.osMinY = pszMinY;
1305 948 : if (pszMaxX)
1306 635 : oDesc.osMaxX = pszMaxX;
1307 948 : if (pszMaxY)
1308 635 : oDesc.osMaxY = pszMaxY;
1309 1896 : m_oMapTableToContents[CPLString(pszTableName).toupper()] =
1310 1896 : std::move(oDesc);
1311 : }
1312 : }
1313 :
1314 827 : return m_oMapTableToContents;
1315 : }
1316 :
1317 : /************************************************************************/
1318 : /* Open() */
1319 : /************************************************************************/
1320 :
1321 1285 : int GDALGeoPackageDataset::Open(GDALOpenInfo *poOpenInfo,
1322 : const std::string &osFilenameInZip)
1323 : {
1324 1285 : m_osFilenameInZip = osFilenameInZip;
1325 1285 : CPLAssert(m_apoLayers.empty());
1326 1285 : CPLAssert(hDB == nullptr);
1327 :
1328 1285 : SetDescription(poOpenInfo->pszFilename);
1329 2570 : CPLString osFilename(poOpenInfo->pszFilename);
1330 2570 : CPLString osSubdatasetTableName;
1331 : GByte abyHeaderLetMeHerePlease[100];
1332 1285 : const GByte *pabyHeader = poOpenInfo->pabyHeader;
1333 1285 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GPKG:"))
1334 : {
1335 248 : char **papszTokens = CSLTokenizeString2(poOpenInfo->pszFilename, ":",
1336 : CSLT_HONOURSTRINGS);
1337 248 : int nCount = CSLCount(papszTokens);
1338 248 : if (nCount < 2)
1339 : {
1340 0 : CSLDestroy(papszTokens);
1341 0 : return FALSE;
1342 : }
1343 :
1344 248 : if (nCount <= 3)
1345 : {
1346 246 : osFilename = papszTokens[1];
1347 : }
1348 : /* GPKG:C:\BLA.GPKG:foo */
1349 2 : else if (nCount == 4 && strlen(papszTokens[1]) == 1 &&
1350 2 : (papszTokens[2][0] == '/' || papszTokens[2][0] == '\\'))
1351 : {
1352 2 : osFilename = CPLString(papszTokens[1]) + ":" + papszTokens[2];
1353 : }
1354 : // GPKG:/vsicurl/http[s]://[user:passwd@]example.com[:8080]/foo.gpkg:bar
1355 0 : else if (/*nCount >= 4 && */
1356 0 : (EQUAL(papszTokens[1], "/vsicurl/http") ||
1357 0 : EQUAL(papszTokens[1], "/vsicurl/https")))
1358 : {
1359 0 : osFilename = CPLString(papszTokens[1]);
1360 0 : for (int i = 2; i < nCount - 1; i++)
1361 : {
1362 0 : osFilename += ':';
1363 0 : osFilename += papszTokens[i];
1364 : }
1365 : }
1366 248 : if (nCount >= 3)
1367 14 : osSubdatasetTableName = papszTokens[nCount - 1];
1368 :
1369 248 : CSLDestroy(papszTokens);
1370 248 : VSILFILE *fp = VSIFOpenL(osFilename, "rb");
1371 248 : if (fp != nullptr)
1372 : {
1373 248 : VSIFReadL(abyHeaderLetMeHerePlease, 1, 100, fp);
1374 248 : VSIFCloseL(fp);
1375 : }
1376 248 : pabyHeader = abyHeaderLetMeHerePlease;
1377 : }
1378 1037 : else if (poOpenInfo->pabyHeader &&
1379 1037 : STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
1380 : "SQLite format 3"))
1381 : {
1382 1030 : m_bCallUndeclareFileNotToOpen = true;
1383 1030 : GDALOpenInfoDeclareFileNotToOpen(osFilename, poOpenInfo->pabyHeader,
1384 : poOpenInfo->nHeaderBytes);
1385 : }
1386 :
1387 1285 : eAccess = poOpenInfo->eAccess;
1388 1285 : if (!m_osFilenameInZip.empty())
1389 : {
1390 2 : m_pszFilename = CPLStrdup(CPLSPrintf(
1391 : "/vsizip/{%s}/%s", osFilename.c_str(), m_osFilenameInZip.c_str()));
1392 : }
1393 : else
1394 : {
1395 1283 : m_pszFilename = CPLStrdup(osFilename);
1396 : }
1397 :
1398 1285 : if (poOpenInfo->papszOpenOptions)
1399 : {
1400 100 : CSLDestroy(papszOpenOptions);
1401 100 : papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
1402 : }
1403 :
1404 : #ifdef ENABLE_SQL_GPKG_FORMAT
1405 1285 : if (poOpenInfo->pabyHeader &&
1406 1037 : STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
1407 5 : "-- SQL GPKG") &&
1408 5 : poOpenInfo->fpL != nullptr)
1409 : {
1410 5 : if (sqlite3_open_v2(":memory:", &hDB, SQLITE_OPEN_READWRITE, nullptr) !=
1411 : SQLITE_OK)
1412 : {
1413 0 : return FALSE;
1414 : }
1415 :
1416 5 : InstallSQLFunctions();
1417 :
1418 : // Ingest the lines of the dump
1419 5 : VSIFSeekL(poOpenInfo->fpL, 0, SEEK_SET);
1420 : const char *pszLine;
1421 76 : while ((pszLine = CPLReadLineL(poOpenInfo->fpL)) != nullptr)
1422 : {
1423 71 : if (STARTS_WITH(pszLine, "--"))
1424 5 : continue;
1425 :
1426 66 : if (!SQLCheckLineIsSafe(pszLine))
1427 0 : return false;
1428 :
1429 66 : char *pszErrMsg = nullptr;
1430 66 : if (sqlite3_exec(hDB, pszLine, nullptr, nullptr, &pszErrMsg) !=
1431 : SQLITE_OK)
1432 : {
1433 0 : if (pszErrMsg)
1434 0 : CPLDebug("SQLITE", "Error %s", pszErrMsg);
1435 : }
1436 66 : sqlite3_free(pszErrMsg);
1437 5 : }
1438 : }
1439 :
1440 1280 : else if (pabyHeader != nullptr)
1441 : #endif
1442 : {
1443 1280 : if (poOpenInfo->fpL)
1444 : {
1445 : // See above comment about -wal locking for the importance of
1446 : // closing that file, prior to calling sqlite3_open()
1447 932 : VSIFCloseL(poOpenInfo->fpL);
1448 932 : poOpenInfo->fpL = nullptr;
1449 : }
1450 :
1451 : /* See if we can open the SQLite database */
1452 1280 : if (!OpenOrCreateDB(GetUpdate() ? SQLITE_OPEN_READWRITE
1453 : : SQLITE_OPEN_READONLY))
1454 2 : return FALSE;
1455 :
1456 1278 : memcpy(&m_nApplicationId, pabyHeader + knApplicationIdPos, 4);
1457 1278 : m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
1458 1278 : memcpy(&m_nUserVersion, pabyHeader + knUserVersionPos, 4);
1459 1278 : m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
1460 1278 : if (m_nApplicationId == GP10_APPLICATION_ID)
1461 : {
1462 7 : CPLDebug("GPKG", "GeoPackage v1.0");
1463 : }
1464 1271 : else if (m_nApplicationId == GP11_APPLICATION_ID)
1465 : {
1466 2 : CPLDebug("GPKG", "GeoPackage v1.1");
1467 : }
1468 1269 : else if (m_nApplicationId == GPKG_APPLICATION_ID &&
1469 1265 : m_nUserVersion >= GPKG_1_2_VERSION)
1470 : {
1471 1263 : CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
1472 1263 : (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
1473 : }
1474 : }
1475 :
1476 : /* Requirement 6: The SQLite PRAGMA integrity_check SQL command SHALL return
1477 : * “ok” */
1478 : /* http://opengis.github.io/geopackage/#_file_integrity */
1479 : /* Disable integrity check by default, since it is expensive on big files */
1480 1283 : if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_INTEGRITY_CHECK", "NO")) &&
1481 0 : OGRERR_NONE != PragmaCheck("integrity_check", "ok", 1))
1482 : {
1483 0 : CPLError(CE_Failure, CPLE_AppDefined,
1484 : "pragma integrity_check on '%s' failed", m_pszFilename);
1485 0 : return FALSE;
1486 : }
1487 :
1488 : /* Requirement 7: The SQLite PRAGMA foreign_key_check() SQL with no */
1489 : /* parameter value SHALL return an empty result set */
1490 : /* http://opengis.github.io/geopackage/#_file_integrity */
1491 : /* Disable the check by default, since it is to corrupt databases, and */
1492 : /* that causes issues to downstream software that can't open them. */
1493 1283 : if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_FOREIGN_KEY_CHECK", "NO")) &&
1494 0 : OGRERR_NONE != PragmaCheck("foreign_key_check", "", 0))
1495 : {
1496 0 : CPLError(CE_Failure, CPLE_AppDefined,
1497 : "pragma foreign_key_check on '%s' failed.", m_pszFilename);
1498 0 : return FALSE;
1499 : }
1500 :
1501 : /* Check for requirement metadata tables */
1502 : /* Requirement 10: gpkg_spatial_ref_sys must exist */
1503 : /* Requirement 13: gpkg_contents must exist */
1504 1283 : if (SQLGetInteger(hDB,
1505 : "SELECT COUNT(*) FROM sqlite_master WHERE "
1506 : "name IN ('gpkg_spatial_ref_sys', 'gpkg_contents') AND "
1507 : "type IN ('table', 'view')",
1508 1283 : nullptr) != 2)
1509 : {
1510 0 : CPLError(CE_Failure, CPLE_AppDefined,
1511 : "At least one of the required GeoPackage tables, "
1512 : "gpkg_spatial_ref_sys or gpkg_contents, is missing");
1513 0 : return FALSE;
1514 : }
1515 :
1516 1283 : DetectSpatialRefSysColumns();
1517 :
1518 : #ifdef ENABLE_GPKG_OGR_CONTENTS
1519 1283 : if (SQLGetInteger(hDB,
1520 : "SELECT 1 FROM sqlite_master WHERE "
1521 : "name = 'gpkg_ogr_contents' AND type = 'table'",
1522 1283 : nullptr) == 1)
1523 : {
1524 1275 : m_bHasGPKGOGRContents = true;
1525 : }
1526 : #endif
1527 :
1528 1283 : CheckUnknownExtensions();
1529 :
1530 1283 : int bRet = FALSE;
1531 1283 : bool bHasGPKGExtRelations = false;
1532 1283 : if (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR)
1533 : {
1534 1096 : m_bHasGPKGGeometryColumns =
1535 1096 : SQLGetInteger(hDB,
1536 : "SELECT 1 FROM sqlite_master WHERE "
1537 : "name = 'gpkg_geometry_columns' AND "
1538 : "type IN ('table', 'view')",
1539 1096 : nullptr) == 1;
1540 1096 : bHasGPKGExtRelations = HasGpkgextRelationsTable();
1541 : }
1542 1283 : if (m_bHasGPKGGeometryColumns)
1543 : {
1544 : /* Load layer definitions for all tables in gpkg_contents &
1545 : * gpkg_geometry_columns */
1546 : /* and non-spatial tables as well */
1547 : std::string osSQL =
1548 : "SELECT c.table_name, c.identifier, 1 as is_spatial, "
1549 : "g.column_name, g.geometry_type_name, g.z, g.m, c.min_x, c.min_y, "
1550 : "c.max_x, c.max_y, 1 AS is_in_gpkg_contents, "
1551 : "(SELECT type FROM sqlite_master WHERE lower(name) = "
1552 : "lower(c.table_name) AND type IN ('table', 'view')) AS object_type "
1553 : " FROM gpkg_geometry_columns g "
1554 : " JOIN gpkg_contents c ON (g.table_name = c.table_name)"
1555 : " WHERE "
1556 : " c.table_name <> 'ogr_empty_table' AND"
1557 : " c.data_type = 'features' "
1558 : // aspatial: Was the only method available in OGR 2.0 and 2.1
1559 : // attributes: GPKG 1.2 or later
1560 : "UNION ALL "
1561 : "SELECT table_name, identifier, 0 as is_spatial, NULL, NULL, 0, 0, "
1562 : "0 AS xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 1 AS "
1563 : "is_in_gpkg_contents, "
1564 : "(SELECT type FROM sqlite_master WHERE lower(name) = "
1565 : "lower(table_name) AND type IN ('table', 'view')) AS object_type "
1566 : " FROM gpkg_contents"
1567 1095 : " WHERE data_type IN ('aspatial', 'attributes') ";
1568 :
1569 2190 : const char *pszListAllTables = CSLFetchNameValueDef(
1570 1095 : poOpenInfo->papszOpenOptions, "LIST_ALL_TABLES", "AUTO");
1571 1095 : bool bHasASpatialOrAttributes = HasGDALAspatialExtension();
1572 1095 : if (!bHasASpatialOrAttributes)
1573 : {
1574 : auto oResultTable =
1575 : SQLQuery(hDB, "SELECT * FROM gpkg_contents WHERE "
1576 1094 : "data_type = 'attributes' LIMIT 1");
1577 1094 : bHasASpatialOrAttributes =
1578 1094 : (oResultTable && oResultTable->RowCount() == 1);
1579 : }
1580 1095 : if (bHasGPKGExtRelations)
1581 : {
1582 : osSQL += "UNION ALL "
1583 : "SELECT mapping_table_name, mapping_table_name, 0 as "
1584 : "is_spatial, NULL, NULL, 0, 0, 0 AS "
1585 : "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
1586 : "is_in_gpkg_contents, 'table' AS object_type "
1587 : "FROM gpkgext_relations WHERE "
1588 : "lower(mapping_table_name) NOT IN (SELECT "
1589 : "lower(table_name) FROM gpkg_contents) AND "
1590 : "EXISTS (SELECT 1 FROM sqlite_master WHERE "
1591 : "type IN ('table', 'view') AND "
1592 18 : "lower(name) = lower(mapping_table_name))";
1593 : }
1594 1095 : if (EQUAL(pszListAllTables, "YES") ||
1595 1094 : (!bHasASpatialOrAttributes && EQUAL(pszListAllTables, "AUTO")))
1596 : {
1597 : // vgpkg_ is Spatialite virtual table
1598 : osSQL +=
1599 : "UNION ALL "
1600 : "SELECT name, name, 0 as is_spatial, NULL, NULL, 0, 0, 0 AS "
1601 : "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
1602 : "is_in_gpkg_contents, type AS object_type "
1603 : "FROM sqlite_master WHERE type IN ('table', 'view') "
1604 : "AND name NOT LIKE 'gpkg_%' "
1605 : "AND name NOT LIKE 'vgpkg_%' "
1606 : "AND name NOT LIKE 'rtree_%' AND name NOT LIKE 'sqlite_%' "
1607 : // Avoid reading those views from simple_sewer_features.gpkg
1608 : "AND name NOT IN ('st_spatial_ref_sys', 'spatial_ref_sys', "
1609 : "'st_geometry_columns', 'geometry_columns') "
1610 : "AND lower(name) NOT IN (SELECT lower(table_name) FROM "
1611 1025 : "gpkg_contents)";
1612 1025 : if (bHasGPKGExtRelations)
1613 : {
1614 : osSQL += " AND lower(name) NOT IN (SELECT "
1615 : "lower(mapping_table_name) FROM "
1616 13 : "gpkgext_relations)";
1617 : }
1618 : }
1619 1095 : const int nTableLimit = GetOGRTableLimit();
1620 1095 : if (nTableLimit > 0)
1621 : {
1622 1095 : osSQL += " LIMIT ";
1623 1095 : osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1624 : }
1625 :
1626 1095 : auto oResult = SQLQuery(hDB, osSQL.c_str());
1627 1095 : if (!oResult)
1628 : {
1629 0 : return FALSE;
1630 : }
1631 :
1632 1095 : if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
1633 : {
1634 1 : CPLError(CE_Warning, CPLE_AppDefined,
1635 : "File has more than %d vector tables. "
1636 : "Limiting to first %d (can be overridden with "
1637 : "OGR_TABLE_LIMIT config option)",
1638 : nTableLimit, nTableLimit);
1639 1 : oResult->LimitRowCount(nTableLimit);
1640 : }
1641 :
1642 1095 : if (oResult->RowCount() > 0)
1643 : {
1644 978 : bRet = TRUE;
1645 :
1646 978 : m_apoLayers.reserve(oResult->RowCount());
1647 :
1648 1956 : std::map<std::string, int> oMapTableRefCount;
1649 4155 : for (int i = 0; i < oResult->RowCount(); i++)
1650 : {
1651 3177 : const char *pszTableName = oResult->GetValue(0, i);
1652 3177 : if (pszTableName == nullptr)
1653 0 : continue;
1654 3177 : if (++oMapTableRefCount[pszTableName] == 2)
1655 : {
1656 : // This should normally not happen if all constraints are
1657 : // properly set
1658 2 : CPLError(CE_Warning, CPLE_AppDefined,
1659 : "Table %s appearing several times in "
1660 : "gpkg_contents and/or gpkg_geometry_columns",
1661 : pszTableName);
1662 : }
1663 : }
1664 :
1665 1956 : std::set<std::string> oExistingLayers;
1666 4155 : for (int i = 0; i < oResult->RowCount(); i++)
1667 : {
1668 3177 : const char *pszTableName = oResult->GetValue(0, i);
1669 3177 : if (pszTableName == nullptr)
1670 2 : continue;
1671 : const bool bTableHasSeveralGeomColumns =
1672 3177 : oMapTableRefCount[pszTableName] > 1;
1673 3177 : bool bIsSpatial = CPL_TO_BOOL(oResult->GetValueAsInteger(2, i));
1674 3177 : const char *pszGeomColName = oResult->GetValue(3, i);
1675 3177 : const char *pszGeomType = oResult->GetValue(4, i);
1676 3177 : const char *pszZ = oResult->GetValue(5, i);
1677 3177 : const char *pszM = oResult->GetValue(6, i);
1678 : bool bIsInGpkgContents =
1679 3177 : CPL_TO_BOOL(oResult->GetValueAsInteger(11, i));
1680 3177 : if (!bIsInGpkgContents)
1681 44 : m_bNonSpatialTablesNonRegisteredInGpkgContentsFound = true;
1682 3177 : const char *pszObjectType = oResult->GetValue(12, i);
1683 3177 : if (pszObjectType == nullptr ||
1684 3176 : !(EQUAL(pszObjectType, "table") ||
1685 21 : EQUAL(pszObjectType, "view")))
1686 : {
1687 1 : CPLError(CE_Warning, CPLE_AppDefined,
1688 : "Table/view %s is referenced in gpkg_contents, "
1689 : "but does not exist",
1690 : pszTableName);
1691 1 : continue;
1692 : }
1693 : // Non-standard and undocumented behavior:
1694 : // if the same table appears to have several geometry columns,
1695 : // handle it for now as multiple layers named
1696 : // "table_name (geom_col_name)"
1697 : // The way we handle that might change in the future (e.g
1698 : // could be a single layer with multiple geometry columns)
1699 : std::string osLayerNameWithGeomColName =
1700 6350 : pszGeomColName ? std::string(pszTableName) + " (" +
1701 : pszGeomColName + ')'
1702 6352 : : std::string(pszTableName);
1703 3176 : if (cpl::contains(oExistingLayers, osLayerNameWithGeomColName))
1704 1 : continue;
1705 3175 : oExistingLayers.insert(osLayerNameWithGeomColName);
1706 : const std::string osLayerName =
1707 : bTableHasSeveralGeomColumns
1708 3 : ? std::move(osLayerNameWithGeomColName)
1709 6353 : : std::string(pszTableName);
1710 : auto poLayer = std::make_unique<OGRGeoPackageTableLayer>(
1711 6350 : this, osLayerName.c_str());
1712 3175 : bool bHasZ = pszZ && atoi(pszZ) > 0;
1713 3175 : bool bHasM = pszM && atoi(pszM) > 0;
1714 3175 : if (pszGeomType && EQUAL(pszGeomType, "GEOMETRY"))
1715 : {
1716 629 : if (pszZ && atoi(pszZ) == 2)
1717 7 : bHasZ = false;
1718 629 : if (pszM && atoi(pszM) == 2)
1719 6 : bHasM = false;
1720 : }
1721 3175 : poLayer->SetOpeningParameters(
1722 : pszTableName, pszObjectType, bIsInGpkgContents, bIsSpatial,
1723 : pszGeomColName, pszGeomType, bHasZ, bHasM);
1724 3175 : m_apoLayers.push_back(std::move(poLayer));
1725 : }
1726 : }
1727 : }
1728 :
1729 1283 : bool bHasTileMatrixSet = false;
1730 1283 : if (poOpenInfo->nOpenFlags & GDAL_OF_RASTER)
1731 : {
1732 574 : bHasTileMatrixSet = SQLGetInteger(hDB,
1733 : "SELECT 1 FROM sqlite_master WHERE "
1734 : "name = 'gpkg_tile_matrix_set' AND "
1735 : "type IN ('table', 'view')",
1736 : nullptr) == 1;
1737 : }
1738 1283 : if (bHasTileMatrixSet)
1739 : {
1740 : std::string osSQL =
1741 : "SELECT c.table_name, c.identifier, c.description, c.srs_id, "
1742 : "c.min_x, c.min_y, c.max_x, c.max_y, "
1743 : "tms.min_x, tms.min_y, tms.max_x, tms.max_y, c.data_type "
1744 : "FROM gpkg_contents c JOIN gpkg_tile_matrix_set tms ON "
1745 : "c.table_name = tms.table_name WHERE "
1746 572 : "data_type IN ('tiles', '2d-gridded-coverage')";
1747 572 : if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE"))
1748 : osSubdatasetTableName =
1749 2 : CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE");
1750 572 : if (!osSubdatasetTableName.empty())
1751 : {
1752 16 : char *pszTmp = sqlite3_mprintf(" AND c.table_name='%q'",
1753 : osSubdatasetTableName.c_str());
1754 16 : osSQL += pszTmp;
1755 16 : sqlite3_free(pszTmp);
1756 16 : SetPhysicalFilename(osFilename.c_str());
1757 : }
1758 572 : const int nTableLimit = GetOGRTableLimit();
1759 572 : if (nTableLimit > 0)
1760 : {
1761 572 : osSQL += " LIMIT ";
1762 572 : osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1763 : }
1764 :
1765 572 : auto oResult = SQLQuery(hDB, osSQL.c_str());
1766 572 : if (!oResult)
1767 : {
1768 0 : return FALSE;
1769 : }
1770 :
1771 572 : if (oResult->RowCount() == 0 && !osSubdatasetTableName.empty())
1772 : {
1773 1 : CPLError(CE_Failure, CPLE_AppDefined,
1774 : "Cannot find table '%s' in GeoPackage dataset",
1775 : osSubdatasetTableName.c_str());
1776 : }
1777 571 : else if (oResult->RowCount() == 1)
1778 : {
1779 274 : const char *pszTableName = oResult->GetValue(0, 0);
1780 274 : const char *pszIdentifier = oResult->GetValue(1, 0);
1781 274 : const char *pszDescription = oResult->GetValue(2, 0);
1782 274 : const char *pszSRSId = oResult->GetValue(3, 0);
1783 274 : const char *pszMinX = oResult->GetValue(4, 0);
1784 274 : const char *pszMinY = oResult->GetValue(5, 0);
1785 274 : const char *pszMaxX = oResult->GetValue(6, 0);
1786 274 : const char *pszMaxY = oResult->GetValue(7, 0);
1787 274 : const char *pszTMSMinX = oResult->GetValue(8, 0);
1788 274 : const char *pszTMSMinY = oResult->GetValue(9, 0);
1789 274 : const char *pszTMSMaxX = oResult->GetValue(10, 0);
1790 274 : const char *pszTMSMaxY = oResult->GetValue(11, 0);
1791 274 : const char *pszDataType = oResult->GetValue(12, 0);
1792 274 : if (pszTableName && pszTMSMinX && pszTMSMinY && pszTMSMaxX &&
1793 : pszTMSMaxY)
1794 : {
1795 548 : bRet = OpenRaster(
1796 : pszTableName, pszIdentifier, pszDescription,
1797 274 : pszSRSId ? atoi(pszSRSId) : 0, CPLAtof(pszTMSMinX),
1798 : CPLAtof(pszTMSMinY), CPLAtof(pszTMSMaxX),
1799 : CPLAtof(pszTMSMaxY), pszMinX, pszMinY, pszMaxX, pszMaxY,
1800 274 : EQUAL(pszDataType, "tiles"), poOpenInfo->papszOpenOptions);
1801 : }
1802 : }
1803 297 : else if (oResult->RowCount() >= 1)
1804 : {
1805 5 : bRet = TRUE;
1806 :
1807 5 : if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
1808 : {
1809 1 : CPLError(CE_Warning, CPLE_AppDefined,
1810 : "File has more than %d raster tables. "
1811 : "Limiting to first %d (can be overridden with "
1812 : "OGR_TABLE_LIMIT config option)",
1813 : nTableLimit, nTableLimit);
1814 1 : oResult->LimitRowCount(nTableLimit);
1815 : }
1816 :
1817 5 : int nSDSCount = 0;
1818 2013 : for (int i = 0; i < oResult->RowCount(); i++)
1819 : {
1820 2008 : const char *pszTableName = oResult->GetValue(0, i);
1821 2008 : const char *pszIdentifier = oResult->GetValue(1, i);
1822 2008 : if (pszTableName == nullptr)
1823 0 : continue;
1824 : m_aosSubDatasets.AddNameValue(
1825 : CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
1826 2008 : CPLSPrintf("GPKG:%s:%s", m_pszFilename, pszTableName));
1827 : m_aosSubDatasets.AddNameValue(
1828 : CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
1829 : pszIdentifier
1830 2008 : ? CPLSPrintf("%s - %s", pszTableName, pszIdentifier)
1831 4016 : : pszTableName);
1832 2008 : nSDSCount++;
1833 : }
1834 : }
1835 : }
1836 :
1837 1283 : if (!bRet && (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR))
1838 : {
1839 33 : if ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE))
1840 : {
1841 22 : bRet = TRUE;
1842 : }
1843 : else
1844 : {
1845 11 : CPLDebug("GPKG",
1846 : "This GeoPackage has no vector content and is opened "
1847 : "in read-only mode. If you open it in update mode, "
1848 : "opening will be successful.");
1849 : }
1850 : }
1851 :
1852 1283 : if (eAccess == GA_Update)
1853 : {
1854 259 : FixupWrongRTreeTrigger();
1855 259 : FixupWrongMedataReferenceColumnNameUpdate();
1856 : }
1857 :
1858 1283 : SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
1859 :
1860 1283 : return bRet;
1861 : }
1862 :
1863 : /************************************************************************/
1864 : /* DetectSpatialRefSysColumns() */
1865 : /************************************************************************/
1866 :
1867 1293 : void GDALGeoPackageDataset::DetectSpatialRefSysColumns()
1868 : {
1869 : // Detect definition_12_063 column
1870 : {
1871 1293 : sqlite3_stmt *hSQLStmt = nullptr;
1872 1293 : int rc = sqlite3_prepare_v2(
1873 : hDB, "SELECT definition_12_063 FROM gpkg_spatial_ref_sys ", -1,
1874 : &hSQLStmt, nullptr);
1875 1293 : if (rc == SQLITE_OK)
1876 : {
1877 85 : m_bHasDefinition12_063 = true;
1878 85 : sqlite3_finalize(hSQLStmt);
1879 : }
1880 : }
1881 :
1882 : // Detect epoch column
1883 1293 : if (m_bHasDefinition12_063)
1884 : {
1885 85 : sqlite3_stmt *hSQLStmt = nullptr;
1886 : int rc =
1887 85 : sqlite3_prepare_v2(hDB, "SELECT epoch FROM gpkg_spatial_ref_sys ",
1888 : -1, &hSQLStmt, nullptr);
1889 85 : if (rc == SQLITE_OK)
1890 : {
1891 76 : m_bHasEpochColumn = true;
1892 76 : sqlite3_finalize(hSQLStmt);
1893 : }
1894 : }
1895 1293 : }
1896 :
1897 : /************************************************************************/
1898 : /* FixupWrongRTreeTrigger() */
1899 : /************************************************************************/
1900 :
1901 259 : void GDALGeoPackageDataset::FixupWrongRTreeTrigger()
1902 : {
1903 : auto oResult = SQLQuery(
1904 : hDB,
1905 : "SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND "
1906 259 : "NAME LIKE 'rtree_%_update3' AND sql LIKE '% AFTER UPDATE OF % ON %'");
1907 259 : if (oResult == nullptr)
1908 0 : return;
1909 259 : if (oResult->RowCount() > 0)
1910 : {
1911 1 : CPLDebug("GPKG", "Fixing incorrect trigger(s) related to RTree");
1912 : }
1913 261 : for (int i = 0; i < oResult->RowCount(); i++)
1914 : {
1915 2 : const char *pszName = oResult->GetValue(0, i);
1916 2 : const char *pszSQL = oResult->GetValue(1, i);
1917 2 : const char *pszPtr1 = strstr(pszSQL, " AFTER UPDATE OF ");
1918 2 : if (pszPtr1)
1919 : {
1920 2 : const char *pszPtr = pszPtr1 + strlen(" AFTER UPDATE OF ");
1921 : // Skipping over geometry column name
1922 4 : while (*pszPtr == ' ')
1923 2 : pszPtr++;
1924 2 : if (pszPtr[0] == '"' || pszPtr[0] == '\'')
1925 : {
1926 1 : char chStringDelim = pszPtr[0];
1927 1 : pszPtr++;
1928 9 : while (*pszPtr != '\0' && *pszPtr != chStringDelim)
1929 : {
1930 8 : if (*pszPtr == '\\' && pszPtr[1] == chStringDelim)
1931 0 : pszPtr += 2;
1932 : else
1933 8 : pszPtr += 1;
1934 : }
1935 1 : if (*pszPtr == chStringDelim)
1936 1 : pszPtr++;
1937 : }
1938 : else
1939 : {
1940 1 : pszPtr++;
1941 8 : while (*pszPtr != ' ')
1942 7 : pszPtr++;
1943 : }
1944 2 : if (*pszPtr == ' ')
1945 : {
1946 2 : SQLCommand(hDB,
1947 4 : ("DROP TRIGGER \"" + SQLEscapeName(pszName) + "\"")
1948 : .c_str());
1949 4 : CPLString newSQL;
1950 2 : newSQL.assign(pszSQL, pszPtr1 - pszSQL);
1951 2 : newSQL += " AFTER UPDATE";
1952 2 : newSQL += pszPtr;
1953 2 : SQLCommand(hDB, newSQL);
1954 : }
1955 : }
1956 : }
1957 : }
1958 :
1959 : /************************************************************************/
1960 : /* FixupWrongMedataReferenceColumnNameUpdate() */
1961 : /************************************************************************/
1962 :
1963 259 : void GDALGeoPackageDataset::FixupWrongMedataReferenceColumnNameUpdate()
1964 : {
1965 : // Fix wrong trigger that was generated by GDAL < 2.4.0
1966 : // See https://github.com/qgis/QGIS/issues/42768
1967 : auto oResult = SQLQuery(
1968 : hDB, "SELECT sql FROM sqlite_master WHERE type = 'trigger' AND "
1969 : "NAME ='gpkg_metadata_reference_column_name_update' AND "
1970 259 : "sql LIKE '%column_nameIS%'");
1971 259 : if (oResult == nullptr)
1972 0 : return;
1973 259 : if (oResult->RowCount() == 1)
1974 : {
1975 1 : CPLDebug("GPKG", "Fixing incorrect trigger "
1976 : "gpkg_metadata_reference_column_name_update");
1977 1 : const char *pszSQL = oResult->GetValue(0, 0);
1978 : std::string osNewSQL(
1979 3 : CPLString(pszSQL).replaceAll("column_nameIS", "column_name IS"));
1980 :
1981 1 : SQLCommand(hDB,
1982 : "DROP TRIGGER gpkg_metadata_reference_column_name_update");
1983 1 : SQLCommand(hDB, osNewSQL.c_str());
1984 : }
1985 : }
1986 :
1987 : /************************************************************************/
1988 : /* ClearCachedRelationships() */
1989 : /************************************************************************/
1990 :
1991 36 : void GDALGeoPackageDataset::ClearCachedRelationships()
1992 : {
1993 36 : m_bHasPopulatedRelationships = false;
1994 36 : m_osMapRelationships.clear();
1995 36 : }
1996 :
1997 : /************************************************************************/
1998 : /* LoadRelationships() */
1999 : /************************************************************************/
2000 :
2001 86 : void GDALGeoPackageDataset::LoadRelationships() const
2002 : {
2003 86 : m_osMapRelationships.clear();
2004 :
2005 86 : std::vector<std::string> oExcludedTables;
2006 86 : if (HasGpkgextRelationsTable())
2007 : {
2008 37 : LoadRelationshipsUsingRelatedTablesExtension();
2009 :
2010 89 : for (const auto &oRelationship : m_osMapRelationships)
2011 : {
2012 : oExcludedTables.emplace_back(
2013 52 : oRelationship.second->GetMappingTableName());
2014 : }
2015 : }
2016 :
2017 : // Also load relationships defined using foreign keys (i.e. one-to-many
2018 : // relationships). Here we must exclude any relationships defined from the
2019 : // related tables extension, we don't want them included twice.
2020 86 : LoadRelationshipsFromForeignKeys(oExcludedTables);
2021 86 : m_bHasPopulatedRelationships = true;
2022 86 : }
2023 :
2024 : /************************************************************************/
2025 : /* LoadRelationshipsUsingRelatedTablesExtension() */
2026 : /************************************************************************/
2027 :
2028 37 : void GDALGeoPackageDataset::LoadRelationshipsUsingRelatedTablesExtension() const
2029 : {
2030 37 : m_osMapRelationships.clear();
2031 :
2032 : auto oResultTable = SQLQuery(
2033 37 : hDB, "SELECT base_table_name, base_primary_column, "
2034 : "related_table_name, related_primary_column, relation_name, "
2035 74 : "mapping_table_name FROM gpkgext_relations");
2036 37 : if (oResultTable && oResultTable->RowCount() > 0)
2037 : {
2038 86 : for (int i = 0; i < oResultTable->RowCount(); i++)
2039 : {
2040 53 : const char *pszBaseTableName = oResultTable->GetValue(0, i);
2041 53 : if (!pszBaseTableName)
2042 : {
2043 0 : CPLError(CE_Warning, CPLE_AppDefined,
2044 : "Could not retrieve base_table_name from "
2045 : "gpkgext_relations");
2046 1 : continue;
2047 : }
2048 53 : const char *pszBasePrimaryColumn = oResultTable->GetValue(1, i);
2049 53 : if (!pszBasePrimaryColumn)
2050 : {
2051 0 : CPLError(CE_Warning, CPLE_AppDefined,
2052 : "Could not retrieve base_primary_column from "
2053 : "gpkgext_relations");
2054 0 : continue;
2055 : }
2056 53 : const char *pszRelatedTableName = oResultTable->GetValue(2, i);
2057 53 : if (!pszRelatedTableName)
2058 : {
2059 0 : CPLError(CE_Warning, CPLE_AppDefined,
2060 : "Could not retrieve related_table_name from "
2061 : "gpkgext_relations");
2062 0 : continue;
2063 : }
2064 53 : const char *pszRelatedPrimaryColumn = oResultTable->GetValue(3, i);
2065 53 : if (!pszRelatedPrimaryColumn)
2066 : {
2067 0 : CPLError(CE_Warning, CPLE_AppDefined,
2068 : "Could not retrieve related_primary_column from "
2069 : "gpkgext_relations");
2070 0 : continue;
2071 : }
2072 53 : const char *pszRelationName = oResultTable->GetValue(4, i);
2073 53 : if (!pszRelationName)
2074 : {
2075 0 : CPLError(
2076 : CE_Warning, CPLE_AppDefined,
2077 : "Could not retrieve relation_name from gpkgext_relations");
2078 0 : continue;
2079 : }
2080 53 : const char *pszMappingTableName = oResultTable->GetValue(5, i);
2081 53 : if (!pszMappingTableName)
2082 : {
2083 0 : CPLError(CE_Warning, CPLE_AppDefined,
2084 : "Could not retrieve mapping_table_name from "
2085 : "gpkgext_relations");
2086 0 : continue;
2087 : }
2088 :
2089 : // confirm that mapping table exists
2090 : char *pszSQL =
2091 53 : sqlite3_mprintf("SELECT 1 FROM sqlite_master WHERE "
2092 : "name='%q' AND type IN ('table', 'view')",
2093 : pszMappingTableName);
2094 53 : const int nMappingTableCount = SQLGetInteger(hDB, pszSQL, nullptr);
2095 53 : sqlite3_free(pszSQL);
2096 :
2097 55 : if (nMappingTableCount < 1 &&
2098 2 : !const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
2099 2 : pszMappingTableName))
2100 : {
2101 1 : CPLError(CE_Warning, CPLE_AppDefined,
2102 : "Relationship mapping table %s does not exist",
2103 : pszMappingTableName);
2104 1 : continue;
2105 : }
2106 :
2107 : const std::string osRelationName = GenerateNameForRelationship(
2108 104 : pszBaseTableName, pszRelatedTableName, pszRelationName);
2109 :
2110 104 : std::string osType{};
2111 : // defined requirement classes -- for these types the relation name
2112 : // will be specific string value from the related tables extension.
2113 : // In this case we need to construct a unique relationship name
2114 : // based on the related tables
2115 52 : if (EQUAL(pszRelationName, "media") ||
2116 40 : EQUAL(pszRelationName, "simple_attributes") ||
2117 40 : EQUAL(pszRelationName, "features") ||
2118 18 : EQUAL(pszRelationName, "attributes") ||
2119 2 : EQUAL(pszRelationName, "tiles"))
2120 : {
2121 50 : osType = pszRelationName;
2122 : }
2123 : else
2124 : {
2125 : // user defined types default to features
2126 2 : osType = "features";
2127 : }
2128 :
2129 : auto poRelationship = std::make_unique<GDALRelationship>(
2130 : osRelationName, pszBaseTableName, pszRelatedTableName,
2131 104 : GRC_MANY_TO_MANY);
2132 :
2133 104 : poRelationship->SetLeftTableFields({pszBasePrimaryColumn});
2134 104 : poRelationship->SetRightTableFields({pszRelatedPrimaryColumn});
2135 104 : poRelationship->SetLeftMappingTableFields({"base_id"});
2136 104 : poRelationship->SetRightMappingTableFields({"related_id"});
2137 52 : poRelationship->SetMappingTableName(pszMappingTableName);
2138 52 : poRelationship->SetRelatedTableType(osType);
2139 :
2140 52 : m_osMapRelationships[osRelationName] = std::move(poRelationship);
2141 : }
2142 : }
2143 37 : }
2144 :
2145 : /************************************************************************/
2146 : /* GenerateNameForRelationship() */
2147 : /************************************************************************/
2148 :
2149 76 : std::string GDALGeoPackageDataset::GenerateNameForRelationship(
2150 : const char *pszBaseTableName, const char *pszRelatedTableName,
2151 : const char *pszType)
2152 : {
2153 : // defined requirement classes -- for these types the relation name will be
2154 : // specific string value from the related tables extension. In this case we
2155 : // need to construct a unique relationship name based on the related tables
2156 76 : if (EQUAL(pszType, "media") || EQUAL(pszType, "simple_attributes") ||
2157 53 : EQUAL(pszType, "features") || EQUAL(pszType, "attributes") ||
2158 8 : EQUAL(pszType, "tiles"))
2159 : {
2160 136 : std::ostringstream stream;
2161 : stream << pszBaseTableName << '_' << pszRelatedTableName << '_'
2162 68 : << pszType;
2163 68 : return stream.str();
2164 : }
2165 : else
2166 : {
2167 : // user defined types default to features
2168 8 : return pszType;
2169 : }
2170 : }
2171 :
2172 : /************************************************************************/
2173 : /* ValidateRelationship() */
2174 : /************************************************************************/
2175 :
2176 28 : bool GDALGeoPackageDataset::ValidateRelationship(
2177 : const GDALRelationship *poRelationship, std::string &failureReason)
2178 : {
2179 :
2180 28 : if (poRelationship->GetCardinality() !=
2181 : GDALRelationshipCardinality::GRC_MANY_TO_MANY)
2182 : {
2183 3 : failureReason = "Only many to many relationships are supported";
2184 3 : return false;
2185 : }
2186 :
2187 50 : std::string osRelatedTableType = poRelationship->GetRelatedTableType();
2188 65 : if (!osRelatedTableType.empty() && osRelatedTableType != "features" &&
2189 30 : osRelatedTableType != "media" &&
2190 20 : osRelatedTableType != "simple_attributes" &&
2191 55 : osRelatedTableType != "attributes" && osRelatedTableType != "tiles")
2192 : {
2193 : failureReason =
2194 4 : ("Related table type " + osRelatedTableType +
2195 : " is not a valid value for the GeoPackage specification. "
2196 : "Valid values are: features, media, simple_attributes, "
2197 : "attributes, tiles.")
2198 2 : .c_str();
2199 2 : return false;
2200 : }
2201 :
2202 23 : const std::string &osLeftTableName = poRelationship->GetLeftTableName();
2203 23 : OGRGeoPackageLayer *poLeftTable = cpl::down_cast<OGRGeoPackageLayer *>(
2204 23 : GetLayerByName(osLeftTableName.c_str()));
2205 23 : if (!poLeftTable)
2206 : {
2207 4 : failureReason = ("Left table " + osLeftTableName +
2208 : " is not an existing layer in the dataset")
2209 2 : .c_str();
2210 2 : return false;
2211 : }
2212 21 : const std::string &osRightTableName = poRelationship->GetRightTableName();
2213 21 : OGRGeoPackageLayer *poRightTable = cpl::down_cast<OGRGeoPackageLayer *>(
2214 21 : GetLayerByName(osRightTableName.c_str()));
2215 21 : if (!poRightTable)
2216 : {
2217 4 : failureReason = ("Right table " + osRightTableName +
2218 : " is not an existing layer in the dataset")
2219 2 : .c_str();
2220 2 : return false;
2221 : }
2222 :
2223 19 : const auto &aosLeftTableFields = poRelationship->GetLeftTableFields();
2224 19 : if (aosLeftTableFields.empty())
2225 : {
2226 1 : failureReason = "No left table fields were specified";
2227 1 : return false;
2228 : }
2229 18 : else if (aosLeftTableFields.size() > 1)
2230 : {
2231 : failureReason = "Only a single left table field is permitted for the "
2232 1 : "GeoPackage specification";
2233 1 : return false;
2234 : }
2235 : else
2236 : {
2237 : // validate left field exists
2238 34 : if (poLeftTable->GetLayerDefn()->GetFieldIndex(
2239 37 : aosLeftTableFields[0].c_str()) < 0 &&
2240 3 : !EQUAL(poLeftTable->GetFIDColumn(), aosLeftTableFields[0].c_str()))
2241 : {
2242 2 : failureReason = ("Left table field " + aosLeftTableFields[0] +
2243 2 : " does not exist in " + osLeftTableName)
2244 1 : .c_str();
2245 1 : return false;
2246 : }
2247 : }
2248 :
2249 16 : const auto &aosRightTableFields = poRelationship->GetRightTableFields();
2250 16 : if (aosRightTableFields.empty())
2251 : {
2252 1 : failureReason = "No right table fields were specified";
2253 1 : return false;
2254 : }
2255 15 : else if (aosRightTableFields.size() > 1)
2256 : {
2257 : failureReason = "Only a single right table field is permitted for the "
2258 1 : "GeoPackage specification";
2259 1 : return false;
2260 : }
2261 : else
2262 : {
2263 : // validate right field exists
2264 28 : if (poRightTable->GetLayerDefn()->GetFieldIndex(
2265 32 : aosRightTableFields[0].c_str()) < 0 &&
2266 4 : !EQUAL(poRightTable->GetFIDColumn(),
2267 : aosRightTableFields[0].c_str()))
2268 : {
2269 4 : failureReason = ("Right table field " + aosRightTableFields[0] +
2270 4 : " does not exist in " + osRightTableName)
2271 2 : .c_str();
2272 2 : return false;
2273 : }
2274 : }
2275 :
2276 12 : return true;
2277 : }
2278 :
2279 : /************************************************************************/
2280 : /* InitRaster() */
2281 : /************************************************************************/
2282 :
2283 358 : bool GDALGeoPackageDataset::InitRaster(
2284 : GDALGeoPackageDataset *poParentDS, const char *pszTableName, double dfMinX,
2285 : double dfMinY, double dfMaxX, double dfMaxY, const char *pszContentsMinX,
2286 : const char *pszContentsMinY, const char *pszContentsMaxX,
2287 : const char *pszContentsMaxY, char **papszOpenOptionsIn,
2288 : const SQLResult &oResult, int nIdxInResult)
2289 : {
2290 358 : m_osRasterTable = pszTableName;
2291 358 : m_dfTMSMinX = dfMinX;
2292 358 : m_dfTMSMaxY = dfMaxY;
2293 :
2294 : // Despite prior checking, the type might be Binary and
2295 : // SQLResultGetValue() not working properly on it
2296 358 : int nZoomLevel = atoi(oResult.GetValue(0, nIdxInResult));
2297 358 : if (nZoomLevel < 0 || nZoomLevel > 65536)
2298 : {
2299 0 : return false;
2300 : }
2301 358 : double dfPixelXSize = CPLAtof(oResult.GetValue(1, nIdxInResult));
2302 358 : double dfPixelYSize = CPLAtof(oResult.GetValue(2, nIdxInResult));
2303 358 : if (dfPixelXSize <= 0 || dfPixelYSize <= 0)
2304 : {
2305 0 : return false;
2306 : }
2307 358 : int nTileWidth = atoi(oResult.GetValue(3, nIdxInResult));
2308 358 : int nTileHeight = atoi(oResult.GetValue(4, nIdxInResult));
2309 358 : if (nTileWidth <= 0 || nTileWidth > 65536 || nTileHeight <= 0 ||
2310 : nTileHeight > 65536)
2311 : {
2312 0 : return false;
2313 : }
2314 : int nTileMatrixWidth = static_cast<int>(
2315 716 : std::min(static_cast<GIntBig>(INT_MAX),
2316 358 : CPLAtoGIntBig(oResult.GetValue(5, nIdxInResult))));
2317 : int nTileMatrixHeight = static_cast<int>(
2318 716 : std::min(static_cast<GIntBig>(INT_MAX),
2319 358 : CPLAtoGIntBig(oResult.GetValue(6, nIdxInResult))));
2320 358 : if (nTileMatrixWidth <= 0 || nTileMatrixHeight <= 0)
2321 : {
2322 0 : return false;
2323 : }
2324 :
2325 : /* Use content bounds in priority over tile_matrix_set bounds */
2326 358 : double dfGDALMinX = dfMinX;
2327 358 : double dfGDALMinY = dfMinY;
2328 358 : double dfGDALMaxX = dfMaxX;
2329 358 : double dfGDALMaxY = dfMaxY;
2330 : pszContentsMinX =
2331 358 : CSLFetchNameValueDef(papszOpenOptionsIn, "MINX", pszContentsMinX);
2332 : pszContentsMinY =
2333 358 : CSLFetchNameValueDef(papszOpenOptionsIn, "MINY", pszContentsMinY);
2334 : pszContentsMaxX =
2335 358 : CSLFetchNameValueDef(papszOpenOptionsIn, "MAXX", pszContentsMaxX);
2336 : pszContentsMaxY =
2337 358 : CSLFetchNameValueDef(papszOpenOptionsIn, "MAXY", pszContentsMaxY);
2338 358 : if (pszContentsMinX != nullptr && pszContentsMinY != nullptr &&
2339 358 : pszContentsMaxX != nullptr && pszContentsMaxY != nullptr)
2340 : {
2341 715 : if (CPLAtof(pszContentsMinX) < CPLAtof(pszContentsMaxX) &&
2342 357 : CPLAtof(pszContentsMinY) < CPLAtof(pszContentsMaxY))
2343 : {
2344 357 : dfGDALMinX = CPLAtof(pszContentsMinX);
2345 357 : dfGDALMinY = CPLAtof(pszContentsMinY);
2346 357 : dfGDALMaxX = CPLAtof(pszContentsMaxX);
2347 357 : dfGDALMaxY = CPLAtof(pszContentsMaxY);
2348 : }
2349 : else
2350 : {
2351 1 : CPLError(CE_Warning, CPLE_AppDefined,
2352 : "Illegal min_x/min_y/max_x/max_y values for %s in open "
2353 : "options and/or gpkg_contents. Using bounds of "
2354 : "gpkg_tile_matrix_set instead",
2355 : pszTableName);
2356 : }
2357 : }
2358 358 : if (dfGDALMinX >= dfGDALMaxX || dfGDALMinY >= dfGDALMaxY)
2359 : {
2360 0 : CPLError(CE_Failure, CPLE_AppDefined,
2361 : "Illegal min_x/min_y/max_x/max_y values for %s", pszTableName);
2362 0 : return false;
2363 : }
2364 :
2365 358 : int nBandCount = 0;
2366 : const char *pszBAND_COUNT =
2367 358 : CSLFetchNameValue(papszOpenOptionsIn, "BAND_COUNT");
2368 358 : if (poParentDS)
2369 : {
2370 86 : nBandCount = poParentDS->GetRasterCount();
2371 : }
2372 272 : else if (m_eDT != GDT_UInt8)
2373 : {
2374 65 : if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO") &&
2375 0 : !EQUAL(pszBAND_COUNT, "1"))
2376 : {
2377 0 : CPLError(CE_Warning, CPLE_AppDefined,
2378 : "BAND_COUNT ignored for non-Byte data");
2379 : }
2380 65 : nBandCount = 1;
2381 : }
2382 : else
2383 : {
2384 207 : if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO"))
2385 : {
2386 69 : nBandCount = atoi(pszBAND_COUNT);
2387 69 : if (nBandCount == 1)
2388 5 : GetMetadata("IMAGE_STRUCTURE");
2389 : }
2390 : else
2391 : {
2392 138 : GetMetadata("IMAGE_STRUCTURE");
2393 138 : nBandCount = m_nBandCountFromMetadata;
2394 138 : if (nBandCount == 1)
2395 39 : m_eTF = GPKG_TF_PNG;
2396 : }
2397 207 : if (nBandCount == 1 && !m_osTFFromMetadata.empty())
2398 : {
2399 2 : m_eTF = GDALGPKGMBTilesGetTileFormat(m_osTFFromMetadata.c_str());
2400 : }
2401 207 : if (nBandCount <= 0 || nBandCount > 4)
2402 85 : nBandCount = 4;
2403 : }
2404 :
2405 358 : return InitRaster(poParentDS, pszTableName, nZoomLevel, nBandCount, dfMinX,
2406 : dfMaxY, dfPixelXSize, dfPixelYSize, nTileWidth,
2407 : nTileHeight, nTileMatrixWidth, nTileMatrixHeight,
2408 358 : dfGDALMinX, dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
2409 : }
2410 :
2411 : /************************************************************************/
2412 : /* ComputeTileAndPixelShifts() */
2413 : /************************************************************************/
2414 :
2415 784 : bool GDALGeoPackageDataset::ComputeTileAndPixelShifts()
2416 : {
2417 : int nTileWidth, nTileHeight;
2418 784 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
2419 :
2420 : // Compute shift between GDAL origin and TileMatrixSet origin
2421 784 : const double dfShiftXPixels = (m_gt[0] - m_dfTMSMinX) / m_gt[1];
2422 784 : if (!(dfShiftXPixels / nTileWidth >= INT_MIN &&
2423 781 : dfShiftXPixels / nTileWidth < INT_MAX))
2424 : {
2425 3 : return false;
2426 : }
2427 781 : const int64_t nShiftXPixels =
2428 781 : static_cast<int64_t>(floor(0.5 + dfShiftXPixels));
2429 781 : m_nShiftXTiles = static_cast<int>(nShiftXPixels / nTileWidth);
2430 781 : if (nShiftXPixels < 0 && (nShiftXPixels % nTileWidth) != 0)
2431 11 : m_nShiftXTiles--;
2432 781 : m_nShiftXPixelsMod =
2433 781 : (static_cast<int>(nShiftXPixels % nTileWidth) + nTileWidth) %
2434 : nTileWidth;
2435 :
2436 781 : const double dfShiftYPixels = (m_gt[3] - m_dfTMSMaxY) / m_gt[5];
2437 781 : if (!(dfShiftYPixels / nTileHeight >= INT_MIN &&
2438 781 : dfShiftYPixels / nTileHeight < INT_MAX))
2439 : {
2440 1 : return false;
2441 : }
2442 780 : const int64_t nShiftYPixels =
2443 780 : static_cast<int64_t>(floor(0.5 + dfShiftYPixels));
2444 780 : m_nShiftYTiles = static_cast<int>(nShiftYPixels / nTileHeight);
2445 780 : if (nShiftYPixels < 0 && (nShiftYPixels % nTileHeight) != 0)
2446 11 : m_nShiftYTiles--;
2447 780 : m_nShiftYPixelsMod =
2448 780 : (static_cast<int>(nShiftYPixels % nTileHeight) + nTileHeight) %
2449 : nTileHeight;
2450 780 : return true;
2451 : }
2452 :
2453 : /************************************************************************/
2454 : /* AllocCachedTiles() */
2455 : /************************************************************************/
2456 :
2457 780 : bool GDALGeoPackageDataset::AllocCachedTiles()
2458 : {
2459 : int nTileWidth, nTileHeight;
2460 780 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
2461 :
2462 : // We currently need 4 caches because of
2463 : // GDALGPKGMBTilesLikePseudoDataset::ReadTile(int nRow, int nCol)
2464 780 : const int nCacheCount = 4;
2465 : /*
2466 : (m_nShiftXPixelsMod != 0 || m_nShiftYPixelsMod != 0) ? 4 :
2467 : (GetUpdate() && m_eDT == GDT_UInt8) ? 2 : 1;
2468 : */
2469 780 : m_pabyCachedTiles = static_cast<GByte *>(VSI_MALLOC3_VERBOSE(
2470 : cpl::fits_on<int>(nCacheCount * (m_eDT == GDT_UInt8 ? 4 : 1) *
2471 : m_nDTSize),
2472 : nTileWidth, nTileHeight));
2473 780 : if (m_pabyCachedTiles == nullptr)
2474 : {
2475 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too big tiles: %d x %d",
2476 : nTileWidth, nTileHeight);
2477 0 : return false;
2478 : }
2479 :
2480 780 : return true;
2481 : }
2482 :
2483 : /************************************************************************/
2484 : /* InitRaster() */
2485 : /************************************************************************/
2486 :
2487 597 : bool GDALGeoPackageDataset::InitRaster(
2488 : GDALGeoPackageDataset *poParentDS, const char *pszTableName, int nZoomLevel,
2489 : int nBandCount, double dfTMSMinX, double dfTMSMaxY, double dfPixelXSize,
2490 : double dfPixelYSize, int nTileWidth, int nTileHeight, int nTileMatrixWidth,
2491 : int nTileMatrixHeight, double dfGDALMinX, double dfGDALMinY,
2492 : double dfGDALMaxX, double dfGDALMaxY)
2493 : {
2494 597 : m_osRasterTable = pszTableName;
2495 597 : m_dfTMSMinX = dfTMSMinX;
2496 597 : m_dfTMSMaxY = dfTMSMaxY;
2497 597 : m_nZoomLevel = nZoomLevel;
2498 597 : m_nTileMatrixWidth = nTileMatrixWidth;
2499 597 : m_nTileMatrixHeight = nTileMatrixHeight;
2500 :
2501 597 : m_bGeoTransformValid = true;
2502 597 : m_gt[0] = dfGDALMinX;
2503 597 : m_gt[1] = dfPixelXSize;
2504 597 : m_gt[3] = dfGDALMaxY;
2505 597 : m_gt[5] = -dfPixelYSize;
2506 597 : double dfRasterXSize = 0.5 + (dfGDALMaxX - dfGDALMinX) / dfPixelXSize;
2507 597 : double dfRasterYSize = 0.5 + (dfGDALMaxY - dfGDALMinY) / dfPixelYSize;
2508 597 : if (dfRasterXSize > INT_MAX || dfRasterYSize > INT_MAX)
2509 : {
2510 0 : CPLError(CE_Failure, CPLE_NotSupported, "Too big raster: %f x %f",
2511 : dfRasterXSize, dfRasterYSize);
2512 0 : return false;
2513 : }
2514 597 : nRasterXSize = std::max(1, static_cast<int>(dfRasterXSize));
2515 597 : nRasterYSize = std::max(1, static_cast<int>(dfRasterYSize));
2516 :
2517 597 : if (poParentDS)
2518 : {
2519 325 : m_poParentDS = poParentDS;
2520 325 : eAccess = poParentDS->eAccess;
2521 325 : hDB = poParentDS->hDB;
2522 325 : m_eTF = poParentDS->m_eTF;
2523 325 : m_eDT = poParentDS->m_eDT;
2524 325 : m_nDTSize = poParentDS->m_nDTSize;
2525 325 : m_dfScale = poParentDS->m_dfScale;
2526 325 : m_dfOffset = poParentDS->m_dfOffset;
2527 325 : m_dfPrecision = poParentDS->m_dfPrecision;
2528 325 : m_usGPKGNull = poParentDS->m_usGPKGNull;
2529 325 : m_nQuality = poParentDS->m_nQuality;
2530 325 : m_nZLevel = poParentDS->m_nZLevel;
2531 325 : m_bDither = poParentDS->m_bDither;
2532 : /*m_nSRID = poParentDS->m_nSRID;*/
2533 325 : m_osWHERE = poParentDS->m_osWHERE;
2534 325 : SetDescription(CPLSPrintf("%s - zoom_level=%d",
2535 325 : poParentDS->GetDescription(), m_nZoomLevel));
2536 : }
2537 :
2538 2091 : for (int i = 1; i <= nBandCount; i++)
2539 : {
2540 : auto poNewBand = std::make_unique<GDALGeoPackageRasterBand>(
2541 1494 : this, nTileWidth, nTileHeight);
2542 1494 : if (poParentDS)
2543 : {
2544 761 : int bHasNoData = FALSE;
2545 : double dfNoDataValue =
2546 761 : poParentDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
2547 761 : if (bHasNoData)
2548 24 : poNewBand->SetNoDataValueInternal(dfNoDataValue);
2549 : }
2550 :
2551 1494 : if (nBandCount == 1 && m_poCTFromMetadata)
2552 : {
2553 3 : poNewBand->AssignColorTable(m_poCTFromMetadata.get());
2554 : }
2555 1494 : if (!m_osNodataValueFromMetadata.empty())
2556 : {
2557 8 : poNewBand->SetNoDataValueInternal(
2558 : CPLAtof(m_osNodataValueFromMetadata.c_str()));
2559 : }
2560 :
2561 1494 : SetBand(i, std::move(poNewBand));
2562 : }
2563 :
2564 597 : if (!ComputeTileAndPixelShifts())
2565 : {
2566 3 : CPLError(CE_Failure, CPLE_AppDefined,
2567 : "Overflow occurred in ComputeTileAndPixelShifts()");
2568 3 : return false;
2569 : }
2570 :
2571 594 : GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
2572 594 : GDALPamDataset::SetMetadataItem("ZOOM_LEVEL",
2573 : CPLSPrintf("%d", m_nZoomLevel));
2574 :
2575 594 : return AllocCachedTiles();
2576 : }
2577 :
2578 : /************************************************************************/
2579 : /* GDALGPKGMBTilesGetTileFormat() */
2580 : /************************************************************************/
2581 :
2582 80 : GPKGTileFormat GDALGPKGMBTilesGetTileFormat(const char *pszTF)
2583 : {
2584 80 : GPKGTileFormat eTF = GPKG_TF_PNG_JPEG;
2585 80 : if (pszTF)
2586 : {
2587 80 : if (EQUAL(pszTF, "PNG_JPEG") || EQUAL(pszTF, "AUTO"))
2588 1 : eTF = GPKG_TF_PNG_JPEG;
2589 79 : else if (EQUAL(pszTF, "PNG"))
2590 46 : eTF = GPKG_TF_PNG;
2591 33 : else if (EQUAL(pszTF, "PNG8"))
2592 6 : eTF = GPKG_TF_PNG8;
2593 27 : else if (EQUAL(pszTF, "JPEG"))
2594 14 : eTF = GPKG_TF_JPEG;
2595 13 : else if (EQUAL(pszTF, "WEBP"))
2596 13 : eTF = GPKG_TF_WEBP;
2597 : else
2598 : {
2599 0 : CPLError(CE_Failure, CPLE_NotSupported,
2600 : "Unsuppoted value for TILE_FORMAT: %s", pszTF);
2601 : }
2602 : }
2603 80 : return eTF;
2604 : }
2605 :
2606 28 : const char *GDALMBTilesGetTileFormatName(GPKGTileFormat eTF)
2607 : {
2608 28 : switch (eTF)
2609 : {
2610 26 : case GPKG_TF_PNG:
2611 : case GPKG_TF_PNG8:
2612 26 : return "png";
2613 1 : case GPKG_TF_JPEG:
2614 1 : return "jpg";
2615 1 : case GPKG_TF_WEBP:
2616 1 : return "webp";
2617 0 : default:
2618 0 : break;
2619 : }
2620 0 : CPLError(CE_Failure, CPLE_NotSupported,
2621 : "Unsuppoted value for TILE_FORMAT: %d", static_cast<int>(eTF));
2622 0 : return nullptr;
2623 : }
2624 :
2625 : /************************************************************************/
2626 : /* OpenRaster() */
2627 : /************************************************************************/
2628 :
2629 274 : bool GDALGeoPackageDataset::OpenRaster(
2630 : const char *pszTableName, const char *pszIdentifier,
2631 : const char *pszDescription, int nSRSId, double dfMinX, double dfMinY,
2632 : double dfMaxX, double dfMaxY, const char *pszContentsMinX,
2633 : const char *pszContentsMinY, const char *pszContentsMaxX,
2634 : const char *pszContentsMaxY, bool bIsTiles, char **papszOpenOptionsIn)
2635 : {
2636 274 : if (dfMinX >= dfMaxX || dfMinY >= dfMaxY)
2637 0 : return false;
2638 :
2639 : // Config option just for debug, and for example force set to NaN
2640 : // which is not supported
2641 548 : CPLString osDataNull = CPLGetConfigOption("GPKG_NODATA", "");
2642 548 : CPLString osUom;
2643 548 : CPLString osFieldName;
2644 548 : CPLString osGridCellEncoding;
2645 274 : if (!bIsTiles)
2646 : {
2647 65 : char *pszSQL = sqlite3_mprintf(
2648 : "SELECT datatype, scale, offset, data_null, precision FROM "
2649 : "gpkg_2d_gridded_coverage_ancillary "
2650 : "WHERE tile_matrix_set_name = '%q' "
2651 : "AND datatype IN ('integer', 'float')"
2652 : "AND (scale > 0 OR scale IS NULL)",
2653 : pszTableName);
2654 65 : auto oResult = SQLQuery(hDB, pszSQL);
2655 65 : sqlite3_free(pszSQL);
2656 65 : if (!oResult || oResult->RowCount() == 0)
2657 : {
2658 0 : return false;
2659 : }
2660 65 : const char *pszDataType = oResult->GetValue(0, 0);
2661 65 : const char *pszScale = oResult->GetValue(1, 0);
2662 65 : const char *pszOffset = oResult->GetValue(2, 0);
2663 65 : const char *pszDataNull = oResult->GetValue(3, 0);
2664 65 : const char *pszPrecision = oResult->GetValue(4, 0);
2665 65 : if (pszDataNull)
2666 23 : osDataNull = pszDataNull;
2667 65 : if (EQUAL(pszDataType, "float"))
2668 : {
2669 6 : SetDataType(GDT_Float32);
2670 6 : m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
2671 : }
2672 : else
2673 : {
2674 59 : SetDataType(GDT_Float32);
2675 59 : m_eTF = GPKG_TF_PNG_16BIT;
2676 59 : const double dfScale = pszScale ? CPLAtof(pszScale) : 1.0;
2677 59 : const double dfOffset = pszOffset ? CPLAtof(pszOffset) : 0.0;
2678 59 : if (dfScale == 1.0)
2679 : {
2680 59 : if (dfOffset == 0.0)
2681 : {
2682 24 : SetDataType(GDT_UInt16);
2683 : }
2684 35 : else if (dfOffset == -32768.0)
2685 : {
2686 35 : SetDataType(GDT_Int16);
2687 : }
2688 : // coverity[tainted_data]
2689 0 : else if (dfOffset == -32767.0 && !osDataNull.empty() &&
2690 0 : CPLAtof(osDataNull) == 65535.0)
2691 : // Given that we will map the nodata value to -32768
2692 : {
2693 0 : SetDataType(GDT_Int16);
2694 : }
2695 : }
2696 :
2697 : // Check that the tile offset and scales are compatible of a
2698 : // final integer result.
2699 59 : if (m_eDT != GDT_Float32)
2700 : {
2701 : // coverity[tainted_data]
2702 59 : if (dfScale == 1.0 && dfOffset == -32768.0 &&
2703 118 : !osDataNull.empty() && CPLAtof(osDataNull) == 65535.0)
2704 : {
2705 : // Given that we will map the nodata value to -32768
2706 9 : pszSQL = sqlite3_mprintf(
2707 : "SELECT 1 FROM "
2708 : "gpkg_2d_gridded_tile_ancillary WHERE "
2709 : "tpudt_name = '%q' "
2710 : "AND NOT ((offset = 0.0 or offset = 1.0) "
2711 : "AND scale = 1.0) "
2712 : "LIMIT 1",
2713 : pszTableName);
2714 : }
2715 : else
2716 : {
2717 50 : pszSQL = sqlite3_mprintf(
2718 : "SELECT 1 FROM "
2719 : "gpkg_2d_gridded_tile_ancillary WHERE "
2720 : "tpudt_name = '%q' "
2721 : "AND NOT (offset = 0.0 AND scale = 1.0) LIMIT 1",
2722 : pszTableName);
2723 : }
2724 59 : sqlite3_stmt *hSQLStmt = nullptr;
2725 : int rc =
2726 59 : SQLPrepareWithError(hDB, pszSQL, -1, &hSQLStmt, nullptr);
2727 :
2728 59 : if (rc == SQLITE_OK)
2729 : {
2730 59 : if (sqlite3_step(hSQLStmt) == SQLITE_ROW)
2731 : {
2732 8 : SetDataType(GDT_Float32);
2733 : }
2734 59 : sqlite3_finalize(hSQLStmt);
2735 : }
2736 59 : sqlite3_free(pszSQL);
2737 : }
2738 :
2739 59 : SetGlobalOffsetScale(dfOffset, dfScale);
2740 : }
2741 65 : if (pszPrecision)
2742 65 : m_dfPrecision = CPLAtof(pszPrecision);
2743 :
2744 : // Request those columns in a separate query, so as to keep
2745 : // compatibility with pre OGC 17-066r1 databases
2746 : pszSQL =
2747 65 : sqlite3_mprintf("SELECT uom, field_name, grid_cell_encoding FROM "
2748 : "gpkg_2d_gridded_coverage_ancillary "
2749 : "WHERE tile_matrix_set_name = '%q'",
2750 : pszTableName);
2751 65 : CPLPushErrorHandler(CPLQuietErrorHandler);
2752 65 : oResult = SQLQuery(hDB, pszSQL);
2753 65 : CPLPopErrorHandler();
2754 65 : sqlite3_free(pszSQL);
2755 65 : if (oResult && oResult->RowCount() == 1)
2756 : {
2757 64 : const char *pszUom = oResult->GetValue(0, 0);
2758 64 : if (pszUom)
2759 2 : osUom = pszUom;
2760 64 : const char *pszFieldName = oResult->GetValue(1, 0);
2761 64 : if (pszFieldName)
2762 64 : osFieldName = pszFieldName;
2763 64 : const char *pszGridCellEncoding = oResult->GetValue(2, 0);
2764 64 : if (pszGridCellEncoding)
2765 64 : osGridCellEncoding = pszGridCellEncoding;
2766 : }
2767 : }
2768 :
2769 274 : m_bRecordInsertedInGPKGContent = true;
2770 274 : m_nSRID = nSRSId;
2771 :
2772 547 : if (auto poSRS = GetSpatialRef(nSRSId))
2773 : {
2774 273 : m_oSRS = *(poSRS.get());
2775 : }
2776 :
2777 : /* Various sanity checks added in the SELECT */
2778 274 : char *pszQuotedTableName = sqlite3_mprintf("'%q'", pszTableName);
2779 548 : CPLString osQuotedTableName(pszQuotedTableName);
2780 274 : sqlite3_free(pszQuotedTableName);
2781 274 : char *pszSQL = sqlite3_mprintf(
2782 : "SELECT zoom_level, pixel_x_size, pixel_y_size, tile_width, "
2783 : "tile_height, matrix_width, matrix_height "
2784 : "FROM gpkg_tile_matrix tm "
2785 : "WHERE table_name = %s "
2786 : // INT_MAX would be the theoretical maximum value to avoid
2787 : // overflows, but that's already a insane value.
2788 : "AND zoom_level >= 0 AND zoom_level <= 65536 "
2789 : "AND pixel_x_size > 0 AND pixel_y_size > 0 "
2790 : "AND tile_width >= 1 AND tile_width <= 65536 "
2791 : "AND tile_height >= 1 AND tile_height <= 65536 "
2792 : "AND matrix_width >= 1 AND matrix_height >= 1",
2793 : osQuotedTableName.c_str());
2794 548 : CPLString osSQL(pszSQL);
2795 : const char *pszZoomLevel =
2796 274 : CSLFetchNameValue(papszOpenOptionsIn, "ZOOM_LEVEL");
2797 274 : if (pszZoomLevel)
2798 : {
2799 5 : if (GetUpdate())
2800 1 : osSQL += CPLSPrintf(" AND zoom_level <= %d", atoi(pszZoomLevel));
2801 : else
2802 : {
2803 : osSQL += CPLSPrintf(
2804 : " AND (zoom_level = %d OR (zoom_level < %d AND EXISTS(SELECT 1 "
2805 : "FROM %s WHERE zoom_level = tm.zoom_level LIMIT 1)))",
2806 : atoi(pszZoomLevel), atoi(pszZoomLevel),
2807 4 : osQuotedTableName.c_str());
2808 : }
2809 : }
2810 : // In read-only mode, only lists non empty zoom levels
2811 269 : else if (!GetUpdate())
2812 : {
2813 : osSQL += CPLSPrintf(" AND EXISTS(SELECT 1 FROM %s WHERE zoom_level = "
2814 : "tm.zoom_level LIMIT 1)",
2815 215 : osQuotedTableName.c_str());
2816 : }
2817 : else // if( pszZoomLevel == nullptr )
2818 : {
2819 : osSQL +=
2820 : CPLSPrintf(" AND zoom_level <= (SELECT MAX(zoom_level) FROM %s)",
2821 54 : osQuotedTableName.c_str());
2822 : }
2823 274 : osSQL += " ORDER BY zoom_level DESC";
2824 : // To avoid denial of service.
2825 274 : osSQL += " LIMIT 100";
2826 :
2827 548 : auto oResult = SQLQuery(hDB, osSQL.c_str());
2828 274 : if (!oResult || oResult->RowCount() == 0)
2829 : {
2830 114 : if (oResult && oResult->RowCount() == 0 && pszContentsMinX != nullptr &&
2831 114 : pszContentsMinY != nullptr && pszContentsMaxX != nullptr &&
2832 : pszContentsMaxY != nullptr)
2833 : {
2834 56 : osSQL = pszSQL;
2835 56 : osSQL += " ORDER BY zoom_level DESC";
2836 56 : if (!GetUpdate())
2837 30 : osSQL += " LIMIT 1";
2838 56 : oResult = SQLQuery(hDB, osSQL.c_str());
2839 : }
2840 57 : if (!oResult || oResult->RowCount() == 0)
2841 : {
2842 1 : if (oResult && pszZoomLevel != nullptr)
2843 : {
2844 1 : CPLError(CE_Failure, CPLE_AppDefined,
2845 : "ZOOM_LEVEL is probably not valid w.r.t tile "
2846 : "table content");
2847 : }
2848 1 : sqlite3_free(pszSQL);
2849 1 : return false;
2850 : }
2851 : }
2852 273 : sqlite3_free(pszSQL);
2853 :
2854 : // If USE_TILE_EXTENT=YES, then query the tile table to find which tiles
2855 : // actually exist.
2856 :
2857 : // CAUTION: Do not move those variables inside inner scope !
2858 546 : CPLString osContentsMinX, osContentsMinY, osContentsMaxX, osContentsMaxY;
2859 :
2860 273 : if (CPLTestBool(
2861 : CSLFetchNameValueDef(papszOpenOptionsIn, "USE_TILE_EXTENT", "NO")))
2862 : {
2863 13 : pszSQL = sqlite3_mprintf(
2864 : "SELECT MIN(tile_column), MIN(tile_row), MAX(tile_column), "
2865 : "MAX(tile_row) FROM \"%w\" WHERE zoom_level = %d",
2866 : pszTableName, atoi(oResult->GetValue(0, 0)));
2867 13 : auto oResult2 = SQLQuery(hDB, pszSQL);
2868 13 : sqlite3_free(pszSQL);
2869 26 : if (!oResult2 || oResult2->RowCount() == 0 ||
2870 : // Can happen if table is empty
2871 38 : oResult2->GetValue(0, 0) == nullptr ||
2872 : // Can happen if table has no NOT NULL constraint on tile_row
2873 : // and that all tile_row are NULL
2874 12 : oResult2->GetValue(1, 0) == nullptr)
2875 : {
2876 1 : return false;
2877 : }
2878 12 : const double dfPixelXSize = CPLAtof(oResult->GetValue(1, 0));
2879 12 : const double dfPixelYSize = CPLAtof(oResult->GetValue(2, 0));
2880 12 : const int nTileWidth = atoi(oResult->GetValue(3, 0));
2881 12 : const int nTileHeight = atoi(oResult->GetValue(4, 0));
2882 : osContentsMinX =
2883 24 : CPLSPrintf("%.17g", dfMinX + dfPixelXSize * nTileWidth *
2884 12 : atoi(oResult2->GetValue(0, 0)));
2885 : osContentsMaxY =
2886 24 : CPLSPrintf("%.17g", dfMaxY - dfPixelYSize * nTileHeight *
2887 12 : atoi(oResult2->GetValue(1, 0)));
2888 : osContentsMaxX = CPLSPrintf(
2889 24 : "%.17g", dfMinX + dfPixelXSize * nTileWidth *
2890 12 : (1 + atoi(oResult2->GetValue(2, 0))));
2891 : osContentsMinY = CPLSPrintf(
2892 24 : "%.17g", dfMaxY - dfPixelYSize * nTileHeight *
2893 12 : (1 + atoi(oResult2->GetValue(3, 0))));
2894 12 : pszContentsMinX = osContentsMinX.c_str();
2895 12 : pszContentsMinY = osContentsMinY.c_str();
2896 12 : pszContentsMaxX = osContentsMaxX.c_str();
2897 12 : pszContentsMaxY = osContentsMaxY.c_str();
2898 : }
2899 :
2900 272 : if (!InitRaster(nullptr, pszTableName, dfMinX, dfMinY, dfMaxX, dfMaxY,
2901 : pszContentsMinX, pszContentsMinY, pszContentsMaxX,
2902 272 : pszContentsMaxY, papszOpenOptionsIn, *oResult, 0))
2903 : {
2904 3 : return false;
2905 : }
2906 :
2907 269 : auto poBand = cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(1));
2908 269 : if (!osDataNull.empty())
2909 : {
2910 23 : double dfGPKGNoDataValue = CPLAtof(osDataNull);
2911 23 : if (m_eTF == GPKG_TF_PNG_16BIT)
2912 : {
2913 21 : if (dfGPKGNoDataValue < 0 || dfGPKGNoDataValue > 65535 ||
2914 21 : static_cast<int>(dfGPKGNoDataValue) != dfGPKGNoDataValue)
2915 : {
2916 0 : CPLError(CE_Warning, CPLE_AppDefined,
2917 : "data_null = %.17g is invalid for integer data_type",
2918 : dfGPKGNoDataValue);
2919 : }
2920 : else
2921 : {
2922 21 : m_usGPKGNull = static_cast<GUInt16>(dfGPKGNoDataValue);
2923 21 : if (m_eDT == GDT_Int16 && m_usGPKGNull > 32767)
2924 9 : dfGPKGNoDataValue = -32768.0;
2925 12 : else if (m_eDT == GDT_Float32)
2926 : {
2927 : // Pick a value that is unlikely to be hit with offset &
2928 : // scale
2929 4 : dfGPKGNoDataValue = -std::numeric_limits<float>::max();
2930 : }
2931 21 : poBand->SetNoDataValueInternal(dfGPKGNoDataValue);
2932 : }
2933 : }
2934 : else
2935 : {
2936 2 : poBand->SetNoDataValueInternal(
2937 2 : static_cast<float>(dfGPKGNoDataValue));
2938 : }
2939 : }
2940 269 : if (!osUom.empty())
2941 : {
2942 2 : poBand->SetUnitTypeInternal(osUom);
2943 : }
2944 269 : if (!osFieldName.empty())
2945 : {
2946 64 : GetRasterBand(1)->GDALRasterBand::SetDescription(osFieldName);
2947 : }
2948 269 : if (!osGridCellEncoding.empty())
2949 : {
2950 64 : if (osGridCellEncoding == "grid-value-is-center")
2951 : {
2952 15 : GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
2953 : GDALMD_AOP_POINT);
2954 : }
2955 49 : else if (osGridCellEncoding == "grid-value-is-area")
2956 : {
2957 45 : GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
2958 : GDALMD_AOP_AREA);
2959 : }
2960 : else
2961 : {
2962 4 : GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
2963 : GDALMD_AOP_POINT);
2964 4 : GetRasterBand(1)->GDALRasterBand::SetMetadataItem(
2965 : "GRID_CELL_ENCODING", osGridCellEncoding);
2966 : }
2967 : }
2968 :
2969 269 : CheckUnknownExtensions(true);
2970 :
2971 : // Do this after CheckUnknownExtensions() so that m_eTF is set to
2972 : // GPKG_TF_WEBP if the table already registers the gpkg_webp extension
2973 269 : const char *pszTF = CSLFetchNameValue(papszOpenOptionsIn, "TILE_FORMAT");
2974 269 : if (pszTF)
2975 : {
2976 4 : if (!GetUpdate())
2977 : {
2978 0 : CPLError(CE_Warning, CPLE_AppDefined,
2979 : "TILE_FORMAT open option ignored in read-only mode");
2980 : }
2981 4 : else if (m_eTF == GPKG_TF_PNG_16BIT ||
2982 4 : m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
2983 : {
2984 0 : CPLError(CE_Warning, CPLE_AppDefined,
2985 : "TILE_FORMAT open option ignored on gridded coverages");
2986 : }
2987 : else
2988 : {
2989 4 : GPKGTileFormat eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
2990 4 : if (eTF == GPKG_TF_WEBP && m_eTF != eTF)
2991 : {
2992 1 : if (!RegisterWebPExtension())
2993 0 : return false;
2994 : }
2995 4 : m_eTF = eTF;
2996 : }
2997 : }
2998 :
2999 269 : ParseCompressionOptions(papszOpenOptionsIn);
3000 :
3001 269 : m_osWHERE = CSLFetchNameValueDef(papszOpenOptionsIn, "WHERE", "");
3002 :
3003 : // Set metadata
3004 269 : if (pszIdentifier && pszIdentifier[0])
3005 269 : GDALPamDataset::SetMetadataItem("IDENTIFIER", pszIdentifier);
3006 269 : if (pszDescription && pszDescription[0])
3007 21 : GDALPamDataset::SetMetadataItem("DESCRIPTION", pszDescription);
3008 :
3009 : // Add overviews
3010 354 : for (int i = 1; i < oResult->RowCount(); i++)
3011 : {
3012 86 : auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
3013 86 : poOvrDS->ShareLockWithParentDataset(this);
3014 172 : if (!poOvrDS->InitRaster(this, pszTableName, dfMinX, dfMinY, dfMaxX,
3015 : dfMaxY, pszContentsMinX, pszContentsMinY,
3016 : pszContentsMaxX, pszContentsMaxY,
3017 86 : papszOpenOptionsIn, *oResult, i))
3018 : {
3019 0 : break;
3020 : }
3021 :
3022 : int nTileWidth, nTileHeight;
3023 86 : poOvrDS->GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3024 : const bool bStop =
3025 87 : (eAccess == GA_ReadOnly && poOvrDS->GetRasterXSize() < nTileWidth &&
3026 1 : poOvrDS->GetRasterYSize() < nTileHeight);
3027 :
3028 86 : m_apoOverviewDS.push_back(std::move(poOvrDS));
3029 :
3030 86 : if (bStop)
3031 : {
3032 1 : break;
3033 : }
3034 : }
3035 :
3036 269 : return true;
3037 : }
3038 :
3039 : /************************************************************************/
3040 : /* GetSpatialRef() */
3041 : /************************************************************************/
3042 :
3043 17 : const OGRSpatialReference *GDALGeoPackageDataset::GetSpatialRef() const
3044 : {
3045 17 : if (GetLayerCount())
3046 1 : return GDALDataset::GetSpatialRef();
3047 16 : return GetSpatialRefRasterOnly();
3048 : }
3049 :
3050 : /************************************************************************/
3051 : /* GetSpatialRefRasterOnly() */
3052 : /************************************************************************/
3053 :
3054 : const OGRSpatialReference *
3055 17 : GDALGeoPackageDataset::GetSpatialRefRasterOnly() const
3056 :
3057 : {
3058 17 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
3059 : }
3060 :
3061 : /************************************************************************/
3062 : /* SetSpatialRef() */
3063 : /************************************************************************/
3064 :
3065 152 : CPLErr GDALGeoPackageDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
3066 : {
3067 152 : if (nBands == 0)
3068 : {
3069 1 : CPLError(CE_Failure, CPLE_NotSupported,
3070 : "SetProjection() not supported on a dataset with 0 band");
3071 1 : return CE_Failure;
3072 : }
3073 151 : if (eAccess != GA_Update)
3074 : {
3075 1 : CPLError(CE_Failure, CPLE_NotSupported,
3076 : "SetProjection() not supported on read-only dataset");
3077 1 : return CE_Failure;
3078 : }
3079 :
3080 150 : const int nSRID = GetSrsId(poSRS);
3081 300 : const auto poTS = GetTilingScheme(m_osTilingScheme);
3082 150 : if (poTS && nSRID != poTS->nEPSGCode)
3083 : {
3084 2 : CPLError(CE_Failure, CPLE_NotSupported,
3085 : "Projection should be EPSG:%d for %s tiling scheme",
3086 1 : poTS->nEPSGCode, m_osTilingScheme.c_str());
3087 1 : return CE_Failure;
3088 : }
3089 :
3090 149 : m_nSRID = nSRID;
3091 149 : m_oSRS.Clear();
3092 149 : if (poSRS)
3093 148 : m_oSRS = *poSRS;
3094 :
3095 149 : if (m_bRecordInsertedInGPKGContent)
3096 : {
3097 121 : char *pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET srs_id = %d "
3098 : "WHERE lower(table_name) = lower('%q')",
3099 : m_nSRID, m_osRasterTable.c_str());
3100 121 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3101 121 : sqlite3_free(pszSQL);
3102 121 : if (eErr != OGRERR_NONE)
3103 0 : return CE_Failure;
3104 :
3105 121 : pszSQL = sqlite3_mprintf("UPDATE gpkg_tile_matrix_set SET srs_id = %d "
3106 : "WHERE lower(table_name) = lower('%q')",
3107 : m_nSRID, m_osRasterTable.c_str());
3108 121 : eErr = SQLCommand(hDB, pszSQL);
3109 121 : sqlite3_free(pszSQL);
3110 121 : if (eErr != OGRERR_NONE)
3111 0 : return CE_Failure;
3112 : }
3113 :
3114 149 : return CE_None;
3115 : }
3116 :
3117 : /************************************************************************/
3118 : /* GetGeoTransform() */
3119 : /************************************************************************/
3120 :
3121 33 : CPLErr GDALGeoPackageDataset::GetGeoTransform(GDALGeoTransform >) const
3122 : {
3123 33 : gt = m_gt;
3124 33 : if (!m_bGeoTransformValid)
3125 2 : return CE_Failure;
3126 : else
3127 31 : return CE_None;
3128 : }
3129 :
3130 : /************************************************************************/
3131 : /* SetGeoTransform() */
3132 : /************************************************************************/
3133 :
3134 192 : CPLErr GDALGeoPackageDataset::SetGeoTransform(const GDALGeoTransform >)
3135 : {
3136 192 : if (nBands == 0)
3137 : {
3138 2 : CPLError(CE_Failure, CPLE_NotSupported,
3139 : "SetGeoTransform() not supported on a dataset with 0 band");
3140 2 : return CE_Failure;
3141 : }
3142 190 : if (eAccess != GA_Update)
3143 : {
3144 1 : CPLError(CE_Failure, CPLE_NotSupported,
3145 : "SetGeoTransform() not supported on read-only dataset");
3146 1 : return CE_Failure;
3147 : }
3148 189 : if (m_bGeoTransformValid)
3149 : {
3150 1 : CPLError(CE_Failure, CPLE_NotSupported,
3151 : "Cannot modify geotransform once set");
3152 1 : return CE_Failure;
3153 : }
3154 188 : if (gt[2] != 0.0 || gt[4] != 0 || gt[5] > 0.0)
3155 : {
3156 0 : CPLError(CE_Failure, CPLE_NotSupported,
3157 : "Only north-up non rotated geotransform supported");
3158 0 : return CE_Failure;
3159 : }
3160 :
3161 188 : if (m_nZoomLevel < 0)
3162 : {
3163 187 : const auto poTS = GetTilingScheme(m_osTilingScheme);
3164 187 : if (poTS)
3165 : {
3166 20 : double dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
3167 20 : double dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
3168 199 : for (m_nZoomLevel = 0; m_nZoomLevel < MAX_ZOOM_LEVEL;
3169 179 : m_nZoomLevel++)
3170 : {
3171 198 : double dfExpectedPixelXSize =
3172 198 : dfPixelXSizeZoomLevel0 / (1 << m_nZoomLevel);
3173 198 : double dfExpectedPixelYSize =
3174 198 : dfPixelYSizeZoomLevel0 / (1 << m_nZoomLevel);
3175 198 : if (fabs(gt[1] - dfExpectedPixelXSize) <
3176 217 : 1e-8 * dfExpectedPixelXSize &&
3177 19 : fabs(fabs(gt[5]) - dfExpectedPixelYSize) <
3178 19 : 1e-8 * dfExpectedPixelYSize)
3179 : {
3180 19 : break;
3181 : }
3182 : }
3183 20 : if (m_nZoomLevel == MAX_ZOOM_LEVEL)
3184 : {
3185 1 : m_nZoomLevel = -1;
3186 1 : CPLError(
3187 : CE_Failure, CPLE_NotSupported,
3188 : "Could not find an appropriate zoom level of %s tiling "
3189 : "scheme that matches raster pixel size",
3190 : m_osTilingScheme.c_str());
3191 1 : return CE_Failure;
3192 : }
3193 : }
3194 : }
3195 :
3196 187 : m_gt = gt;
3197 187 : m_bGeoTransformValid = true;
3198 :
3199 187 : return FinalizeRasterRegistration();
3200 : }
3201 :
3202 : /************************************************************************/
3203 : /* FinalizeRasterRegistration() */
3204 : /************************************************************************/
3205 :
3206 187 : CPLErr GDALGeoPackageDataset::FinalizeRasterRegistration()
3207 : {
3208 : OGRErr eErr;
3209 :
3210 187 : m_dfTMSMinX = m_gt[0];
3211 187 : m_dfTMSMaxY = m_gt[3];
3212 :
3213 : int nTileWidth, nTileHeight;
3214 187 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3215 :
3216 187 : if (m_nZoomLevel < 0)
3217 : {
3218 167 : m_nZoomLevel = 0;
3219 241 : while ((nRasterXSize >> m_nZoomLevel) > nTileWidth ||
3220 167 : (nRasterYSize >> m_nZoomLevel) > nTileHeight)
3221 74 : m_nZoomLevel++;
3222 : }
3223 :
3224 187 : double dfPixelXSizeZoomLevel0 = m_gt[1] * (1 << m_nZoomLevel);
3225 187 : double dfPixelYSizeZoomLevel0 = fabs(m_gt[5]) * (1 << m_nZoomLevel);
3226 : int nTileXCountZoomLevel0 =
3227 187 : std::max(1, DIV_ROUND_UP((nRasterXSize >> m_nZoomLevel), nTileWidth));
3228 : int nTileYCountZoomLevel0 =
3229 187 : std::max(1, DIV_ROUND_UP((nRasterYSize >> m_nZoomLevel), nTileHeight));
3230 :
3231 374 : const auto poTS = GetTilingScheme(m_osTilingScheme);
3232 187 : if (poTS)
3233 : {
3234 20 : CPLAssert(m_nZoomLevel >= 0);
3235 20 : m_dfTMSMinX = poTS->dfMinX;
3236 20 : m_dfTMSMaxY = poTS->dfMaxY;
3237 20 : dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
3238 20 : dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
3239 20 : nTileXCountZoomLevel0 = poTS->nTileXCountZoomLevel0;
3240 20 : nTileYCountZoomLevel0 = poTS->nTileYCountZoomLevel0;
3241 : }
3242 187 : m_nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << m_nZoomLevel);
3243 187 : m_nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << m_nZoomLevel);
3244 :
3245 187 : if (!ComputeTileAndPixelShifts())
3246 : {
3247 1 : CPLError(CE_Failure, CPLE_AppDefined,
3248 : "Overflow occurred in ComputeTileAndPixelShifts()");
3249 1 : return CE_Failure;
3250 : }
3251 :
3252 186 : if (!AllocCachedTiles())
3253 : {
3254 0 : return CE_Failure;
3255 : }
3256 :
3257 186 : double dfGDALMinX = m_gt[0];
3258 186 : double dfGDALMinY = m_gt[3] + nRasterYSize * m_gt[5];
3259 186 : double dfGDALMaxX = m_gt[0] + nRasterXSize * m_gt[1];
3260 186 : double dfGDALMaxY = m_gt[3];
3261 :
3262 186 : if (SoftStartTransaction() != OGRERR_NONE)
3263 0 : return CE_Failure;
3264 :
3265 : const char *pszCurrentDate =
3266 186 : CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
3267 : CPLString osInsertGpkgContentsFormatting(
3268 : "INSERT INTO gpkg_contents "
3269 : "(table_name,data_type,identifier,description,min_x,min_y,max_x,max_y,"
3270 : "last_change,srs_id) VALUES "
3271 372 : "('%q','%q','%q','%q',%.17g,%.17g,%.17g,%.17g,");
3272 186 : osInsertGpkgContentsFormatting += (pszCurrentDate) ? "'%q'" : "%s";
3273 186 : osInsertGpkgContentsFormatting += ",%d)";
3274 372 : char *pszSQL = sqlite3_mprintf(
3275 : osInsertGpkgContentsFormatting.c_str(), m_osRasterTable.c_str(),
3276 186 : (m_eDT == GDT_UInt8) ? "tiles" : "2d-gridded-coverage",
3277 : m_osIdentifier.c_str(), m_osDescription.c_str(), dfGDALMinX, dfGDALMinY,
3278 : dfGDALMaxX, dfGDALMaxY,
3279 : pszCurrentDate ? pszCurrentDate
3280 : : "strftime('%Y-%m-%dT%H:%M:%fZ','now')",
3281 : m_nSRID);
3282 :
3283 186 : eErr = SQLCommand(hDB, pszSQL);
3284 186 : sqlite3_free(pszSQL);
3285 186 : if (eErr != OGRERR_NONE)
3286 : {
3287 8 : SoftRollbackTransaction();
3288 8 : return CE_Failure;
3289 : }
3290 :
3291 178 : double dfTMSMaxX = m_dfTMSMinX + nTileXCountZoomLevel0 * nTileWidth *
3292 : dfPixelXSizeZoomLevel0;
3293 178 : double dfTMSMinY = m_dfTMSMaxY - nTileYCountZoomLevel0 * nTileHeight *
3294 : dfPixelYSizeZoomLevel0;
3295 :
3296 : pszSQL =
3297 178 : sqlite3_mprintf("INSERT INTO gpkg_tile_matrix_set "
3298 : "(table_name,srs_id,min_x,min_y,max_x,max_y) VALUES "
3299 : "('%q',%d,%.17g,%.17g,%.17g,%.17g)",
3300 : m_osRasterTable.c_str(), m_nSRID, m_dfTMSMinX,
3301 : dfTMSMinY, dfTMSMaxX, m_dfTMSMaxY);
3302 178 : eErr = SQLCommand(hDB, pszSQL);
3303 178 : sqlite3_free(pszSQL);
3304 178 : if (eErr != OGRERR_NONE)
3305 : {
3306 0 : SoftRollbackTransaction();
3307 0 : return CE_Failure;
3308 : }
3309 :
3310 178 : m_apoOverviewDS.resize(m_nZoomLevel);
3311 :
3312 591 : for (int i = 0; i <= m_nZoomLevel; i++)
3313 : {
3314 413 : double dfPixelXSizeZoomLevel = 0.0;
3315 413 : double dfPixelYSizeZoomLevel = 0.0;
3316 413 : int nTileMatrixWidth = 0;
3317 413 : int nTileMatrixHeight = 0;
3318 413 : if (EQUAL(m_osTilingScheme, "CUSTOM"))
3319 : {
3320 232 : dfPixelXSizeZoomLevel = m_gt[1] * (1 << (m_nZoomLevel - i));
3321 232 : dfPixelYSizeZoomLevel = fabs(m_gt[5]) * (1 << (m_nZoomLevel - i));
3322 : }
3323 : else
3324 : {
3325 181 : dfPixelXSizeZoomLevel = dfPixelXSizeZoomLevel0 / (1 << i);
3326 181 : dfPixelYSizeZoomLevel = dfPixelYSizeZoomLevel0 / (1 << i);
3327 : }
3328 413 : nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << i);
3329 413 : nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << i);
3330 :
3331 413 : pszSQL = sqlite3_mprintf(
3332 : "INSERT INTO gpkg_tile_matrix "
3333 : "(table_name,zoom_level,matrix_width,matrix_height,tile_width,tile_"
3334 : "height,pixel_x_size,pixel_y_size) VALUES "
3335 : "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
3336 : m_osRasterTable.c_str(), i, nTileMatrixWidth, nTileMatrixHeight,
3337 : nTileWidth, nTileHeight, dfPixelXSizeZoomLevel,
3338 : dfPixelYSizeZoomLevel);
3339 413 : eErr = SQLCommand(hDB, pszSQL);
3340 413 : sqlite3_free(pszSQL);
3341 413 : if (eErr != OGRERR_NONE)
3342 : {
3343 0 : SoftRollbackTransaction();
3344 0 : return CE_Failure;
3345 : }
3346 :
3347 413 : if (i < m_nZoomLevel)
3348 : {
3349 470 : auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
3350 235 : poOvrDS->ShareLockWithParentDataset(this);
3351 235 : poOvrDS->InitRaster(this, m_osRasterTable, i, nBands, m_dfTMSMinX,
3352 : m_dfTMSMaxY, dfPixelXSizeZoomLevel,
3353 : dfPixelYSizeZoomLevel, nTileWidth, nTileHeight,
3354 : nTileMatrixWidth, nTileMatrixHeight, dfGDALMinX,
3355 : dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
3356 :
3357 235 : m_apoOverviewDS[m_nZoomLevel - 1 - i] = std::move(poOvrDS);
3358 : }
3359 : }
3360 :
3361 178 : if (!m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.empty())
3362 : {
3363 40 : eErr = SQLCommand(
3364 : hDB, m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.c_str());
3365 40 : m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.clear();
3366 40 : if (eErr != OGRERR_NONE)
3367 : {
3368 0 : SoftRollbackTransaction();
3369 0 : return CE_Failure;
3370 : }
3371 : }
3372 :
3373 178 : SoftCommitTransaction();
3374 :
3375 178 : m_apoOverviewDS.resize(m_nZoomLevel);
3376 178 : m_bRecordInsertedInGPKGContent = true;
3377 :
3378 178 : return CE_None;
3379 : }
3380 :
3381 : /************************************************************************/
3382 : /* FlushCache() */
3383 : /************************************************************************/
3384 :
3385 2797 : CPLErr GDALGeoPackageDataset::FlushCache(bool bAtClosing)
3386 : {
3387 2797 : if (m_bInFlushCache)
3388 0 : return CE_None;
3389 :
3390 2797 : if (eAccess == GA_Update || !m_bMetadataDirty)
3391 : {
3392 2794 : SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
3393 : }
3394 :
3395 2797 : if (m_bRemoveOGREmptyTable)
3396 : {
3397 749 : m_bRemoveOGREmptyTable = false;
3398 749 : RemoveOGREmptyTable();
3399 : }
3400 :
3401 2797 : CPLErr eErr = IFlushCacheWithErrCode(bAtClosing);
3402 :
3403 2797 : FlushMetadata();
3404 :
3405 2797 : if (eAccess == GA_Update || !m_bMetadataDirty)
3406 : {
3407 : // Needed again as above IFlushCacheWithErrCode()
3408 : // may have call GDALGeoPackageRasterBand::InvalidateStatistics()
3409 : // which modifies metadata
3410 2797 : SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
3411 : }
3412 :
3413 2797 : return eErr;
3414 : }
3415 :
3416 5032 : CPLErr GDALGeoPackageDataset::IFlushCacheWithErrCode(bool bAtClosing)
3417 :
3418 : {
3419 5032 : if (m_bInFlushCache)
3420 2168 : return CE_None;
3421 2864 : m_bInFlushCache = true;
3422 2864 : if (hDB && eAccess == GA_ReadOnly && bAtClosing)
3423 : {
3424 : // Clean-up metadata that will go to PAM by removing items that
3425 : // are reconstructed.
3426 2074 : CPLStringList aosMD;
3427 1660 : for (CSLConstList papszIter = GetMetadata(); papszIter && *papszIter;
3428 : ++papszIter)
3429 : {
3430 623 : char *pszKey = nullptr;
3431 623 : CPLParseNameValue(*papszIter, &pszKey);
3432 1246 : if (pszKey &&
3433 623 : (EQUAL(pszKey, "AREA_OR_POINT") ||
3434 477 : EQUAL(pszKey, "IDENTIFIER") || EQUAL(pszKey, "DESCRIPTION") ||
3435 256 : EQUAL(pszKey, "ZOOM_LEVEL") ||
3436 653 : STARTS_WITH(pszKey, "GPKG_METADATA_ITEM_")))
3437 : {
3438 : // remove it
3439 : }
3440 : else
3441 : {
3442 30 : aosMD.AddString(*papszIter);
3443 : }
3444 623 : CPLFree(pszKey);
3445 : }
3446 1037 : oMDMD.SetMetadata(aosMD.List());
3447 1037 : oMDMD.SetMetadata(nullptr, "IMAGE_STRUCTURE");
3448 :
3449 2074 : GDALPamDataset::FlushCache(bAtClosing);
3450 : }
3451 : else
3452 : {
3453 : // Short circuit GDALPamDataset to avoid serialization to .aux.xml
3454 1827 : GDALDataset::FlushCache(bAtClosing);
3455 : }
3456 :
3457 6992 : for (auto &poLayer : m_apoLayers)
3458 : {
3459 4128 : poLayer->RunDeferredCreationIfNecessary();
3460 4128 : poLayer->CreateSpatialIndexIfNecessary();
3461 : }
3462 :
3463 : // Update raster table last_change column in gpkg_contents if needed
3464 2864 : if (m_bHasModifiedTiles)
3465 : {
3466 540 : for (int i = 1; i <= nBands; ++i)
3467 : {
3468 : auto poBand =
3469 359 : cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
3470 359 : if (!poBand->HaveStatsMetadataBeenSetInThisSession())
3471 : {
3472 346 : poBand->InvalidateStatistics();
3473 346 : if (psPam && psPam->pszPamFilename)
3474 346 : VSIUnlink(psPam->pszPamFilename);
3475 : }
3476 : }
3477 :
3478 181 : UpdateGpkgContentsLastChange(m_osRasterTable);
3479 :
3480 181 : m_bHasModifiedTiles = false;
3481 : }
3482 :
3483 2864 : CPLErr eErr = FlushTiles();
3484 :
3485 2864 : m_bInFlushCache = false;
3486 2864 : return eErr;
3487 : }
3488 :
3489 : /************************************************************************/
3490 : /* GetCurrentDateEscapedSQL() */
3491 : /************************************************************************/
3492 :
3493 2106 : std::string GDALGeoPackageDataset::GetCurrentDateEscapedSQL()
3494 : {
3495 : const char *pszCurrentDate =
3496 2106 : CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
3497 2106 : if (pszCurrentDate)
3498 10 : return '\'' + SQLEscapeLiteral(pszCurrentDate) + '\'';
3499 2101 : return "strftime('%Y-%m-%dT%H:%M:%fZ','now')";
3500 : }
3501 :
3502 : /************************************************************************/
3503 : /* UpdateGpkgContentsLastChange() */
3504 : /************************************************************************/
3505 :
3506 : OGRErr
3507 920 : GDALGeoPackageDataset::UpdateGpkgContentsLastChange(const char *pszTableName)
3508 : {
3509 : char *pszSQL =
3510 920 : sqlite3_mprintf("UPDATE gpkg_contents SET "
3511 : "last_change = %s "
3512 : "WHERE lower(table_name) = lower('%q')",
3513 1840 : GetCurrentDateEscapedSQL().c_str(), pszTableName);
3514 920 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3515 920 : sqlite3_free(pszSQL);
3516 920 : return eErr;
3517 : }
3518 :
3519 : /************************************************************************/
3520 : /* IBuildOverviews() */
3521 : /************************************************************************/
3522 :
3523 20 : CPLErr GDALGeoPackageDataset::IBuildOverviews(
3524 : const char *pszResampling, int nOverviews, const int *panOverviewList,
3525 : int nBandsIn, const int * /*panBandList*/, GDALProgressFunc pfnProgress,
3526 : void *pProgressData, CSLConstList papszOptions)
3527 : {
3528 20 : if (GetAccess() != GA_Update)
3529 : {
3530 1 : CPLError(CE_Failure, CPLE_NotSupported,
3531 : "Overview building not supported on a database opened in "
3532 : "read-only mode");
3533 1 : return CE_Failure;
3534 : }
3535 19 : if (m_poParentDS != nullptr)
3536 : {
3537 1 : CPLError(CE_Failure, CPLE_NotSupported,
3538 : "Overview building not supported on overview dataset");
3539 1 : return CE_Failure;
3540 : }
3541 :
3542 18 : if (nOverviews == 0)
3543 : {
3544 5 : for (auto &poOvrDS : m_apoOverviewDS)
3545 3 : poOvrDS->FlushCache(false);
3546 :
3547 2 : SoftStartTransaction();
3548 :
3549 2 : if (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
3550 : {
3551 1 : char *pszSQL = sqlite3_mprintf(
3552 : "DELETE FROM gpkg_2d_gridded_tile_ancillary WHERE id IN "
3553 : "(SELECT y.id FROM \"%w\" x "
3554 : "JOIN gpkg_2d_gridded_tile_ancillary y "
3555 : "ON x.id = y.tpudt_id AND y.tpudt_name = '%q' AND "
3556 : "x.zoom_level < %d)",
3557 : m_osRasterTable.c_str(), m_osRasterTable.c_str(), m_nZoomLevel);
3558 1 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3559 1 : sqlite3_free(pszSQL);
3560 1 : if (eErr != OGRERR_NONE)
3561 : {
3562 0 : SoftRollbackTransaction();
3563 0 : return CE_Failure;
3564 : }
3565 : }
3566 :
3567 : char *pszSQL =
3568 2 : sqlite3_mprintf("DELETE FROM \"%w\" WHERE zoom_level < %d",
3569 : m_osRasterTable.c_str(), m_nZoomLevel);
3570 2 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3571 2 : sqlite3_free(pszSQL);
3572 2 : if (eErr != OGRERR_NONE)
3573 : {
3574 0 : SoftRollbackTransaction();
3575 0 : return CE_Failure;
3576 : }
3577 :
3578 2 : SoftCommitTransaction();
3579 :
3580 2 : return CE_None;
3581 : }
3582 :
3583 16 : if (nBandsIn != nBands)
3584 : {
3585 0 : CPLError(CE_Failure, CPLE_NotSupported,
3586 : "Generation of overviews in GPKG only"
3587 : "supported when operating on all bands.");
3588 0 : return CE_Failure;
3589 : }
3590 :
3591 16 : if (m_apoOverviewDS.empty())
3592 : {
3593 0 : CPLError(CE_Failure, CPLE_AppDefined,
3594 : "Image too small to support overviews");
3595 0 : return CE_Failure;
3596 : }
3597 :
3598 16 : FlushCache(false);
3599 60 : for (int i = 0; i < nOverviews; i++)
3600 : {
3601 47 : if (panOverviewList[i] < 2)
3602 : {
3603 1 : CPLError(CE_Failure, CPLE_IllegalArg,
3604 : "Overview factor must be >= 2");
3605 1 : return CE_Failure;
3606 : }
3607 :
3608 46 : bool bFound = false;
3609 46 : int jCandidate = -1;
3610 46 : int nMaxOvFactor = 0;
3611 196 : for (int j = 0; j < static_cast<int>(m_apoOverviewDS.size()); j++)
3612 : {
3613 190 : const auto poODS = m_apoOverviewDS[j].get();
3614 : const int nOvFactor =
3615 190 : static_cast<int>(0.5 + poODS->m_gt[1] / m_gt[1]);
3616 :
3617 190 : nMaxOvFactor = nOvFactor;
3618 :
3619 190 : if (nOvFactor == panOverviewList[i])
3620 : {
3621 40 : bFound = true;
3622 40 : break;
3623 : }
3624 :
3625 150 : if (jCandidate < 0 && nOvFactor > panOverviewList[i])
3626 1 : jCandidate = j;
3627 : }
3628 :
3629 46 : if (!bFound)
3630 : {
3631 : /* Mostly for debug */
3632 6 : if (!CPLTestBool(CPLGetConfigOption(
3633 : "ALLOW_GPKG_ZOOM_OTHER_EXTENSION", "YES")))
3634 : {
3635 2 : CPLString osOvrList;
3636 4 : for (const auto &poODS : m_apoOverviewDS)
3637 : {
3638 : const int nOvFactor =
3639 2 : static_cast<int>(0.5 + poODS->m_gt[1] / m_gt[1]);
3640 :
3641 2 : if (!osOvrList.empty())
3642 0 : osOvrList += ' ';
3643 2 : osOvrList += CPLSPrintf("%d", nOvFactor);
3644 : }
3645 2 : CPLError(CE_Failure, CPLE_NotSupported,
3646 : "Only overviews %s can be computed",
3647 : osOvrList.c_str());
3648 2 : return CE_Failure;
3649 : }
3650 : else
3651 : {
3652 4 : int nOvFactor = panOverviewList[i];
3653 4 : if (jCandidate < 0)
3654 3 : jCandidate = static_cast<int>(m_apoOverviewDS.size());
3655 :
3656 4 : int nOvXSize = std::max(1, GetRasterXSize() / nOvFactor);
3657 4 : int nOvYSize = std::max(1, GetRasterYSize() / nOvFactor);
3658 4 : if (!(jCandidate == static_cast<int>(m_apoOverviewDS.size()) &&
3659 5 : nOvFactor == 2 * nMaxOvFactor) &&
3660 1 : !m_bZoomOther)
3661 : {
3662 1 : CPLError(CE_Warning, CPLE_AppDefined,
3663 : "Use of overview factor %d causes gpkg_zoom_other "
3664 : "extension to be needed",
3665 : nOvFactor);
3666 1 : RegisterZoomOtherExtension();
3667 1 : m_bZoomOther = true;
3668 : }
3669 :
3670 4 : SoftStartTransaction();
3671 :
3672 4 : CPLAssert(jCandidate > 0);
3673 : const int nNewZoomLevel =
3674 4 : m_apoOverviewDS[jCandidate - 1]->m_nZoomLevel;
3675 :
3676 : char *pszSQL;
3677 : OGRErr eErr;
3678 24 : for (int k = 0; k <= jCandidate; k++)
3679 : {
3680 60 : pszSQL = sqlite3_mprintf(
3681 : "UPDATE gpkg_tile_matrix SET zoom_level = %d "
3682 : "WHERE lower(table_name) = lower('%q') AND zoom_level "
3683 : "= %d",
3684 20 : m_nZoomLevel - k + 1, m_osRasterTable.c_str(),
3685 20 : m_nZoomLevel - k);
3686 20 : eErr = SQLCommand(hDB, pszSQL);
3687 20 : sqlite3_free(pszSQL);
3688 20 : if (eErr != OGRERR_NONE)
3689 : {
3690 0 : SoftRollbackTransaction();
3691 0 : return CE_Failure;
3692 : }
3693 :
3694 : pszSQL =
3695 20 : sqlite3_mprintf("UPDATE \"%w\" SET zoom_level = %d "
3696 : "WHERE zoom_level = %d",
3697 : m_osRasterTable.c_str(),
3698 20 : m_nZoomLevel - k + 1, m_nZoomLevel - k);
3699 20 : eErr = SQLCommand(hDB, pszSQL);
3700 20 : sqlite3_free(pszSQL);
3701 20 : if (eErr != OGRERR_NONE)
3702 : {
3703 0 : SoftRollbackTransaction();
3704 0 : return CE_Failure;
3705 : }
3706 : }
3707 :
3708 4 : double dfGDALMinX = m_gt[0];
3709 4 : double dfGDALMinY = m_gt[3] + nRasterYSize * m_gt[5];
3710 4 : double dfGDALMaxX = m_gt[0] + nRasterXSize * m_gt[1];
3711 4 : double dfGDALMaxY = m_gt[3];
3712 4 : double dfPixelXSizeZoomLevel = m_gt[1] * nOvFactor;
3713 4 : double dfPixelYSizeZoomLevel = fabs(m_gt[5]) * nOvFactor;
3714 : int nTileWidth, nTileHeight;
3715 4 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3716 4 : int nTileMatrixWidth = DIV_ROUND_UP(nOvXSize, nTileWidth);
3717 4 : int nTileMatrixHeight = DIV_ROUND_UP(nOvYSize, nTileHeight);
3718 4 : pszSQL = sqlite3_mprintf(
3719 : "INSERT INTO gpkg_tile_matrix "
3720 : "(table_name,zoom_level,matrix_width,matrix_height,tile_"
3721 : "width,tile_height,pixel_x_size,pixel_y_size) VALUES "
3722 : "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
3723 : m_osRasterTable.c_str(), nNewZoomLevel, nTileMatrixWidth,
3724 : nTileMatrixHeight, nTileWidth, nTileHeight,
3725 : dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel);
3726 4 : eErr = SQLCommand(hDB, pszSQL);
3727 4 : sqlite3_free(pszSQL);
3728 4 : if (eErr != OGRERR_NONE)
3729 : {
3730 0 : SoftRollbackTransaction();
3731 0 : return CE_Failure;
3732 : }
3733 :
3734 4 : SoftCommitTransaction();
3735 :
3736 4 : m_nZoomLevel++; /* this change our zoom level as well as
3737 : previous overviews */
3738 20 : for (int k = 0; k < jCandidate; k++)
3739 16 : m_apoOverviewDS[k]->m_nZoomLevel++;
3740 :
3741 4 : auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
3742 4 : poOvrDS->ShareLockWithParentDataset(this);
3743 4 : poOvrDS->InitRaster(
3744 : this, m_osRasterTable, nNewZoomLevel, nBands, m_dfTMSMinX,
3745 : m_dfTMSMaxY, dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel,
3746 : nTileWidth, nTileHeight, nTileMatrixWidth,
3747 : nTileMatrixHeight, dfGDALMinX, dfGDALMinY, dfGDALMaxX,
3748 : dfGDALMaxY);
3749 4 : m_apoOverviewDS.insert(m_apoOverviewDS.begin() + jCandidate,
3750 8 : std::move(poOvrDS));
3751 : }
3752 : }
3753 : }
3754 :
3755 : GDALRasterBand ***papapoOverviewBands = static_cast<GDALRasterBand ***>(
3756 13 : CPLCalloc(sizeof(GDALRasterBand **), nBands));
3757 13 : CPLErr eErr = CE_None;
3758 49 : for (int iBand = 0; eErr == CE_None && iBand < nBands; iBand++)
3759 : {
3760 72 : papapoOverviewBands[iBand] = static_cast<GDALRasterBand **>(
3761 36 : CPLCalloc(sizeof(GDALRasterBand *), nOverviews));
3762 36 : int iCurOverview = 0;
3763 185 : for (int i = 0; i < nOverviews; i++)
3764 : {
3765 149 : bool bFound = false;
3766 724 : for (const auto &poODS : m_apoOverviewDS)
3767 : {
3768 : const int nOvFactor =
3769 724 : static_cast<int>(0.5 + poODS->m_gt[1] / m_gt[1]);
3770 :
3771 724 : if (nOvFactor == panOverviewList[i])
3772 : {
3773 298 : papapoOverviewBands[iBand][iCurOverview] =
3774 149 : poODS->GetRasterBand(iBand + 1);
3775 149 : iCurOverview++;
3776 149 : bFound = true;
3777 149 : break;
3778 : }
3779 : }
3780 149 : if (!bFound)
3781 : {
3782 0 : CPLError(CE_Failure, CPLE_AppDefined,
3783 : "Could not find dataset corresponding to ov factor %d",
3784 0 : panOverviewList[i]);
3785 0 : eErr = CE_Failure;
3786 : }
3787 : }
3788 36 : if (eErr == CE_None)
3789 : {
3790 36 : CPLAssert(iCurOverview == nOverviews);
3791 : }
3792 : }
3793 :
3794 13 : if (eErr == CE_None)
3795 13 : eErr = GDALRegenerateOverviewsMultiBand(
3796 13 : nBands, papoBands, nOverviews, papapoOverviewBands, pszResampling,
3797 : pfnProgress, pProgressData, papszOptions);
3798 :
3799 49 : for (int iBand = 0; iBand < nBands; iBand++)
3800 : {
3801 36 : CPLFree(papapoOverviewBands[iBand]);
3802 : }
3803 13 : CPLFree(papapoOverviewBands);
3804 :
3805 13 : return eErr;
3806 : }
3807 :
3808 : /************************************************************************/
3809 : /* GetFileList() */
3810 : /************************************************************************/
3811 :
3812 38 : char **GDALGeoPackageDataset::GetFileList()
3813 : {
3814 38 : TryLoadXML();
3815 38 : return GDALPamDataset::GetFileList();
3816 : }
3817 :
3818 : /************************************************************************/
3819 : /* GetMetadataDomainList() */
3820 : /************************************************************************/
3821 :
3822 48 : char **GDALGeoPackageDataset::GetMetadataDomainList()
3823 : {
3824 48 : GetMetadata();
3825 48 : if (!m_osRasterTable.empty())
3826 5 : GetMetadata("GEOPACKAGE");
3827 48 : return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
3828 48 : TRUE, "SUBDATASETS", nullptr);
3829 : }
3830 :
3831 : /************************************************************************/
3832 : /* CheckMetadataDomain() */
3833 : /************************************************************************/
3834 :
3835 5959 : const char *GDALGeoPackageDataset::CheckMetadataDomain(const char *pszDomain)
3836 : {
3837 6144 : if (pszDomain != nullptr && EQUAL(pszDomain, "GEOPACKAGE") &&
3838 185 : m_osRasterTable.empty())
3839 : {
3840 4 : CPLError(
3841 : CE_Warning, CPLE_IllegalArg,
3842 : "Using GEOPACKAGE for a non-raster geopackage is not supported. "
3843 : "Using default domain instead");
3844 4 : return nullptr;
3845 : }
3846 5955 : return pszDomain;
3847 : }
3848 :
3849 : /************************************************************************/
3850 : /* HasMetadataTables() */
3851 : /************************************************************************/
3852 :
3853 5651 : bool GDALGeoPackageDataset::HasMetadataTables() const
3854 : {
3855 5651 : if (m_nHasMetadataTables < 0)
3856 : {
3857 : const int nCount =
3858 2147 : SQLGetInteger(hDB,
3859 : "SELECT COUNT(*) FROM sqlite_master WHERE name IN "
3860 : "('gpkg_metadata', 'gpkg_metadata_reference') "
3861 : "AND type IN ('table', 'view')",
3862 : nullptr);
3863 2147 : m_nHasMetadataTables = nCount == 2;
3864 : }
3865 5651 : return CPL_TO_BOOL(m_nHasMetadataTables);
3866 : }
3867 :
3868 : /************************************************************************/
3869 : /* HasDataColumnsTable() */
3870 : /************************************************************************/
3871 :
3872 1273 : bool GDALGeoPackageDataset::HasDataColumnsTable() const
3873 : {
3874 2546 : const int nCount = SQLGetInteger(
3875 1273 : hDB,
3876 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_data_columns'"
3877 : "AND type IN ('table', 'view')",
3878 : nullptr);
3879 1273 : return nCount == 1;
3880 : }
3881 :
3882 : /************************************************************************/
3883 : /* HasDataColumnConstraintsTable() */
3884 : /************************************************************************/
3885 :
3886 158 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTable() const
3887 : {
3888 158 : const int nCount = SQLGetInteger(hDB,
3889 : "SELECT 1 FROM sqlite_master WHERE name = "
3890 : "'gpkg_data_column_constraints'"
3891 : "AND type IN ('table', 'view')",
3892 : nullptr);
3893 158 : return nCount == 1;
3894 : }
3895 :
3896 : /************************************************************************/
3897 : /* HasDataColumnConstraintsTableGPKG_1_0() */
3898 : /************************************************************************/
3899 :
3900 109 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTableGPKG_1_0() const
3901 : {
3902 109 : if (m_nApplicationId != GP10_APPLICATION_ID)
3903 107 : return false;
3904 : // In GPKG 1.0, the columns were named minIsInclusive, maxIsInclusive
3905 : // They were changed in 1.1 to min_is_inclusive, max_is_inclusive
3906 2 : bool bRet = false;
3907 2 : sqlite3_stmt *hSQLStmt = nullptr;
3908 2 : int rc = sqlite3_prepare_v2(hDB,
3909 : "SELECT minIsInclusive, maxIsInclusive FROM "
3910 : "gpkg_data_column_constraints",
3911 : -1, &hSQLStmt, nullptr);
3912 2 : if (rc == SQLITE_OK)
3913 : {
3914 2 : bRet = true;
3915 2 : sqlite3_finalize(hSQLStmt);
3916 : }
3917 2 : return bRet;
3918 : }
3919 :
3920 : /************************************************************************/
3921 : /* CreateColumnsTableAndColumnConstraintsTablesIfNecessary() */
3922 : /************************************************************************/
3923 :
3924 50 : bool GDALGeoPackageDataset::
3925 : CreateColumnsTableAndColumnConstraintsTablesIfNecessary()
3926 : {
3927 50 : if (!HasDataColumnsTable())
3928 : {
3929 : // Geopackage < 1.3 had
3930 : // CONSTRAINT fk_gdc_tn FOREIGN KEY (table_name) REFERENCES
3931 : // gpkg_contents(table_name) instead of the unique constraint.
3932 10 : if (OGRERR_NONE !=
3933 10 : SQLCommand(
3934 : GetDB(),
3935 : "CREATE TABLE gpkg_data_columns ("
3936 : "table_name TEXT NOT NULL,"
3937 : "column_name TEXT NOT NULL,"
3938 : "name TEXT,"
3939 : "title TEXT,"
3940 : "description TEXT,"
3941 : "mime_type TEXT,"
3942 : "constraint_name TEXT,"
3943 : "CONSTRAINT pk_gdc PRIMARY KEY (table_name, column_name),"
3944 : "CONSTRAINT gdc_tn UNIQUE (table_name, name));"))
3945 : {
3946 0 : return false;
3947 : }
3948 : }
3949 50 : if (!HasDataColumnConstraintsTable())
3950 : {
3951 22 : const char *min_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
3952 11 : ? "min_is_inclusive"
3953 : : "minIsInclusive";
3954 22 : const char *max_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
3955 11 : ? "max_is_inclusive"
3956 : : "maxIsInclusive";
3957 :
3958 : const std::string osSQL(
3959 : CPLSPrintf("CREATE TABLE gpkg_data_column_constraints ("
3960 : "constraint_name TEXT NOT NULL,"
3961 : "constraint_type TEXT NOT NULL,"
3962 : "value TEXT,"
3963 : "min NUMERIC,"
3964 : "%s BOOLEAN,"
3965 : "max NUMERIC,"
3966 : "%s BOOLEAN,"
3967 : "description TEXT,"
3968 : "CONSTRAINT gdcc_ntv UNIQUE (constraint_name, "
3969 : "constraint_type, value));",
3970 11 : min_is_inclusive, max_is_inclusive));
3971 11 : if (OGRERR_NONE != SQLCommand(GetDB(), osSQL.c_str()))
3972 : {
3973 0 : return false;
3974 : }
3975 : }
3976 50 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
3977 : {
3978 0 : return false;
3979 : }
3980 50 : if (SQLGetInteger(GetDB(),
3981 : "SELECT 1 FROM gpkg_extensions WHERE "
3982 : "table_name = 'gpkg_data_columns'",
3983 50 : nullptr) != 1)
3984 : {
3985 11 : if (OGRERR_NONE !=
3986 11 : SQLCommand(
3987 : GetDB(),
3988 : "INSERT INTO gpkg_extensions "
3989 : "(table_name,column_name,extension_name,definition,scope) "
3990 : "VALUES ('gpkg_data_columns', NULL, 'gpkg_schema', "
3991 : "'http://www.geopackage.org/spec121/#extension_schema', "
3992 : "'read-write')"))
3993 : {
3994 0 : return false;
3995 : }
3996 : }
3997 50 : if (SQLGetInteger(GetDB(),
3998 : "SELECT 1 FROM gpkg_extensions WHERE "
3999 : "table_name = 'gpkg_data_column_constraints'",
4000 50 : nullptr) != 1)
4001 : {
4002 11 : if (OGRERR_NONE !=
4003 11 : SQLCommand(
4004 : GetDB(),
4005 : "INSERT INTO gpkg_extensions "
4006 : "(table_name,column_name,extension_name,definition,scope) "
4007 : "VALUES ('gpkg_data_column_constraints', NULL, 'gpkg_schema', "
4008 : "'http://www.geopackage.org/spec121/#extension_schema', "
4009 : "'read-write')"))
4010 : {
4011 0 : return false;
4012 : }
4013 : }
4014 :
4015 50 : return true;
4016 : }
4017 :
4018 : /************************************************************************/
4019 : /* HasGpkgextRelationsTable() */
4020 : /************************************************************************/
4021 :
4022 1255 : bool GDALGeoPackageDataset::HasGpkgextRelationsTable() const
4023 : {
4024 2510 : const int nCount = SQLGetInteger(
4025 1255 : hDB,
4026 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkgext_relations'"
4027 : "AND type IN ('table', 'view')",
4028 : nullptr);
4029 1255 : return nCount == 1;
4030 : }
4031 :
4032 : /************************************************************************/
4033 : /* CreateRelationsTableIfNecessary() */
4034 : /************************************************************************/
4035 :
4036 9 : bool GDALGeoPackageDataset::CreateRelationsTableIfNecessary()
4037 : {
4038 9 : if (HasGpkgextRelationsTable())
4039 : {
4040 5 : return true;
4041 : }
4042 :
4043 4 : if (OGRERR_NONE !=
4044 4 : SQLCommand(GetDB(), "CREATE TABLE gpkgext_relations ("
4045 : "id INTEGER PRIMARY KEY AUTOINCREMENT,"
4046 : "base_table_name TEXT NOT NULL,"
4047 : "base_primary_column TEXT NOT NULL DEFAULT 'id',"
4048 : "related_table_name TEXT NOT NULL,"
4049 : "related_primary_column TEXT NOT NULL DEFAULT 'id',"
4050 : "relation_name TEXT NOT NULL,"
4051 : "mapping_table_name TEXT NOT NULL UNIQUE);"))
4052 : {
4053 0 : return false;
4054 : }
4055 :
4056 4 : return true;
4057 : }
4058 :
4059 : /************************************************************************/
4060 : /* HasQGISLayerStyles() */
4061 : /************************************************************************/
4062 :
4063 11 : bool GDALGeoPackageDataset::HasQGISLayerStyles() const
4064 : {
4065 : // QGIS layer_styles extension:
4066 : // https://github.com/pka/qgpkg/blob/master/qgis_geopackage_extension.md
4067 11 : bool bRet = false;
4068 : const int nCount =
4069 11 : SQLGetInteger(hDB,
4070 : "SELECT 1 FROM sqlite_master WHERE name = 'layer_styles'"
4071 : "AND type = 'table'",
4072 : nullptr);
4073 11 : if (nCount == 1)
4074 : {
4075 1 : sqlite3_stmt *hSQLStmt = nullptr;
4076 2 : int rc = sqlite3_prepare_v2(
4077 1 : hDB, "SELECT f_table_name, f_geometry_column FROM layer_styles", -1,
4078 : &hSQLStmt, nullptr);
4079 1 : if (rc == SQLITE_OK)
4080 : {
4081 1 : bRet = true;
4082 1 : sqlite3_finalize(hSQLStmt);
4083 : }
4084 : }
4085 11 : return bRet;
4086 : }
4087 :
4088 : /************************************************************************/
4089 : /* GetMetadata() */
4090 : /************************************************************************/
4091 :
4092 3913 : char **GDALGeoPackageDataset::GetMetadata(const char *pszDomain)
4093 :
4094 : {
4095 3913 : pszDomain = CheckMetadataDomain(pszDomain);
4096 3913 : if (pszDomain != nullptr && EQUAL(pszDomain, "SUBDATASETS"))
4097 68 : return m_aosSubDatasets.List();
4098 :
4099 3845 : if (m_bHasReadMetadataFromStorage)
4100 1765 : return GDALPamDataset::GetMetadata(pszDomain);
4101 :
4102 2080 : m_bHasReadMetadataFromStorage = true;
4103 :
4104 2080 : TryLoadXML();
4105 :
4106 2080 : if (!HasMetadataTables())
4107 1568 : return GDALPamDataset::GetMetadata(pszDomain);
4108 :
4109 512 : char *pszSQL = nullptr;
4110 512 : if (!m_osRasterTable.empty())
4111 : {
4112 170 : pszSQL = sqlite3_mprintf(
4113 : "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
4114 : "mdr.reference_scope FROM gpkg_metadata md "
4115 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4116 : "WHERE "
4117 : "(mdr.reference_scope = 'geopackage' OR "
4118 : "(mdr.reference_scope = 'table' AND lower(mdr.table_name) = "
4119 : "lower('%q'))) ORDER BY md.id "
4120 : "LIMIT 1000", // to avoid denial of service
4121 : m_osRasterTable.c_str());
4122 : }
4123 : else
4124 : {
4125 342 : pszSQL = sqlite3_mprintf(
4126 : "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
4127 : "mdr.reference_scope FROM gpkg_metadata md "
4128 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4129 : "WHERE "
4130 : "mdr.reference_scope = 'geopackage' ORDER BY md.id "
4131 : "LIMIT 1000" // to avoid denial of service
4132 : );
4133 : }
4134 :
4135 1024 : auto oResult = SQLQuery(hDB, pszSQL);
4136 512 : sqlite3_free(pszSQL);
4137 512 : if (!oResult)
4138 : {
4139 0 : return GDALPamDataset::GetMetadata(pszDomain);
4140 : }
4141 :
4142 512 : char **papszMetadata = CSLDuplicate(GDALPamDataset::GetMetadata());
4143 :
4144 : /* GDAL metadata */
4145 702 : for (int i = 0; i < oResult->RowCount(); i++)
4146 : {
4147 190 : const char *pszMetadata = oResult->GetValue(0, i);
4148 190 : const char *pszMDStandardURI = oResult->GetValue(1, i);
4149 190 : const char *pszMimeType = oResult->GetValue(2, i);
4150 190 : const char *pszReferenceScope = oResult->GetValue(3, i);
4151 190 : if (pszMetadata && pszMDStandardURI && pszMimeType &&
4152 190 : pszReferenceScope && EQUAL(pszMDStandardURI, "http://gdal.org") &&
4153 174 : EQUAL(pszMimeType, "text/xml"))
4154 : {
4155 174 : CPLXMLNode *psXMLNode = CPLParseXMLString(pszMetadata);
4156 174 : if (psXMLNode)
4157 : {
4158 348 : GDALMultiDomainMetadata oLocalMDMD;
4159 174 : oLocalMDMD.XMLInit(psXMLNode, FALSE);
4160 333 : if (!m_osRasterTable.empty() &&
4161 159 : EQUAL(pszReferenceScope, "geopackage"))
4162 : {
4163 6 : oMDMD.SetMetadata(oLocalMDMD.GetMetadata(), "GEOPACKAGE");
4164 : }
4165 : else
4166 : {
4167 : papszMetadata =
4168 168 : CSLMerge(papszMetadata, oLocalMDMD.GetMetadata());
4169 168 : CSLConstList papszDomainList = oLocalMDMD.GetDomainList();
4170 168 : CSLConstList papszIter = papszDomainList;
4171 447 : while (papszIter && *papszIter)
4172 : {
4173 279 : if (EQUAL(*papszIter, "IMAGE_STRUCTURE"))
4174 : {
4175 : CSLConstList papszMD =
4176 126 : oLocalMDMD.GetMetadata(*papszIter);
4177 : const char *pszBAND_COUNT =
4178 126 : CSLFetchNameValue(papszMD, "BAND_COUNT");
4179 126 : if (pszBAND_COUNT)
4180 124 : m_nBandCountFromMetadata = atoi(pszBAND_COUNT);
4181 :
4182 : const char *pszCOLOR_TABLE =
4183 126 : CSLFetchNameValue(papszMD, "COLOR_TABLE");
4184 126 : if (pszCOLOR_TABLE)
4185 : {
4186 : const CPLStringList aosTokens(
4187 : CSLTokenizeString2(pszCOLOR_TABLE, "{,",
4188 26 : 0));
4189 13 : if ((aosTokens.size() % 4) == 0)
4190 : {
4191 13 : const int nColors = aosTokens.size() / 4;
4192 : m_poCTFromMetadata =
4193 13 : std::make_unique<GDALColorTable>();
4194 3341 : for (int iColor = 0; iColor < nColors;
4195 : ++iColor)
4196 : {
4197 : GDALColorEntry sEntry;
4198 3328 : sEntry.c1 = static_cast<short>(
4199 3328 : atoi(aosTokens[4 * iColor + 0]));
4200 3328 : sEntry.c2 = static_cast<short>(
4201 3328 : atoi(aosTokens[4 * iColor + 1]));
4202 3328 : sEntry.c3 = static_cast<short>(
4203 3328 : atoi(aosTokens[4 * iColor + 2]));
4204 3328 : sEntry.c4 = static_cast<short>(
4205 3328 : atoi(aosTokens[4 * iColor + 3]));
4206 3328 : m_poCTFromMetadata->SetColorEntry(
4207 : iColor, &sEntry);
4208 : }
4209 : }
4210 : }
4211 :
4212 : const char *pszTILE_FORMAT =
4213 126 : CSLFetchNameValue(papszMD, "TILE_FORMAT");
4214 126 : if (pszTILE_FORMAT)
4215 : {
4216 8 : m_osTFFromMetadata = pszTILE_FORMAT;
4217 8 : oMDMD.SetMetadataItem("TILE_FORMAT",
4218 : pszTILE_FORMAT,
4219 : "IMAGE_STRUCTURE");
4220 : }
4221 :
4222 : const char *pszNodataValue =
4223 126 : CSLFetchNameValue(papszMD, "NODATA_VALUE");
4224 126 : if (pszNodataValue)
4225 : {
4226 2 : m_osNodataValueFromMetadata = pszNodataValue;
4227 : }
4228 : }
4229 :
4230 153 : else if (!EQUAL(*papszIter, "") &&
4231 16 : !STARTS_WITH(*papszIter, "BAND_"))
4232 : {
4233 12 : oMDMD.SetMetadata(
4234 6 : oLocalMDMD.GetMetadata(*papszIter), *papszIter);
4235 : }
4236 279 : papszIter++;
4237 : }
4238 : }
4239 174 : CPLDestroyXMLNode(psXMLNode);
4240 : }
4241 : }
4242 : }
4243 :
4244 512 : GDALPamDataset::SetMetadata(papszMetadata);
4245 512 : CSLDestroy(papszMetadata);
4246 512 : papszMetadata = nullptr;
4247 :
4248 : /* Add non-GDAL metadata now */
4249 512 : int nNonGDALMDILocal = 1;
4250 512 : int nNonGDALMDIGeopackage = 1;
4251 702 : for (int i = 0; i < oResult->RowCount(); i++)
4252 : {
4253 190 : const char *pszMetadata = oResult->GetValue(0, i);
4254 190 : const char *pszMDStandardURI = oResult->GetValue(1, i);
4255 190 : const char *pszMimeType = oResult->GetValue(2, i);
4256 190 : const char *pszReferenceScope = oResult->GetValue(3, i);
4257 190 : if (pszMetadata == nullptr || pszMDStandardURI == nullptr ||
4258 190 : pszMimeType == nullptr || pszReferenceScope == nullptr)
4259 : {
4260 : // should not happen as there are NOT NULL constraints
4261 : // But a database could lack such NOT NULL constraints or have
4262 : // large values that would cause a memory allocation failure.
4263 0 : continue;
4264 : }
4265 190 : int bIsGPKGScope = EQUAL(pszReferenceScope, "geopackage");
4266 190 : if (EQUAL(pszMDStandardURI, "http://gdal.org") &&
4267 174 : EQUAL(pszMimeType, "text/xml"))
4268 174 : continue;
4269 :
4270 16 : if (!m_osRasterTable.empty() && bIsGPKGScope)
4271 : {
4272 8 : oMDMD.SetMetadataItem(
4273 : CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDIGeopackage),
4274 : pszMetadata, "GEOPACKAGE");
4275 8 : nNonGDALMDIGeopackage++;
4276 : }
4277 : /*else if( strcmp( pszMDStandardURI, "http://www.isotc211.org/2005/gmd"
4278 : ) == 0 && strcmp( pszMimeType, "text/xml" ) == 0 )
4279 : {
4280 : char* apszMD[2];
4281 : apszMD[0] = (char*)pszMetadata;
4282 : apszMD[1] = NULL;
4283 : oMDMD.SetMetadata(apszMD, "xml:MD_Metadata");
4284 : }*/
4285 : else
4286 : {
4287 8 : oMDMD.SetMetadataItem(
4288 : CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDILocal),
4289 : pszMetadata);
4290 8 : nNonGDALMDILocal++;
4291 : }
4292 : }
4293 :
4294 512 : return GDALPamDataset::GetMetadata(pszDomain);
4295 : }
4296 :
4297 : /************************************************************************/
4298 : /* WriteMetadata() */
4299 : /************************************************************************/
4300 :
4301 732 : void GDALGeoPackageDataset::WriteMetadata(
4302 : CPLXMLNode *psXMLNode, /* will be destroyed by the method */
4303 : const char *pszTableName)
4304 : {
4305 732 : const bool bIsEmpty = (psXMLNode == nullptr);
4306 732 : if (!HasMetadataTables())
4307 : {
4308 532 : if (bIsEmpty || !CreateMetadataTables())
4309 : {
4310 243 : CPLDestroyXMLNode(psXMLNode);
4311 243 : return;
4312 : }
4313 : }
4314 :
4315 489 : char *pszXML = nullptr;
4316 489 : if (!bIsEmpty)
4317 : {
4318 : CPLXMLNode *psMasterXMLNode =
4319 336 : CPLCreateXMLNode(nullptr, CXT_Element, "GDALMultiDomainMetadata");
4320 336 : psMasterXMLNode->psChild = psXMLNode;
4321 336 : pszXML = CPLSerializeXMLTree(psMasterXMLNode);
4322 336 : CPLDestroyXMLNode(psMasterXMLNode);
4323 : }
4324 : // cppcheck-suppress uselessAssignmentPtrArg
4325 489 : psXMLNode = nullptr;
4326 :
4327 489 : char *pszSQL = nullptr;
4328 489 : if (pszTableName && pszTableName[0] != '\0')
4329 : {
4330 344 : pszSQL = sqlite3_mprintf(
4331 : "SELECT md.id FROM gpkg_metadata md "
4332 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4333 : "WHERE md.md_scope = 'dataset' AND "
4334 : "md.md_standard_uri='http://gdal.org' "
4335 : "AND md.mime_type='text/xml' AND mdr.reference_scope = 'table' AND "
4336 : "lower(mdr.table_name) = lower('%q')",
4337 : pszTableName);
4338 : }
4339 : else
4340 : {
4341 145 : pszSQL = sqlite3_mprintf(
4342 : "SELECT md.id FROM gpkg_metadata md "
4343 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4344 : "WHERE md.md_scope = 'dataset' AND "
4345 : "md.md_standard_uri='http://gdal.org' "
4346 : "AND md.mime_type='text/xml' AND mdr.reference_scope = "
4347 : "'geopackage'");
4348 : }
4349 : OGRErr err;
4350 489 : int mdId = SQLGetInteger(hDB, pszSQL, &err);
4351 489 : if (err != OGRERR_NONE)
4352 457 : mdId = -1;
4353 489 : sqlite3_free(pszSQL);
4354 :
4355 489 : if (bIsEmpty)
4356 : {
4357 153 : if (mdId >= 0)
4358 : {
4359 6 : SQLCommand(
4360 : hDB,
4361 : CPLSPrintf(
4362 : "DELETE FROM gpkg_metadata_reference WHERE md_file_id = %d",
4363 : mdId));
4364 6 : SQLCommand(
4365 : hDB,
4366 : CPLSPrintf("DELETE FROM gpkg_metadata WHERE id = %d", mdId));
4367 : }
4368 : }
4369 : else
4370 : {
4371 336 : if (mdId >= 0)
4372 : {
4373 26 : pszSQL = sqlite3_mprintf(
4374 : "UPDATE gpkg_metadata SET metadata = '%q' WHERE id = %d",
4375 : pszXML, mdId);
4376 : }
4377 : else
4378 : {
4379 : pszSQL =
4380 310 : sqlite3_mprintf("INSERT INTO gpkg_metadata (md_scope, "
4381 : "md_standard_uri, mime_type, metadata) VALUES "
4382 : "('dataset','http://gdal.org','text/xml','%q')",
4383 : pszXML);
4384 : }
4385 336 : SQLCommand(hDB, pszSQL);
4386 336 : sqlite3_free(pszSQL);
4387 :
4388 336 : CPLFree(pszXML);
4389 :
4390 336 : if (mdId < 0)
4391 : {
4392 310 : const sqlite_int64 nFID = sqlite3_last_insert_rowid(hDB);
4393 310 : if (pszTableName != nullptr && pszTableName[0] != '\0')
4394 : {
4395 298 : pszSQL = sqlite3_mprintf(
4396 : "INSERT INTO gpkg_metadata_reference (reference_scope, "
4397 : "table_name, timestamp, md_file_id) VALUES "
4398 : "('table', '%q', %s, %d)",
4399 596 : pszTableName, GetCurrentDateEscapedSQL().c_str(),
4400 : static_cast<int>(nFID));
4401 : }
4402 : else
4403 : {
4404 12 : pszSQL = sqlite3_mprintf(
4405 : "INSERT INTO gpkg_metadata_reference (reference_scope, "
4406 : "timestamp, md_file_id) VALUES "
4407 : "('geopackage', %s, %d)",
4408 24 : GetCurrentDateEscapedSQL().c_str(), static_cast<int>(nFID));
4409 : }
4410 : }
4411 : else
4412 : {
4413 26 : pszSQL = sqlite3_mprintf("UPDATE gpkg_metadata_reference SET "
4414 : "timestamp = %s WHERE md_file_id = %d",
4415 52 : GetCurrentDateEscapedSQL().c_str(), mdId);
4416 : }
4417 336 : SQLCommand(hDB, pszSQL);
4418 336 : sqlite3_free(pszSQL);
4419 : }
4420 : }
4421 :
4422 : /************************************************************************/
4423 : /* CreateMetadataTables() */
4424 : /************************************************************************/
4425 :
4426 308 : bool GDALGeoPackageDataset::CreateMetadataTables()
4427 : {
4428 : const bool bCreateTriggers =
4429 308 : CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "NO"));
4430 :
4431 : /* From C.10. gpkg_metadata Table 35. gpkg_metadata Table Definition SQL */
4432 : CPLString osSQL = "CREATE TABLE gpkg_metadata ("
4433 : "id INTEGER CONSTRAINT m_pk PRIMARY KEY ASC NOT NULL,"
4434 : "md_scope TEXT NOT NULL DEFAULT 'dataset',"
4435 : "md_standard_uri TEXT NOT NULL,"
4436 : "mime_type TEXT NOT NULL DEFAULT 'text/xml',"
4437 : "metadata TEXT NOT NULL DEFAULT ''"
4438 616 : ")";
4439 :
4440 : /* From D.2. metadata Table 40. metadata Trigger Definition SQL */
4441 308 : const char *pszMetadataTriggers =
4442 : "CREATE TRIGGER 'gpkg_metadata_md_scope_insert' "
4443 : "BEFORE INSERT ON 'gpkg_metadata' "
4444 : "FOR EACH ROW BEGIN "
4445 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata violates "
4446 : "constraint: md_scope must be one of undefined | fieldSession | "
4447 : "collectionSession | series | dataset | featureType | feature | "
4448 : "attributeType | attribute | tile | model | catalogue | schema | "
4449 : "taxonomy software | service | collectionHardware | "
4450 : "nonGeographicDataset | dimensionGroup') "
4451 : "WHERE NOT(NEW.md_scope IN "
4452 : "('undefined','fieldSession','collectionSession','series','dataset', "
4453 : "'featureType','feature','attributeType','attribute','tile','model', "
4454 : "'catalogue','schema','taxonomy','software','service', "
4455 : "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
4456 : "END; "
4457 : "CREATE TRIGGER 'gpkg_metadata_md_scope_update' "
4458 : "BEFORE UPDATE OF 'md_scope' ON 'gpkg_metadata' "
4459 : "FOR EACH ROW BEGIN "
4460 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata violates "
4461 : "constraint: md_scope must be one of undefined | fieldSession | "
4462 : "collectionSession | series | dataset | featureType | feature | "
4463 : "attributeType | attribute | tile | model | catalogue | schema | "
4464 : "taxonomy software | service | collectionHardware | "
4465 : "nonGeographicDataset | dimensionGroup') "
4466 : "WHERE NOT(NEW.md_scope IN "
4467 : "('undefined','fieldSession','collectionSession','series','dataset', "
4468 : "'featureType','feature','attributeType','attribute','tile','model', "
4469 : "'catalogue','schema','taxonomy','software','service', "
4470 : "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
4471 : "END";
4472 308 : if (bCreateTriggers)
4473 : {
4474 0 : osSQL += ";";
4475 0 : osSQL += pszMetadataTriggers;
4476 : }
4477 :
4478 : /* From C.11. gpkg_metadata_reference Table 36. gpkg_metadata_reference
4479 : * Table Definition SQL */
4480 : osSQL += ";"
4481 : "CREATE TABLE gpkg_metadata_reference ("
4482 : "reference_scope TEXT NOT NULL,"
4483 : "table_name TEXT,"
4484 : "column_name TEXT,"
4485 : "row_id_value INTEGER,"
4486 : "timestamp DATETIME NOT NULL DEFAULT "
4487 : "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
4488 : "md_file_id INTEGER NOT NULL,"
4489 : "md_parent_id INTEGER,"
4490 : "CONSTRAINT crmr_mfi_fk FOREIGN KEY (md_file_id) REFERENCES "
4491 : "gpkg_metadata(id),"
4492 : "CONSTRAINT crmr_mpi_fk FOREIGN KEY (md_parent_id) REFERENCES "
4493 : "gpkg_metadata(id)"
4494 308 : ")";
4495 :
4496 : /* From D.3. metadata_reference Table 41. gpkg_metadata_reference Trigger
4497 : * Definition SQL */
4498 308 : const char *pszMetadataReferenceTriggers =
4499 : "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_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: reference_scope must be one of \"geopackage\", "
4504 : "table\", \"column\", \"row\", \"row/col\"') "
4505 : "WHERE NOT NEW.reference_scope IN "
4506 : "('geopackage','table','column','row','row/col'); "
4507 : "END; "
4508 : "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_update' "
4509 : "BEFORE UPDATE OF 'reference_scope' ON 'gpkg_metadata_reference' "
4510 : "FOR EACH ROW BEGIN "
4511 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4512 : "violates constraint: reference_scope must be one of \"geopackage\", "
4513 : "\"table\", \"column\", \"row\", \"row/col\"') "
4514 : "WHERE NOT NEW.reference_scope IN "
4515 : "('geopackage','table','column','row','row/col'); "
4516 : "END; "
4517 : "CREATE TRIGGER 'gpkg_metadata_reference_column_name_insert' "
4518 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4519 : "FOR EACH ROW BEGIN "
4520 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4521 : "violates constraint: column name must be NULL when reference_scope "
4522 : "is \"geopackage\", \"table\" or \"row\"') "
4523 : "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
4524 : "AND NEW.column_name IS NOT NULL); "
4525 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4526 : "violates constraint: column name must be defined for the specified "
4527 : "table when reference_scope is \"column\" or \"row/col\"') "
4528 : "WHERE (NEW.reference_scope IN ('column','row/col') "
4529 : "AND NOT NEW.table_name IN ( "
4530 : "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
4531 : "AND name = NEW.table_name "
4532 : "AND sql LIKE ('%' || NEW.column_name || '%'))); "
4533 : "END; "
4534 : "CREATE TRIGGER 'gpkg_metadata_reference_column_name_update' "
4535 : "BEFORE UPDATE OF column_name ON 'gpkg_metadata_reference' "
4536 : "FOR EACH ROW BEGIN "
4537 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4538 : "violates constraint: column name must be NULL when reference_scope "
4539 : "is \"geopackage\", \"table\" or \"row\"') "
4540 : "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
4541 : "AND NEW.column_name IS NOT NULL); "
4542 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4543 : "violates constraint: column name must be defined for the specified "
4544 : "table when reference_scope is \"column\" or \"row/col\"') "
4545 : "WHERE (NEW.reference_scope IN ('column','row/col') "
4546 : "AND NOT NEW.table_name IN ( "
4547 : "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
4548 : "AND name = NEW.table_name "
4549 : "AND sql LIKE ('%' || NEW.column_name || '%'))); "
4550 : "END; "
4551 : "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_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: row_id_value must be NULL when reference_scope "
4556 : "is \"geopackage\", \"table\" or \"column\"') "
4557 : "WHERE NEW.reference_scope IN ('geopackage','table','column') "
4558 : "AND NEW.row_id_value IS NOT NULL; "
4559 : "END; "
4560 : "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_update' "
4561 : "BEFORE UPDATE OF 'row_id_value' ON 'gpkg_metadata_reference' "
4562 : "FOR EACH ROW BEGIN "
4563 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4564 : "violates constraint: row_id_value must be NULL when reference_scope "
4565 : "is \"geopackage\", \"table\" or \"column\"') "
4566 : "WHERE NEW.reference_scope IN ('geopackage','table','column') "
4567 : "AND NEW.row_id_value IS NOT NULL; "
4568 : "END; "
4569 : "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_insert' "
4570 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4571 : "FOR EACH ROW BEGIN "
4572 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4573 : "violates constraint: timestamp must be a valid time in ISO 8601 "
4574 : "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
4575 : "WHERE NOT (NEW.timestamp GLOB "
4576 : "'[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-"
4577 : "5][0-9].[0-9][0-9][0-9]Z' "
4578 : "AND strftime('%s',NEW.timestamp) NOT NULL); "
4579 : "END; "
4580 : "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_update' "
4581 : "BEFORE UPDATE OF 'timestamp' ON 'gpkg_metadata_reference' "
4582 : "FOR EACH ROW BEGIN "
4583 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4584 : "violates constraint: timestamp must be a valid time in ISO 8601 "
4585 : "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
4586 : "WHERE NOT (NEW.timestamp GLOB "
4587 : "'[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-"
4588 : "5][0-9].[0-9][0-9][0-9]Z' "
4589 : "AND strftime('%s',NEW.timestamp) NOT NULL); "
4590 : "END";
4591 308 : if (bCreateTriggers)
4592 : {
4593 0 : osSQL += ";";
4594 0 : osSQL += pszMetadataReferenceTriggers;
4595 : }
4596 :
4597 308 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
4598 2 : return false;
4599 :
4600 306 : osSQL += ";";
4601 : osSQL += "INSERT INTO gpkg_extensions "
4602 : "(table_name, column_name, extension_name, definition, scope) "
4603 : "VALUES "
4604 : "('gpkg_metadata', NULL, 'gpkg_metadata', "
4605 : "'http://www.geopackage.org/spec120/#extension_metadata', "
4606 306 : "'read-write')";
4607 :
4608 306 : osSQL += ";";
4609 : osSQL += "INSERT INTO gpkg_extensions "
4610 : "(table_name, column_name, extension_name, definition, scope) "
4611 : "VALUES "
4612 : "('gpkg_metadata_reference', NULL, 'gpkg_metadata', "
4613 : "'http://www.geopackage.org/spec120/#extension_metadata', "
4614 306 : "'read-write')";
4615 :
4616 306 : const bool bOK = SQLCommand(hDB, osSQL) == OGRERR_NONE;
4617 306 : m_nHasMetadataTables = bOK;
4618 306 : return bOK;
4619 : }
4620 :
4621 : /************************************************************************/
4622 : /* FlushMetadata() */
4623 : /************************************************************************/
4624 :
4625 8808 : void GDALGeoPackageDataset::FlushMetadata()
4626 : {
4627 8808 : if (!m_bMetadataDirty || m_poParentDS != nullptr ||
4628 367 : m_nCreateMetadataTables == FALSE)
4629 8447 : return;
4630 361 : m_bMetadataDirty = false;
4631 :
4632 361 : if (eAccess == GA_ReadOnly)
4633 : {
4634 3 : return;
4635 : }
4636 :
4637 358 : bool bCanWriteAreaOrPoint =
4638 714 : !m_bGridCellEncodingAsCO &&
4639 356 : (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT);
4640 358 : if (!m_osRasterTable.empty())
4641 : {
4642 : const char *pszIdentifier =
4643 144 : GDALGeoPackageDataset::GetMetadataItem("IDENTIFIER");
4644 : const char *pszDescription =
4645 144 : GDALGeoPackageDataset::GetMetadataItem("DESCRIPTION");
4646 173 : if (!m_bIdentifierAsCO && pszIdentifier != nullptr &&
4647 29 : pszIdentifier != m_osIdentifier)
4648 : {
4649 14 : m_osIdentifier = pszIdentifier;
4650 : char *pszSQL =
4651 14 : sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
4652 : "WHERE lower(table_name) = lower('%q')",
4653 : pszIdentifier, m_osRasterTable.c_str());
4654 14 : SQLCommand(hDB, pszSQL);
4655 14 : sqlite3_free(pszSQL);
4656 : }
4657 151 : if (!m_bDescriptionAsCO && pszDescription != nullptr &&
4658 7 : pszDescription != m_osDescription)
4659 : {
4660 7 : m_osDescription = pszDescription;
4661 : char *pszSQL =
4662 7 : sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
4663 : "WHERE lower(table_name) = lower('%q')",
4664 : pszDescription, m_osRasterTable.c_str());
4665 7 : SQLCommand(hDB, pszSQL);
4666 7 : sqlite3_free(pszSQL);
4667 : }
4668 144 : if (bCanWriteAreaOrPoint)
4669 : {
4670 : const char *pszAreaOrPoint =
4671 28 : GDALGeoPackageDataset::GetMetadataItem(GDALMD_AREA_OR_POINT);
4672 28 : if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_AREA))
4673 : {
4674 23 : bCanWriteAreaOrPoint = false;
4675 23 : char *pszSQL = sqlite3_mprintf(
4676 : "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
4677 : "grid_cell_encoding = 'grid-value-is-area' WHERE "
4678 : "lower(tile_matrix_set_name) = lower('%q')",
4679 : m_osRasterTable.c_str());
4680 23 : SQLCommand(hDB, pszSQL);
4681 23 : sqlite3_free(pszSQL);
4682 : }
4683 5 : else if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_POINT))
4684 : {
4685 1 : bCanWriteAreaOrPoint = false;
4686 1 : char *pszSQL = sqlite3_mprintf(
4687 : "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
4688 : "grid_cell_encoding = 'grid-value-is-center' WHERE "
4689 : "lower(tile_matrix_set_name) = lower('%q')",
4690 : m_osRasterTable.c_str());
4691 1 : SQLCommand(hDB, pszSQL);
4692 1 : sqlite3_free(pszSQL);
4693 : }
4694 : }
4695 : }
4696 :
4697 358 : char **papszMDDup = nullptr;
4698 563 : for (char **papszIter = GDALGeoPackageDataset::GetMetadata();
4699 563 : papszIter && *papszIter; ++papszIter)
4700 : {
4701 205 : if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
4702 29 : continue;
4703 176 : if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
4704 8 : continue;
4705 168 : if (STARTS_WITH_CI(*papszIter, "ZOOM_LEVEL="))
4706 14 : continue;
4707 154 : if (STARTS_WITH_CI(*papszIter, "GPKG_METADATA_ITEM_"))
4708 4 : continue;
4709 150 : if ((m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT) &&
4710 29 : !bCanWriteAreaOrPoint &&
4711 26 : STARTS_WITH_CI(*papszIter, GDALMD_AREA_OR_POINT))
4712 : {
4713 26 : continue;
4714 : }
4715 124 : papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
4716 : }
4717 :
4718 358 : CPLXMLNode *psXMLNode = nullptr;
4719 : {
4720 358 : GDALMultiDomainMetadata oLocalMDMD;
4721 358 : CSLConstList papszDomainList = oMDMD.GetDomainList();
4722 358 : CSLConstList papszIter = papszDomainList;
4723 358 : oLocalMDMD.SetMetadata(papszMDDup);
4724 687 : while (papszIter && *papszIter)
4725 : {
4726 329 : if (!EQUAL(*papszIter, "") &&
4727 159 : !EQUAL(*papszIter, "IMAGE_STRUCTURE") &&
4728 15 : !EQUAL(*papszIter, "GEOPACKAGE"))
4729 : {
4730 8 : oLocalMDMD.SetMetadata(oMDMD.GetMetadata(*papszIter),
4731 : *papszIter);
4732 : }
4733 329 : papszIter++;
4734 : }
4735 358 : if (m_nBandCountFromMetadata > 0)
4736 : {
4737 74 : oLocalMDMD.SetMetadataItem(
4738 : "BAND_COUNT", CPLSPrintf("%d", m_nBandCountFromMetadata),
4739 : "IMAGE_STRUCTURE");
4740 74 : if (nBands == 1)
4741 : {
4742 50 : const auto poCT = GetRasterBand(1)->GetColorTable();
4743 50 : if (poCT)
4744 : {
4745 16 : std::string osVal("{");
4746 8 : const int nColorCount = poCT->GetColorEntryCount();
4747 2056 : for (int i = 0; i < nColorCount; ++i)
4748 : {
4749 2048 : if (i > 0)
4750 2040 : osVal += ',';
4751 2048 : const GDALColorEntry *psEntry = poCT->GetColorEntry(i);
4752 : osVal +=
4753 2048 : CPLSPrintf("{%d,%d,%d,%d}", psEntry->c1,
4754 2048 : psEntry->c2, psEntry->c3, psEntry->c4);
4755 : }
4756 8 : osVal += '}';
4757 8 : oLocalMDMD.SetMetadataItem("COLOR_TABLE", osVal.c_str(),
4758 : "IMAGE_STRUCTURE");
4759 : }
4760 : }
4761 74 : if (nBands == 1)
4762 : {
4763 50 : const char *pszTILE_FORMAT = nullptr;
4764 50 : switch (m_eTF)
4765 : {
4766 0 : case GPKG_TF_PNG_JPEG:
4767 0 : pszTILE_FORMAT = "JPEG_PNG";
4768 0 : break;
4769 44 : case GPKG_TF_PNG:
4770 44 : break;
4771 0 : case GPKG_TF_PNG8:
4772 0 : pszTILE_FORMAT = "PNG8";
4773 0 : break;
4774 3 : case GPKG_TF_JPEG:
4775 3 : pszTILE_FORMAT = "JPEG";
4776 3 : break;
4777 3 : case GPKG_TF_WEBP:
4778 3 : pszTILE_FORMAT = "WEBP";
4779 3 : break;
4780 0 : case GPKG_TF_PNG_16BIT:
4781 0 : break;
4782 0 : case GPKG_TF_TIFF_32BIT_FLOAT:
4783 0 : break;
4784 : }
4785 50 : if (pszTILE_FORMAT)
4786 6 : oLocalMDMD.SetMetadataItem("TILE_FORMAT", pszTILE_FORMAT,
4787 : "IMAGE_STRUCTURE");
4788 : }
4789 : }
4790 502 : if (GetRasterCount() > 0 &&
4791 144 : GetRasterBand(1)->GetRasterDataType() == GDT_UInt8)
4792 : {
4793 114 : int bHasNoData = FALSE;
4794 : const double dfNoDataValue =
4795 114 : GetRasterBand(1)->GetNoDataValue(&bHasNoData);
4796 114 : if (bHasNoData)
4797 : {
4798 3 : oLocalMDMD.SetMetadataItem("NODATA_VALUE",
4799 : CPLSPrintf("%.17g", dfNoDataValue),
4800 : "IMAGE_STRUCTURE");
4801 : }
4802 : }
4803 607 : for (int i = 1; i <= GetRasterCount(); ++i)
4804 : {
4805 : auto poBand =
4806 249 : cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
4807 249 : poBand->AddImplicitStatistics(false);
4808 249 : char **papszMD = GetRasterBand(i)->GetMetadata();
4809 249 : poBand->AddImplicitStatistics(true);
4810 249 : if (papszMD)
4811 : {
4812 14 : oLocalMDMD.SetMetadata(papszMD, CPLSPrintf("BAND_%d", i));
4813 : }
4814 : }
4815 358 : psXMLNode = oLocalMDMD.Serialize();
4816 : }
4817 :
4818 358 : CSLDestroy(papszMDDup);
4819 358 : papszMDDup = nullptr;
4820 :
4821 358 : WriteMetadata(psXMLNode, m_osRasterTable.c_str());
4822 :
4823 358 : if (!m_osRasterTable.empty())
4824 : {
4825 : char **papszGeopackageMD =
4826 144 : GDALGeoPackageDataset::GetMetadata("GEOPACKAGE");
4827 :
4828 144 : papszMDDup = nullptr;
4829 153 : for (char **papszIter = papszGeopackageMD; papszIter && *papszIter;
4830 : ++papszIter)
4831 : {
4832 9 : papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
4833 : }
4834 :
4835 288 : GDALMultiDomainMetadata oLocalMDMD;
4836 144 : oLocalMDMD.SetMetadata(papszMDDup);
4837 144 : CSLDestroy(papszMDDup);
4838 144 : papszMDDup = nullptr;
4839 144 : psXMLNode = oLocalMDMD.Serialize();
4840 :
4841 144 : WriteMetadata(psXMLNode, nullptr);
4842 : }
4843 :
4844 588 : for (auto &poLayer : m_apoLayers)
4845 : {
4846 230 : const char *pszIdentifier = poLayer->GetMetadataItem("IDENTIFIER");
4847 230 : const char *pszDescription = poLayer->GetMetadataItem("DESCRIPTION");
4848 230 : if (pszIdentifier != nullptr)
4849 : {
4850 : char *pszSQL =
4851 3 : sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
4852 : "WHERE lower(table_name) = lower('%q')",
4853 : pszIdentifier, poLayer->GetName());
4854 3 : SQLCommand(hDB, pszSQL);
4855 3 : sqlite3_free(pszSQL);
4856 : }
4857 230 : if (pszDescription != nullptr)
4858 : {
4859 : char *pszSQL =
4860 3 : sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
4861 : "WHERE lower(table_name) = lower('%q')",
4862 : pszDescription, poLayer->GetName());
4863 3 : SQLCommand(hDB, pszSQL);
4864 3 : sqlite3_free(pszSQL);
4865 : }
4866 :
4867 230 : papszMDDup = nullptr;
4868 624 : for (char **papszIter = poLayer->GetMetadata(); papszIter && *papszIter;
4869 : ++papszIter)
4870 : {
4871 394 : if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
4872 3 : continue;
4873 391 : if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
4874 3 : continue;
4875 388 : if (STARTS_WITH_CI(*papszIter, "OLMD_FID64="))
4876 0 : continue;
4877 388 : papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
4878 : }
4879 :
4880 : {
4881 230 : GDALMultiDomainMetadata oLocalMDMD;
4882 230 : char **papszDomainList = poLayer->GetMetadataDomainList();
4883 230 : char **papszIter = papszDomainList;
4884 230 : oLocalMDMD.SetMetadata(papszMDDup);
4885 513 : while (papszIter && *papszIter)
4886 : {
4887 283 : if (!EQUAL(*papszIter, ""))
4888 66 : oLocalMDMD.SetMetadata(poLayer->GetMetadata(*papszIter),
4889 : *papszIter);
4890 283 : papszIter++;
4891 : }
4892 230 : CSLDestroy(papszDomainList);
4893 230 : psXMLNode = oLocalMDMD.Serialize();
4894 : }
4895 :
4896 230 : CSLDestroy(papszMDDup);
4897 230 : papszMDDup = nullptr;
4898 :
4899 230 : WriteMetadata(psXMLNode, poLayer->GetName());
4900 : }
4901 : }
4902 :
4903 : /************************************************************************/
4904 : /* GetMetadataItem() */
4905 : /************************************************************************/
4906 :
4907 1892 : const char *GDALGeoPackageDataset::GetMetadataItem(const char *pszName,
4908 : const char *pszDomain)
4909 : {
4910 1892 : pszDomain = CheckMetadataDomain(pszDomain);
4911 1892 : return CSLFetchNameValue(GetMetadata(pszDomain), pszName);
4912 : }
4913 :
4914 : /************************************************************************/
4915 : /* SetMetadata() */
4916 : /************************************************************************/
4917 :
4918 133 : CPLErr GDALGeoPackageDataset::SetMetadata(char **papszMetadata,
4919 : const char *pszDomain)
4920 : {
4921 133 : pszDomain = CheckMetadataDomain(pszDomain);
4922 133 : m_bMetadataDirty = true;
4923 133 : GetMetadata(); /* force loading from storage if needed */
4924 133 : return GDALPamDataset::SetMetadata(papszMetadata, pszDomain);
4925 : }
4926 :
4927 : /************************************************************************/
4928 : /* SetMetadataItem() */
4929 : /************************************************************************/
4930 :
4931 21 : CPLErr GDALGeoPackageDataset::SetMetadataItem(const char *pszName,
4932 : const char *pszValue,
4933 : const char *pszDomain)
4934 : {
4935 21 : pszDomain = CheckMetadataDomain(pszDomain);
4936 21 : m_bMetadataDirty = true;
4937 21 : GetMetadata(); /* force loading from storage if needed */
4938 21 : return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
4939 : }
4940 :
4941 : /************************************************************************/
4942 : /* Create() */
4943 : /************************************************************************/
4944 :
4945 988 : int GDALGeoPackageDataset::Create(const char *pszFilename, int nXSize,
4946 : int nYSize, int nBandsIn, GDALDataType eDT,
4947 : char **papszOptions)
4948 : {
4949 1976 : CPLString osCommand;
4950 :
4951 : /* First, ensure there isn't any such file yet. */
4952 : VSIStatBufL sStatBuf;
4953 :
4954 988 : if (nBandsIn != 0)
4955 : {
4956 226 : if (eDT == GDT_UInt8)
4957 : {
4958 156 : if (nBandsIn != 1 && nBandsIn != 2 && nBandsIn != 3 &&
4959 : nBandsIn != 4)
4960 : {
4961 1 : CPLError(CE_Failure, CPLE_NotSupported,
4962 : "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), "
4963 : "3 (RGB) or 4 (RGBA) band dataset supported for "
4964 : "Byte datatype");
4965 1 : return FALSE;
4966 : }
4967 : }
4968 70 : else if (eDT == GDT_Int16 || eDT == GDT_UInt16 || eDT == GDT_Float32)
4969 : {
4970 43 : if (nBandsIn != 1)
4971 : {
4972 3 : CPLError(CE_Failure, CPLE_NotSupported,
4973 : "Only single band dataset supported for non Byte "
4974 : "datatype");
4975 3 : return FALSE;
4976 : }
4977 : }
4978 : else
4979 : {
4980 27 : CPLError(CE_Failure, CPLE_NotSupported,
4981 : "Only Byte, Int16, UInt16 or Float32 supported");
4982 27 : return FALSE;
4983 : }
4984 : }
4985 :
4986 957 : const size_t nFilenameLen = strlen(pszFilename);
4987 957 : const bool bGpkgZip =
4988 952 : (nFilenameLen > strlen(".gpkg.zip") &&
4989 1909 : !STARTS_WITH(pszFilename, "/vsizip/") &&
4990 952 : EQUAL(pszFilename + nFilenameLen - strlen(".gpkg.zip"), ".gpkg.zip"));
4991 :
4992 : const bool bUseTempFile =
4993 958 : bGpkgZip || (CPLTestBool(CPLGetConfigOption(
4994 1 : "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", "NO")) &&
4995 1 : (VSIHasOptimizedReadMultiRange(pszFilename) != FALSE ||
4996 1 : EQUAL(CPLGetConfigOption(
4997 : "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", ""),
4998 957 : "FORCED")));
4999 :
5000 957 : bool bFileExists = false;
5001 957 : if (VSIStatL(pszFilename, &sStatBuf) == 0)
5002 : {
5003 10 : bFileExists = true;
5004 20 : if (nBandsIn == 0 || bUseTempFile ||
5005 10 : !CPLTestBool(
5006 : CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")))
5007 : {
5008 0 : CPLError(CE_Failure, CPLE_AppDefined,
5009 : "A file system object called '%s' already exists.",
5010 : pszFilename);
5011 :
5012 0 : return FALSE;
5013 : }
5014 : }
5015 :
5016 957 : if (bUseTempFile)
5017 : {
5018 3 : if (bGpkgZip)
5019 : {
5020 2 : std::string osFilenameInZip(CPLGetFilename(pszFilename));
5021 2 : osFilenameInZip.resize(osFilenameInZip.size() - strlen(".zip"));
5022 : m_osFinalFilename =
5023 2 : std::string("/vsizip/{") + pszFilename + "}/" + osFilenameInZip;
5024 : }
5025 : else
5026 : {
5027 1 : m_osFinalFilename = pszFilename;
5028 : }
5029 3 : m_pszFilename = CPLStrdup(
5030 6 : CPLGenerateTempFilenameSafe(CPLGetFilename(pszFilename)).c_str());
5031 3 : CPLDebug("GPKG", "Creating temporary file %s", m_pszFilename);
5032 : }
5033 : else
5034 : {
5035 954 : m_pszFilename = CPLStrdup(pszFilename);
5036 : }
5037 957 : m_bNew = true;
5038 957 : eAccess = GA_Update;
5039 957 : m_bDateTimeWithTZ =
5040 957 : EQUAL(CSLFetchNameValueDef(papszOptions, "DATETIME_FORMAT", "WITH_TZ"),
5041 : "WITH_TZ");
5042 :
5043 : // for test/debug purposes only. true is the nominal value
5044 957 : m_bPNGSupports2Bands =
5045 957 : CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_2BANDS", "TRUE"));
5046 957 : m_bPNGSupportsCT =
5047 957 : CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_CT", "TRUE"));
5048 :
5049 957 : if (!OpenOrCreateDB(bFileExists
5050 : ? SQLITE_OPEN_READWRITE
5051 : : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE))
5052 7 : return FALSE;
5053 :
5054 : /* Default to synchronous=off for performance for new file */
5055 1890 : if (!bFileExists &&
5056 940 : CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
5057 : {
5058 441 : SQLCommand(hDB, "PRAGMA synchronous = OFF");
5059 : }
5060 :
5061 : /* OGR UTF-8 support. If we set the UTF-8 Pragma early on, it */
5062 : /* will be written into the main file and supported henceforth */
5063 950 : SQLCommand(hDB, "PRAGMA encoding = \"UTF-8\"");
5064 :
5065 950 : if (bFileExists)
5066 : {
5067 10 : VSILFILE *fp = VSIFOpenL(pszFilename, "rb");
5068 10 : if (fp)
5069 : {
5070 : GByte abyHeader[100];
5071 10 : VSIFReadL(abyHeader, 1, sizeof(abyHeader), fp);
5072 10 : VSIFCloseL(fp);
5073 :
5074 10 : memcpy(&m_nApplicationId, abyHeader + knApplicationIdPos, 4);
5075 10 : m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
5076 10 : memcpy(&m_nUserVersion, abyHeader + knUserVersionPos, 4);
5077 10 : m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
5078 :
5079 10 : if (m_nApplicationId == GP10_APPLICATION_ID)
5080 : {
5081 0 : CPLDebug("GPKG", "GeoPackage v1.0");
5082 : }
5083 10 : else if (m_nApplicationId == GP11_APPLICATION_ID)
5084 : {
5085 0 : CPLDebug("GPKG", "GeoPackage v1.1");
5086 : }
5087 10 : else if (m_nApplicationId == GPKG_APPLICATION_ID &&
5088 10 : m_nUserVersion >= GPKG_1_2_VERSION)
5089 : {
5090 10 : CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
5091 10 : (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
5092 : }
5093 : }
5094 :
5095 10 : DetectSpatialRefSysColumns();
5096 : }
5097 :
5098 950 : const char *pszVersion = CSLFetchNameValue(papszOptions, "VERSION");
5099 950 : if (pszVersion && !EQUAL(pszVersion, "AUTO"))
5100 : {
5101 40 : if (EQUAL(pszVersion, "1.0"))
5102 : {
5103 2 : m_nApplicationId = GP10_APPLICATION_ID;
5104 2 : m_nUserVersion = 0;
5105 : }
5106 38 : else if (EQUAL(pszVersion, "1.1"))
5107 : {
5108 1 : m_nApplicationId = GP11_APPLICATION_ID;
5109 1 : m_nUserVersion = 0;
5110 : }
5111 37 : else if (EQUAL(pszVersion, "1.2"))
5112 : {
5113 15 : m_nApplicationId = GPKG_APPLICATION_ID;
5114 15 : m_nUserVersion = GPKG_1_2_VERSION;
5115 : }
5116 22 : else if (EQUAL(pszVersion, "1.3"))
5117 : {
5118 3 : m_nApplicationId = GPKG_APPLICATION_ID;
5119 3 : m_nUserVersion = GPKG_1_3_VERSION;
5120 : }
5121 19 : else if (EQUAL(pszVersion, "1.4"))
5122 : {
5123 19 : m_nApplicationId = GPKG_APPLICATION_ID;
5124 19 : m_nUserVersion = GPKG_1_4_VERSION;
5125 : }
5126 : }
5127 :
5128 950 : SoftStartTransaction();
5129 :
5130 1900 : CPLString osSQL;
5131 950 : if (!bFileExists)
5132 : {
5133 : /* Requirement 10: A GeoPackage SHALL include a gpkg_spatial_ref_sys
5134 : * table */
5135 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5136 : osSQL = "CREATE TABLE gpkg_spatial_ref_sys ("
5137 : "srs_name TEXT NOT NULL,"
5138 : "srs_id INTEGER NOT NULL PRIMARY KEY,"
5139 : "organization TEXT NOT NULL,"
5140 : "organization_coordsys_id INTEGER NOT NULL,"
5141 : "definition TEXT NOT NULL,"
5142 940 : "description TEXT";
5143 940 : if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "CRS_WKT_EXTENSION",
5144 1121 : "NO")) ||
5145 181 : (nBandsIn != 0 && eDT != GDT_UInt8))
5146 : {
5147 42 : m_bHasDefinition12_063 = true;
5148 42 : osSQL += ", definition_12_063 TEXT NOT NULL";
5149 42 : if (m_nUserVersion >= GPKG_1_4_VERSION)
5150 : {
5151 40 : osSQL += ", epoch DOUBLE";
5152 40 : m_bHasEpochColumn = true;
5153 : }
5154 : }
5155 : osSQL += ")"
5156 : ";"
5157 : /* Requirement 11: The gpkg_spatial_ref_sys table in a
5158 : GeoPackage SHALL */
5159 : /* contain a record for EPSG:4326, the geodetic WGS84 SRS */
5160 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5161 :
5162 : "INSERT INTO gpkg_spatial_ref_sys ("
5163 : "srs_name, srs_id, organization, organization_coordsys_id, "
5164 940 : "definition, description";
5165 940 : if (m_bHasDefinition12_063)
5166 42 : osSQL += ", definition_12_063";
5167 : osSQL +=
5168 : ") VALUES ("
5169 : "'WGS 84 geodetic', 4326, 'EPSG', 4326, '"
5170 : "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS "
5171 : "84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],"
5172 : "AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY["
5173 : "\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY["
5174 : "\"EPSG\",\"9122\"]],AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\","
5175 : "EAST],AUTHORITY[\"EPSG\",\"4326\"]]"
5176 : "', 'longitude/latitude coordinates in decimal degrees on the WGS "
5177 940 : "84 spheroid'";
5178 940 : if (m_bHasDefinition12_063)
5179 : osSQL +=
5180 : ", 'GEODCRS[\"WGS 84\", DATUM[\"World Geodetic System 1984\", "
5181 : "ELLIPSOID[\"WGS 84\",6378137, 298.257223563, "
5182 : "LENGTHUNIT[\"metre\", 1.0]]], PRIMEM[\"Greenwich\", 0.0, "
5183 : "ANGLEUNIT[\"degree\",0.0174532925199433]], CS[ellipsoidal, "
5184 : "2], AXIS[\"latitude\", north, ORDER[1]], AXIS[\"longitude\", "
5185 : "east, ORDER[2]], ANGLEUNIT[\"degree\", 0.0174532925199433], "
5186 42 : "ID[\"EPSG\", 4326]]'";
5187 : osSQL +=
5188 : ")"
5189 : ";"
5190 : /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
5191 : SHALL */
5192 : /* contain a record with an srs_id of -1, an organization of “NONE”,
5193 : */
5194 : /* an organization_coordsys_id of -1, and definition “undefined” */
5195 : /* for undefined Cartesian coordinate reference systems */
5196 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5197 : "INSERT INTO gpkg_spatial_ref_sys ("
5198 : "srs_name, srs_id, organization, organization_coordsys_id, "
5199 940 : "definition, description";
5200 940 : if (m_bHasDefinition12_063)
5201 42 : osSQL += ", definition_12_063";
5202 : osSQL += ") VALUES ("
5203 : "'Undefined Cartesian SRS', -1, 'NONE', -1, 'undefined', "
5204 940 : "'undefined Cartesian coordinate reference system'";
5205 940 : if (m_bHasDefinition12_063)
5206 42 : osSQL += ", 'undefined'";
5207 : osSQL +=
5208 : ")"
5209 : ";"
5210 : /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
5211 : SHALL */
5212 : /* contain a record with an srs_id of 0, an organization of “NONE”,
5213 : */
5214 : /* an organization_coordsys_id of 0, and definition “undefined” */
5215 : /* for undefined geographic coordinate reference systems */
5216 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5217 : "INSERT INTO gpkg_spatial_ref_sys ("
5218 : "srs_name, srs_id, organization, organization_coordsys_id, "
5219 940 : "definition, description";
5220 940 : if (m_bHasDefinition12_063)
5221 42 : osSQL += ", definition_12_063";
5222 : osSQL += ") VALUES ("
5223 : "'Undefined geographic SRS', 0, 'NONE', 0, 'undefined', "
5224 940 : "'undefined geographic coordinate reference system'";
5225 940 : if (m_bHasDefinition12_063)
5226 42 : osSQL += ", 'undefined'";
5227 : osSQL += ")"
5228 : ";"
5229 : /* Requirement 13: A GeoPackage file SHALL include a
5230 : gpkg_contents table */
5231 : /* http://opengis.github.io/geopackage/#_contents */
5232 : "CREATE TABLE gpkg_contents ("
5233 : "table_name TEXT NOT NULL PRIMARY KEY,"
5234 : "data_type TEXT NOT NULL,"
5235 : "identifier TEXT UNIQUE,"
5236 : "description TEXT DEFAULT '',"
5237 : "last_change DATETIME NOT NULL DEFAULT "
5238 : "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
5239 : "min_x DOUBLE, min_y DOUBLE,"
5240 : "max_x DOUBLE, max_y DOUBLE,"
5241 : "srs_id INTEGER,"
5242 : "CONSTRAINT fk_gc_r_srs_id FOREIGN KEY (srs_id) REFERENCES "
5243 : "gpkg_spatial_ref_sys(srs_id)"
5244 940 : ")";
5245 :
5246 : #ifdef ENABLE_GPKG_OGR_CONTENTS
5247 940 : if (CPLFetchBool(papszOptions, "ADD_GPKG_OGR_CONTENTS", true))
5248 : {
5249 935 : m_bHasGPKGOGRContents = true;
5250 : osSQL += ";"
5251 : "CREATE TABLE gpkg_ogr_contents("
5252 : "table_name TEXT NOT NULL PRIMARY KEY,"
5253 : "feature_count INTEGER DEFAULT NULL"
5254 935 : ")";
5255 : }
5256 : #endif
5257 :
5258 : /* Requirement 21: A GeoPackage with a gpkg_contents table row with a
5259 : * “features” */
5260 : /* data_type SHALL contain a gpkg_geometry_columns table or updateable
5261 : * view */
5262 : /* http://opengis.github.io/geopackage/#_geometry_columns */
5263 : const bool bCreateGeometryColumns =
5264 940 : CPLTestBool(CPLGetConfigOption("CREATE_GEOMETRY_COLUMNS", "YES"));
5265 940 : if (bCreateGeometryColumns)
5266 : {
5267 939 : m_bHasGPKGGeometryColumns = true;
5268 939 : osSQL += ";";
5269 939 : osSQL += pszCREATE_GPKG_GEOMETRY_COLUMNS;
5270 : }
5271 : }
5272 :
5273 : const bool bCreateTriggers =
5274 950 : CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "YES"));
5275 10 : if ((bFileExists && nBandsIn != 0 &&
5276 10 : SQLGetInteger(
5277 : hDB,
5278 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_tile_matrix_set' "
5279 : "AND type in ('table', 'view')",
5280 1900 : nullptr) == 0) ||
5281 949 : (!bFileExists &&
5282 940 : CPLTestBool(CPLGetConfigOption("CREATE_RASTER_TABLES", "YES"))))
5283 : {
5284 940 : if (!osSQL.empty())
5285 939 : osSQL += ";";
5286 :
5287 : /* From C.5. gpkg_tile_matrix_set Table 28. gpkg_tile_matrix_set Table
5288 : * Creation SQL */
5289 : osSQL += "CREATE TABLE gpkg_tile_matrix_set ("
5290 : "table_name TEXT NOT NULL PRIMARY KEY,"
5291 : "srs_id INTEGER NOT NULL,"
5292 : "min_x DOUBLE NOT NULL,"
5293 : "min_y DOUBLE NOT NULL,"
5294 : "max_x DOUBLE NOT NULL,"
5295 : "max_y DOUBLE NOT NULL,"
5296 : "CONSTRAINT fk_gtms_table_name FOREIGN KEY (table_name) "
5297 : "REFERENCES gpkg_contents(table_name),"
5298 : "CONSTRAINT fk_gtms_srs FOREIGN KEY (srs_id) REFERENCES "
5299 : "gpkg_spatial_ref_sys (srs_id)"
5300 : ")"
5301 : ";"
5302 :
5303 : /* From C.6. gpkg_tile_matrix Table 29. gpkg_tile_matrix Table
5304 : Creation SQL */
5305 : "CREATE TABLE gpkg_tile_matrix ("
5306 : "table_name TEXT NOT NULL,"
5307 : "zoom_level INTEGER NOT NULL,"
5308 : "matrix_width INTEGER NOT NULL,"
5309 : "matrix_height INTEGER NOT NULL,"
5310 : "tile_width INTEGER NOT NULL,"
5311 : "tile_height INTEGER NOT NULL,"
5312 : "pixel_x_size DOUBLE NOT NULL,"
5313 : "pixel_y_size DOUBLE NOT NULL,"
5314 : "CONSTRAINT pk_ttm PRIMARY KEY (table_name, zoom_level),"
5315 : "CONSTRAINT fk_tmm_table_name FOREIGN KEY (table_name) "
5316 : "REFERENCES gpkg_contents(table_name)"
5317 940 : ")";
5318 :
5319 940 : if (bCreateTriggers)
5320 : {
5321 : /* From D.1. gpkg_tile_matrix Table 39. gpkg_tile_matrix Trigger
5322 : * Definition SQL */
5323 940 : const char *pszTileMatrixTrigger =
5324 : "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_insert' "
5325 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5326 : "FOR EACH ROW BEGIN "
5327 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5328 : "violates constraint: zoom_level cannot be less than 0') "
5329 : "WHERE (NEW.zoom_level < 0); "
5330 : "END; "
5331 : "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_update' "
5332 : "BEFORE UPDATE of zoom_level ON 'gpkg_tile_matrix' "
5333 : "FOR EACH ROW BEGIN "
5334 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5335 : "violates constraint: zoom_level cannot be less than 0') "
5336 : "WHERE (NEW.zoom_level < 0); "
5337 : "END; "
5338 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_insert' "
5339 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5340 : "FOR EACH ROW BEGIN "
5341 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5342 : "violates constraint: matrix_width cannot be less than 1') "
5343 : "WHERE (NEW.matrix_width < 1); "
5344 : "END; "
5345 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_update' "
5346 : "BEFORE UPDATE OF matrix_width ON 'gpkg_tile_matrix' "
5347 : "FOR EACH ROW BEGIN "
5348 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5349 : "violates constraint: matrix_width cannot be less than 1') "
5350 : "WHERE (NEW.matrix_width < 1); "
5351 : "END; "
5352 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_insert' "
5353 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5354 : "FOR EACH ROW BEGIN "
5355 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5356 : "violates constraint: matrix_height cannot be less than 1') "
5357 : "WHERE (NEW.matrix_height < 1); "
5358 : "END; "
5359 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_update' "
5360 : "BEFORE UPDATE OF matrix_height ON 'gpkg_tile_matrix' "
5361 : "FOR EACH ROW BEGIN "
5362 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5363 : "violates constraint: matrix_height cannot be less than 1') "
5364 : "WHERE (NEW.matrix_height < 1); "
5365 : "END; "
5366 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_insert' "
5367 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5368 : "FOR EACH ROW BEGIN "
5369 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5370 : "violates constraint: pixel_x_size must be greater than 0') "
5371 : "WHERE NOT (NEW.pixel_x_size > 0); "
5372 : "END; "
5373 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_update' "
5374 : "BEFORE UPDATE OF pixel_x_size ON 'gpkg_tile_matrix' "
5375 : "FOR EACH ROW BEGIN "
5376 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5377 : "violates constraint: pixel_x_size must be greater than 0') "
5378 : "WHERE NOT (NEW.pixel_x_size > 0); "
5379 : "END; "
5380 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_insert' "
5381 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5382 : "FOR EACH ROW BEGIN "
5383 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5384 : "violates constraint: pixel_y_size must be greater than 0') "
5385 : "WHERE NOT (NEW.pixel_y_size > 0); "
5386 : "END; "
5387 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_update' "
5388 : "BEFORE UPDATE OF pixel_y_size ON 'gpkg_tile_matrix' "
5389 : "FOR EACH ROW BEGIN "
5390 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5391 : "violates constraint: pixel_y_size must be greater than 0') "
5392 : "WHERE NOT (NEW.pixel_y_size > 0); "
5393 : "END;";
5394 940 : osSQL += ";";
5395 940 : osSQL += pszTileMatrixTrigger;
5396 : }
5397 : }
5398 :
5399 950 : if (!osSQL.empty() && OGRERR_NONE != SQLCommand(hDB, osSQL))
5400 1 : return FALSE;
5401 :
5402 949 : if (!bFileExists)
5403 : {
5404 : const char *pszMetadataTables =
5405 939 : CSLFetchNameValue(papszOptions, "METADATA_TABLES");
5406 939 : if (pszMetadataTables)
5407 10 : m_nCreateMetadataTables = int(CPLTestBool(pszMetadataTables));
5408 :
5409 939 : if (m_nCreateMetadataTables == TRUE && !CreateMetadataTables())
5410 0 : return FALSE;
5411 :
5412 939 : if (m_bHasDefinition12_063)
5413 : {
5414 84 : if (OGRERR_NONE != CreateExtensionsTableIfNecessary() ||
5415 : OGRERR_NONE !=
5416 42 : SQLCommand(hDB, "INSERT INTO gpkg_extensions "
5417 : "(table_name, column_name, extension_name, "
5418 : "definition, scope) "
5419 : "VALUES "
5420 : "('gpkg_spatial_ref_sys', "
5421 : "'definition_12_063', 'gpkg_crs_wkt', "
5422 : "'http://www.geopackage.org/spec120/"
5423 : "#extension_crs_wkt', 'read-write')"))
5424 : {
5425 0 : return FALSE;
5426 : }
5427 42 : if (m_bHasEpochColumn)
5428 : {
5429 40 : if (OGRERR_NONE !=
5430 40 : SQLCommand(
5431 : hDB, "UPDATE gpkg_extensions SET extension_name = "
5432 : "'gpkg_crs_wkt_1_1' "
5433 80 : "WHERE extension_name = 'gpkg_crs_wkt'") ||
5434 : OGRERR_NONE !=
5435 40 : SQLCommand(hDB, "INSERT INTO gpkg_extensions "
5436 : "(table_name, column_name, "
5437 : "extension_name, definition, scope) "
5438 : "VALUES "
5439 : "('gpkg_spatial_ref_sys', 'epoch', "
5440 : "'gpkg_crs_wkt_1_1', "
5441 : "'http://www.geopackage.org/spec/"
5442 : "#extension_crs_wkt', "
5443 : "'read-write')"))
5444 : {
5445 0 : return FALSE;
5446 : }
5447 : }
5448 : }
5449 : }
5450 :
5451 949 : if (nBandsIn != 0)
5452 : {
5453 190 : const std::string osTableName = CPLGetBasenameSafe(m_pszFilename);
5454 : m_osRasterTable = CSLFetchNameValueDef(papszOptions, "RASTER_TABLE",
5455 190 : osTableName.c_str());
5456 190 : if (m_osRasterTable.empty())
5457 : {
5458 0 : CPLError(CE_Failure, CPLE_AppDefined,
5459 : "RASTER_TABLE must be set to a non empty value");
5460 0 : return FALSE;
5461 : }
5462 190 : m_bIdentifierAsCO =
5463 190 : CSLFetchNameValue(papszOptions, "RASTER_IDENTIFIER") != nullptr;
5464 : m_osIdentifier = CSLFetchNameValueDef(papszOptions, "RASTER_IDENTIFIER",
5465 190 : m_osRasterTable);
5466 190 : m_bDescriptionAsCO =
5467 190 : CSLFetchNameValue(papszOptions, "RASTER_DESCRIPTION") != nullptr;
5468 : m_osDescription =
5469 190 : CSLFetchNameValueDef(papszOptions, "RASTER_DESCRIPTION", "");
5470 190 : SetDataType(eDT);
5471 190 : if (eDT == GDT_Int16)
5472 16 : SetGlobalOffsetScale(-32768.0, 1.0);
5473 :
5474 : /* From C.7. sample_tile_pyramid (Informative) Table 31. EXAMPLE: tiles
5475 : * table Create Table SQL (Informative) */
5476 : char *pszSQL =
5477 190 : sqlite3_mprintf("CREATE TABLE \"%w\" ("
5478 : "id INTEGER PRIMARY KEY AUTOINCREMENT,"
5479 : "zoom_level INTEGER NOT NULL,"
5480 : "tile_column INTEGER NOT NULL,"
5481 : "tile_row INTEGER NOT NULL,"
5482 : "tile_data BLOB NOT NULL,"
5483 : "UNIQUE (zoom_level, tile_column, tile_row)"
5484 : ")",
5485 : m_osRasterTable.c_str());
5486 190 : osSQL = pszSQL;
5487 190 : sqlite3_free(pszSQL);
5488 :
5489 190 : if (bCreateTriggers)
5490 : {
5491 190 : osSQL += ";";
5492 190 : osSQL += CreateRasterTriggersSQL(m_osRasterTable);
5493 : }
5494 :
5495 190 : OGRErr eErr = SQLCommand(hDB, osSQL);
5496 190 : if (OGRERR_NONE != eErr)
5497 0 : return FALSE;
5498 :
5499 190 : const char *pszTF = CSLFetchNameValue(papszOptions, "TILE_FORMAT");
5500 190 : if (eDT == GDT_Int16 || eDT == GDT_UInt16)
5501 : {
5502 27 : m_eTF = GPKG_TF_PNG_16BIT;
5503 27 : if (pszTF)
5504 : {
5505 1 : if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "PNG"))
5506 : {
5507 0 : CPLError(CE_Warning, CPLE_NotSupported,
5508 : "Only AUTO or PNG supported "
5509 : "as tile format for Int16 / UInt16");
5510 : }
5511 : }
5512 : }
5513 163 : else if (eDT == GDT_Float32)
5514 : {
5515 13 : m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
5516 13 : if (pszTF)
5517 : {
5518 5 : if (EQUAL(pszTF, "PNG"))
5519 5 : m_eTF = GPKG_TF_PNG_16BIT;
5520 0 : else if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "TIFF"))
5521 : {
5522 0 : CPLError(CE_Warning, CPLE_NotSupported,
5523 : "Only AUTO, PNG or TIFF supported "
5524 : "as tile format for Float32");
5525 : }
5526 : }
5527 : }
5528 : else
5529 : {
5530 150 : if (pszTF)
5531 : {
5532 71 : m_eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
5533 71 : if (nBandsIn == 1 && m_eTF != GPKG_TF_PNG)
5534 7 : m_bMetadataDirty = true;
5535 : }
5536 79 : else if (nBandsIn == 1)
5537 68 : m_eTF = GPKG_TF_PNG;
5538 : }
5539 :
5540 190 : if (eDT != GDT_UInt8)
5541 : {
5542 40 : if (!CreateTileGriddedTable(papszOptions))
5543 0 : return FALSE;
5544 : }
5545 :
5546 190 : nRasterXSize = nXSize;
5547 190 : nRasterYSize = nYSize;
5548 :
5549 : const char *pszTileSize =
5550 190 : CSLFetchNameValueDef(papszOptions, "BLOCKSIZE", "256");
5551 : const char *pszTileWidth =
5552 190 : CSLFetchNameValueDef(papszOptions, "BLOCKXSIZE", pszTileSize);
5553 : const char *pszTileHeight =
5554 190 : CSLFetchNameValueDef(papszOptions, "BLOCKYSIZE", pszTileSize);
5555 190 : int nTileWidth = atoi(pszTileWidth);
5556 190 : int nTileHeight = atoi(pszTileHeight);
5557 190 : if ((nTileWidth < 8 || nTileWidth > 4096 || nTileHeight < 8 ||
5558 380 : nTileHeight > 4096) &&
5559 1 : !CPLTestBool(CPLGetConfigOption("GPKG_ALLOW_CRAZY_SETTINGS", "NO")))
5560 : {
5561 0 : CPLError(CE_Failure, CPLE_AppDefined,
5562 : "Invalid block dimensions: %dx%d", nTileWidth,
5563 : nTileHeight);
5564 0 : return FALSE;
5565 : }
5566 :
5567 513 : for (int i = 1; i <= nBandsIn; i++)
5568 : {
5569 323 : SetBand(i, std::make_unique<GDALGeoPackageRasterBand>(
5570 : this, nTileWidth, nTileHeight));
5571 : }
5572 :
5573 190 : GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL",
5574 : "IMAGE_STRUCTURE");
5575 190 : GDALPamDataset::SetMetadataItem("IDENTIFIER", m_osIdentifier);
5576 190 : if (!m_osDescription.empty())
5577 1 : GDALPamDataset::SetMetadataItem("DESCRIPTION", m_osDescription);
5578 :
5579 190 : ParseCompressionOptions(papszOptions);
5580 :
5581 190 : if (m_eTF == GPKG_TF_WEBP)
5582 : {
5583 10 : if (!RegisterWebPExtension())
5584 0 : return FALSE;
5585 : }
5586 :
5587 : m_osTilingScheme =
5588 190 : CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
5589 190 : if (!EQUAL(m_osTilingScheme, "CUSTOM"))
5590 : {
5591 22 : const auto poTS = GetTilingScheme(m_osTilingScheme);
5592 22 : if (!poTS)
5593 0 : return FALSE;
5594 :
5595 43 : if (nTileWidth != poTS->nTileWidth ||
5596 21 : nTileHeight != poTS->nTileHeight)
5597 : {
5598 2 : CPLError(CE_Failure, CPLE_NotSupported,
5599 : "Tile dimension should be %dx%d for %s tiling scheme",
5600 1 : poTS->nTileWidth, poTS->nTileHeight,
5601 : m_osTilingScheme.c_str());
5602 1 : return FALSE;
5603 : }
5604 :
5605 : const char *pszZoomLevel =
5606 21 : CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
5607 21 : if (pszZoomLevel)
5608 : {
5609 1 : m_nZoomLevel = atoi(pszZoomLevel);
5610 1 : int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
5611 1 : while ((1 << nMaxZoomLevelForThisTM) >
5612 2 : INT_MAX / poTS->nTileXCountZoomLevel0 ||
5613 1 : (1 << nMaxZoomLevelForThisTM) >
5614 1 : INT_MAX / poTS->nTileYCountZoomLevel0)
5615 : {
5616 0 : --nMaxZoomLevelForThisTM;
5617 : }
5618 :
5619 1 : if (m_nZoomLevel < 0 || m_nZoomLevel > nMaxZoomLevelForThisTM)
5620 : {
5621 0 : CPLError(CE_Failure, CPLE_AppDefined,
5622 : "ZOOM_LEVEL = %s is invalid. It should be in "
5623 : "[0,%d] range",
5624 : pszZoomLevel, nMaxZoomLevelForThisTM);
5625 0 : return FALSE;
5626 : }
5627 : }
5628 :
5629 : // Implicitly sets SRS.
5630 21 : OGRSpatialReference oSRS;
5631 21 : if (oSRS.importFromEPSG(poTS->nEPSGCode) != OGRERR_NONE)
5632 0 : return FALSE;
5633 21 : char *pszWKT = nullptr;
5634 21 : oSRS.exportToWkt(&pszWKT);
5635 21 : SetProjection(pszWKT);
5636 21 : CPLFree(pszWKT);
5637 : }
5638 : else
5639 : {
5640 168 : if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
5641 : {
5642 0 : CPLError(
5643 : CE_Failure, CPLE_NotSupported,
5644 : "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
5645 0 : return false;
5646 : }
5647 : }
5648 : }
5649 :
5650 948 : if (bFileExists && nBandsIn > 0 && eDT == GDT_UInt8)
5651 : {
5652 : // If there was an ogr_empty_table table, we can remove it
5653 9 : RemoveOGREmptyTable();
5654 : }
5655 :
5656 948 : SoftCommitTransaction();
5657 :
5658 : /* Requirement 2 */
5659 : /* We have to do this after there's some content so the database file */
5660 : /* is not zero length */
5661 948 : SetApplicationAndUserVersionId();
5662 :
5663 : /* Default to synchronous=off for performance for new file */
5664 1886 : if (!bFileExists &&
5665 938 : CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
5666 : {
5667 441 : SQLCommand(hDB, "PRAGMA synchronous = OFF");
5668 : }
5669 :
5670 948 : return TRUE;
5671 : }
5672 :
5673 : /************************************************************************/
5674 : /* RemoveOGREmptyTable() */
5675 : /************************************************************************/
5676 :
5677 758 : void GDALGeoPackageDataset::RemoveOGREmptyTable()
5678 : {
5679 : // Run with sqlite3_exec since we don't want errors to be emitted
5680 758 : sqlite3_exec(hDB, "DROP TABLE IF EXISTS ogr_empty_table", nullptr, nullptr,
5681 : nullptr);
5682 758 : sqlite3_exec(
5683 : hDB, "DELETE FROM gpkg_contents WHERE table_name = 'ogr_empty_table'",
5684 : nullptr, nullptr, nullptr);
5685 : #ifdef ENABLE_GPKG_OGR_CONTENTS
5686 758 : if (m_bHasGPKGOGRContents)
5687 : {
5688 744 : sqlite3_exec(hDB,
5689 : "DELETE FROM gpkg_ogr_contents WHERE "
5690 : "table_name = 'ogr_empty_table'",
5691 : nullptr, nullptr, nullptr);
5692 : }
5693 : #endif
5694 758 : sqlite3_exec(hDB,
5695 : "DELETE FROM gpkg_geometry_columns WHERE "
5696 : "table_name = 'ogr_empty_table'",
5697 : nullptr, nullptr, nullptr);
5698 758 : }
5699 :
5700 : /************************************************************************/
5701 : /* CreateTileGriddedTable() */
5702 : /************************************************************************/
5703 :
5704 40 : bool GDALGeoPackageDataset::CreateTileGriddedTable(char **papszOptions)
5705 : {
5706 80 : CPLString osSQL;
5707 40 : if (!HasGriddedCoverageAncillaryTable())
5708 : {
5709 : // It doesn't exist. So create gpkg_extensions table if necessary, and
5710 : // gpkg_2d_gridded_coverage_ancillary & gpkg_2d_gridded_tile_ancillary,
5711 : // and register them as extensions.
5712 40 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
5713 0 : return false;
5714 :
5715 : // Req 1 /table-defs/coverage-ancillary
5716 : osSQL = "CREATE TABLE gpkg_2d_gridded_coverage_ancillary ("
5717 : "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
5718 : "tile_matrix_set_name TEXT NOT NULL UNIQUE,"
5719 : "datatype TEXT NOT NULL DEFAULT 'integer',"
5720 : "scale REAL NOT NULL DEFAULT 1.0,"
5721 : "offset REAL NOT NULL DEFAULT 0.0,"
5722 : "precision REAL DEFAULT 1.0,"
5723 : "data_null REAL,"
5724 : "grid_cell_encoding TEXT DEFAULT 'grid-value-is-center',"
5725 : "uom TEXT,"
5726 : "field_name TEXT DEFAULT 'Height',"
5727 : "quantity_definition TEXT DEFAULT 'Height',"
5728 : "CONSTRAINT fk_g2dgtct_name FOREIGN KEY(tile_matrix_set_name) "
5729 : "REFERENCES gpkg_tile_matrix_set ( table_name ) "
5730 : "CHECK (datatype in ('integer','float')))"
5731 : ";"
5732 : // Requirement 2 /table-defs/tile-ancillary
5733 : "CREATE TABLE gpkg_2d_gridded_tile_ancillary ("
5734 : "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
5735 : "tpudt_name TEXT NOT NULL,"
5736 : "tpudt_id INTEGER NOT NULL,"
5737 : "scale REAL NOT NULL DEFAULT 1.0,"
5738 : "offset REAL NOT NULL DEFAULT 0.0,"
5739 : "min REAL DEFAULT NULL,"
5740 : "max REAL DEFAULT NULL,"
5741 : "mean REAL DEFAULT NULL,"
5742 : "std_dev REAL DEFAULT NULL,"
5743 : "CONSTRAINT fk_g2dgtat_name FOREIGN KEY (tpudt_name) "
5744 : "REFERENCES gpkg_contents(table_name),"
5745 : "UNIQUE (tpudt_name, tpudt_id))"
5746 : ";"
5747 : // Requirement 6 /gpkg-extensions
5748 : "INSERT INTO gpkg_extensions "
5749 : "(table_name, column_name, extension_name, definition, scope) "
5750 : "VALUES ('gpkg_2d_gridded_coverage_ancillary', NULL, "
5751 : "'gpkg_2d_gridded_coverage', "
5752 : "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5753 : "'read-write')"
5754 : ";"
5755 : // Requirement 6 /gpkg-extensions
5756 : "INSERT INTO gpkg_extensions "
5757 : "(table_name, column_name, extension_name, definition, scope) "
5758 : "VALUES ('gpkg_2d_gridded_tile_ancillary', NULL, "
5759 : "'gpkg_2d_gridded_coverage', "
5760 : "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5761 : "'read-write')"
5762 40 : ";";
5763 : }
5764 :
5765 : // Requirement 6 /gpkg-extensions
5766 40 : char *pszSQL = sqlite3_mprintf(
5767 : "INSERT INTO gpkg_extensions "
5768 : "(table_name, column_name, extension_name, definition, scope) "
5769 : "VALUES ('%q', 'tile_data', "
5770 : "'gpkg_2d_gridded_coverage', "
5771 : "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5772 : "'read-write')",
5773 : m_osRasterTable.c_str());
5774 40 : osSQL += pszSQL;
5775 40 : osSQL += ";";
5776 40 : sqlite3_free(pszSQL);
5777 :
5778 : // Requirement 7 /gpkg-2d-gridded-coverage-ancillary
5779 : // Requirement 8 /gpkg-2d-gridded-coverage-ancillary-set-name
5780 : // Requirement 9 /gpkg-2d-gridded-coverage-ancillary-datatype
5781 40 : m_dfPrecision =
5782 40 : CPLAtof(CSLFetchNameValueDef(papszOptions, "PRECISION", "1"));
5783 : CPLString osGridCellEncoding(CSLFetchNameValueDef(
5784 80 : papszOptions, "GRID_CELL_ENCODING", "grid-value-is-center"));
5785 40 : m_bGridCellEncodingAsCO =
5786 40 : CSLFetchNameValue(papszOptions, "GRID_CELL_ENCODING") != nullptr;
5787 80 : CPLString osUom(CSLFetchNameValueDef(papszOptions, "UOM", ""));
5788 : CPLString osFieldName(
5789 80 : CSLFetchNameValueDef(papszOptions, "FIELD_NAME", "Height"));
5790 : CPLString osQuantityDefinition(
5791 80 : CSLFetchNameValueDef(papszOptions, "QUANTITY_DEFINITION", "Height"));
5792 :
5793 121 : pszSQL = sqlite3_mprintf(
5794 : "INSERT INTO gpkg_2d_gridded_coverage_ancillary "
5795 : "(tile_matrix_set_name, datatype, scale, offset, precision, "
5796 : "grid_cell_encoding, uom, field_name, quantity_definition) "
5797 : "VALUES (%Q, '%s', %.17g, %.17g, %.17g, %Q, %Q, %Q, %Q)",
5798 : m_osRasterTable.c_str(),
5799 40 : (m_eTF == GPKG_TF_PNG_16BIT) ? "integer" : "float", m_dfScale,
5800 : m_dfOffset, m_dfPrecision, osGridCellEncoding.c_str(),
5801 41 : osUom.empty() ? nullptr : osUom.c_str(), osFieldName.c_str(),
5802 : osQuantityDefinition.c_str());
5803 40 : m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary = pszSQL;
5804 40 : sqlite3_free(pszSQL);
5805 :
5806 : // Requirement 3 /gpkg-spatial-ref-sys-row
5807 : auto oResultTable = SQLQuery(
5808 80 : hDB, "SELECT * FROM gpkg_spatial_ref_sys WHERE srs_id = 4979 LIMIT 2");
5809 40 : bool bHasEPSG4979 = (oResultTable && oResultTable->RowCount() == 1);
5810 40 : if (!bHasEPSG4979)
5811 : {
5812 41 : if (!m_bHasDefinition12_063 &&
5813 1 : !ConvertGpkgSpatialRefSysToExtensionWkt2(/*bForceEpoch=*/false))
5814 : {
5815 0 : return false;
5816 : }
5817 :
5818 : // This is WKT 2...
5819 40 : const char *pszWKT =
5820 : "GEODCRS[\"WGS 84\","
5821 : "DATUM[\"World Geodetic System 1984\","
5822 : " ELLIPSOID[\"WGS 84\",6378137,298.257223563,"
5823 : "LENGTHUNIT[\"metre\",1.0]]],"
5824 : "CS[ellipsoidal,3],"
5825 : " AXIS[\"latitude\",north,ORDER[1],ANGLEUNIT[\"degree\","
5826 : "0.0174532925199433]],"
5827 : " AXIS[\"longitude\",east,ORDER[2],ANGLEUNIT[\"degree\","
5828 : "0.0174532925199433]],"
5829 : " AXIS[\"ellipsoidal height\",up,ORDER[3],"
5830 : "LENGTHUNIT[\"metre\",1.0]],"
5831 : "ID[\"EPSG\",4979]]";
5832 :
5833 40 : pszSQL = sqlite3_mprintf(
5834 : "INSERT INTO gpkg_spatial_ref_sys "
5835 : "(srs_name,srs_id,organization,organization_coordsys_id,"
5836 : "definition,definition_12_063) VALUES "
5837 : "('WGS 84 3D', 4979, 'EPSG', 4979, 'undefined', '%q')",
5838 : pszWKT);
5839 40 : osSQL += ";";
5840 40 : osSQL += pszSQL;
5841 40 : sqlite3_free(pszSQL);
5842 : }
5843 :
5844 40 : return SQLCommand(hDB, osSQL) == OGRERR_NONE;
5845 : }
5846 :
5847 : /************************************************************************/
5848 : /* HasGriddedCoverageAncillaryTable() */
5849 : /************************************************************************/
5850 :
5851 44 : bool GDALGeoPackageDataset::HasGriddedCoverageAncillaryTable()
5852 : {
5853 : auto oResultTable = SQLQuery(
5854 : hDB, "SELECT * FROM sqlite_master WHERE type IN ('table', 'view') AND "
5855 44 : "name = 'gpkg_2d_gridded_coverage_ancillary'");
5856 44 : bool bHasTable = (oResultTable && oResultTable->RowCount() == 1);
5857 88 : return bHasTable;
5858 : }
5859 :
5860 : /************************************************************************/
5861 : /* GetUnderlyingDataset() */
5862 : /************************************************************************/
5863 :
5864 3 : static GDALDataset *GetUnderlyingDataset(GDALDataset *poSrcDS)
5865 : {
5866 3 : if (auto poVRTDS = dynamic_cast<VRTDataset *>(poSrcDS))
5867 : {
5868 0 : auto poTmpDS = poVRTDS->GetSingleSimpleSource();
5869 0 : if (poTmpDS)
5870 0 : return poTmpDS;
5871 : }
5872 :
5873 3 : return poSrcDS;
5874 : }
5875 :
5876 : /************************************************************************/
5877 : /* CreateCopy() */
5878 : /************************************************************************/
5879 :
5880 : typedef struct
5881 : {
5882 : const char *pszName;
5883 : GDALResampleAlg eResampleAlg;
5884 : } WarpResamplingAlg;
5885 :
5886 : static const WarpResamplingAlg asResamplingAlg[] = {
5887 : {"NEAREST", GRA_NearestNeighbour},
5888 : {"BILINEAR", GRA_Bilinear},
5889 : {"CUBIC", GRA_Cubic},
5890 : {"CUBICSPLINE", GRA_CubicSpline},
5891 : {"LANCZOS", GRA_Lanczos},
5892 : {"MODE", GRA_Mode},
5893 : {"AVERAGE", GRA_Average},
5894 : {"RMS", GRA_RMS},
5895 : };
5896 :
5897 162 : GDALDataset *GDALGeoPackageDataset::CreateCopy(const char *pszFilename,
5898 : GDALDataset *poSrcDS,
5899 : int bStrict, char **papszOptions,
5900 : GDALProgressFunc pfnProgress,
5901 : void *pProgressData)
5902 : {
5903 162 : const int nBands = poSrcDS->GetRasterCount();
5904 162 : if (nBands == 0)
5905 : {
5906 2 : GDALDataset *poDS = nullptr;
5907 : GDALDriver *poThisDriver =
5908 2 : GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
5909 2 : if (poThisDriver != nullptr)
5910 : {
5911 2 : poDS = poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS,
5912 : bStrict, papszOptions,
5913 : pfnProgress, pProgressData);
5914 : }
5915 2 : return poDS;
5916 : }
5917 :
5918 : const char *pszTilingScheme =
5919 160 : CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
5920 :
5921 320 : CPLStringList apszUpdatedOptions(CSLDuplicate(papszOptions));
5922 160 : if (CPLTestBool(
5923 166 : CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")) &&
5924 6 : CSLFetchNameValue(papszOptions, "RASTER_TABLE") == nullptr)
5925 : {
5926 : const std::string osBasename(CPLGetBasenameSafe(
5927 6 : GetUnderlyingDataset(poSrcDS)->GetDescription()));
5928 3 : apszUpdatedOptions.SetNameValue("RASTER_TABLE", osBasename.c_str());
5929 : }
5930 :
5931 160 : if (nBands != 1 && nBands != 2 && nBands != 3 && nBands != 4)
5932 : {
5933 1 : CPLError(CE_Failure, CPLE_NotSupported,
5934 : "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), 3 (RGB) or "
5935 : "4 (RGBA) band dataset supported");
5936 1 : return nullptr;
5937 : }
5938 :
5939 159 : const char *pszUnitType = poSrcDS->GetRasterBand(1)->GetUnitType();
5940 318 : if (CSLFetchNameValue(papszOptions, "UOM") == nullptr && pszUnitType &&
5941 159 : !EQUAL(pszUnitType, ""))
5942 : {
5943 1 : apszUpdatedOptions.SetNameValue("UOM", pszUnitType);
5944 : }
5945 :
5946 159 : if (EQUAL(pszTilingScheme, "CUSTOM"))
5947 : {
5948 135 : if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
5949 : {
5950 0 : CPLError(CE_Failure, CPLE_NotSupported,
5951 : "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
5952 0 : return nullptr;
5953 : }
5954 :
5955 135 : GDALGeoPackageDataset *poDS = nullptr;
5956 : GDALDriver *poThisDriver =
5957 135 : GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
5958 135 : if (poThisDriver != nullptr)
5959 : {
5960 135 : apszUpdatedOptions.SetNameValue("SKIP_HOLES", "YES");
5961 135 : poDS = cpl::down_cast<GDALGeoPackageDataset *>(
5962 : poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
5963 : apszUpdatedOptions, pfnProgress,
5964 135 : pProgressData));
5965 :
5966 250 : if (poDS != nullptr &&
5967 135 : poSrcDS->GetRasterBand(1)->GetRasterDataType() == GDT_UInt8 &&
5968 : nBands <= 3)
5969 : {
5970 75 : poDS->m_nBandCountFromMetadata = nBands;
5971 75 : poDS->m_bMetadataDirty = true;
5972 : }
5973 : }
5974 135 : if (poDS)
5975 115 : poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
5976 135 : return poDS;
5977 : }
5978 :
5979 48 : const auto poTS = GetTilingScheme(pszTilingScheme);
5980 24 : if (!poTS)
5981 : {
5982 2 : return nullptr;
5983 : }
5984 22 : const int nEPSGCode = poTS->nEPSGCode;
5985 :
5986 44 : OGRSpatialReference oSRS;
5987 22 : if (oSRS.importFromEPSG(nEPSGCode) != OGRERR_NONE)
5988 : {
5989 0 : return nullptr;
5990 : }
5991 22 : char *pszWKT = nullptr;
5992 22 : oSRS.exportToWkt(&pszWKT);
5993 22 : char **papszTO = CSLSetNameValue(nullptr, "DST_SRS", pszWKT);
5994 :
5995 22 : void *hTransformArg = nullptr;
5996 :
5997 : // Hack to compensate for GDALSuggestedWarpOutput2() failure (or not
5998 : // ideal suggestion with PROJ 8) when reprojecting latitude = +/- 90 to
5999 : // EPSG:3857.
6000 22 : GDALGeoTransform srcGT;
6001 22 : std::unique_ptr<GDALDataset> poTmpDS;
6002 22 : bool bEPSG3857Adjust = false;
6003 8 : if (nEPSGCode == 3857 && poSrcDS->GetGeoTransform(srcGT) == CE_None &&
6004 30 : srcGT[2] == 0 && srcGT[4] == 0 && srcGT[5] < 0)
6005 : {
6006 8 : const auto poSrcSRS = poSrcDS->GetSpatialRef();
6007 8 : if (poSrcSRS && poSrcSRS->IsGeographic())
6008 : {
6009 2 : double maxLat = srcGT[3];
6010 2 : double minLat = srcGT[3] + poSrcDS->GetRasterYSize() * srcGT[5];
6011 : // Corresponds to the latitude of below MAX_GM
6012 2 : constexpr double MAX_LAT = 85.0511287798066;
6013 2 : bool bModified = false;
6014 2 : if (maxLat > MAX_LAT)
6015 : {
6016 2 : maxLat = MAX_LAT;
6017 2 : bModified = true;
6018 : }
6019 2 : if (minLat < -MAX_LAT)
6020 : {
6021 2 : minLat = -MAX_LAT;
6022 2 : bModified = true;
6023 : }
6024 2 : if (bModified)
6025 : {
6026 4 : CPLStringList aosOptions;
6027 2 : aosOptions.AddString("-of");
6028 2 : aosOptions.AddString("VRT");
6029 2 : aosOptions.AddString("-projwin");
6030 2 : aosOptions.AddString(CPLSPrintf("%.17g", srcGT[0]));
6031 2 : aosOptions.AddString(CPLSPrintf("%.17g", maxLat));
6032 : aosOptions.AddString(CPLSPrintf(
6033 2 : "%.17g", srcGT[0] + poSrcDS->GetRasterXSize() * srcGT[1]));
6034 2 : aosOptions.AddString(CPLSPrintf("%.17g", minLat));
6035 : auto psOptions =
6036 2 : GDALTranslateOptionsNew(aosOptions.List(), nullptr);
6037 2 : poTmpDS.reset(GDALDataset::FromHandle(GDALTranslate(
6038 : "", GDALDataset::ToHandle(poSrcDS), psOptions, nullptr)));
6039 2 : GDALTranslateOptionsFree(psOptions);
6040 2 : if (poTmpDS)
6041 : {
6042 2 : bEPSG3857Adjust = true;
6043 2 : hTransformArg = GDALCreateGenImgProjTransformer2(
6044 2 : GDALDataset::FromHandle(poTmpDS.get()), nullptr,
6045 : papszTO);
6046 : }
6047 : }
6048 : }
6049 : }
6050 22 : if (hTransformArg == nullptr)
6051 : {
6052 : hTransformArg =
6053 20 : GDALCreateGenImgProjTransformer2(poSrcDS, nullptr, papszTO);
6054 : }
6055 :
6056 22 : if (hTransformArg == nullptr)
6057 : {
6058 1 : CPLFree(pszWKT);
6059 1 : CSLDestroy(papszTO);
6060 1 : return nullptr;
6061 : }
6062 :
6063 21 : GDALTransformerInfo *psInfo =
6064 : static_cast<GDALTransformerInfo *>(hTransformArg);
6065 21 : GDALGeoTransform gt;
6066 : double adfExtent[4];
6067 : int nXSize, nYSize;
6068 :
6069 21 : if (GDALSuggestedWarpOutput2(poSrcDS, psInfo->pfnTransform, hTransformArg,
6070 : gt.data(), &nXSize, &nYSize, adfExtent,
6071 21 : 0) != CE_None)
6072 : {
6073 0 : CPLFree(pszWKT);
6074 0 : CSLDestroy(papszTO);
6075 0 : GDALDestroyGenImgProjTransformer(hTransformArg);
6076 0 : return nullptr;
6077 : }
6078 :
6079 21 : GDALDestroyGenImgProjTransformer(hTransformArg);
6080 21 : hTransformArg = nullptr;
6081 21 : poTmpDS.reset();
6082 :
6083 21 : if (bEPSG3857Adjust)
6084 : {
6085 2 : constexpr double SPHERICAL_RADIUS = 6378137.0;
6086 2 : constexpr double MAX_GM =
6087 : SPHERICAL_RADIUS * M_PI; // 20037508.342789244
6088 2 : double maxNorthing = gt[3];
6089 2 : double minNorthing = gt[3] + gt[5] * nYSize;
6090 2 : bool bChanged = false;
6091 2 : if (maxNorthing > MAX_GM)
6092 : {
6093 2 : bChanged = true;
6094 2 : maxNorthing = MAX_GM;
6095 : }
6096 2 : if (minNorthing < -MAX_GM)
6097 : {
6098 2 : bChanged = true;
6099 2 : minNorthing = -MAX_GM;
6100 : }
6101 2 : if (bChanged)
6102 : {
6103 2 : gt[3] = maxNorthing;
6104 2 : nYSize = int((maxNorthing - minNorthing) / (-gt[5]) + 0.5);
6105 2 : adfExtent[1] = maxNorthing + nYSize * gt[5];
6106 2 : adfExtent[3] = maxNorthing;
6107 : }
6108 : }
6109 :
6110 21 : double dfComputedRes = gt[1];
6111 21 : double dfPrevRes = 0.0;
6112 21 : double dfRes = 0.0;
6113 21 : int nZoomLevel = 0; // Used after for.
6114 21 : const char *pszZoomLevel = CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
6115 21 : if (pszZoomLevel)
6116 : {
6117 2 : nZoomLevel = atoi(pszZoomLevel);
6118 :
6119 2 : int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
6120 2 : while ((1 << nMaxZoomLevelForThisTM) >
6121 4 : INT_MAX / poTS->nTileXCountZoomLevel0 ||
6122 2 : (1 << nMaxZoomLevelForThisTM) >
6123 2 : INT_MAX / poTS->nTileYCountZoomLevel0)
6124 : {
6125 0 : --nMaxZoomLevelForThisTM;
6126 : }
6127 :
6128 2 : if (nZoomLevel < 0 || nZoomLevel > nMaxZoomLevelForThisTM)
6129 : {
6130 1 : CPLError(CE_Failure, CPLE_AppDefined,
6131 : "ZOOM_LEVEL = %s is invalid. It should be in [0,%d] range",
6132 : pszZoomLevel, nMaxZoomLevelForThisTM);
6133 1 : CPLFree(pszWKT);
6134 1 : CSLDestroy(papszTO);
6135 1 : return nullptr;
6136 : }
6137 : }
6138 : else
6139 : {
6140 171 : for (; nZoomLevel < MAX_ZOOM_LEVEL; nZoomLevel++)
6141 : {
6142 171 : dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
6143 171 : if (dfComputedRes > dfRes ||
6144 152 : fabs(dfComputedRes - dfRes) / dfRes <= 1e-8)
6145 : break;
6146 152 : dfPrevRes = dfRes;
6147 : }
6148 38 : if (nZoomLevel == MAX_ZOOM_LEVEL ||
6149 38 : (1 << nZoomLevel) > INT_MAX / poTS->nTileXCountZoomLevel0 ||
6150 19 : (1 << nZoomLevel) > INT_MAX / poTS->nTileYCountZoomLevel0)
6151 : {
6152 0 : CPLError(CE_Failure, CPLE_AppDefined,
6153 : "Could not find an appropriate zoom level");
6154 0 : CPLFree(pszWKT);
6155 0 : CSLDestroy(papszTO);
6156 0 : return nullptr;
6157 : }
6158 :
6159 19 : if (nZoomLevel > 0 && fabs(dfComputedRes - dfRes) / dfRes > 1e-8)
6160 : {
6161 17 : const char *pszZoomLevelStrategy = CSLFetchNameValueDef(
6162 : papszOptions, "ZOOM_LEVEL_STRATEGY", "AUTO");
6163 17 : if (EQUAL(pszZoomLevelStrategy, "LOWER"))
6164 : {
6165 1 : nZoomLevel--;
6166 : }
6167 16 : else if (EQUAL(pszZoomLevelStrategy, "UPPER"))
6168 : {
6169 : /* do nothing */
6170 : }
6171 : else
6172 : {
6173 15 : if (dfPrevRes / dfComputedRes < dfComputedRes / dfRes)
6174 13 : nZoomLevel--;
6175 : }
6176 : }
6177 : }
6178 :
6179 20 : dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
6180 :
6181 20 : double dfMinX = adfExtent[0];
6182 20 : double dfMinY = adfExtent[1];
6183 20 : double dfMaxX = adfExtent[2];
6184 20 : double dfMaxY = adfExtent[3];
6185 :
6186 20 : nXSize = static_cast<int>(0.5 + (dfMaxX - dfMinX) / dfRes);
6187 20 : nYSize = static_cast<int>(0.5 + (dfMaxY - dfMinY) / dfRes);
6188 20 : gt[1] = dfRes;
6189 20 : gt[5] = -dfRes;
6190 :
6191 20 : const GDALDataType eDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
6192 20 : int nTargetBands = nBands;
6193 : /* For grey level or RGB, if there's reprojection involved, add an alpha */
6194 : /* channel */
6195 37 : if (eDT == GDT_UInt8 &&
6196 13 : ((nBands == 1 &&
6197 17 : poSrcDS->GetRasterBand(1)->GetColorTable() == nullptr) ||
6198 : nBands == 3))
6199 : {
6200 30 : OGRSpatialReference oSrcSRS;
6201 15 : oSrcSRS.SetFromUserInput(poSrcDS->GetProjectionRef());
6202 15 : oSrcSRS.AutoIdentifyEPSG();
6203 30 : if (oSrcSRS.GetAuthorityCode(nullptr) == nullptr ||
6204 15 : atoi(oSrcSRS.GetAuthorityCode(nullptr)) != nEPSGCode)
6205 : {
6206 13 : nTargetBands++;
6207 : }
6208 : }
6209 :
6210 20 : GDALResampleAlg eResampleAlg = GRA_Bilinear;
6211 20 : const char *pszResampling = CSLFetchNameValue(papszOptions, "RESAMPLING");
6212 20 : if (pszResampling)
6213 : {
6214 6 : for (size_t iAlg = 0;
6215 6 : iAlg < sizeof(asResamplingAlg) / sizeof(asResamplingAlg[0]);
6216 : iAlg++)
6217 : {
6218 6 : if (EQUAL(pszResampling, asResamplingAlg[iAlg].pszName))
6219 : {
6220 3 : eResampleAlg = asResamplingAlg[iAlg].eResampleAlg;
6221 3 : break;
6222 : }
6223 : }
6224 : }
6225 :
6226 16 : if (nBands == 1 && poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr &&
6227 36 : eResampleAlg != GRA_NearestNeighbour && eResampleAlg != GRA_Mode)
6228 : {
6229 0 : CPLError(
6230 : CE_Warning, CPLE_AppDefined,
6231 : "Input dataset has a color table, which will likely lead to "
6232 : "bad results when using a resampling method other than "
6233 : "nearest neighbour or mode. Converting the dataset to 24/32 bit "
6234 : "(e.g. with gdal_translate -expand rgb/rgba) is advised.");
6235 : }
6236 :
6237 40 : auto poDS = std::make_unique<GDALGeoPackageDataset>();
6238 20 : if (!(poDS->Create(pszFilename, nXSize, nYSize, nTargetBands, eDT,
6239 : apszUpdatedOptions)))
6240 : {
6241 1 : CPLFree(pszWKT);
6242 1 : CSLDestroy(papszTO);
6243 1 : return nullptr;
6244 : }
6245 :
6246 : // Assign nodata values before the SetGeoTransform call.
6247 : // SetGeoTransform will trigger creation of the overview datasets for each
6248 : // zoom level and at that point the nodata value needs to be known.
6249 19 : int bHasNoData = FALSE;
6250 : double dfNoDataValue =
6251 19 : poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
6252 19 : if (eDT != GDT_UInt8 && bHasNoData)
6253 : {
6254 3 : poDS->GetRasterBand(1)->SetNoDataValue(dfNoDataValue);
6255 : }
6256 :
6257 19 : poDS->SetGeoTransform(gt);
6258 19 : poDS->SetProjection(pszWKT);
6259 19 : CPLFree(pszWKT);
6260 19 : pszWKT = nullptr;
6261 24 : if (nTargetBands == 1 && nBands == 1 &&
6262 5 : poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr)
6263 : {
6264 2 : poDS->GetRasterBand(1)->SetColorTable(
6265 1 : poSrcDS->GetRasterBand(1)->GetColorTable());
6266 : }
6267 :
6268 : hTransformArg =
6269 19 : GDALCreateGenImgProjTransformer2(poSrcDS, poDS.get(), papszTO);
6270 19 : CSLDestroy(papszTO);
6271 19 : if (hTransformArg == nullptr)
6272 : {
6273 0 : return nullptr;
6274 : }
6275 :
6276 19 : poDS->SetMetadata(poSrcDS->GetMetadata());
6277 :
6278 : /* -------------------------------------------------------------------- */
6279 : /* Warp the transformer with a linear approximator */
6280 : /* -------------------------------------------------------------------- */
6281 19 : hTransformArg = GDALCreateApproxTransformer(GDALGenImgProjTransform,
6282 : hTransformArg, 0.125);
6283 19 : GDALApproxTransformerOwnsSubtransformer(hTransformArg, TRUE);
6284 :
6285 : /* -------------------------------------------------------------------- */
6286 : /* Setup warp options. */
6287 : /* -------------------------------------------------------------------- */
6288 19 : GDALWarpOptions *psWO = GDALCreateWarpOptions();
6289 :
6290 19 : psWO->papszWarpOptions = CSLSetNameValue(nullptr, "OPTIMIZE_SIZE", "YES");
6291 19 : psWO->papszWarpOptions =
6292 19 : CSLSetNameValue(psWO->papszWarpOptions, "SAMPLE_GRID", "YES");
6293 19 : if (bHasNoData)
6294 : {
6295 3 : if (dfNoDataValue == 0.0)
6296 : {
6297 : // Do not initialize in the case where nodata != 0, since we
6298 : // want the GeoPackage driver to return empty tiles at the nodata
6299 : // value instead of 0 as GDAL core would
6300 0 : psWO->papszWarpOptions =
6301 0 : CSLSetNameValue(psWO->papszWarpOptions, "INIT_DEST", "0");
6302 : }
6303 :
6304 3 : psWO->padfSrcNoDataReal =
6305 3 : static_cast<double *>(CPLMalloc(sizeof(double)));
6306 3 : psWO->padfSrcNoDataReal[0] = dfNoDataValue;
6307 :
6308 3 : psWO->padfDstNoDataReal =
6309 3 : static_cast<double *>(CPLMalloc(sizeof(double)));
6310 3 : psWO->padfDstNoDataReal[0] = dfNoDataValue;
6311 : }
6312 19 : psWO->eWorkingDataType = eDT;
6313 19 : psWO->eResampleAlg = eResampleAlg;
6314 :
6315 19 : psWO->hSrcDS = poSrcDS;
6316 19 : psWO->hDstDS = poDS.get();
6317 :
6318 19 : psWO->pfnTransformer = GDALApproxTransform;
6319 19 : psWO->pTransformerArg = hTransformArg;
6320 :
6321 19 : psWO->pfnProgress = pfnProgress;
6322 19 : psWO->pProgressArg = pProgressData;
6323 :
6324 : /* -------------------------------------------------------------------- */
6325 : /* Setup band mapping. */
6326 : /* -------------------------------------------------------------------- */
6327 :
6328 19 : if (nBands == 2 || nBands == 4)
6329 1 : psWO->nBandCount = nBands - 1;
6330 : else
6331 18 : psWO->nBandCount = nBands;
6332 :
6333 19 : psWO->panSrcBands =
6334 19 : static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
6335 19 : psWO->panDstBands =
6336 19 : static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
6337 :
6338 46 : for (int i = 0; i < psWO->nBandCount; i++)
6339 : {
6340 27 : psWO->panSrcBands[i] = i + 1;
6341 27 : psWO->panDstBands[i] = i + 1;
6342 : }
6343 :
6344 19 : if (nBands == 2 || nBands == 4)
6345 : {
6346 1 : psWO->nSrcAlphaBand = nBands;
6347 : }
6348 19 : if (nTargetBands == 2 || nTargetBands == 4)
6349 : {
6350 13 : psWO->nDstAlphaBand = nTargetBands;
6351 : }
6352 :
6353 : /* -------------------------------------------------------------------- */
6354 : /* Initialize and execute the warp. */
6355 : /* -------------------------------------------------------------------- */
6356 38 : GDALWarpOperation oWO;
6357 :
6358 19 : CPLErr eErr = oWO.Initialize(psWO);
6359 19 : if (eErr == CE_None)
6360 : {
6361 : /*if( bMulti )
6362 : eErr = oWO.ChunkAndWarpMulti( 0, 0, nXSize, nYSize );
6363 : else*/
6364 19 : eErr = oWO.ChunkAndWarpImage(0, 0, nXSize, nYSize);
6365 : }
6366 19 : if (eErr != CE_None)
6367 : {
6368 0 : poDS.reset();
6369 : }
6370 :
6371 19 : GDALDestroyTransformer(hTransformArg);
6372 19 : GDALDestroyWarpOptions(psWO);
6373 :
6374 19 : if (poDS)
6375 19 : poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
6376 :
6377 19 : return poDS.release();
6378 : }
6379 :
6380 : /************************************************************************/
6381 : /* ParseCompressionOptions() */
6382 : /************************************************************************/
6383 :
6384 459 : void GDALGeoPackageDataset::ParseCompressionOptions(char **papszOptions)
6385 : {
6386 459 : const char *pszZLevel = CSLFetchNameValue(papszOptions, "ZLEVEL");
6387 459 : if (pszZLevel)
6388 0 : m_nZLevel = atoi(pszZLevel);
6389 :
6390 459 : const char *pszQuality = CSLFetchNameValue(papszOptions, "QUALITY");
6391 459 : if (pszQuality)
6392 0 : m_nQuality = atoi(pszQuality);
6393 :
6394 459 : const char *pszDither = CSLFetchNameValue(papszOptions, "DITHER");
6395 459 : if (pszDither)
6396 0 : m_bDither = CPLTestBool(pszDither);
6397 459 : }
6398 :
6399 : /************************************************************************/
6400 : /* RegisterWebPExtension() */
6401 : /************************************************************************/
6402 :
6403 11 : bool GDALGeoPackageDataset::RegisterWebPExtension()
6404 : {
6405 11 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
6406 0 : return false;
6407 :
6408 11 : char *pszSQL = sqlite3_mprintf(
6409 : "INSERT INTO gpkg_extensions "
6410 : "(table_name, column_name, extension_name, definition, scope) "
6411 : "VALUES "
6412 : "('%q', 'tile_data', 'gpkg_webp', "
6413 : "'http://www.geopackage.org/spec120/#extension_tiles_webp', "
6414 : "'read-write')",
6415 : m_osRasterTable.c_str());
6416 11 : const OGRErr eErr = SQLCommand(hDB, pszSQL);
6417 11 : sqlite3_free(pszSQL);
6418 :
6419 11 : return OGRERR_NONE == eErr;
6420 : }
6421 :
6422 : /************************************************************************/
6423 : /* RegisterZoomOtherExtension() */
6424 : /************************************************************************/
6425 :
6426 1 : bool GDALGeoPackageDataset::RegisterZoomOtherExtension()
6427 : {
6428 1 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
6429 0 : return false;
6430 :
6431 1 : char *pszSQL = sqlite3_mprintf(
6432 : "INSERT INTO gpkg_extensions "
6433 : "(table_name, column_name, extension_name, definition, scope) "
6434 : "VALUES "
6435 : "('%q', 'tile_data', 'gpkg_zoom_other', "
6436 : "'http://www.geopackage.org/spec120/#extension_zoom_other_intervals', "
6437 : "'read-write')",
6438 : m_osRasterTable.c_str());
6439 1 : const OGRErr eErr = SQLCommand(hDB, pszSQL);
6440 1 : sqlite3_free(pszSQL);
6441 1 : return OGRERR_NONE == eErr;
6442 : }
6443 :
6444 : /************************************************************************/
6445 : /* GetLayer() */
6446 : /************************************************************************/
6447 :
6448 15952 : const OGRLayer *GDALGeoPackageDataset::GetLayer(int iLayer) const
6449 :
6450 : {
6451 15952 : if (iLayer < 0 || iLayer >= static_cast<int>(m_apoLayers.size()))
6452 7 : return nullptr;
6453 : else
6454 15945 : return m_apoLayers[iLayer].get();
6455 : }
6456 :
6457 : /************************************************************************/
6458 : /* LaunderName() */
6459 : /************************************************************************/
6460 :
6461 : /** Launder identifiers (table, column names) according to guidance at
6462 : * https://www.geopackage.org/guidance/getting-started.html:
6463 : * "For maximum interoperability, start your database identifiers (table names,
6464 : * column names, etc.) with a lowercase character and only use lowercase
6465 : * characters, numbers 0-9, and underscores (_)."
6466 : */
6467 :
6468 : /* static */
6469 5 : std::string GDALGeoPackageDataset::LaunderName(const std::string &osStr)
6470 : {
6471 5 : char *pszASCII = CPLUTF8ForceToASCII(osStr.c_str(), '_');
6472 10 : const std::string osStrASCII(pszASCII);
6473 5 : CPLFree(pszASCII);
6474 :
6475 10 : std::string osRet;
6476 5 : osRet.reserve(osStrASCII.size());
6477 :
6478 29 : for (size_t i = 0; i < osStrASCII.size(); ++i)
6479 : {
6480 24 : if (osRet.empty())
6481 : {
6482 5 : if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
6483 : {
6484 2 : osRet += (osStrASCII[i] - 'A' + 'a');
6485 : }
6486 3 : else if (osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z')
6487 : {
6488 2 : osRet += osStrASCII[i];
6489 : }
6490 : else
6491 : {
6492 1 : continue;
6493 : }
6494 : }
6495 19 : else if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
6496 : {
6497 11 : osRet += (osStrASCII[i] - 'A' + 'a');
6498 : }
6499 9 : else if ((osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z') ||
6500 14 : (osStrASCII[i] >= '0' && osStrASCII[i] <= '9') ||
6501 5 : osStrASCII[i] == '_')
6502 : {
6503 7 : osRet += osStrASCII[i];
6504 : }
6505 : else
6506 : {
6507 1 : osRet += '_';
6508 : }
6509 : }
6510 :
6511 5 : if (osRet.empty() && !osStrASCII.empty())
6512 2 : return LaunderName(std::string("x").append(osStrASCII));
6513 :
6514 4 : if (osRet != osStr)
6515 : {
6516 3 : CPLDebug("PG", "LaunderName('%s') -> '%s'", osStr.c_str(),
6517 : osRet.c_str());
6518 : }
6519 :
6520 4 : return osRet;
6521 : }
6522 :
6523 : /************************************************************************/
6524 : /* ICreateLayer() */
6525 : /************************************************************************/
6526 :
6527 : OGRLayer *
6528 855 : GDALGeoPackageDataset::ICreateLayer(const char *pszLayerName,
6529 : const OGRGeomFieldDefn *poSrcGeomFieldDefn,
6530 : CSLConstList papszOptions)
6531 : {
6532 : /* -------------------------------------------------------------------- */
6533 : /* Verify we are in update mode. */
6534 : /* -------------------------------------------------------------------- */
6535 855 : if (!GetUpdate())
6536 : {
6537 0 : CPLError(CE_Failure, CPLE_NoWriteAccess,
6538 : "Data source %s opened read-only.\n"
6539 : "New layer %s cannot be created.\n",
6540 : m_pszFilename, pszLayerName);
6541 :
6542 0 : return nullptr;
6543 : }
6544 :
6545 : const bool bLaunder =
6546 855 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "LAUNDER", "NO"));
6547 : const std::string osTableName(bLaunder ? LaunderName(pszLayerName)
6548 2565 : : std::string(pszLayerName));
6549 :
6550 : const auto eGType =
6551 855 : poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone;
6552 : const auto poSpatialRef =
6553 855 : poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr;
6554 :
6555 855 : if (!m_bHasGPKGGeometryColumns)
6556 : {
6557 1 : if (SQLCommand(hDB, pszCREATE_GPKG_GEOMETRY_COLUMNS) != OGRERR_NONE)
6558 : {
6559 0 : return nullptr;
6560 : }
6561 1 : m_bHasGPKGGeometryColumns = true;
6562 : }
6563 :
6564 : // Check identifier unicity
6565 855 : const char *pszIdentifier = CSLFetchNameValue(papszOptions, "IDENTIFIER");
6566 855 : if (pszIdentifier != nullptr && pszIdentifier[0] == '\0')
6567 0 : pszIdentifier = nullptr;
6568 855 : if (pszIdentifier != nullptr)
6569 : {
6570 13 : for (auto &poLayer : m_apoLayers)
6571 : {
6572 : const char *pszOtherIdentifier =
6573 9 : poLayer->GetMetadataItem("IDENTIFIER");
6574 9 : if (pszOtherIdentifier == nullptr)
6575 6 : pszOtherIdentifier = poLayer->GetName();
6576 18 : if (pszOtherIdentifier != nullptr &&
6577 12 : EQUAL(pszOtherIdentifier, pszIdentifier) &&
6578 3 : !EQUAL(poLayer->GetName(), osTableName.c_str()))
6579 : {
6580 2 : CPLError(CE_Failure, CPLE_AppDefined,
6581 : "Identifier %s is already used by table %s",
6582 : pszIdentifier, poLayer->GetName());
6583 2 : return nullptr;
6584 : }
6585 : }
6586 :
6587 : // In case there would be table in gpkg_contents not listed as a
6588 : // vector layer
6589 4 : char *pszSQL = sqlite3_mprintf(
6590 : "SELECT table_name FROM gpkg_contents WHERE identifier = '%q' "
6591 : "LIMIT 2",
6592 : pszIdentifier);
6593 4 : auto oResult = SQLQuery(hDB, pszSQL);
6594 4 : sqlite3_free(pszSQL);
6595 8 : if (oResult && oResult->RowCount() > 0 &&
6596 9 : oResult->GetValue(0, 0) != nullptr &&
6597 1 : !EQUAL(oResult->GetValue(0, 0), osTableName.c_str()))
6598 : {
6599 1 : CPLError(CE_Failure, CPLE_AppDefined,
6600 : "Identifier %s is already used by table %s", pszIdentifier,
6601 : oResult->GetValue(0, 0));
6602 1 : return nullptr;
6603 : }
6604 : }
6605 :
6606 : /* Read GEOMETRY_NAME option */
6607 : const char *pszGeomColumnName =
6608 852 : CSLFetchNameValue(papszOptions, "GEOMETRY_NAME");
6609 852 : if (pszGeomColumnName == nullptr) /* deprecated name */
6610 767 : pszGeomColumnName = CSLFetchNameValue(papszOptions, "GEOMETRY_COLUMN");
6611 852 : if (pszGeomColumnName == nullptr && poSrcGeomFieldDefn)
6612 : {
6613 689 : pszGeomColumnName = poSrcGeomFieldDefn->GetNameRef();
6614 689 : if (pszGeomColumnName && pszGeomColumnName[0] == 0)
6615 685 : pszGeomColumnName = nullptr;
6616 : }
6617 852 : if (pszGeomColumnName == nullptr)
6618 763 : pszGeomColumnName = "geom";
6619 : const bool bGeomNullable =
6620 852 : CPLFetchBool(papszOptions, "GEOMETRY_NULLABLE", true);
6621 :
6622 : /* Read FID option */
6623 852 : const char *pszFIDColumnName = CSLFetchNameValue(papszOptions, "FID");
6624 852 : if (pszFIDColumnName == nullptr)
6625 773 : pszFIDColumnName = "fid";
6626 :
6627 852 : if (CPLTestBool(CPLGetConfigOption("GPKG_NAME_CHECK", "YES")))
6628 : {
6629 852 : if (strspn(pszFIDColumnName, "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") > 0)
6630 : {
6631 2 : CPLError(CE_Failure, CPLE_AppDefined,
6632 : "The primary key (%s) name may not contain special "
6633 : "characters or spaces",
6634 : pszFIDColumnName);
6635 2 : return nullptr;
6636 : }
6637 :
6638 : /* Avoiding gpkg prefixes is not an official requirement, but seems wise
6639 : */
6640 850 : if (STARTS_WITH(osTableName.c_str(), "gpkg"))
6641 : {
6642 0 : CPLError(CE_Failure, CPLE_AppDefined,
6643 : "The layer name may not begin with 'gpkg' as it is a "
6644 : "reserved geopackage prefix");
6645 0 : return nullptr;
6646 : }
6647 :
6648 : /* Preemptively try and avoid sqlite3 syntax errors due to */
6649 : /* illegal characters. */
6650 850 : if (strspn(osTableName.c_str(), "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") >
6651 : 0)
6652 : {
6653 0 : CPLError(
6654 : CE_Failure, CPLE_AppDefined,
6655 : "The layer name may not contain special characters or spaces");
6656 0 : return nullptr;
6657 : }
6658 : }
6659 :
6660 : /* Check for any existing layers that already use this name */
6661 1058 : for (int iLayer = 0; iLayer < static_cast<int>(m_apoLayers.size());
6662 : iLayer++)
6663 : {
6664 209 : if (EQUAL(osTableName.c_str(), m_apoLayers[iLayer]->GetName()))
6665 : {
6666 : const char *pszOverwrite =
6667 2 : CSLFetchNameValue(papszOptions, "OVERWRITE");
6668 2 : if (pszOverwrite != nullptr && CPLTestBool(pszOverwrite))
6669 : {
6670 1 : DeleteLayer(iLayer);
6671 : }
6672 : else
6673 : {
6674 1 : CPLError(CE_Failure, CPLE_AppDefined,
6675 : "Layer %s already exists, CreateLayer failed.\n"
6676 : "Use the layer creation option OVERWRITE=YES to "
6677 : "replace it.",
6678 : osTableName.c_str());
6679 1 : return nullptr;
6680 : }
6681 : }
6682 : }
6683 :
6684 849 : if (m_apoLayers.size() == 1)
6685 : {
6686 : // Async RTree building doesn't play well with multiple layer:
6687 : // SQLite3 locks being hold for a long time, random failed commits,
6688 : // etc.
6689 82 : m_apoLayers[0]->FinishOrDisableThreadedRTree();
6690 : }
6691 :
6692 : /* Create a blank layer. */
6693 : auto poLayer =
6694 1698 : std::make_unique<OGRGeoPackageTableLayer>(this, osTableName.c_str());
6695 :
6696 849 : OGRSpatialReference *poSRS = nullptr;
6697 849 : if (poSpatialRef)
6698 : {
6699 255 : poSRS = poSpatialRef->Clone();
6700 255 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
6701 : }
6702 1699 : poLayer->SetCreationParameters(
6703 : eGType,
6704 850 : bLaunder ? LaunderName(pszGeomColumnName).c_str() : pszGeomColumnName,
6705 : bGeomNullable, poSRS, CSLFetchNameValue(papszOptions, "SRID"),
6706 1698 : poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetCoordinatePrecision()
6707 : : OGRGeomCoordinatePrecision(),
6708 849 : CPLTestBool(
6709 : CSLFetchNameValueDef(papszOptions, "DISCARD_COORD_LSB", "NO")),
6710 849 : CPLTestBool(CSLFetchNameValueDef(
6711 : papszOptions, "UNDO_DISCARD_COORD_LSB_ON_READING", "NO")),
6712 850 : bLaunder ? LaunderName(pszFIDColumnName).c_str() : pszFIDColumnName,
6713 : pszIdentifier, CSLFetchNameValue(papszOptions, "DESCRIPTION"));
6714 849 : if (poSRS)
6715 : {
6716 255 : poSRS->Release();
6717 : }
6718 :
6719 849 : poLayer->SetLaunder(bLaunder);
6720 :
6721 : /* Should we create a spatial index ? */
6722 849 : const char *pszSI = CSLFetchNameValue(papszOptions, "SPATIAL_INDEX");
6723 849 : int bCreateSpatialIndex = (pszSI == nullptr || CPLTestBool(pszSI));
6724 849 : if (eGType != wkbNone && bCreateSpatialIndex)
6725 : {
6726 749 : poLayer->SetDeferredSpatialIndexCreation(true);
6727 : }
6728 :
6729 849 : poLayer->SetPrecisionFlag(CPLFetchBool(papszOptions, "PRECISION", true));
6730 849 : poLayer->SetTruncateFieldsFlag(
6731 849 : CPLFetchBool(papszOptions, "TRUNCATE_FIELDS", false));
6732 849 : if (eGType == wkbNone)
6733 : {
6734 78 : const char *pszASpatialVariant = CSLFetchNameValueDef(
6735 : papszOptions, "ASPATIAL_VARIANT",
6736 78 : m_bNonSpatialTablesNonRegisteredInGpkgContentsFound
6737 : ? "NOT_REGISTERED"
6738 : : "GPKG_ATTRIBUTES");
6739 78 : GPKGASpatialVariant eASpatialVariant = GPKG_ATTRIBUTES;
6740 78 : if (EQUAL(pszASpatialVariant, "GPKG_ATTRIBUTES"))
6741 66 : eASpatialVariant = GPKG_ATTRIBUTES;
6742 12 : else if (EQUAL(pszASpatialVariant, "OGR_ASPATIAL"))
6743 : {
6744 0 : CPLError(CE_Failure, CPLE_NotSupported,
6745 : "ASPATIAL_VARIANT=OGR_ASPATIAL is no longer supported");
6746 0 : return nullptr;
6747 : }
6748 12 : else if (EQUAL(pszASpatialVariant, "NOT_REGISTERED"))
6749 12 : eASpatialVariant = NOT_REGISTERED;
6750 : else
6751 : {
6752 0 : CPLError(CE_Failure, CPLE_NotSupported,
6753 : "Unsupported value for ASPATIAL_VARIANT: %s",
6754 : pszASpatialVariant);
6755 0 : return nullptr;
6756 : }
6757 78 : poLayer->SetASpatialVariant(eASpatialVariant);
6758 : }
6759 :
6760 : const char *pszDateTimePrecision =
6761 849 : CSLFetchNameValueDef(papszOptions, "DATETIME_PRECISION", "AUTO");
6762 849 : if (EQUAL(pszDateTimePrecision, "MILLISECOND"))
6763 : {
6764 2 : poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
6765 : }
6766 847 : else if (EQUAL(pszDateTimePrecision, "SECOND"))
6767 : {
6768 1 : if (m_nUserVersion < GPKG_1_4_VERSION)
6769 0 : CPLError(
6770 : CE_Warning, CPLE_AppDefined,
6771 : "DATETIME_PRECISION=SECOND is only valid since GeoPackage 1.4");
6772 1 : poLayer->SetDateTimePrecision(OGRISO8601Precision::SECOND);
6773 : }
6774 846 : else if (EQUAL(pszDateTimePrecision, "MINUTE"))
6775 : {
6776 1 : if (m_nUserVersion < GPKG_1_4_VERSION)
6777 0 : CPLError(
6778 : CE_Warning, CPLE_AppDefined,
6779 : "DATETIME_PRECISION=MINUTE is only valid since GeoPackage 1.4");
6780 1 : poLayer->SetDateTimePrecision(OGRISO8601Precision::MINUTE);
6781 : }
6782 845 : else if (EQUAL(pszDateTimePrecision, "AUTO"))
6783 : {
6784 844 : if (m_nUserVersion < GPKG_1_4_VERSION)
6785 13 : poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
6786 : }
6787 : else
6788 : {
6789 1 : CPLError(CE_Failure, CPLE_NotSupported,
6790 : "Unsupported value for DATETIME_PRECISION: %s",
6791 : pszDateTimePrecision);
6792 1 : return nullptr;
6793 : }
6794 :
6795 : // If there was an ogr_empty_table table, we can remove it
6796 : // But do it at dataset closing, otherwise locking performance issues
6797 : // can arise (probably when transactions are used).
6798 848 : m_bRemoveOGREmptyTable = true;
6799 :
6800 848 : m_apoLayers.emplace_back(std::move(poLayer));
6801 848 : return m_apoLayers.back().get();
6802 : }
6803 :
6804 : /************************************************************************/
6805 : /* FindLayerIndex() */
6806 : /************************************************************************/
6807 :
6808 27 : int GDALGeoPackageDataset::FindLayerIndex(const char *pszLayerName)
6809 :
6810 : {
6811 42 : for (int iLayer = 0; iLayer < static_cast<int>(m_apoLayers.size());
6812 : iLayer++)
6813 : {
6814 28 : if (EQUAL(pszLayerName, m_apoLayers[iLayer]->GetName()))
6815 13 : return iLayer;
6816 : }
6817 14 : return -1;
6818 : }
6819 :
6820 : /************************************************************************/
6821 : /* DeleteLayerCommon() */
6822 : /************************************************************************/
6823 :
6824 41 : OGRErr GDALGeoPackageDataset::DeleteLayerCommon(const char *pszLayerName)
6825 : {
6826 : // Temporary remove foreign key checks
6827 : const GPKGTemporaryForeignKeyCheckDisabler
6828 41 : oGPKGTemporaryForeignKeyCheckDisabler(this);
6829 :
6830 41 : char *pszSQL = sqlite3_mprintf(
6831 : "DELETE FROM gpkg_contents WHERE lower(table_name) = lower('%q')",
6832 : pszLayerName);
6833 41 : OGRErr eErr = SQLCommand(hDB, pszSQL);
6834 41 : sqlite3_free(pszSQL);
6835 :
6836 41 : if (eErr == OGRERR_NONE && HasExtensionsTable())
6837 : {
6838 39 : pszSQL = sqlite3_mprintf(
6839 : "DELETE FROM gpkg_extensions WHERE lower(table_name) = lower('%q')",
6840 : pszLayerName);
6841 39 : eErr = SQLCommand(hDB, pszSQL);
6842 39 : sqlite3_free(pszSQL);
6843 : }
6844 :
6845 41 : if (eErr == OGRERR_NONE && HasMetadataTables())
6846 : {
6847 : // Delete from gpkg_metadata metadata records that are only referenced
6848 : // by the table we are about to drop
6849 11 : pszSQL = sqlite3_mprintf(
6850 : "DELETE FROM gpkg_metadata WHERE id IN ("
6851 : "SELECT DISTINCT md_file_id FROM "
6852 : "gpkg_metadata_reference WHERE "
6853 : "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
6854 : "AND id NOT IN ("
6855 : "SELECT DISTINCT md_file_id FROM gpkg_metadata_reference WHERE "
6856 : "md_file_id IN (SELECT DISTINCT md_file_id FROM "
6857 : "gpkg_metadata_reference WHERE "
6858 : "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
6859 : "AND lower(table_name) <> lower('%q'))",
6860 : pszLayerName, pszLayerName, pszLayerName);
6861 11 : eErr = SQLCommand(hDB, pszSQL);
6862 11 : sqlite3_free(pszSQL);
6863 :
6864 11 : if (eErr == OGRERR_NONE)
6865 : {
6866 : pszSQL =
6867 11 : sqlite3_mprintf("DELETE FROM gpkg_metadata_reference WHERE "
6868 : "lower(table_name) = lower('%q')",
6869 : pszLayerName);
6870 11 : eErr = SQLCommand(hDB, pszSQL);
6871 11 : sqlite3_free(pszSQL);
6872 : }
6873 : }
6874 :
6875 41 : if (eErr == OGRERR_NONE && HasGpkgextRelationsTable())
6876 : {
6877 : // Remove reference to potential corresponding mapping table in
6878 : // gpkg_extensions
6879 4 : pszSQL = sqlite3_mprintf(
6880 : "DELETE FROM gpkg_extensions WHERE "
6881 : "extension_name IN ('related_tables', "
6882 : "'gpkg_related_tables') AND lower(table_name) = "
6883 : "(SELECT lower(mapping_table_name) FROM gpkgext_relations WHERE "
6884 : "lower(base_table_name) = lower('%q') OR "
6885 : "lower(related_table_name) = lower('%q') OR "
6886 : "lower(mapping_table_name) = lower('%q'))",
6887 : pszLayerName, pszLayerName, pszLayerName);
6888 4 : eErr = SQLCommand(hDB, pszSQL);
6889 4 : sqlite3_free(pszSQL);
6890 :
6891 4 : if (eErr == OGRERR_NONE)
6892 : {
6893 : // Remove reference to potential corresponding mapping table in
6894 : // gpkgext_relations
6895 : pszSQL =
6896 4 : sqlite3_mprintf("DELETE FROM gpkgext_relations WHERE "
6897 : "lower(base_table_name) = lower('%q') OR "
6898 : "lower(related_table_name) = lower('%q') OR "
6899 : "lower(mapping_table_name) = lower('%q')",
6900 : pszLayerName, pszLayerName, pszLayerName);
6901 4 : eErr = SQLCommand(hDB, pszSQL);
6902 4 : sqlite3_free(pszSQL);
6903 : }
6904 :
6905 4 : if (eErr == OGRERR_NONE && HasExtensionsTable())
6906 : {
6907 : // If there is no longer any mapping table, then completely
6908 : // remove any reference to the extension in gpkg_extensions
6909 : // as mandated per the related table specification.
6910 : OGRErr err;
6911 4 : if (SQLGetInteger(hDB,
6912 : "SELECT COUNT(*) FROM gpkg_extensions WHERE "
6913 : "extension_name IN ('related_tables', "
6914 : "'gpkg_related_tables') AND "
6915 : "lower(table_name) != 'gpkgext_relations'",
6916 4 : &err) == 0)
6917 : {
6918 2 : eErr = SQLCommand(hDB, "DELETE FROM gpkg_extensions WHERE "
6919 : "extension_name IN ('related_tables', "
6920 : "'gpkg_related_tables')");
6921 : }
6922 :
6923 4 : ClearCachedRelationships();
6924 : }
6925 : }
6926 :
6927 41 : if (eErr == OGRERR_NONE)
6928 : {
6929 41 : pszSQL = sqlite3_mprintf("DROP TABLE \"%w\"", pszLayerName);
6930 41 : eErr = SQLCommand(hDB, pszSQL);
6931 41 : sqlite3_free(pszSQL);
6932 : }
6933 :
6934 : // Check foreign key integrity
6935 41 : if (eErr == OGRERR_NONE)
6936 : {
6937 41 : eErr = PragmaCheck("foreign_key_check", "", 0);
6938 : }
6939 :
6940 82 : return eErr;
6941 : }
6942 :
6943 : /************************************************************************/
6944 : /* DeleteLayer() */
6945 : /************************************************************************/
6946 :
6947 38 : OGRErr GDALGeoPackageDataset::DeleteLayer(int iLayer)
6948 : {
6949 75 : if (!GetUpdate() || iLayer < 0 ||
6950 37 : iLayer >= static_cast<int>(m_apoLayers.size()))
6951 2 : return OGRERR_FAILURE;
6952 :
6953 36 : m_apoLayers[iLayer]->ResetReading();
6954 36 : m_apoLayers[iLayer]->SyncToDisk();
6955 :
6956 72 : CPLString osLayerName = m_apoLayers[iLayer]->GetName();
6957 :
6958 36 : CPLDebug("GPKG", "DeleteLayer(%s)", osLayerName.c_str());
6959 :
6960 : // Temporary remove foreign key checks
6961 : const GPKGTemporaryForeignKeyCheckDisabler
6962 36 : oGPKGTemporaryForeignKeyCheckDisabler(this);
6963 :
6964 36 : OGRErr eErr = SoftStartTransaction();
6965 :
6966 36 : if (eErr == OGRERR_NONE)
6967 : {
6968 36 : if (m_apoLayers[iLayer]->HasSpatialIndex())
6969 33 : m_apoLayers[iLayer]->DropSpatialIndex();
6970 :
6971 : char *pszSQL =
6972 36 : sqlite3_mprintf("DELETE FROM gpkg_geometry_columns WHERE "
6973 : "lower(table_name) = lower('%q')",
6974 : osLayerName.c_str());
6975 36 : eErr = SQLCommand(hDB, pszSQL);
6976 36 : sqlite3_free(pszSQL);
6977 : }
6978 :
6979 36 : if (eErr == OGRERR_NONE && HasDataColumnsTable())
6980 : {
6981 1 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_data_columns WHERE "
6982 : "lower(table_name) = lower('%q')",
6983 : osLayerName.c_str());
6984 1 : eErr = SQLCommand(hDB, pszSQL);
6985 1 : sqlite3_free(pszSQL);
6986 : }
6987 :
6988 : #ifdef ENABLE_GPKG_OGR_CONTENTS
6989 36 : if (eErr == OGRERR_NONE && m_bHasGPKGOGRContents)
6990 : {
6991 36 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_ogr_contents WHERE "
6992 : "lower(table_name) = lower('%q')",
6993 : osLayerName.c_str());
6994 36 : eErr = SQLCommand(hDB, pszSQL);
6995 36 : sqlite3_free(pszSQL);
6996 : }
6997 : #endif
6998 :
6999 36 : if (eErr == OGRERR_NONE)
7000 : {
7001 36 : eErr = DeleteLayerCommon(osLayerName.c_str());
7002 : }
7003 :
7004 36 : if (eErr == OGRERR_NONE)
7005 : {
7006 36 : eErr = SoftCommitTransaction();
7007 36 : if (eErr == OGRERR_NONE)
7008 : {
7009 : /* Delete the layer object */
7010 36 : m_apoLayers.erase(m_apoLayers.begin() + iLayer);
7011 : }
7012 : }
7013 : else
7014 : {
7015 0 : SoftRollbackTransaction();
7016 : }
7017 :
7018 36 : return eErr;
7019 : }
7020 :
7021 : /************************************************************************/
7022 : /* DeleteRasterLayer() */
7023 : /************************************************************************/
7024 :
7025 2 : OGRErr GDALGeoPackageDataset::DeleteRasterLayer(const char *pszLayerName)
7026 : {
7027 : // Temporary remove foreign key checks
7028 : const GPKGTemporaryForeignKeyCheckDisabler
7029 2 : oGPKGTemporaryForeignKeyCheckDisabler(this);
7030 :
7031 2 : OGRErr eErr = SoftStartTransaction();
7032 :
7033 2 : if (eErr == OGRERR_NONE)
7034 : {
7035 2 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix WHERE "
7036 : "lower(table_name) = lower('%q')",
7037 : pszLayerName);
7038 2 : eErr = SQLCommand(hDB, pszSQL);
7039 2 : sqlite3_free(pszSQL);
7040 : }
7041 :
7042 2 : if (eErr == OGRERR_NONE)
7043 : {
7044 2 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix_set WHERE "
7045 : "lower(table_name) = lower('%q')",
7046 : pszLayerName);
7047 2 : eErr = SQLCommand(hDB, pszSQL);
7048 2 : sqlite3_free(pszSQL);
7049 : }
7050 :
7051 2 : if (eErr == OGRERR_NONE && HasGriddedCoverageAncillaryTable())
7052 : {
7053 : char *pszSQL =
7054 1 : sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_coverage_ancillary "
7055 : "WHERE lower(tile_matrix_set_name) = lower('%q')",
7056 : pszLayerName);
7057 1 : eErr = SQLCommand(hDB, pszSQL);
7058 1 : sqlite3_free(pszSQL);
7059 :
7060 1 : if (eErr == OGRERR_NONE)
7061 : {
7062 : pszSQL =
7063 1 : sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_tile_ancillary "
7064 : "WHERE lower(tpudt_name) = lower('%q')",
7065 : pszLayerName);
7066 1 : eErr = SQLCommand(hDB, pszSQL);
7067 1 : sqlite3_free(pszSQL);
7068 : }
7069 : }
7070 :
7071 2 : if (eErr == OGRERR_NONE)
7072 : {
7073 2 : eErr = DeleteLayerCommon(pszLayerName);
7074 : }
7075 :
7076 2 : if (eErr == OGRERR_NONE)
7077 : {
7078 2 : eErr = SoftCommitTransaction();
7079 : }
7080 : else
7081 : {
7082 0 : SoftRollbackTransaction();
7083 : }
7084 :
7085 4 : return eErr;
7086 : }
7087 :
7088 : /************************************************************************/
7089 : /* DeleteVectorOrRasterLayer() */
7090 : /************************************************************************/
7091 :
7092 13 : bool GDALGeoPackageDataset::DeleteVectorOrRasterLayer(const char *pszLayerName)
7093 : {
7094 :
7095 13 : int idx = FindLayerIndex(pszLayerName);
7096 13 : if (idx >= 0)
7097 : {
7098 5 : DeleteLayer(idx);
7099 5 : return true;
7100 : }
7101 :
7102 : char *pszSQL =
7103 8 : sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
7104 : "lower(table_name) = lower('%q') "
7105 : "AND data_type IN ('tiles', '2d-gridded-coverage')",
7106 : pszLayerName);
7107 8 : bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
7108 8 : sqlite3_free(pszSQL);
7109 8 : if (bIsRasterTable)
7110 : {
7111 2 : DeleteRasterLayer(pszLayerName);
7112 2 : return true;
7113 : }
7114 6 : return false;
7115 : }
7116 :
7117 7 : bool GDALGeoPackageDataset::RenameVectorOrRasterLayer(
7118 : const char *pszLayerName, const char *pszNewLayerName)
7119 : {
7120 7 : int idx = FindLayerIndex(pszLayerName);
7121 7 : if (idx >= 0)
7122 : {
7123 4 : m_apoLayers[idx]->Rename(pszNewLayerName);
7124 4 : return true;
7125 : }
7126 :
7127 : char *pszSQL =
7128 3 : sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
7129 : "lower(table_name) = lower('%q') "
7130 : "AND data_type IN ('tiles', '2d-gridded-coverage')",
7131 : pszLayerName);
7132 3 : const bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
7133 3 : sqlite3_free(pszSQL);
7134 :
7135 3 : if (bIsRasterTable)
7136 : {
7137 2 : return RenameRasterLayer(pszLayerName, pszNewLayerName);
7138 : }
7139 :
7140 1 : return false;
7141 : }
7142 :
7143 2 : bool GDALGeoPackageDataset::RenameRasterLayer(const char *pszLayerName,
7144 : const char *pszNewLayerName)
7145 : {
7146 4 : std::string osSQL;
7147 :
7148 2 : char *pszSQL = sqlite3_mprintf(
7149 : "SELECT 1 FROM sqlite_master WHERE lower(name) = lower('%q') "
7150 : "AND type IN ('table', 'view')",
7151 : pszNewLayerName);
7152 2 : const bool bAlreadyExists = SQLGetInteger(GetDB(), pszSQL, nullptr) == 1;
7153 2 : sqlite3_free(pszSQL);
7154 2 : if (bAlreadyExists)
7155 : {
7156 0 : CPLError(CE_Failure, CPLE_AppDefined, "Table %s already exists",
7157 : pszNewLayerName);
7158 0 : return false;
7159 : }
7160 :
7161 : // Temporary remove foreign key checks
7162 : const GPKGTemporaryForeignKeyCheckDisabler
7163 4 : oGPKGTemporaryForeignKeyCheckDisabler(this);
7164 :
7165 2 : if (SoftStartTransaction() != OGRERR_NONE)
7166 : {
7167 0 : return false;
7168 : }
7169 :
7170 2 : pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET table_name = '%q' WHERE "
7171 : "lower(table_name) = lower('%q');",
7172 : pszNewLayerName, pszLayerName);
7173 2 : osSQL = pszSQL;
7174 2 : sqlite3_free(pszSQL);
7175 :
7176 2 : pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' WHERE "
7177 : "lower(identifier) = lower('%q');",
7178 : pszNewLayerName, pszLayerName);
7179 2 : osSQL += pszSQL;
7180 2 : sqlite3_free(pszSQL);
7181 :
7182 : pszSQL =
7183 2 : sqlite3_mprintf("UPDATE gpkg_tile_matrix SET table_name = '%q' WHERE "
7184 : "lower(table_name) = lower('%q');",
7185 : pszNewLayerName, pszLayerName);
7186 2 : osSQL += pszSQL;
7187 2 : sqlite3_free(pszSQL);
7188 :
7189 2 : pszSQL = sqlite3_mprintf(
7190 : "UPDATE gpkg_tile_matrix_set SET table_name = '%q' WHERE "
7191 : "lower(table_name) = lower('%q');",
7192 : pszNewLayerName, pszLayerName);
7193 2 : osSQL += pszSQL;
7194 2 : sqlite3_free(pszSQL);
7195 :
7196 2 : if (HasGriddedCoverageAncillaryTable())
7197 : {
7198 1 : pszSQL = sqlite3_mprintf("UPDATE gpkg_2d_gridded_coverage_ancillary "
7199 : "SET tile_matrix_set_name = '%q' WHERE "
7200 : "lower(tile_matrix_set_name) = lower('%q');",
7201 : pszNewLayerName, pszLayerName);
7202 1 : osSQL += pszSQL;
7203 1 : sqlite3_free(pszSQL);
7204 :
7205 1 : pszSQL = sqlite3_mprintf(
7206 : "UPDATE gpkg_2d_gridded_tile_ancillary SET tpudt_name = '%q' WHERE "
7207 : "lower(tpudt_name) = lower('%q');",
7208 : pszNewLayerName, pszLayerName);
7209 1 : osSQL += pszSQL;
7210 1 : sqlite3_free(pszSQL);
7211 : }
7212 :
7213 2 : if (HasExtensionsTable())
7214 : {
7215 2 : pszSQL = sqlite3_mprintf(
7216 : "UPDATE gpkg_extensions SET table_name = '%q' WHERE "
7217 : "lower(table_name) = lower('%q');",
7218 : pszNewLayerName, pszLayerName);
7219 2 : osSQL += pszSQL;
7220 2 : sqlite3_free(pszSQL);
7221 : }
7222 :
7223 2 : if (HasMetadataTables())
7224 : {
7225 1 : pszSQL = sqlite3_mprintf(
7226 : "UPDATE gpkg_metadata_reference SET table_name = '%q' WHERE "
7227 : "lower(table_name) = lower('%q');",
7228 : pszNewLayerName, pszLayerName);
7229 1 : osSQL += pszSQL;
7230 1 : sqlite3_free(pszSQL);
7231 : }
7232 :
7233 2 : if (HasDataColumnsTable())
7234 : {
7235 0 : pszSQL = sqlite3_mprintf(
7236 : "UPDATE gpkg_data_columns SET table_name = '%q' WHERE "
7237 : "lower(table_name) = lower('%q');",
7238 : pszNewLayerName, pszLayerName);
7239 0 : osSQL += pszSQL;
7240 0 : sqlite3_free(pszSQL);
7241 : }
7242 :
7243 2 : if (HasQGISLayerStyles())
7244 : {
7245 : // Update QGIS styles
7246 : pszSQL =
7247 0 : sqlite3_mprintf("UPDATE layer_styles SET f_table_name = '%q' WHERE "
7248 : "lower(f_table_name) = lower('%q');",
7249 : pszNewLayerName, pszLayerName);
7250 0 : osSQL += pszSQL;
7251 0 : sqlite3_free(pszSQL);
7252 : }
7253 :
7254 : #ifdef ENABLE_GPKG_OGR_CONTENTS
7255 2 : if (m_bHasGPKGOGRContents)
7256 : {
7257 2 : pszSQL = sqlite3_mprintf(
7258 : "UPDATE gpkg_ogr_contents SET table_name = '%q' WHERE "
7259 : "lower(table_name) = lower('%q');",
7260 : pszNewLayerName, pszLayerName);
7261 2 : osSQL += pszSQL;
7262 2 : sqlite3_free(pszSQL);
7263 : }
7264 : #endif
7265 :
7266 2 : if (HasGpkgextRelationsTable())
7267 : {
7268 0 : pszSQL = sqlite3_mprintf(
7269 : "UPDATE gpkgext_relations SET base_table_name = '%q' WHERE "
7270 : "lower(base_table_name) = lower('%q');",
7271 : pszNewLayerName, pszLayerName);
7272 0 : osSQL += pszSQL;
7273 0 : sqlite3_free(pszSQL);
7274 :
7275 0 : pszSQL = sqlite3_mprintf(
7276 : "UPDATE gpkgext_relations SET related_table_name = '%q' WHERE "
7277 : "lower(related_table_name) = lower('%q');",
7278 : pszNewLayerName, pszLayerName);
7279 0 : osSQL += pszSQL;
7280 0 : sqlite3_free(pszSQL);
7281 :
7282 0 : pszSQL = sqlite3_mprintf(
7283 : "UPDATE gpkgext_relations SET mapping_table_name = '%q' WHERE "
7284 : "lower(mapping_table_name) = lower('%q');",
7285 : pszNewLayerName, pszLayerName);
7286 0 : osSQL += pszSQL;
7287 0 : sqlite3_free(pszSQL);
7288 : }
7289 :
7290 : // Drop all triggers for the layer
7291 2 : pszSQL = sqlite3_mprintf("SELECT name FROM sqlite_master WHERE type = "
7292 : "'trigger' AND tbl_name = '%q'",
7293 : pszLayerName);
7294 2 : auto oTriggerResult = SQLQuery(GetDB(), pszSQL);
7295 2 : sqlite3_free(pszSQL);
7296 2 : if (oTriggerResult)
7297 : {
7298 14 : for (int i = 0; i < oTriggerResult->RowCount(); i++)
7299 : {
7300 12 : const char *pszTriggerName = oTriggerResult->GetValue(0, i);
7301 12 : pszSQL = sqlite3_mprintf("DROP TRIGGER IF EXISTS \"%w\";",
7302 : pszTriggerName);
7303 12 : osSQL += pszSQL;
7304 12 : sqlite3_free(pszSQL);
7305 : }
7306 : }
7307 :
7308 2 : pszSQL = sqlite3_mprintf("ALTER TABLE \"%w\" RENAME TO \"%w\";",
7309 : pszLayerName, pszNewLayerName);
7310 2 : osSQL += pszSQL;
7311 2 : sqlite3_free(pszSQL);
7312 :
7313 : // Recreate all zoom/tile triggers
7314 2 : if (oTriggerResult)
7315 : {
7316 2 : osSQL += CreateRasterTriggersSQL(pszNewLayerName);
7317 : }
7318 :
7319 2 : OGRErr eErr = SQLCommand(GetDB(), osSQL.c_str());
7320 :
7321 : // Check foreign key integrity
7322 2 : if (eErr == OGRERR_NONE)
7323 : {
7324 2 : eErr = PragmaCheck("foreign_key_check", "", 0);
7325 : }
7326 :
7327 2 : if (eErr == OGRERR_NONE)
7328 : {
7329 2 : eErr = SoftCommitTransaction();
7330 : }
7331 : else
7332 : {
7333 0 : SoftRollbackTransaction();
7334 : }
7335 :
7336 2 : return eErr == OGRERR_NONE;
7337 : }
7338 :
7339 : /************************************************************************/
7340 : /* TestCapability() */
7341 : /************************************************************************/
7342 :
7343 522 : int GDALGeoPackageDataset::TestCapability(const char *pszCap) const
7344 : {
7345 522 : if (EQUAL(pszCap, ODsCCreateLayer) || EQUAL(pszCap, ODsCDeleteLayer) ||
7346 339 : EQUAL(pszCap, "RenameLayer"))
7347 : {
7348 183 : return GetUpdate();
7349 : }
7350 339 : else if (EQUAL(pszCap, ODsCCurveGeometries))
7351 12 : return TRUE;
7352 327 : else if (EQUAL(pszCap, ODsCMeasuredGeometries))
7353 8 : return TRUE;
7354 319 : else if (EQUAL(pszCap, ODsCZGeometries))
7355 8 : return TRUE;
7356 311 : else if (EQUAL(pszCap, ODsCRandomLayerWrite) ||
7357 311 : EQUAL(pszCap, GDsCAddRelationship) ||
7358 311 : EQUAL(pszCap, GDsCDeleteRelationship) ||
7359 311 : EQUAL(pszCap, GDsCUpdateRelationship) ||
7360 311 : EQUAL(pszCap, ODsCAddFieldDomain) ||
7361 309 : EQUAL(pszCap, ODsCUpdateFieldDomain) ||
7362 307 : EQUAL(pszCap, ODsCDeleteFieldDomain))
7363 : {
7364 6 : return GetUpdate();
7365 : }
7366 :
7367 305 : return OGRSQLiteBaseDataSource::TestCapability(pszCap);
7368 : }
7369 :
7370 : /************************************************************************/
7371 : /* ResetReadingAllLayers() */
7372 : /************************************************************************/
7373 :
7374 205 : void GDALGeoPackageDataset::ResetReadingAllLayers()
7375 : {
7376 415 : for (auto &poLayer : m_apoLayers)
7377 : {
7378 210 : poLayer->ResetReading();
7379 : }
7380 205 : }
7381 :
7382 : /************************************************************************/
7383 : /* ExecuteSQL() */
7384 : /************************************************************************/
7385 :
7386 : static const char *const apszFuncsWithSideEffects[] = {
7387 : "CreateSpatialIndex",
7388 : "DisableSpatialIndex",
7389 : "HasSpatialIndex",
7390 : "RegisterGeometryExtension",
7391 : };
7392 :
7393 5652 : OGRLayer *GDALGeoPackageDataset::ExecuteSQL(const char *pszSQLCommand,
7394 : OGRGeometry *poSpatialFilter,
7395 : const char *pszDialect)
7396 :
7397 : {
7398 5652 : m_bHasReadMetadataFromStorage = false;
7399 :
7400 5652 : FlushMetadata();
7401 :
7402 5670 : while (*pszSQLCommand != '\0' &&
7403 5670 : isspace(static_cast<unsigned char>(*pszSQLCommand)))
7404 18 : pszSQLCommand++;
7405 :
7406 11304 : CPLString osSQLCommand(pszSQLCommand);
7407 5652 : if (!osSQLCommand.empty() && osSQLCommand.back() == ';')
7408 48 : osSQLCommand.pop_back();
7409 :
7410 11303 : if (osSQLCommand.ifind("AsGPB(ST_") != std::string::npos ||
7411 5651 : osSQLCommand.ifind("AsGPB( ST_") != std::string::npos)
7412 : {
7413 1 : CPLError(CE_Warning, CPLE_AppDefined,
7414 : "Use of AsGPB(ST_xxx(...)) found in \"%s\". Since GDAL 3.13, "
7415 : "ST_xxx() functions return a GeoPackage geometry when used "
7416 : "with a GeoPackage connection, and the use of AsGPB() is no "
7417 : "longer needed. It is here automatically removed",
7418 : osSQLCommand.c_str());
7419 1 : osSQLCommand.replaceAll("AsGPB(ST_", "(ST_");
7420 1 : osSQLCommand.replaceAll("AsGPB( ST_", "(ST_");
7421 : }
7422 :
7423 5652 : if (pszDialect == nullptr || !EQUAL(pszDialect, "DEBUG"))
7424 : {
7425 : // Some SQL commands will influence the feature count behind our
7426 : // back, so disable it in that case.
7427 : #ifdef ENABLE_GPKG_OGR_CONTENTS
7428 : const bool bInsertOrDelete =
7429 5583 : osSQLCommand.ifind("insert into ") != std::string::npos ||
7430 2462 : osSQLCommand.ifind("insert or replace into ") !=
7431 8045 : std::string::npos ||
7432 2425 : osSQLCommand.ifind("delete from ") != std::string::npos;
7433 : const bool bRollback =
7434 5583 : osSQLCommand.ifind("rollback ") != std::string::npos;
7435 : #endif
7436 :
7437 7414 : for (auto &poLayer : m_apoLayers)
7438 : {
7439 1831 : if (poLayer->SyncToDisk() != OGRERR_NONE)
7440 0 : return nullptr;
7441 : #ifdef ENABLE_GPKG_OGR_CONTENTS
7442 2036 : if (bRollback ||
7443 205 : (bInsertOrDelete &&
7444 205 : osSQLCommand.ifind(poLayer->GetName()) != std::string::npos))
7445 : {
7446 203 : poLayer->DisableFeatureCount();
7447 : }
7448 : #endif
7449 : }
7450 : }
7451 :
7452 5652 : if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 0") ||
7453 5651 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=0") ||
7454 5651 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =0") ||
7455 5651 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 0"))
7456 : {
7457 1 : OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, false);
7458 : }
7459 5651 : else if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 1") ||
7460 5650 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=1") ||
7461 5650 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =1") ||
7462 5650 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 1"))
7463 : {
7464 1 : OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, true);
7465 : }
7466 :
7467 : /* -------------------------------------------------------------------- */
7468 : /* DEBUG "SELECT nolock" command. */
7469 : /* -------------------------------------------------------------------- */
7470 5721 : if (pszDialect != nullptr && EQUAL(pszDialect, "DEBUG") &&
7471 69 : EQUAL(osSQLCommand, "SELECT nolock"))
7472 : {
7473 3 : return new OGRSQLiteSingleFeatureLayer(osSQLCommand, m_bNoLock ? 1 : 0);
7474 : }
7475 :
7476 : /* -------------------------------------------------------------------- */
7477 : /* Special case DELLAYER: command. */
7478 : /* -------------------------------------------------------------------- */
7479 5649 : if (STARTS_WITH_CI(osSQLCommand, "DELLAYER:"))
7480 : {
7481 4 : const char *pszLayerName = osSQLCommand.c_str() + strlen("DELLAYER:");
7482 :
7483 4 : while (*pszLayerName == ' ')
7484 0 : pszLayerName++;
7485 :
7486 4 : if (!DeleteVectorOrRasterLayer(pszLayerName))
7487 : {
7488 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
7489 : pszLayerName);
7490 : }
7491 4 : return nullptr;
7492 : }
7493 :
7494 : /* -------------------------------------------------------------------- */
7495 : /* Special case RECOMPUTE EXTENT ON command. */
7496 : /* -------------------------------------------------------------------- */
7497 5645 : if (STARTS_WITH_CI(osSQLCommand, "RECOMPUTE EXTENT ON "))
7498 : {
7499 : const char *pszLayerName =
7500 4 : osSQLCommand.c_str() + strlen("RECOMPUTE EXTENT ON ");
7501 :
7502 4 : while (*pszLayerName == ' ')
7503 0 : pszLayerName++;
7504 :
7505 4 : int idx = FindLayerIndex(pszLayerName);
7506 4 : if (idx >= 0)
7507 : {
7508 4 : m_apoLayers[idx]->RecomputeExtent();
7509 : }
7510 : else
7511 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
7512 : pszLayerName);
7513 4 : return nullptr;
7514 : }
7515 :
7516 : /* -------------------------------------------------------------------- */
7517 : /* Intercept DROP TABLE */
7518 : /* -------------------------------------------------------------------- */
7519 5641 : if (STARTS_WITH_CI(osSQLCommand, "DROP TABLE "))
7520 : {
7521 9 : const char *pszLayerName = osSQLCommand.c_str() + strlen("DROP TABLE ");
7522 :
7523 9 : while (*pszLayerName == ' ')
7524 0 : pszLayerName++;
7525 :
7526 9 : if (DeleteVectorOrRasterLayer(SQLUnescape(pszLayerName)))
7527 4 : return nullptr;
7528 : }
7529 :
7530 : /* -------------------------------------------------------------------- */
7531 : /* Intercept ALTER TABLE src_table RENAME TO dst_table */
7532 : /* and ALTER TABLE table RENAME COLUMN src_name TO dst_name */
7533 : /* and ALTER TABLE table DROP COLUMN col_name */
7534 : /* */
7535 : /* We do this because SQLite mechanisms can't deal with updating */
7536 : /* literal values in gpkg_ tables that refer to table and column */
7537 : /* names. */
7538 : /* -------------------------------------------------------------------- */
7539 5637 : if (STARTS_WITH_CI(osSQLCommand, "ALTER TABLE "))
7540 : {
7541 9 : char **papszTokens = SQLTokenize(osSQLCommand);
7542 : /* ALTER TABLE src_table RENAME TO dst_table */
7543 16 : if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "RENAME") &&
7544 7 : EQUAL(papszTokens[4], "TO"))
7545 : {
7546 7 : const char *pszSrcTableName = papszTokens[2];
7547 7 : const char *pszDstTableName = papszTokens[5];
7548 7 : if (RenameVectorOrRasterLayer(SQLUnescape(pszSrcTableName),
7549 14 : SQLUnescape(pszDstTableName)))
7550 : {
7551 6 : CSLDestroy(papszTokens);
7552 6 : return nullptr;
7553 : }
7554 : }
7555 : /* ALTER TABLE table RENAME COLUMN src_name TO dst_name */
7556 2 : else if (CSLCount(papszTokens) == 8 &&
7557 1 : EQUAL(papszTokens[3], "RENAME") &&
7558 3 : EQUAL(papszTokens[4], "COLUMN") && EQUAL(papszTokens[6], "TO"))
7559 : {
7560 1 : const char *pszTableName = papszTokens[2];
7561 1 : const char *pszSrcColumn = papszTokens[5];
7562 1 : const char *pszDstColumn = papszTokens[7];
7563 : OGRGeoPackageTableLayer *poLayer =
7564 0 : dynamic_cast<OGRGeoPackageTableLayer *>(
7565 1 : GetLayerByName(SQLUnescape(pszTableName)));
7566 1 : if (poLayer)
7567 : {
7568 2 : int nSrcFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
7569 2 : SQLUnescape(pszSrcColumn));
7570 1 : if (nSrcFieldIdx >= 0)
7571 : {
7572 : // OFTString or any type will do as we just alter the name
7573 : // so it will be ignored.
7574 1 : OGRFieldDefn oFieldDefn(SQLUnescape(pszDstColumn),
7575 1 : OFTString);
7576 1 : poLayer->AlterFieldDefn(nSrcFieldIdx, &oFieldDefn,
7577 : ALTER_NAME_FLAG);
7578 1 : CSLDestroy(papszTokens);
7579 1 : return nullptr;
7580 : }
7581 : }
7582 : }
7583 : /* ALTER TABLE table DROP COLUMN col_name */
7584 2 : else if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "DROP") &&
7585 1 : EQUAL(papszTokens[4], "COLUMN"))
7586 : {
7587 1 : const char *pszTableName = papszTokens[2];
7588 1 : const char *pszColumnName = papszTokens[5];
7589 : OGRGeoPackageTableLayer *poLayer =
7590 0 : dynamic_cast<OGRGeoPackageTableLayer *>(
7591 1 : GetLayerByName(SQLUnescape(pszTableName)));
7592 1 : if (poLayer)
7593 : {
7594 2 : int nFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
7595 2 : SQLUnescape(pszColumnName));
7596 1 : if (nFieldIdx >= 0)
7597 : {
7598 1 : poLayer->DeleteField(nFieldIdx);
7599 1 : CSLDestroy(papszTokens);
7600 1 : return nullptr;
7601 : }
7602 : }
7603 : }
7604 1 : CSLDestroy(papszTokens);
7605 : }
7606 :
7607 5629 : if (ProcessTransactionSQL(osSQLCommand))
7608 : {
7609 253 : return nullptr;
7610 : }
7611 :
7612 5376 : if (EQUAL(osSQLCommand, "VACUUM"))
7613 : {
7614 13 : ResetReadingAllLayers();
7615 : }
7616 5363 : else if (STARTS_WITH_CI(osSQLCommand, "DELETE FROM "))
7617 : {
7618 : // Optimize truncation of a table, especially if it has a spatial
7619 : // index.
7620 24 : const CPLStringList aosTokens(SQLTokenize(osSQLCommand));
7621 24 : if (aosTokens.size() == 3)
7622 : {
7623 16 : const char *pszTableName = aosTokens[2];
7624 : OGRGeoPackageTableLayer *poLayer =
7625 8 : dynamic_cast<OGRGeoPackageTableLayer *>(
7626 24 : GetLayerByName(SQLUnescape(pszTableName)));
7627 16 : if (poLayer)
7628 : {
7629 8 : poLayer->Truncate();
7630 8 : return nullptr;
7631 : }
7632 : }
7633 : }
7634 5339 : else if (pszDialect != nullptr && EQUAL(pszDialect, "INDIRECT_SQLITE"))
7635 1 : return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter, "SQLITE");
7636 5338 : else if (pszDialect != nullptr && !EQUAL(pszDialect, "") &&
7637 67 : !EQUAL(pszDialect, "NATIVE") && !EQUAL(pszDialect, "SQLITE") &&
7638 67 : !EQUAL(pszDialect, "DEBUG"))
7639 1 : return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter,
7640 1 : pszDialect);
7641 :
7642 : /* -------------------------------------------------------------------- */
7643 : /* Prepare statement. */
7644 : /* -------------------------------------------------------------------- */
7645 5366 : sqlite3_stmt *hSQLStmt = nullptr;
7646 :
7647 : /* This will speed-up layer creation */
7648 : /* ORDER BY are costly to evaluate and are not necessary to establish */
7649 : /* the layer definition. */
7650 5366 : bool bUseStatementForGetNextFeature = true;
7651 5366 : bool bEmptyLayer = false;
7652 10732 : CPLString osSQLCommandTruncated(osSQLCommand);
7653 :
7654 17708 : if (osSQLCommand.ifind("SELECT ") == 0 &&
7655 6171 : CPLString(osSQLCommand.substr(1)).ifind("SELECT ") ==
7656 770 : std::string::npos &&
7657 770 : osSQLCommand.ifind(" UNION ") == std::string::npos &&
7658 6941 : osSQLCommand.ifind(" INTERSECT ") == std::string::npos &&
7659 770 : osSQLCommand.ifind(" EXCEPT ") == std::string::npos)
7660 : {
7661 770 : size_t nOrderByPos = osSQLCommand.ifind(" ORDER BY ");
7662 770 : if (nOrderByPos != std::string::npos)
7663 : {
7664 9 : osSQLCommandTruncated.resize(nOrderByPos);
7665 9 : bUseStatementForGetNextFeature = false;
7666 : }
7667 : }
7668 :
7669 5366 : int rc = prepareSql(hDB, osSQLCommandTruncated.c_str(),
7670 5366 : static_cast<int>(osSQLCommandTruncated.size()),
7671 : &hSQLStmt, nullptr);
7672 :
7673 5366 : if (rc != SQLITE_OK)
7674 : {
7675 9 : CPLError(CE_Failure, CPLE_AppDefined,
7676 : "In ExecuteSQL(): sqlite3_prepare_v2(%s): %s",
7677 : osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
7678 :
7679 9 : if (hSQLStmt != nullptr)
7680 : {
7681 0 : sqlite3_finalize(hSQLStmt);
7682 : }
7683 :
7684 9 : return nullptr;
7685 : }
7686 :
7687 : /* -------------------------------------------------------------------- */
7688 : /* Do we get a resultset? */
7689 : /* -------------------------------------------------------------------- */
7690 5357 : rc = sqlite3_step(hSQLStmt);
7691 :
7692 6953 : for (auto &poLayer : m_apoLayers)
7693 : {
7694 1596 : poLayer->RunDeferredDropRTreeTableIfNecessary();
7695 : }
7696 :
7697 5357 : if (rc != SQLITE_ROW)
7698 : {
7699 4635 : if (rc != SQLITE_DONE)
7700 : {
7701 7 : CPLError(CE_Failure, CPLE_AppDefined,
7702 : "In ExecuteSQL(): sqlite3_step(%s):\n %s",
7703 : osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
7704 :
7705 7 : sqlite3_finalize(hSQLStmt);
7706 7 : return nullptr;
7707 : }
7708 :
7709 4628 : if (EQUAL(osSQLCommand, "VACUUM"))
7710 : {
7711 13 : sqlite3_finalize(hSQLStmt);
7712 : /* VACUUM rewrites the DB, so we need to reset the application id */
7713 13 : SetApplicationAndUserVersionId();
7714 13 : return nullptr;
7715 : }
7716 :
7717 4615 : if (!STARTS_WITH_CI(osSQLCommand, "SELECT "))
7718 : {
7719 4488 : sqlite3_finalize(hSQLStmt);
7720 4488 : return nullptr;
7721 : }
7722 :
7723 127 : bUseStatementForGetNextFeature = false;
7724 127 : bEmptyLayer = true;
7725 : }
7726 :
7727 : /* -------------------------------------------------------------------- */
7728 : /* Special case for some functions which must be run */
7729 : /* only once */
7730 : /* -------------------------------------------------------------------- */
7731 849 : if (STARTS_WITH_CI(osSQLCommand, "SELECT "))
7732 : {
7733 3869 : for (unsigned int i = 0; i < sizeof(apszFuncsWithSideEffects) /
7734 : sizeof(apszFuncsWithSideEffects[0]);
7735 : i++)
7736 : {
7737 3121 : if (EQUALN(apszFuncsWithSideEffects[i], osSQLCommand.c_str() + 7,
7738 : strlen(apszFuncsWithSideEffects[i])))
7739 : {
7740 112 : if (sqlite3_column_count(hSQLStmt) == 1 &&
7741 56 : sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
7742 : {
7743 56 : int ret = sqlite3_column_int(hSQLStmt, 0);
7744 :
7745 56 : sqlite3_finalize(hSQLStmt);
7746 :
7747 : return new OGRSQLiteSingleFeatureLayer(
7748 56 : apszFuncsWithSideEffects[i], ret);
7749 : }
7750 : }
7751 : }
7752 : }
7753 45 : else if (STARTS_WITH_CI(osSQLCommand, "PRAGMA "))
7754 : {
7755 63 : if (sqlite3_column_count(hSQLStmt) == 1 &&
7756 18 : sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
7757 : {
7758 15 : int ret = sqlite3_column_int(hSQLStmt, 0);
7759 :
7760 15 : sqlite3_finalize(hSQLStmt);
7761 :
7762 15 : return new OGRSQLiteSingleFeatureLayer(osSQLCommand.c_str() + 7,
7763 15 : ret);
7764 : }
7765 33 : else if (sqlite3_column_count(hSQLStmt) == 1 &&
7766 3 : sqlite3_column_type(hSQLStmt, 0) == SQLITE_TEXT)
7767 : {
7768 : const char *pszRet = reinterpret_cast<const char *>(
7769 3 : sqlite3_column_text(hSQLStmt, 0));
7770 :
7771 : OGRLayer *poRet = new OGRSQLiteSingleFeatureLayer(
7772 3 : osSQLCommand.c_str() + 7, pszRet);
7773 :
7774 3 : sqlite3_finalize(hSQLStmt);
7775 :
7776 3 : return poRet;
7777 : }
7778 : }
7779 :
7780 : /* -------------------------------------------------------------------- */
7781 : /* Create layer. */
7782 : /* -------------------------------------------------------------------- */
7783 :
7784 : auto poLayer = std::make_unique<OGRGeoPackageSelectLayer>(
7785 : this, osSQLCommand, hSQLStmt, bUseStatementForGetNextFeature,
7786 1550 : bEmptyLayer);
7787 :
7788 778 : if (poSpatialFilter != nullptr &&
7789 3 : poLayer->GetLayerDefn()->GetGeomFieldCount() > 0)
7790 3 : poLayer->SetSpatialFilter(0, poSpatialFilter);
7791 :
7792 775 : return poLayer.release();
7793 : }
7794 :
7795 : /************************************************************************/
7796 : /* ReleaseResultSet() */
7797 : /************************************************************************/
7798 :
7799 808 : void GDALGeoPackageDataset::ReleaseResultSet(OGRLayer *poLayer)
7800 :
7801 : {
7802 808 : delete poLayer;
7803 808 : }
7804 :
7805 : /************************************************************************/
7806 : /* HasExtensionsTable() */
7807 : /************************************************************************/
7808 :
7809 6953 : bool GDALGeoPackageDataset::HasExtensionsTable()
7810 : {
7811 6953 : return SQLGetInteger(
7812 : hDB,
7813 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_extensions' "
7814 : "AND type IN ('table', 'view')",
7815 6953 : nullptr) == 1;
7816 : }
7817 :
7818 : /************************************************************************/
7819 : /* CheckUnknownExtensions() */
7820 : /************************************************************************/
7821 :
7822 1552 : void GDALGeoPackageDataset::CheckUnknownExtensions(bool bCheckRasterTable)
7823 : {
7824 1552 : if (!HasExtensionsTable())
7825 209 : return;
7826 :
7827 1343 : char *pszSQL = nullptr;
7828 1343 : if (!bCheckRasterTable)
7829 1134 : pszSQL = sqlite3_mprintf(
7830 : "SELECT extension_name, definition, scope FROM gpkg_extensions "
7831 : "WHERE (table_name IS NULL "
7832 : "AND extension_name IS NOT NULL "
7833 : "AND definition IS NOT NULL "
7834 : "AND scope IS NOT NULL "
7835 : "AND extension_name NOT IN ("
7836 : "'gdal_aspatial', "
7837 : "'gpkg_elevation_tiles', " // Old name before GPKG 1.2 approval
7838 : "'2d_gridded_coverage', " // Old name after GPKG 1.2 and before OGC
7839 : // 17-066r1 finalization
7840 : "'gpkg_2d_gridded_coverage', " // Name in OGC 17-066r1 final
7841 : "'gpkg_metadata', "
7842 : "'gpkg_schema', "
7843 : "'gpkg_crs_wkt', "
7844 : "'gpkg_crs_wkt_1_1', "
7845 : "'related_tables', 'gpkg_related_tables')) "
7846 : #ifdef WORKAROUND_SQLITE3_BUGS
7847 : "OR 0 "
7848 : #endif
7849 : "LIMIT 1000");
7850 : else
7851 209 : pszSQL = sqlite3_mprintf(
7852 : "SELECT extension_name, definition, scope FROM gpkg_extensions "
7853 : "WHERE (lower(table_name) = lower('%q') "
7854 : "AND extension_name IS NOT NULL "
7855 : "AND definition IS NOT NULL "
7856 : "AND scope IS NOT NULL "
7857 : "AND extension_name NOT IN ("
7858 : "'gpkg_elevation_tiles', " // Old name before GPKG 1.2 approval
7859 : "'2d_gridded_coverage', " // Old name after GPKG 1.2 and before OGC
7860 : // 17-066r1 finalization
7861 : "'gpkg_2d_gridded_coverage', " // Name in OGC 17-066r1 final
7862 : "'gpkg_metadata', "
7863 : "'gpkg_schema', "
7864 : "'gpkg_crs_wkt', "
7865 : "'gpkg_crs_wkt_1_1', "
7866 : "'related_tables', 'gpkg_related_tables')) "
7867 : #ifdef WORKAROUND_SQLITE3_BUGS
7868 : "OR 0 "
7869 : #endif
7870 : "LIMIT 1000",
7871 : m_osRasterTable.c_str());
7872 :
7873 2686 : auto oResultTable = SQLQuery(GetDB(), pszSQL);
7874 1343 : sqlite3_free(pszSQL);
7875 1343 : if (oResultTable && oResultTable->RowCount() > 0)
7876 : {
7877 42 : for (int i = 0; i < oResultTable->RowCount(); i++)
7878 : {
7879 21 : const char *pszExtName = oResultTable->GetValue(0, i);
7880 21 : const char *pszDefinition = oResultTable->GetValue(1, i);
7881 21 : const char *pszScope = oResultTable->GetValue(2, i);
7882 21 : if (pszExtName == nullptr || pszDefinition == nullptr ||
7883 : pszScope == nullptr)
7884 : {
7885 0 : continue;
7886 : }
7887 :
7888 21 : if (EQUAL(pszExtName, "gpkg_webp"))
7889 : {
7890 15 : if (GDALGetDriverByName("WEBP") == nullptr)
7891 : {
7892 1 : CPLError(
7893 : CE_Warning, CPLE_AppDefined,
7894 : "Table %s contains WEBP tiles, but GDAL configured "
7895 : "without WEBP support. Data will be missing",
7896 : m_osRasterTable.c_str());
7897 : }
7898 15 : m_eTF = GPKG_TF_WEBP;
7899 15 : continue;
7900 : }
7901 6 : if (EQUAL(pszExtName, "gpkg_zoom_other"))
7902 : {
7903 2 : m_bZoomOther = true;
7904 2 : continue;
7905 : }
7906 :
7907 4 : if (GetUpdate() && EQUAL(pszScope, "write-only"))
7908 : {
7909 1 : CPLError(
7910 : CE_Warning, CPLE_AppDefined,
7911 : "Database relies on the '%s' (%s) extension that should "
7912 : "be implemented for safe write-support, but is not "
7913 : "currently. "
7914 : "Update of that database are strongly discouraged to avoid "
7915 : "corruption.",
7916 : pszExtName, pszDefinition);
7917 : }
7918 3 : else if (GetUpdate() && EQUAL(pszScope, "read-write"))
7919 : {
7920 1 : CPLError(
7921 : CE_Warning, CPLE_AppDefined,
7922 : "Database relies on the '%s' (%s) extension that should "
7923 : "be implemented in order to read/write it safely, but is "
7924 : "not currently. "
7925 : "Some data may be missing while reading that database, and "
7926 : "updates are strongly discouraged.",
7927 : pszExtName, pszDefinition);
7928 : }
7929 2 : else if (EQUAL(pszScope, "read-write") &&
7930 : // None of the NGA extensions at
7931 : // http://ngageoint.github.io/GeoPackage/docs/extensions/
7932 : // affect read-only scenarios
7933 1 : !STARTS_WITH(pszExtName, "nga_"))
7934 : {
7935 1 : CPLError(
7936 : CE_Warning, CPLE_AppDefined,
7937 : "Database relies on the '%s' (%s) extension that should "
7938 : "be implemented in order to read it safely, but is not "
7939 : "currently. "
7940 : "Some data may be missing while reading that database.",
7941 : pszExtName, pszDefinition);
7942 : }
7943 : }
7944 : }
7945 : }
7946 :
7947 : /************************************************************************/
7948 : /* HasGDALAspatialExtension() */
7949 : /************************************************************************/
7950 :
7951 1095 : bool GDALGeoPackageDataset::HasGDALAspatialExtension()
7952 : {
7953 1095 : if (!HasExtensionsTable())
7954 102 : return false;
7955 :
7956 : auto oResultTable = SQLQuery(hDB, "SELECT * FROM gpkg_extensions "
7957 : "WHERE (extension_name = 'gdal_aspatial' "
7958 : "AND table_name IS NULL "
7959 : "AND column_name IS NULL)"
7960 : #ifdef WORKAROUND_SQLITE3_BUGS
7961 : " OR 0"
7962 : #endif
7963 993 : );
7964 993 : bool bHasExtension = (oResultTable && oResultTable->RowCount() == 1);
7965 993 : return bHasExtension;
7966 : }
7967 :
7968 : std::string
7969 192 : GDALGeoPackageDataset::CreateRasterTriggersSQL(const std::string &osTableName)
7970 : {
7971 : char *pszSQL;
7972 192 : std::string osSQL;
7973 : /* From D.5. sample_tile_pyramid Table 43. tiles table Trigger
7974 : * Definition SQL */
7975 192 : pszSQL = sqlite3_mprintf(
7976 : "CREATE TRIGGER \"%w_zoom_insert\" "
7977 : "BEFORE INSERT ON \"%w\" "
7978 : "FOR EACH ROW BEGIN "
7979 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
7980 : "constraint: zoom_level not specified for table in "
7981 : "gpkg_tile_matrix') "
7982 : "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
7983 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
7984 : "END; "
7985 : "CREATE TRIGGER \"%w_zoom_update\" "
7986 : "BEFORE UPDATE OF zoom_level ON \"%w\" "
7987 : "FOR EACH ROW BEGIN "
7988 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
7989 : "constraint: zoom_level not specified for table in "
7990 : "gpkg_tile_matrix') "
7991 : "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
7992 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
7993 : "END; "
7994 : "CREATE TRIGGER \"%w_tile_column_insert\" "
7995 : "BEFORE INSERT ON \"%w\" "
7996 : "FOR EACH ROW BEGIN "
7997 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
7998 : "constraint: tile_column cannot be < 0') "
7999 : "WHERE (NEW.tile_column < 0) ; "
8000 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
8001 : "constraint: tile_column must by < matrix_width specified for "
8002 : "table and zoom level in gpkg_tile_matrix') "
8003 : "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
8004 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8005 : "zoom_level = NEW.zoom_level)); "
8006 : "END; "
8007 : "CREATE TRIGGER \"%w_tile_column_update\" "
8008 : "BEFORE UPDATE OF tile_column ON \"%w\" "
8009 : "FOR EACH ROW BEGIN "
8010 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8011 : "constraint: tile_column cannot be < 0') "
8012 : "WHERE (NEW.tile_column < 0) ; "
8013 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8014 : "constraint: tile_column must by < matrix_width specified for "
8015 : "table and zoom level in gpkg_tile_matrix') "
8016 : "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
8017 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8018 : "zoom_level = NEW.zoom_level)); "
8019 : "END; "
8020 : "CREATE TRIGGER \"%w_tile_row_insert\" "
8021 : "BEFORE INSERT ON \"%w\" "
8022 : "FOR EACH ROW BEGIN "
8023 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
8024 : "constraint: tile_row cannot be < 0') "
8025 : "WHERE (NEW.tile_row < 0) ; "
8026 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
8027 : "constraint: tile_row must by < matrix_height specified for "
8028 : "table and zoom level in gpkg_tile_matrix') "
8029 : "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
8030 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8031 : "zoom_level = NEW.zoom_level)); "
8032 : "END; "
8033 : "CREATE TRIGGER \"%w_tile_row_update\" "
8034 : "BEFORE UPDATE OF tile_row ON \"%w\" "
8035 : "FOR EACH ROW BEGIN "
8036 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8037 : "constraint: tile_row cannot be < 0') "
8038 : "WHERE (NEW.tile_row < 0) ; "
8039 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8040 : "constraint: tile_row must by < matrix_height specified for "
8041 : "table and zoom level in gpkg_tile_matrix') "
8042 : "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
8043 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8044 : "zoom_level = NEW.zoom_level)); "
8045 : "END; ",
8046 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8047 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8048 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8049 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8050 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8051 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8052 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8053 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8054 : osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8055 : osTableName.c_str());
8056 192 : osSQL = pszSQL;
8057 192 : sqlite3_free(pszSQL);
8058 192 : return osSQL;
8059 : }
8060 :
8061 : /************************************************************************/
8062 : /* CreateExtensionsTableIfNecessary() */
8063 : /************************************************************************/
8064 :
8065 1240 : OGRErr GDALGeoPackageDataset::CreateExtensionsTableIfNecessary()
8066 : {
8067 : /* Check if the table gpkg_extensions exists */
8068 1240 : if (HasExtensionsTable())
8069 420 : return OGRERR_NONE;
8070 :
8071 : /* Requirement 79 : Every extension of a GeoPackage SHALL be registered */
8072 : /* in a corresponding row in the gpkg_extensions table. The absence of a */
8073 : /* gpkg_extensions table or the absence of rows in gpkg_extensions table */
8074 : /* SHALL both indicate the absence of extensions to a GeoPackage. */
8075 820 : const char *pszCreateGpkgExtensions =
8076 : "CREATE TABLE gpkg_extensions ("
8077 : "table_name TEXT,"
8078 : "column_name TEXT,"
8079 : "extension_name TEXT NOT NULL,"
8080 : "definition TEXT NOT NULL,"
8081 : "scope TEXT NOT NULL,"
8082 : "CONSTRAINT ge_tce UNIQUE (table_name, column_name, extension_name)"
8083 : ")";
8084 :
8085 820 : return SQLCommand(hDB, pszCreateGpkgExtensions);
8086 : }
8087 :
8088 : /************************************************************************/
8089 : /* OGR_GPKG_Intersects_Spatial_Filter() */
8090 : /************************************************************************/
8091 :
8092 23135 : void OGR_GPKG_Intersects_Spatial_Filter(sqlite3_context *pContext, int argc,
8093 : sqlite3_value **argv)
8094 : {
8095 23135 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8096 : {
8097 0 : sqlite3_result_int(pContext, 0);
8098 23125 : return;
8099 : }
8100 :
8101 : auto poLayer =
8102 23135 : static_cast<OGRGeoPackageTableLayer *>(sqlite3_user_data(pContext));
8103 :
8104 23135 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8105 : const GByte *pabyBLOB =
8106 23135 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8107 :
8108 : GPkgHeader sHeader;
8109 46270 : if (poLayer->m_bFilterIsEnvelope &&
8110 23135 : OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false, 0))
8111 : {
8112 23135 : if (sHeader.bExtentHasXY)
8113 : {
8114 95 : OGREnvelope sEnvelope;
8115 95 : sEnvelope.MinX = sHeader.MinX;
8116 95 : sEnvelope.MinY = sHeader.MinY;
8117 95 : sEnvelope.MaxX = sHeader.MaxX;
8118 95 : sEnvelope.MaxY = sHeader.MaxY;
8119 95 : if (poLayer->m_sFilterEnvelope.Contains(sEnvelope))
8120 : {
8121 31 : sqlite3_result_int(pContext, 1);
8122 31 : return;
8123 : }
8124 : }
8125 :
8126 : // Check if at least one point falls into the layer filter envelope
8127 : // nHeaderLen is > 0 for GeoPackage geometries
8128 46208 : if (sHeader.nHeaderLen > 0 &&
8129 23104 : OGRWKBIntersectsPessimistic(pabyBLOB + sHeader.nHeaderLen,
8130 23104 : nBLOBLen - sHeader.nHeaderLen,
8131 23104 : poLayer->m_sFilterEnvelope))
8132 : {
8133 23094 : sqlite3_result_int(pContext, 1);
8134 23094 : return;
8135 : }
8136 : }
8137 :
8138 : auto poGeom = std::unique_ptr<OGRGeometry>(
8139 10 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8140 10 : if (poGeom == nullptr)
8141 : {
8142 : // Try also spatialite geometry blobs
8143 0 : OGRGeometry *poGeomSpatialite = nullptr;
8144 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8145 0 : &poGeomSpatialite) != OGRERR_NONE)
8146 : {
8147 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8148 0 : sqlite3_result_int(pContext, 0);
8149 0 : return;
8150 : }
8151 0 : poGeom.reset(poGeomSpatialite);
8152 : }
8153 :
8154 10 : sqlite3_result_int(pContext, poLayer->FilterGeometry(poGeom.get()));
8155 : }
8156 :
8157 : /************************************************************************/
8158 : /* OGRGeoPackageSTMinX() */
8159 : /************************************************************************/
8160 :
8161 252301 : static void OGRGeoPackageSTMinX(sqlite3_context *pContext, int argc,
8162 : sqlite3_value **argv)
8163 : {
8164 : GPkgHeader sHeader;
8165 252301 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8166 : {
8167 3 : sqlite3_result_null(pContext);
8168 3 : return;
8169 : }
8170 252298 : sqlite3_result_double(pContext, sHeader.MinX);
8171 : }
8172 :
8173 : /************************************************************************/
8174 : /* OGRGeoPackageSTMinY() */
8175 : /************************************************************************/
8176 :
8177 252299 : static void OGRGeoPackageSTMinY(sqlite3_context *pContext, int argc,
8178 : sqlite3_value **argv)
8179 : {
8180 : GPkgHeader sHeader;
8181 252299 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8182 : {
8183 1 : sqlite3_result_null(pContext);
8184 1 : return;
8185 : }
8186 252298 : sqlite3_result_double(pContext, sHeader.MinY);
8187 : }
8188 :
8189 : /************************************************************************/
8190 : /* OGRGeoPackageSTMaxX() */
8191 : /************************************************************************/
8192 :
8193 252299 : static void OGRGeoPackageSTMaxX(sqlite3_context *pContext, int argc,
8194 : sqlite3_value **argv)
8195 : {
8196 : GPkgHeader sHeader;
8197 252299 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8198 : {
8199 1 : sqlite3_result_null(pContext);
8200 1 : return;
8201 : }
8202 252298 : sqlite3_result_double(pContext, sHeader.MaxX);
8203 : }
8204 :
8205 : /************************************************************************/
8206 : /* OGRGeoPackageSTMaxY() */
8207 : /************************************************************************/
8208 :
8209 252299 : static void OGRGeoPackageSTMaxY(sqlite3_context *pContext, int argc,
8210 : sqlite3_value **argv)
8211 : {
8212 : GPkgHeader sHeader;
8213 252299 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8214 : {
8215 1 : sqlite3_result_null(pContext);
8216 1 : return;
8217 : }
8218 252298 : sqlite3_result_double(pContext, sHeader.MaxY);
8219 : }
8220 :
8221 : /************************************************************************/
8222 : /* OGRGeoPackageSTIsEmpty() */
8223 : /************************************************************************/
8224 :
8225 253711 : static void OGRGeoPackageSTIsEmpty(sqlite3_context *pContext, int argc,
8226 : sqlite3_value **argv)
8227 : {
8228 : GPkgHeader sHeader;
8229 253711 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8230 : {
8231 2 : sqlite3_result_null(pContext);
8232 2 : return;
8233 : }
8234 253709 : sqlite3_result_int(pContext, sHeader.bEmpty);
8235 : }
8236 :
8237 : /************************************************************************/
8238 : /* OGRGeoPackageSTGeometryType() */
8239 : /************************************************************************/
8240 :
8241 7 : static void OGRGeoPackageSTGeometryType(sqlite3_context *pContext, int /*argc*/,
8242 : sqlite3_value **argv)
8243 : {
8244 : GPkgHeader sHeader;
8245 :
8246 7 : int nBLOBLen = sqlite3_value_bytes(argv[0]);
8247 : const GByte *pabyBLOB =
8248 7 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8249 : OGRwkbGeometryType eGeometryType;
8250 :
8251 13 : if (nBLOBLen < 8 ||
8252 6 : GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
8253 : {
8254 2 : if (OGRSQLiteGetSpatialiteGeometryHeader(
8255 : pabyBLOB, nBLOBLen, nullptr, &eGeometryType, nullptr, nullptr,
8256 2 : nullptr, nullptr, nullptr) == OGRERR_NONE)
8257 : {
8258 1 : sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
8259 : SQLITE_TRANSIENT);
8260 4 : return;
8261 : }
8262 : else
8263 : {
8264 1 : sqlite3_result_null(pContext);
8265 1 : return;
8266 : }
8267 : }
8268 :
8269 5 : if (static_cast<size_t>(nBLOBLen) < sHeader.nHeaderLen + 5)
8270 : {
8271 2 : sqlite3_result_null(pContext);
8272 2 : return;
8273 : }
8274 :
8275 3 : OGRErr err = OGRReadWKBGeometryType(pabyBLOB + sHeader.nHeaderLen,
8276 : wkbVariantIso, &eGeometryType);
8277 3 : if (err != OGRERR_NONE)
8278 1 : sqlite3_result_null(pContext);
8279 : else
8280 2 : sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
8281 : SQLITE_TRANSIENT);
8282 : }
8283 :
8284 : /************************************************************************/
8285 : /* OGRGeoPackageSTEnvelopesIntersects() */
8286 : /************************************************************************/
8287 :
8288 118 : static void OGRGeoPackageSTEnvelopesIntersects(sqlite3_context *pContext,
8289 : int argc, sqlite3_value **argv)
8290 : {
8291 : GPkgHeader sHeader;
8292 118 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8293 : {
8294 2 : sqlite3_result_int(pContext, FALSE);
8295 107 : return;
8296 : }
8297 116 : const double dfMinX = sqlite3_value_double(argv[1]);
8298 116 : if (sHeader.MaxX < dfMinX)
8299 : {
8300 93 : sqlite3_result_int(pContext, FALSE);
8301 93 : return;
8302 : }
8303 23 : const double dfMinY = sqlite3_value_double(argv[2]);
8304 23 : if (sHeader.MaxY < dfMinY)
8305 : {
8306 11 : sqlite3_result_int(pContext, FALSE);
8307 11 : return;
8308 : }
8309 12 : const double dfMaxX = sqlite3_value_double(argv[3]);
8310 12 : if (sHeader.MinX > dfMaxX)
8311 : {
8312 1 : sqlite3_result_int(pContext, FALSE);
8313 1 : return;
8314 : }
8315 11 : const double dfMaxY = sqlite3_value_double(argv[4]);
8316 11 : sqlite3_result_int(pContext, sHeader.MinY <= dfMaxY);
8317 : }
8318 :
8319 : /************************************************************************/
8320 : /* OGRGeoPackageSTEnvelopesIntersectsTwoParams() */
8321 : /************************************************************************/
8322 :
8323 : static void
8324 3 : OGRGeoPackageSTEnvelopesIntersectsTwoParams(sqlite3_context *pContext, int argc,
8325 : sqlite3_value **argv)
8326 : {
8327 : GPkgHeader sHeader;
8328 3 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false, 0))
8329 : {
8330 0 : sqlite3_result_int(pContext, FALSE);
8331 2 : return;
8332 : }
8333 : GPkgHeader sHeader2;
8334 3 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader2, true, false,
8335 : 1))
8336 : {
8337 0 : sqlite3_result_int(pContext, FALSE);
8338 0 : return;
8339 : }
8340 3 : if (sHeader.MaxX < sHeader2.MinX)
8341 : {
8342 1 : sqlite3_result_int(pContext, FALSE);
8343 1 : return;
8344 : }
8345 2 : if (sHeader.MaxY < sHeader2.MinY)
8346 : {
8347 0 : sqlite3_result_int(pContext, FALSE);
8348 0 : return;
8349 : }
8350 2 : if (sHeader.MinX > sHeader2.MaxX)
8351 : {
8352 1 : sqlite3_result_int(pContext, FALSE);
8353 1 : return;
8354 : }
8355 1 : sqlite3_result_int(pContext, sHeader.MinY <= sHeader2.MaxY);
8356 : }
8357 :
8358 : /************************************************************************/
8359 : /* OGRGeoPackageGPKGIsAssignable() */
8360 : /************************************************************************/
8361 :
8362 8 : static void OGRGeoPackageGPKGIsAssignable(sqlite3_context *pContext,
8363 : int /*argc*/, sqlite3_value **argv)
8364 : {
8365 15 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8366 7 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
8367 : {
8368 2 : sqlite3_result_int(pContext, 0);
8369 2 : return;
8370 : }
8371 :
8372 : const char *pszExpected =
8373 6 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8374 : const char *pszActual =
8375 6 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8376 6 : int bIsAssignable = OGR_GT_IsSubClassOf(OGRFromOGCGeomType(pszActual),
8377 : OGRFromOGCGeomType(pszExpected));
8378 6 : sqlite3_result_int(pContext, bIsAssignable);
8379 : }
8380 :
8381 : /************************************************************************/
8382 : /* OGRGeoPackageSTSRID() */
8383 : /************************************************************************/
8384 :
8385 12 : static void OGRGeoPackageSTSRID(sqlite3_context *pContext, int argc,
8386 : sqlite3_value **argv)
8387 : {
8388 : GPkgHeader sHeader;
8389 12 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8390 : {
8391 2 : sqlite3_result_null(pContext);
8392 2 : return;
8393 : }
8394 10 : sqlite3_result_int(pContext, sHeader.iSrsId);
8395 : }
8396 :
8397 : /************************************************************************/
8398 : /* OGRGeoPackageSetSRID() */
8399 : /************************************************************************/
8400 :
8401 28 : static void OGRGeoPackageSetSRID(sqlite3_context *pContext, int /* argc */,
8402 : sqlite3_value **argv)
8403 : {
8404 28 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8405 : {
8406 1 : sqlite3_result_null(pContext);
8407 1 : return;
8408 : }
8409 27 : const int nDestSRID = sqlite3_value_int(argv[1]);
8410 : GPkgHeader sHeader;
8411 27 : int nBLOBLen = sqlite3_value_bytes(argv[0]);
8412 : const GByte *pabyBLOB =
8413 27 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8414 :
8415 54 : if (nBLOBLen < 8 ||
8416 27 : GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
8417 : {
8418 : // Try also spatialite geometry blobs
8419 0 : OGRGeometry *poGeom = nullptr;
8420 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeom) !=
8421 : OGRERR_NONE)
8422 : {
8423 0 : sqlite3_result_null(pContext);
8424 0 : return;
8425 : }
8426 0 : size_t nBLOBDestLen = 0;
8427 : GByte *pabyDestBLOB =
8428 0 : GPkgGeometryFromOGR(poGeom, nDestSRID, nullptr, &nBLOBDestLen);
8429 0 : if (!pabyDestBLOB)
8430 : {
8431 0 : sqlite3_result_null(pContext);
8432 0 : return;
8433 : }
8434 0 : sqlite3_result_blob(pContext, pabyDestBLOB,
8435 : static_cast<int>(nBLOBDestLen), VSIFree);
8436 0 : return;
8437 : }
8438 :
8439 27 : GByte *pabyDestBLOB = static_cast<GByte *>(CPLMalloc(nBLOBLen));
8440 27 : memcpy(pabyDestBLOB, pabyBLOB, nBLOBLen);
8441 27 : int32_t nSRIDToSerialize = nDestSRID;
8442 27 : if (OGR_SWAP(sHeader.eByteOrder))
8443 0 : nSRIDToSerialize = CPL_SWAP32(nSRIDToSerialize);
8444 27 : memcpy(pabyDestBLOB + 4, &nSRIDToSerialize, 4);
8445 27 : sqlite3_result_blob(pContext, pabyDestBLOB, nBLOBLen, VSIFree);
8446 : }
8447 :
8448 : /************************************************************************/
8449 : /* OGRGeoPackageSTMakeValid() */
8450 : /************************************************************************/
8451 :
8452 3 : static void OGRGeoPackageSTMakeValid(sqlite3_context *pContext, int argc,
8453 : sqlite3_value **argv)
8454 : {
8455 3 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8456 : {
8457 2 : sqlite3_result_null(pContext);
8458 2 : return;
8459 : }
8460 1 : int nBLOBLen = sqlite3_value_bytes(argv[0]);
8461 : const GByte *pabyBLOB =
8462 1 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8463 :
8464 : GPkgHeader sHeader;
8465 1 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8466 : {
8467 0 : sqlite3_result_null(pContext);
8468 0 : return;
8469 : }
8470 :
8471 : auto poGeom = std::unique_ptr<OGRGeometry>(
8472 1 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8473 1 : if (poGeom == nullptr)
8474 : {
8475 : // Try also spatialite geometry blobs
8476 0 : OGRGeometry *poGeomPtr = nullptr;
8477 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
8478 : OGRERR_NONE)
8479 : {
8480 0 : sqlite3_result_null(pContext);
8481 0 : return;
8482 : }
8483 0 : poGeom.reset(poGeomPtr);
8484 : }
8485 1 : auto poValid = std::unique_ptr<OGRGeometry>(poGeom->MakeValid());
8486 1 : if (poValid == nullptr)
8487 : {
8488 0 : sqlite3_result_null(pContext);
8489 0 : return;
8490 : }
8491 :
8492 1 : size_t nBLOBDestLen = 0;
8493 1 : GByte *pabyDestBLOB = GPkgGeometryFromOGR(poValid.get(), sHeader.iSrsId,
8494 : nullptr, &nBLOBDestLen);
8495 1 : if (!pabyDestBLOB)
8496 : {
8497 0 : sqlite3_result_null(pContext);
8498 0 : return;
8499 : }
8500 1 : sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
8501 : VSIFree);
8502 : }
8503 :
8504 : /************************************************************************/
8505 : /* OGRGeoPackageSTArea() */
8506 : /************************************************************************/
8507 :
8508 19 : static void OGRGeoPackageSTArea(sqlite3_context *pContext, int /*argc*/,
8509 : sqlite3_value **argv)
8510 : {
8511 19 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8512 : {
8513 1 : sqlite3_result_null(pContext);
8514 15 : return;
8515 : }
8516 18 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8517 : const GByte *pabyBLOB =
8518 18 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8519 :
8520 : GPkgHeader sHeader;
8521 0 : std::unique_ptr<OGRGeometry> poGeom;
8522 18 : if (GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) == OGRERR_NONE)
8523 : {
8524 16 : if (sHeader.bEmpty)
8525 : {
8526 3 : sqlite3_result_double(pContext, 0);
8527 13 : return;
8528 : }
8529 13 : const GByte *pabyWkb = pabyBLOB + sHeader.nHeaderLen;
8530 13 : size_t nWKBSize = nBLOBLen - sHeader.nHeaderLen;
8531 : bool bNeedSwap;
8532 : uint32_t nType;
8533 13 : if (OGRWKBGetGeomType(pabyWkb, nWKBSize, bNeedSwap, nType))
8534 : {
8535 13 : if (nType == wkbPolygon || nType == wkbPolygon25D ||
8536 11 : nType == wkbPolygon + 1000 || // wkbPolygonZ
8537 10 : nType == wkbPolygonM || nType == wkbPolygonZM)
8538 : {
8539 : double dfArea;
8540 5 : if (OGRWKBPolygonGetArea(pabyWkb, nWKBSize, dfArea))
8541 : {
8542 5 : sqlite3_result_double(pContext, dfArea);
8543 5 : return;
8544 0 : }
8545 : }
8546 8 : else if (nType == wkbMultiPolygon || nType == wkbMultiPolygon25D ||
8547 6 : nType == wkbMultiPolygon + 1000 || // wkbMultiPolygonZ
8548 5 : nType == wkbMultiPolygonM || nType == wkbMultiPolygonZM)
8549 : {
8550 : double dfArea;
8551 5 : if (OGRWKBMultiPolygonGetArea(pabyWkb, nWKBSize, dfArea))
8552 : {
8553 5 : sqlite3_result_double(pContext, dfArea);
8554 5 : return;
8555 : }
8556 : }
8557 : }
8558 :
8559 : // For curve geometries, fallback to OGRGeometry methods
8560 3 : poGeom.reset(GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8561 : }
8562 : else
8563 : {
8564 : // Try also spatialite geometry blobs
8565 2 : OGRGeometry *poGeomPtr = nullptr;
8566 2 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
8567 : OGRERR_NONE)
8568 : {
8569 1 : sqlite3_result_null(pContext);
8570 1 : return;
8571 : }
8572 1 : poGeom.reset(poGeomPtr);
8573 : }
8574 4 : auto poSurface = dynamic_cast<OGRSurface *>(poGeom.get());
8575 4 : if (poSurface == nullptr)
8576 : {
8577 2 : auto poMultiSurface = dynamic_cast<OGRMultiSurface *>(poGeom.get());
8578 2 : if (poMultiSurface == nullptr)
8579 : {
8580 1 : sqlite3_result_double(pContext, 0);
8581 : }
8582 : else
8583 : {
8584 1 : sqlite3_result_double(pContext, poMultiSurface->get_Area());
8585 : }
8586 : }
8587 : else
8588 : {
8589 2 : sqlite3_result_double(pContext, poSurface->get_Area());
8590 : }
8591 : }
8592 :
8593 : /************************************************************************/
8594 : /* OGRGeoPackageGeodesicArea() */
8595 : /************************************************************************/
8596 :
8597 5 : static void OGRGeoPackageGeodesicArea(sqlite3_context *pContext, int argc,
8598 : sqlite3_value **argv)
8599 : {
8600 5 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8601 : {
8602 1 : sqlite3_result_null(pContext);
8603 3 : return;
8604 : }
8605 4 : if (sqlite3_value_int(argv[1]) != 1)
8606 : {
8607 2 : CPLError(CE_Warning, CPLE_NotSupported,
8608 : "ST_Area(geom, use_ellipsoid) is only supported for "
8609 : "use_ellipsoid = 1");
8610 : }
8611 :
8612 4 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8613 : const GByte *pabyBLOB =
8614 4 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8615 : GPkgHeader sHeader;
8616 4 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8617 : {
8618 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8619 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8620 1 : return;
8621 : }
8622 :
8623 : GDALGeoPackageDataset *poDS =
8624 3 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8625 :
8626 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSrcSRS(
8627 3 : poDS->GetSpatialRef(sHeader.iSrsId, true));
8628 3 : if (poSrcSRS == nullptr)
8629 : {
8630 1 : CPLError(CE_Failure, CPLE_AppDefined,
8631 : "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8632 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8633 1 : return;
8634 : }
8635 :
8636 : auto poGeom = std::unique_ptr<OGRGeometry>(
8637 2 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8638 2 : if (poGeom == nullptr)
8639 : {
8640 : // Try also spatialite geometry blobs
8641 0 : OGRGeometry *poGeomSpatialite = nullptr;
8642 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8643 0 : &poGeomSpatialite) != OGRERR_NONE)
8644 : {
8645 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8646 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8647 0 : return;
8648 : }
8649 0 : poGeom.reset(poGeomSpatialite);
8650 : }
8651 :
8652 2 : poGeom->assignSpatialReference(poSrcSRS.get());
8653 2 : sqlite3_result_double(
8654 : pContext, OGR_G_GeodesicArea(OGRGeometry::ToHandle(poGeom.get())));
8655 : }
8656 :
8657 : /************************************************************************/
8658 : /* OGRGeoPackageLengthOrGeodesicLength() */
8659 : /************************************************************************/
8660 :
8661 8 : static void OGRGeoPackageLengthOrGeodesicLength(sqlite3_context *pContext,
8662 : int argc, sqlite3_value **argv)
8663 : {
8664 8 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8665 : {
8666 2 : sqlite3_result_null(pContext);
8667 5 : return;
8668 : }
8669 6 : if (argc == 2 && sqlite3_value_int(argv[1]) != 1)
8670 : {
8671 2 : CPLError(CE_Warning, CPLE_NotSupported,
8672 : "ST_Length(geom, use_ellipsoid) is only supported for "
8673 : "use_ellipsoid = 1");
8674 : }
8675 :
8676 6 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8677 : const GByte *pabyBLOB =
8678 6 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8679 : GPkgHeader sHeader;
8680 6 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8681 : {
8682 2 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8683 2 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8684 2 : return;
8685 : }
8686 :
8687 : GDALGeoPackageDataset *poDS =
8688 4 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8689 :
8690 0 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSrcSRS;
8691 4 : if (argc == 2)
8692 : {
8693 3 : poSrcSRS = poDS->GetSpatialRef(sHeader.iSrsId, true);
8694 3 : if (!poSrcSRS)
8695 : {
8696 1 : CPLError(CE_Failure, CPLE_AppDefined,
8697 : "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8698 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8699 1 : return;
8700 : }
8701 : }
8702 :
8703 : auto poGeom = std::unique_ptr<OGRGeometry>(
8704 3 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8705 3 : if (poGeom == nullptr)
8706 : {
8707 : // Try also spatialite geometry blobs
8708 0 : OGRGeometry *poGeomSpatialite = nullptr;
8709 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8710 0 : &poGeomSpatialite) != OGRERR_NONE)
8711 : {
8712 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8713 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8714 0 : return;
8715 : }
8716 0 : poGeom.reset(poGeomSpatialite);
8717 : }
8718 :
8719 3 : if (argc == 2)
8720 2 : poGeom->assignSpatialReference(poSrcSRS.get());
8721 :
8722 6 : sqlite3_result_double(
8723 : pContext,
8724 1 : argc == 1 ? OGR_G_Length(OGRGeometry::ToHandle(poGeom.get()))
8725 2 : : OGR_G_GeodesicLength(OGRGeometry::ToHandle(poGeom.get())));
8726 : }
8727 :
8728 : /************************************************************************/
8729 : /* OGRGeoPackageTransform() */
8730 : /************************************************************************/
8731 :
8732 : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
8733 : sqlite3_value **argv);
8734 :
8735 32 : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
8736 : sqlite3_value **argv)
8737 : {
8738 63 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB ||
8739 31 : sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
8740 : {
8741 2 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8742 32 : return;
8743 : }
8744 :
8745 30 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8746 : const GByte *pabyBLOB =
8747 30 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8748 : GPkgHeader sHeader;
8749 30 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8750 : {
8751 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8752 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8753 1 : return;
8754 : }
8755 :
8756 29 : const int nDestSRID = sqlite3_value_int(argv[1]);
8757 29 : if (sHeader.iSrsId == nDestSRID)
8758 : {
8759 : // Return blob unmodified
8760 3 : sqlite3_result_blob(pContext, pabyBLOB, nBLOBLen, SQLITE_TRANSIENT);
8761 3 : return;
8762 : }
8763 :
8764 : GDALGeoPackageDataset *poDS =
8765 26 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8766 :
8767 : // Try to get the cached coordinate transformation
8768 : OGRCoordinateTransformation *poCT;
8769 26 : if (poDS->m_nLastCachedCTSrcSRId == sHeader.iSrsId &&
8770 20 : poDS->m_nLastCachedCTDstSRId == nDestSRID)
8771 : {
8772 20 : poCT = poDS->m_poLastCachedCT.get();
8773 : }
8774 : else
8775 : {
8776 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
8777 6 : poSrcSRS(poDS->GetSpatialRef(sHeader.iSrsId, true));
8778 6 : if (poSrcSRS == nullptr)
8779 : {
8780 0 : CPLError(CE_Failure, CPLE_AppDefined,
8781 : "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8782 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8783 0 : return;
8784 : }
8785 :
8786 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
8787 6 : poDstSRS(poDS->GetSpatialRef(nDestSRID, true));
8788 6 : if (poDstSRS == nullptr)
8789 : {
8790 0 : CPLError(CE_Failure, CPLE_AppDefined, "Target SRID (%d) is invalid",
8791 : nDestSRID);
8792 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8793 0 : return;
8794 : }
8795 : poCT =
8796 6 : OGRCreateCoordinateTransformation(poSrcSRS.get(), poDstSRS.get());
8797 6 : if (poCT == nullptr)
8798 : {
8799 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8800 0 : return;
8801 : }
8802 :
8803 : // Cache coordinate transformation for potential later reuse
8804 6 : poDS->m_nLastCachedCTSrcSRId = sHeader.iSrsId;
8805 6 : poDS->m_nLastCachedCTDstSRId = nDestSRID;
8806 6 : poDS->m_poLastCachedCT.reset(poCT);
8807 6 : poCT = poDS->m_poLastCachedCT.get();
8808 : }
8809 :
8810 26 : if (sHeader.nHeaderLen >= 8)
8811 : {
8812 26 : std::vector<GByte> &abyNewBLOB = poDS->m_abyWKBTransformCache;
8813 26 : abyNewBLOB.resize(nBLOBLen);
8814 26 : memcpy(abyNewBLOB.data(), pabyBLOB, nBLOBLen);
8815 :
8816 26 : OGREnvelope3D oEnv3d;
8817 26 : if (!OGRWKBTransform(abyNewBLOB.data() + sHeader.nHeaderLen,
8818 26 : nBLOBLen - sHeader.nHeaderLen, poCT,
8819 78 : poDS->m_oWKBTransformCache, oEnv3d) ||
8820 26 : !GPkgUpdateHeader(abyNewBLOB.data(), nBLOBLen, nDestSRID,
8821 : oEnv3d.MinX, oEnv3d.MaxX, oEnv3d.MinY,
8822 : oEnv3d.MaxY, oEnv3d.MinZ, oEnv3d.MaxZ))
8823 : {
8824 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8825 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8826 0 : return;
8827 : }
8828 :
8829 26 : sqlite3_result_blob(pContext, abyNewBLOB.data(), nBLOBLen,
8830 : SQLITE_TRANSIENT);
8831 26 : return;
8832 : }
8833 :
8834 : // Try also spatialite geometry blobs
8835 0 : OGRGeometry *poGeomSpatialite = nullptr;
8836 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8837 0 : &poGeomSpatialite) != OGRERR_NONE)
8838 : {
8839 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8840 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8841 0 : return;
8842 : }
8843 0 : auto poGeom = std::unique_ptr<OGRGeometry>(poGeomSpatialite);
8844 :
8845 0 : if (poGeom->transform(poCT) != OGRERR_NONE)
8846 : {
8847 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8848 0 : return;
8849 : }
8850 :
8851 0 : size_t nBLOBDestLen = 0;
8852 : GByte *pabyDestBLOB =
8853 0 : GPkgGeometryFromOGR(poGeom.get(), nDestSRID, nullptr, &nBLOBDestLen);
8854 0 : if (!pabyDestBLOB)
8855 : {
8856 0 : sqlite3_result_null(pContext);
8857 0 : return;
8858 : }
8859 0 : sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
8860 : VSIFree);
8861 : }
8862 :
8863 : /************************************************************************/
8864 : /* OGRGeoPackageSridFromAuthCRS() */
8865 : /************************************************************************/
8866 :
8867 4 : static void OGRGeoPackageSridFromAuthCRS(sqlite3_context *pContext,
8868 : int /*argc*/, sqlite3_value **argv)
8869 : {
8870 7 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8871 3 : sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
8872 : {
8873 2 : sqlite3_result_int(pContext, -1);
8874 2 : return;
8875 : }
8876 :
8877 : GDALGeoPackageDataset *poDS =
8878 2 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8879 :
8880 2 : char *pszSQL = sqlite3_mprintf(
8881 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
8882 : "lower(organization) = lower('%q') AND organization_coordsys_id = %d",
8883 2 : sqlite3_value_text(argv[0]), sqlite3_value_int(argv[1]));
8884 2 : OGRErr err = OGRERR_NONE;
8885 2 : int nSRSId = SQLGetInteger(poDS->GetDB(), pszSQL, &err);
8886 2 : sqlite3_free(pszSQL);
8887 2 : if (err != OGRERR_NONE)
8888 1 : nSRSId = -1;
8889 2 : sqlite3_result_int(pContext, nSRSId);
8890 : }
8891 :
8892 : /************************************************************************/
8893 : /* OGRGeoPackageImportFromEPSG() */
8894 : /************************************************************************/
8895 :
8896 4 : static void OGRGeoPackageImportFromEPSG(sqlite3_context *pContext, int /*argc*/,
8897 : sqlite3_value **argv)
8898 : {
8899 4 : if (sqlite3_value_type(argv[0]) != SQLITE_INTEGER)
8900 : {
8901 1 : sqlite3_result_int(pContext, -1);
8902 2 : return;
8903 : }
8904 :
8905 : GDALGeoPackageDataset *poDS =
8906 3 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8907 3 : OGRSpatialReference oSRS;
8908 3 : if (oSRS.importFromEPSG(sqlite3_value_int(argv[0])) != OGRERR_NONE)
8909 : {
8910 1 : sqlite3_result_int(pContext, -1);
8911 1 : return;
8912 : }
8913 :
8914 2 : sqlite3_result_int(pContext, poDS->GetSrsId(&oSRS));
8915 : }
8916 :
8917 : /************************************************************************/
8918 : /* OGRGeoPackageRegisterGeometryExtension() */
8919 : /************************************************************************/
8920 :
8921 1 : static void OGRGeoPackageRegisterGeometryExtension(sqlite3_context *pContext,
8922 : int /*argc*/,
8923 : sqlite3_value **argv)
8924 : {
8925 1 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8926 2 : sqlite3_value_type(argv[1]) != SQLITE_TEXT ||
8927 1 : sqlite3_value_type(argv[2]) != SQLITE_TEXT)
8928 : {
8929 0 : sqlite3_result_int(pContext, 0);
8930 0 : return;
8931 : }
8932 :
8933 : const char *pszTableName =
8934 1 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8935 : const char *pszGeomName =
8936 1 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8937 : const char *pszGeomType =
8938 1 : reinterpret_cast<const char *>(sqlite3_value_text(argv[2]));
8939 :
8940 : GDALGeoPackageDataset *poDS =
8941 1 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8942 :
8943 1 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
8944 1 : poDS->GetLayerByName(pszTableName));
8945 1 : if (poLyr == nullptr)
8946 : {
8947 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
8948 0 : sqlite3_result_int(pContext, 0);
8949 0 : return;
8950 : }
8951 1 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
8952 : {
8953 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
8954 0 : sqlite3_result_int(pContext, 0);
8955 0 : return;
8956 : }
8957 1 : const OGRwkbGeometryType eGeomType = OGRFromOGCGeomType(pszGeomType);
8958 1 : if (eGeomType == wkbUnknown)
8959 : {
8960 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry type name");
8961 0 : sqlite3_result_int(pContext, 0);
8962 0 : return;
8963 : }
8964 :
8965 1 : sqlite3_result_int(
8966 : pContext,
8967 1 : static_cast<int>(poLyr->CreateGeometryExtensionIfNecessary(eGeomType)));
8968 : }
8969 :
8970 : /************************************************************************/
8971 : /* OGRGeoPackageCreateSpatialIndex() */
8972 : /************************************************************************/
8973 :
8974 14 : static void OGRGeoPackageCreateSpatialIndex(sqlite3_context *pContext,
8975 : int /*argc*/, sqlite3_value **argv)
8976 : {
8977 27 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8978 13 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
8979 : {
8980 2 : sqlite3_result_int(pContext, 0);
8981 2 : return;
8982 : }
8983 :
8984 : const char *pszTableName =
8985 12 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8986 : const char *pszGeomName =
8987 12 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8988 : GDALGeoPackageDataset *poDS =
8989 12 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8990 :
8991 12 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
8992 12 : poDS->GetLayerByName(pszTableName));
8993 12 : if (poLyr == nullptr)
8994 : {
8995 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
8996 1 : sqlite3_result_int(pContext, 0);
8997 1 : return;
8998 : }
8999 11 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
9000 : {
9001 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
9002 1 : sqlite3_result_int(pContext, 0);
9003 1 : return;
9004 : }
9005 :
9006 10 : sqlite3_result_int(pContext, poLyr->CreateSpatialIndex());
9007 : }
9008 :
9009 : /************************************************************************/
9010 : /* OGRGeoPackageDisableSpatialIndex() */
9011 : /************************************************************************/
9012 :
9013 12 : static void OGRGeoPackageDisableSpatialIndex(sqlite3_context *pContext,
9014 : int /*argc*/, sqlite3_value **argv)
9015 : {
9016 23 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9017 11 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9018 : {
9019 2 : sqlite3_result_int(pContext, 0);
9020 2 : return;
9021 : }
9022 :
9023 : const char *pszTableName =
9024 10 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9025 : const char *pszGeomName =
9026 10 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9027 : GDALGeoPackageDataset *poDS =
9028 10 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9029 :
9030 10 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
9031 10 : poDS->GetLayerByName(pszTableName));
9032 10 : if (poLyr == nullptr)
9033 : {
9034 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
9035 1 : sqlite3_result_int(pContext, 0);
9036 1 : return;
9037 : }
9038 9 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
9039 : {
9040 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
9041 1 : sqlite3_result_int(pContext, 0);
9042 1 : return;
9043 : }
9044 :
9045 8 : sqlite3_result_int(pContext, poLyr->DropSpatialIndex(true));
9046 : }
9047 :
9048 : /************************************************************************/
9049 : /* OGRGeoPackageHasSpatialIndex() */
9050 : /************************************************************************/
9051 :
9052 29 : static void OGRGeoPackageHasSpatialIndex(sqlite3_context *pContext,
9053 : int /*argc*/, sqlite3_value **argv)
9054 : {
9055 57 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9056 28 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9057 : {
9058 2 : sqlite3_result_int(pContext, 0);
9059 2 : return;
9060 : }
9061 :
9062 : const char *pszTableName =
9063 27 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9064 : const char *pszGeomName =
9065 27 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9066 : GDALGeoPackageDataset *poDS =
9067 27 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9068 :
9069 27 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
9070 27 : poDS->GetLayerByName(pszTableName));
9071 27 : if (poLyr == nullptr)
9072 : {
9073 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
9074 1 : sqlite3_result_int(pContext, 0);
9075 1 : return;
9076 : }
9077 26 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
9078 : {
9079 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
9080 1 : sqlite3_result_int(pContext, 0);
9081 1 : return;
9082 : }
9083 :
9084 25 : poLyr->RunDeferredCreationIfNecessary();
9085 25 : poLyr->CreateSpatialIndexIfNecessary();
9086 :
9087 25 : sqlite3_result_int(pContext, poLyr->HasSpatialIndex());
9088 : }
9089 :
9090 : /************************************************************************/
9091 : /* GPKG_hstore_get_value() */
9092 : /************************************************************************/
9093 :
9094 4 : static void GPKG_hstore_get_value(sqlite3_context *pContext, int /*argc*/,
9095 : sqlite3_value **argv)
9096 : {
9097 7 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9098 3 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9099 : {
9100 2 : sqlite3_result_null(pContext);
9101 2 : return;
9102 : }
9103 :
9104 : const char *pszHStore =
9105 2 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9106 : const char *pszSearchedKey =
9107 2 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9108 2 : char *pszValue = OGRHStoreGetValue(pszHStore, pszSearchedKey);
9109 2 : if (pszValue != nullptr)
9110 1 : sqlite3_result_text(pContext, pszValue, -1, CPLFree);
9111 : else
9112 1 : sqlite3_result_null(pContext);
9113 : }
9114 :
9115 : /************************************************************************/
9116 : /* GPKG_GDAL_GetMemFileFromBlob() */
9117 : /************************************************************************/
9118 :
9119 105 : static CPLString GPKG_GDAL_GetMemFileFromBlob(sqlite3_value **argv)
9120 : {
9121 105 : int nBytes = sqlite3_value_bytes(argv[0]);
9122 : const GByte *pabyBLOB =
9123 105 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
9124 : CPLString osMemFileName(
9125 105 : VSIMemGenerateHiddenFilename("GPKG_GDAL_GetMemFileFromBlob"));
9126 105 : VSILFILE *fp = VSIFileFromMemBuffer(
9127 : osMemFileName.c_str(), const_cast<GByte *>(pabyBLOB), nBytes, FALSE);
9128 105 : VSIFCloseL(fp);
9129 105 : return osMemFileName;
9130 : }
9131 :
9132 : /************************************************************************/
9133 : /* GPKG_GDAL_GetMimeType() */
9134 : /************************************************************************/
9135 :
9136 35 : static void GPKG_GDAL_GetMimeType(sqlite3_context *pContext, int /*argc*/,
9137 : sqlite3_value **argv)
9138 : {
9139 35 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
9140 : {
9141 0 : sqlite3_result_null(pContext);
9142 0 : return;
9143 : }
9144 :
9145 70 : CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
9146 : GDALDriver *poDriver =
9147 35 : GDALDriver::FromHandle(GDALIdentifyDriver(osMemFileName, nullptr));
9148 35 : if (poDriver != nullptr)
9149 : {
9150 35 : const char *pszRes = nullptr;
9151 35 : if (EQUAL(poDriver->GetDescription(), "PNG"))
9152 23 : pszRes = "image/png";
9153 12 : else if (EQUAL(poDriver->GetDescription(), "JPEG"))
9154 6 : pszRes = "image/jpeg";
9155 6 : else if (EQUAL(poDriver->GetDescription(), "WEBP"))
9156 6 : pszRes = "image/x-webp";
9157 0 : else if (EQUAL(poDriver->GetDescription(), "GTIFF"))
9158 0 : pszRes = "image/tiff";
9159 : else
9160 0 : pszRes = CPLSPrintf("gdal/%s", poDriver->GetDescription());
9161 35 : sqlite3_result_text(pContext, pszRes, -1, SQLITE_TRANSIENT);
9162 : }
9163 : else
9164 0 : sqlite3_result_null(pContext);
9165 35 : VSIUnlink(osMemFileName);
9166 : }
9167 :
9168 : /************************************************************************/
9169 : /* GPKG_GDAL_GetBandCount() */
9170 : /************************************************************************/
9171 :
9172 35 : static void GPKG_GDAL_GetBandCount(sqlite3_context *pContext, int /*argc*/,
9173 : sqlite3_value **argv)
9174 : {
9175 35 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
9176 : {
9177 0 : sqlite3_result_null(pContext);
9178 0 : return;
9179 : }
9180 :
9181 70 : CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
9182 : auto poDS = std::unique_ptr<GDALDataset>(
9183 : GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
9184 70 : nullptr, nullptr, nullptr));
9185 35 : if (poDS != nullptr)
9186 : {
9187 35 : sqlite3_result_int(pContext, poDS->GetRasterCount());
9188 : }
9189 : else
9190 0 : sqlite3_result_null(pContext);
9191 35 : VSIUnlink(osMemFileName);
9192 : }
9193 :
9194 : /************************************************************************/
9195 : /* GPKG_GDAL_HasColorTable() */
9196 : /************************************************************************/
9197 :
9198 35 : static void GPKG_GDAL_HasColorTable(sqlite3_context *pContext, int /*argc*/,
9199 : sqlite3_value **argv)
9200 : {
9201 35 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
9202 : {
9203 0 : sqlite3_result_null(pContext);
9204 0 : return;
9205 : }
9206 :
9207 70 : CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
9208 : auto poDS = std::unique_ptr<GDALDataset>(
9209 : GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
9210 70 : nullptr, nullptr, nullptr));
9211 35 : if (poDS != nullptr)
9212 : {
9213 35 : sqlite3_result_int(
9214 46 : pContext, poDS->GetRasterCount() == 1 &&
9215 11 : poDS->GetRasterBand(1)->GetColorTable() != nullptr);
9216 : }
9217 : else
9218 0 : sqlite3_result_null(pContext);
9219 35 : VSIUnlink(osMemFileName);
9220 : }
9221 :
9222 : /************************************************************************/
9223 : /* GetRasterLayerDataset() */
9224 : /************************************************************************/
9225 :
9226 : GDALDataset *
9227 12 : GDALGeoPackageDataset::GetRasterLayerDataset(const char *pszLayerName)
9228 : {
9229 12 : const auto oIter = m_oCachedRasterDS.find(pszLayerName);
9230 12 : if (oIter != m_oCachedRasterDS.end())
9231 10 : return oIter->second.get();
9232 :
9233 : auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
9234 4 : (std::string("GPKG:\"") + m_pszFilename + "\":" + pszLayerName).c_str(),
9235 4 : GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
9236 2 : if (!poDS)
9237 : {
9238 0 : return nullptr;
9239 : }
9240 2 : m_oCachedRasterDS[pszLayerName] = std::move(poDS);
9241 2 : return m_oCachedRasterDS[pszLayerName].get();
9242 : }
9243 :
9244 : /************************************************************************/
9245 : /* GPKG_gdal_get_layer_pixel_value() */
9246 : /************************************************************************/
9247 :
9248 : // NOTE: keep in sync implementations in ogrsqlitesqlfunctionscommon.cpp
9249 : // and ogrgeopackagedatasource.cpp
9250 13 : static void GPKG_gdal_get_layer_pixel_value(sqlite3_context *pContext, int argc,
9251 : sqlite3_value **argv)
9252 : {
9253 13 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
9254 : {
9255 1 : CPLError(CE_Failure, CPLE_AppDefined,
9256 : "Invalid arguments to gdal_get_layer_pixel_value()");
9257 1 : sqlite3_result_null(pContext);
9258 1 : return;
9259 : }
9260 :
9261 : const char *pszLayerName =
9262 12 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9263 :
9264 : GDALGeoPackageDataset *poGlobalDS =
9265 12 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9266 12 : auto poDS = poGlobalDS->GetRasterLayerDataset(pszLayerName);
9267 12 : if (!poDS)
9268 : {
9269 0 : sqlite3_result_null(pContext);
9270 0 : return;
9271 : }
9272 :
9273 12 : OGRSQLite_gdal_get_pixel_value_common("gdal_get_layer_pixel_value",
9274 : pContext, argc, argv, poDS);
9275 : }
9276 :
9277 : /************************************************************************/
9278 : /* GPKG_ogr_layer_Extent() */
9279 : /************************************************************************/
9280 :
9281 3 : static void GPKG_ogr_layer_Extent(sqlite3_context *pContext, int /*argc*/,
9282 : sqlite3_value **argv)
9283 : {
9284 3 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
9285 : {
9286 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s: Invalid argument type",
9287 : "ogr_layer_Extent");
9288 1 : sqlite3_result_null(pContext);
9289 2 : return;
9290 : }
9291 :
9292 : const char *pszLayerName =
9293 2 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9294 : GDALGeoPackageDataset *poDS =
9295 2 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9296 2 : OGRLayer *poLayer = poDS->GetLayerByName(pszLayerName);
9297 2 : if (!poLayer)
9298 : {
9299 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s: unknown layer",
9300 : "ogr_layer_Extent");
9301 1 : sqlite3_result_null(pContext);
9302 1 : return;
9303 : }
9304 :
9305 1 : if (poLayer->GetGeomType() == wkbNone)
9306 : {
9307 0 : sqlite3_result_null(pContext);
9308 0 : return;
9309 : }
9310 :
9311 1 : OGREnvelope sExtent;
9312 1 : if (poLayer->GetExtent(&sExtent) != OGRERR_NONE)
9313 : {
9314 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s: Cannot fetch layer extent",
9315 : "ogr_layer_Extent");
9316 0 : sqlite3_result_null(pContext);
9317 0 : return;
9318 : }
9319 :
9320 1 : OGRPolygon oPoly;
9321 1 : auto poRing = std::make_unique<OGRLinearRing>();
9322 1 : poRing->addPoint(sExtent.MinX, sExtent.MinY);
9323 1 : poRing->addPoint(sExtent.MaxX, sExtent.MinY);
9324 1 : poRing->addPoint(sExtent.MaxX, sExtent.MaxY);
9325 1 : poRing->addPoint(sExtent.MinX, sExtent.MaxY);
9326 1 : poRing->addPoint(sExtent.MinX, sExtent.MinY);
9327 1 : oPoly.addRing(std::move(poRing));
9328 :
9329 1 : const auto poSRS = poLayer->GetSpatialRef();
9330 1 : const int nSRID = poDS->GetSrsId(poSRS);
9331 1 : size_t nBLOBDestLen = 0;
9332 : GByte *pabyDestBLOB =
9333 1 : GPkgGeometryFromOGR(&oPoly, nSRID, nullptr, &nBLOBDestLen);
9334 1 : if (!pabyDestBLOB)
9335 : {
9336 0 : sqlite3_result_null(pContext);
9337 0 : return;
9338 : }
9339 1 : sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
9340 : VSIFree);
9341 : }
9342 :
9343 : /************************************************************************/
9344 : /* InstallSQLFunctions() */
9345 : /************************************************************************/
9346 :
9347 : #ifndef SQLITE_DETERMINISTIC
9348 : #define SQLITE_DETERMINISTIC 0
9349 : #endif
9350 :
9351 : #ifndef SQLITE_INNOCUOUS
9352 : #define SQLITE_INNOCUOUS 0
9353 : #endif
9354 :
9355 : #ifndef UTF8_INNOCUOUS
9356 : #define UTF8_INNOCUOUS (SQLITE_UTF8 | SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS)
9357 : #endif
9358 :
9359 2244 : void GDALGeoPackageDataset::InstallSQLFunctions()
9360 : {
9361 2244 : InitSpatialite();
9362 :
9363 : // Enable SpatiaLite 4.3 GPKG mode, i.e. that SpatiaLite functions
9364 : // that take geometries will accept and return GPKG encoded geometries without
9365 : // explicit conversion.
9366 : // Use sqlite3_exec() instead of SQLCommand() since we don't want verbose
9367 : // error.
9368 2244 : sqlite3_exec(hDB, "SELECT EnableGpkgMode()", nullptr, nullptr, nullptr);
9369 :
9370 : /* Used by RTree Spatial Index Extension */
9371 2244 : sqlite3_create_function(hDB, "ST_MinX", 1, UTF8_INNOCUOUS, nullptr,
9372 : OGRGeoPackageSTMinX, nullptr, nullptr);
9373 2244 : sqlite3_create_function(hDB, "ST_MinY", 1, UTF8_INNOCUOUS, nullptr,
9374 : OGRGeoPackageSTMinY, nullptr, nullptr);
9375 2244 : sqlite3_create_function(hDB, "ST_MaxX", 1, UTF8_INNOCUOUS, nullptr,
9376 : OGRGeoPackageSTMaxX, nullptr, nullptr);
9377 2244 : sqlite3_create_function(hDB, "ST_MaxY", 1, UTF8_INNOCUOUS, nullptr,
9378 : OGRGeoPackageSTMaxY, nullptr, nullptr);
9379 2244 : sqlite3_create_function(hDB, "ST_IsEmpty", 1, UTF8_INNOCUOUS, nullptr,
9380 : OGRGeoPackageSTIsEmpty, nullptr, nullptr);
9381 :
9382 : /* Used by Geometry Type Triggers Extension */
9383 2244 : sqlite3_create_function(hDB, "ST_GeometryType", 1, UTF8_INNOCUOUS, nullptr,
9384 : OGRGeoPackageSTGeometryType, nullptr, nullptr);
9385 2244 : sqlite3_create_function(hDB, "GPKG_IsAssignable", 2, UTF8_INNOCUOUS,
9386 : nullptr, OGRGeoPackageGPKGIsAssignable, nullptr,
9387 : nullptr);
9388 :
9389 : /* Used by Geometry SRS ID Triggers Extension */
9390 2244 : sqlite3_create_function(hDB, "ST_SRID", 1, UTF8_INNOCUOUS, nullptr,
9391 : OGRGeoPackageSTSRID, nullptr, nullptr);
9392 :
9393 : /* Spatialite-like functions */
9394 2244 : sqlite3_create_function(hDB, "CreateSpatialIndex", 2, SQLITE_UTF8, this,
9395 : OGRGeoPackageCreateSpatialIndex, nullptr, nullptr);
9396 2244 : sqlite3_create_function(hDB, "DisableSpatialIndex", 2, SQLITE_UTF8, this,
9397 : OGRGeoPackageDisableSpatialIndex, nullptr, nullptr);
9398 2244 : sqlite3_create_function(hDB, "HasSpatialIndex", 2, SQLITE_UTF8, this,
9399 : OGRGeoPackageHasSpatialIndex, nullptr, nullptr);
9400 :
9401 : // HSTORE functions
9402 2244 : sqlite3_create_function(hDB, "hstore_get_value", 2, UTF8_INNOCUOUS, nullptr,
9403 : GPKG_hstore_get_value, nullptr, nullptr);
9404 :
9405 : // Override a few Spatialite functions to work with gpkg_spatial_ref_sys
9406 2244 : sqlite3_create_function(hDB, "ST_Transform", 2, UTF8_INNOCUOUS, this,
9407 : OGRGeoPackageTransform, nullptr, nullptr);
9408 2244 : sqlite3_create_function(hDB, "Transform", 2, UTF8_INNOCUOUS, this,
9409 : OGRGeoPackageTransform, nullptr, nullptr);
9410 2244 : sqlite3_create_function(hDB, "SridFromAuthCRS", 2, SQLITE_UTF8, this,
9411 : OGRGeoPackageSridFromAuthCRS, nullptr, nullptr);
9412 :
9413 2244 : sqlite3_create_function(hDB, "ST_EnvIntersects", 2, UTF8_INNOCUOUS, nullptr,
9414 : OGRGeoPackageSTEnvelopesIntersectsTwoParams,
9415 : nullptr, nullptr);
9416 2244 : sqlite3_create_function(
9417 : hDB, "ST_EnvelopesIntersects", 2, UTF8_INNOCUOUS, nullptr,
9418 : OGRGeoPackageSTEnvelopesIntersectsTwoParams, nullptr, nullptr);
9419 :
9420 2244 : sqlite3_create_function(hDB, "ST_EnvIntersects", 5, UTF8_INNOCUOUS, nullptr,
9421 : OGRGeoPackageSTEnvelopesIntersects, nullptr,
9422 : nullptr);
9423 2244 : sqlite3_create_function(hDB, "ST_EnvelopesIntersects", 5, UTF8_INNOCUOUS,
9424 : nullptr, OGRGeoPackageSTEnvelopesIntersects,
9425 : nullptr, nullptr);
9426 :
9427 : // Implementation that directly hacks the GeoPackage geometry blob header
9428 2244 : sqlite3_create_function(hDB, "SetSRID", 2, UTF8_INNOCUOUS, nullptr,
9429 : OGRGeoPackageSetSRID, nullptr, nullptr);
9430 :
9431 : // GDAL specific function
9432 2244 : sqlite3_create_function(hDB, "ImportFromEPSG", 1, SQLITE_UTF8, this,
9433 : OGRGeoPackageImportFromEPSG, nullptr, nullptr);
9434 :
9435 : // May be used by ogrmerge.py
9436 2244 : sqlite3_create_function(hDB, "RegisterGeometryExtension", 3, SQLITE_UTF8,
9437 : this, OGRGeoPackageRegisterGeometryExtension,
9438 : nullptr, nullptr);
9439 :
9440 2244 : if (OGRGeometryFactory::haveGEOS())
9441 : {
9442 2244 : sqlite3_create_function(hDB, "ST_MakeValid", 1, UTF8_INNOCUOUS, nullptr,
9443 : OGRGeoPackageSTMakeValid, nullptr, nullptr);
9444 : }
9445 :
9446 2244 : sqlite3_create_function(hDB, "ST_Length", 1, UTF8_INNOCUOUS, nullptr,
9447 : OGRGeoPackageLengthOrGeodesicLength, nullptr,
9448 : nullptr);
9449 2244 : sqlite3_create_function(hDB, "ST_Length", 2, UTF8_INNOCUOUS, this,
9450 : OGRGeoPackageLengthOrGeodesicLength, nullptr,
9451 : nullptr);
9452 :
9453 2244 : sqlite3_create_function(hDB, "ST_Area", 1, UTF8_INNOCUOUS, nullptr,
9454 : OGRGeoPackageSTArea, nullptr, nullptr);
9455 2244 : sqlite3_create_function(hDB, "ST_Area", 2, UTF8_INNOCUOUS, this,
9456 : OGRGeoPackageGeodesicArea, nullptr, nullptr);
9457 :
9458 : // Debug functions
9459 2244 : if (CPLTestBool(CPLGetConfigOption("GPKG_DEBUG", "FALSE")))
9460 : {
9461 422 : sqlite3_create_function(hDB, "GDAL_GetMimeType", 1,
9462 : SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9463 : GPKG_GDAL_GetMimeType, nullptr, nullptr);
9464 422 : sqlite3_create_function(hDB, "GDAL_GetBandCount", 1,
9465 : SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9466 : GPKG_GDAL_GetBandCount, nullptr, nullptr);
9467 422 : sqlite3_create_function(hDB, "GDAL_HasColorTable", 1,
9468 : SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9469 : GPKG_GDAL_HasColorTable, nullptr, nullptr);
9470 : }
9471 :
9472 2244 : sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 5, SQLITE_UTF8,
9473 : this, GPKG_gdal_get_layer_pixel_value, nullptr,
9474 : nullptr);
9475 2244 : sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 6, SQLITE_UTF8,
9476 : this, GPKG_gdal_get_layer_pixel_value, nullptr,
9477 : nullptr);
9478 :
9479 : // Function from VirtualOGR
9480 2244 : sqlite3_create_function(hDB, "ogr_layer_Extent", 1, SQLITE_UTF8, this,
9481 : GPKG_ogr_layer_Extent, nullptr, nullptr);
9482 :
9483 2244 : m_pSQLFunctionData = OGRSQLiteRegisterSQLFunctionsCommon(hDB);
9484 2244 : }
9485 :
9486 : /************************************************************************/
9487 : /* OpenOrCreateDB() */
9488 : /************************************************************************/
9489 :
9490 2248 : bool GDALGeoPackageDataset::OpenOrCreateDB(int flags)
9491 : {
9492 2248 : const bool bSuccess = OGRSQLiteBaseDataSource::OpenOrCreateDB(
9493 : flags, /*bRegisterOGR2SQLiteExtensions=*/false,
9494 : /*bLoadExtensions=*/true);
9495 2248 : if (!bSuccess)
9496 9 : return false;
9497 :
9498 : // Turning on recursive_triggers is needed so that DELETE triggers fire
9499 : // in a INSERT OR REPLACE statement. In particular this is needed to
9500 : // make sure gpkg_ogr_contents.feature_count is properly updated.
9501 2239 : SQLCommand(hDB, "PRAGMA recursive_triggers = 1");
9502 :
9503 2239 : InstallSQLFunctions();
9504 :
9505 : const char *pszSqlitePragma =
9506 2239 : CPLGetConfigOption("OGR_SQLITE_PRAGMA", nullptr);
9507 2239 : OGRErr eErr = OGRERR_NONE;
9508 6 : if ((!pszSqlitePragma || !strstr(pszSqlitePragma, "trusted_schema")) &&
9509 : // Older sqlite versions don't have this pragma
9510 4484 : SQLGetInteger(hDB, "PRAGMA trusted_schema", &eErr) == 0 &&
9511 2239 : eErr == OGRERR_NONE)
9512 : {
9513 2239 : bool bNeedsTrustedSchema = false;
9514 :
9515 : // Current SQLite versions require PRAGMA trusted_schema = 1 to be
9516 : // able to use the RTree from triggers, which is only needed when
9517 : // modifying the RTree.
9518 5498 : if (((flags & SQLITE_OPEN_READWRITE) != 0 ||
9519 3458 : (flags & SQLITE_OPEN_CREATE) != 0) &&
9520 1219 : OGRSQLiteRTreeRequiresTrustedSchemaOn())
9521 : {
9522 1219 : bNeedsTrustedSchema = true;
9523 : }
9524 :
9525 : #ifdef HAVE_SPATIALITE
9526 : // Spatialite <= 5.1.0 doesn't declare its functions as SQLITE_INNOCUOUS
9527 1020 : if (!bNeedsTrustedSchema && HasExtensionsTable() &&
9528 929 : SQLGetInteger(
9529 : hDB,
9530 : "SELECT 1 FROM gpkg_extensions WHERE "
9531 : "extension_name ='gdal_spatialite_computed_geom_column'",
9532 1 : nullptr) == 1 &&
9533 3259 : SpatialiteRequiresTrustedSchemaOn() && AreSpatialiteTriggersSafe())
9534 : {
9535 1 : bNeedsTrustedSchema = true;
9536 : }
9537 : #endif
9538 :
9539 2239 : if (bNeedsTrustedSchema)
9540 : {
9541 1220 : CPLDebug("GPKG", "Setting PRAGMA trusted_schema = 1");
9542 1220 : SQLCommand(hDB, "PRAGMA trusted_schema = 1");
9543 : }
9544 : }
9545 :
9546 : const char *pszPreludeStatements =
9547 2239 : CSLFetchNameValue(papszOpenOptions, "PRELUDE_STATEMENTS");
9548 2239 : if (pszPreludeStatements)
9549 : {
9550 2 : if (SQLCommand(hDB, pszPreludeStatements) != OGRERR_NONE)
9551 0 : return false;
9552 : }
9553 :
9554 2239 : return true;
9555 : }
9556 :
9557 : /************************************************************************/
9558 : /* GetLayerWithGetSpatialWhereByName() */
9559 : /************************************************************************/
9560 :
9561 : std::pair<OGRLayer *, IOGRSQLiteGetSpatialWhere *>
9562 90 : GDALGeoPackageDataset::GetLayerWithGetSpatialWhereByName(const char *pszName)
9563 : {
9564 : OGRGeoPackageLayer *poRet =
9565 90 : cpl::down_cast<OGRGeoPackageLayer *>(GetLayerByName(pszName));
9566 90 : return std::pair(poRet, poRet);
9567 : }
9568 :
9569 : /************************************************************************/
9570 : /* CommitTransaction() */
9571 : /************************************************************************/
9572 :
9573 331 : OGRErr GDALGeoPackageDataset::CommitTransaction()
9574 :
9575 : {
9576 331 : if (m_nSoftTransactionLevel == 1)
9577 : {
9578 325 : FlushMetadata();
9579 701 : for (auto &poLayer : m_apoLayers)
9580 : {
9581 376 : poLayer->DoJobAtTransactionCommit();
9582 : }
9583 : }
9584 :
9585 331 : return OGRSQLiteBaseDataSource::CommitTransaction();
9586 : }
9587 :
9588 : /************************************************************************/
9589 : /* RollbackTransaction() */
9590 : /************************************************************************/
9591 :
9592 35 : OGRErr GDALGeoPackageDataset::RollbackTransaction()
9593 :
9594 : {
9595 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9596 70 : std::vector<bool> abAddTriggers;
9597 35 : std::vector<bool> abTriggersDeletedInTransaction;
9598 : #endif
9599 35 : if (m_nSoftTransactionLevel == 1)
9600 : {
9601 34 : FlushMetadata();
9602 70 : for (auto &poLayer : m_apoLayers)
9603 : {
9604 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9605 36 : abAddTriggers.push_back(poLayer->GetAddOGRFeatureCountTriggers());
9606 36 : abTriggersDeletedInTransaction.push_back(
9607 36 : poLayer->GetOGRFeatureCountTriggersDeletedInTransaction());
9608 36 : poLayer->SetAddOGRFeatureCountTriggers(false);
9609 : #endif
9610 36 : poLayer->DoJobAtTransactionRollback();
9611 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9612 36 : poLayer->DisableFeatureCount();
9613 : #endif
9614 : }
9615 : }
9616 :
9617 35 : const OGRErr eErr = OGRSQLiteBaseDataSource::RollbackTransaction();
9618 :
9619 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9620 35 : if (!abAddTriggers.empty())
9621 : {
9622 68 : for (size_t i = 0; i < m_apoLayers.size(); ++i)
9623 : {
9624 36 : auto &poLayer = m_apoLayers[i];
9625 36 : if (abTriggersDeletedInTransaction[i])
9626 : {
9627 7 : poLayer->SetOGRFeatureCountTriggersEnabled(true);
9628 : }
9629 : else
9630 : {
9631 29 : poLayer->SetAddOGRFeatureCountTriggers(abAddTriggers[i]);
9632 : }
9633 : }
9634 : }
9635 : #endif
9636 70 : return eErr;
9637 : }
9638 :
9639 : /************************************************************************/
9640 : /* GetGeometryTypeString() */
9641 : /************************************************************************/
9642 :
9643 : const char *
9644 1589 : GDALGeoPackageDataset::GetGeometryTypeString(OGRwkbGeometryType eType)
9645 : {
9646 1589 : const char *pszGPKGGeomType = OGRToOGCGeomType(eType);
9647 1601 : if (EQUAL(pszGPKGGeomType, "GEOMETRYCOLLECTION") &&
9648 12 : CPLTestBool(CPLGetConfigOption("OGR_GPKG_GEOMCOLLECTION", "NO")))
9649 : {
9650 0 : pszGPKGGeomType = "GEOMCOLLECTION";
9651 : }
9652 1589 : return pszGPKGGeomType;
9653 : }
9654 :
9655 : /************************************************************************/
9656 : /* GetFieldDomainNames() */
9657 : /************************************************************************/
9658 :
9659 : std::vector<std::string>
9660 13 : GDALGeoPackageDataset::GetFieldDomainNames(CSLConstList) const
9661 : {
9662 13 : if (!HasDataColumnConstraintsTable())
9663 4 : return std::vector<std::string>();
9664 :
9665 18 : std::vector<std::string> oDomainNamesList;
9666 :
9667 9 : std::unique_ptr<SQLResult> oResultTable;
9668 : {
9669 : std::string osSQL =
9670 : "SELECT DISTINCT constraint_name "
9671 : "FROM gpkg_data_column_constraints "
9672 : "WHERE constraint_name NOT LIKE '_%_domain_description' "
9673 : "ORDER BY constraint_name "
9674 9 : "LIMIT 10000" // to avoid denial of service
9675 : ;
9676 9 : oResultTable = SQLQuery(hDB, osSQL.c_str());
9677 9 : if (!oResultTable)
9678 0 : return oDomainNamesList;
9679 : }
9680 :
9681 9 : if (oResultTable->RowCount() == 10000)
9682 : {
9683 0 : CPLError(CE_Warning, CPLE_AppDefined,
9684 : "Number of rows returned for field domain names has been "
9685 : "truncated.");
9686 : }
9687 9 : else if (oResultTable->RowCount() > 0)
9688 : {
9689 8 : oDomainNamesList.reserve(oResultTable->RowCount());
9690 105 : for (int i = 0; i < oResultTable->RowCount(); i++)
9691 : {
9692 97 : const char *pszConstraintName = oResultTable->GetValue(0, i);
9693 97 : if (!pszConstraintName)
9694 0 : continue;
9695 :
9696 97 : oDomainNamesList.emplace_back(pszConstraintName);
9697 : }
9698 : }
9699 :
9700 9 : return oDomainNamesList;
9701 : }
9702 :
9703 : /************************************************************************/
9704 : /* GetFieldDomain() */
9705 : /************************************************************************/
9706 :
9707 : const OGRFieldDomain *
9708 138 : GDALGeoPackageDataset::GetFieldDomain(const std::string &name) const
9709 : {
9710 138 : const auto baseRet = GDALDataset::GetFieldDomain(name);
9711 138 : if (baseRet)
9712 43 : return baseRet;
9713 :
9714 95 : if (!HasDataColumnConstraintsTable())
9715 4 : return nullptr;
9716 :
9717 91 : const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
9718 91 : const char *min_is_inclusive =
9719 91 : bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
9720 91 : const char *max_is_inclusive =
9721 91 : bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
9722 :
9723 91 : std::unique_ptr<SQLResult> oResultTable;
9724 : // Note: for coded domains, we use a little trick by using a dummy
9725 : // _{domainname}_domain_description enum that has a single entry whose
9726 : // description is the description of the main domain.
9727 : {
9728 91 : char *pszSQL = sqlite3_mprintf(
9729 : "SELECT constraint_type, value, min, %s, "
9730 : "max, %s, description, constraint_name "
9731 : "FROM gpkg_data_column_constraints "
9732 : "WHERE constraint_name IN ('%q', "
9733 : "'_%q_domain_description') "
9734 : "AND length(constraint_type) < 100 " // to
9735 : // avoid
9736 : // denial
9737 : // of
9738 : // service
9739 : "AND (value IS NULL OR length(value) < "
9740 : "10000) " // to avoid denial
9741 : // of service
9742 : "AND (description IS NULL OR "
9743 : "length(description) < 10000) " // to
9744 : // avoid
9745 : // denial
9746 : // of
9747 : // service
9748 : "ORDER BY value "
9749 : "LIMIT 10000", // to avoid denial of
9750 : // service
9751 : min_is_inclusive, max_is_inclusive, name.c_str(), name.c_str());
9752 91 : oResultTable = SQLQuery(hDB, pszSQL);
9753 91 : sqlite3_free(pszSQL);
9754 91 : if (!oResultTable)
9755 0 : return nullptr;
9756 : }
9757 91 : if (oResultTable->RowCount() == 0)
9758 : {
9759 33 : return nullptr;
9760 : }
9761 58 : if (oResultTable->RowCount() == 10000)
9762 : {
9763 0 : CPLError(CE_Warning, CPLE_AppDefined,
9764 : "Number of rows returned for field domain %s has been "
9765 : "truncated.",
9766 : name.c_str());
9767 : }
9768 :
9769 : // Try to find the field domain data type from fields that implement it
9770 58 : int nFieldType = -1;
9771 58 : OGRFieldSubType eSubType = OFSTNone;
9772 58 : if (HasDataColumnsTable())
9773 : {
9774 53 : char *pszSQL = sqlite3_mprintf(
9775 : "SELECT table_name, column_name FROM gpkg_data_columns WHERE "
9776 : "constraint_name = '%q' LIMIT 10",
9777 : name.c_str());
9778 106 : auto oResultTable2 = SQLQuery(hDB, pszSQL);
9779 53 : sqlite3_free(pszSQL);
9780 53 : if (oResultTable2 && oResultTable2->RowCount() >= 1)
9781 : {
9782 58 : for (int iRecord = 0; iRecord < oResultTable2->RowCount();
9783 : iRecord++)
9784 : {
9785 29 : const char *pszTableName = oResultTable2->GetValue(0, iRecord);
9786 29 : const char *pszColumnName = oResultTable2->GetValue(1, iRecord);
9787 29 : if (pszTableName == nullptr || pszColumnName == nullptr)
9788 0 : continue;
9789 : OGRLayer *poLayer =
9790 58 : const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
9791 29 : pszTableName);
9792 29 : if (poLayer)
9793 : {
9794 29 : const auto poFDefn = poLayer->GetLayerDefn();
9795 29 : int nIdx = poFDefn->GetFieldIndex(pszColumnName);
9796 29 : if (nIdx >= 0)
9797 : {
9798 29 : const auto poFieldDefn = poFDefn->GetFieldDefn(nIdx);
9799 29 : const auto eType = poFieldDefn->GetType();
9800 29 : if (nFieldType < 0)
9801 : {
9802 29 : nFieldType = eType;
9803 29 : eSubType = poFieldDefn->GetSubType();
9804 : }
9805 0 : else if ((eType == OFTInteger64 || eType == OFTReal) &&
9806 : nFieldType == OFTInteger)
9807 : {
9808 : // ok
9809 : }
9810 0 : else if (eType == OFTInteger &&
9811 0 : (nFieldType == OFTInteger64 ||
9812 : nFieldType == OFTReal))
9813 : {
9814 0 : nFieldType = OFTInteger;
9815 0 : eSubType = OFSTNone;
9816 : }
9817 0 : else if (nFieldType != eType)
9818 : {
9819 0 : nFieldType = -1;
9820 0 : eSubType = OFSTNone;
9821 0 : break;
9822 : }
9823 : }
9824 : }
9825 : }
9826 : }
9827 : }
9828 :
9829 58 : std::unique_ptr<OGRFieldDomain> poDomain;
9830 116 : std::vector<OGRCodedValue> asValues;
9831 58 : bool error = false;
9832 116 : CPLString osLastConstraintType;
9833 58 : int nFieldTypeFromEnumCode = -1;
9834 116 : std::string osConstraintDescription;
9835 116 : std::string osDescrConstraintName("_");
9836 58 : osDescrConstraintName += name;
9837 58 : osDescrConstraintName += "_domain_description";
9838 145 : for (int iRecord = 0; iRecord < oResultTable->RowCount(); iRecord++)
9839 : {
9840 91 : const char *pszConstraintType = oResultTable->GetValue(0, iRecord);
9841 91 : if (pszConstraintType == nullptr)
9842 2 : continue;
9843 91 : const char *pszValue = oResultTable->GetValue(1, iRecord);
9844 91 : const char *pszMin = oResultTable->GetValue(2, iRecord);
9845 : const bool bIsMinIncluded =
9846 91 : oResultTable->GetValueAsInteger(3, iRecord) == 1;
9847 91 : const char *pszMax = oResultTable->GetValue(4, iRecord);
9848 : const bool bIsMaxIncluded =
9849 91 : oResultTable->GetValueAsInteger(5, iRecord) == 1;
9850 91 : const char *pszDescription = oResultTable->GetValue(6, iRecord);
9851 91 : const char *pszConstraintName = oResultTable->GetValue(7, iRecord);
9852 :
9853 91 : if (!osLastConstraintType.empty() && osLastConstraintType != "enum")
9854 : {
9855 1 : CPLError(CE_Failure, CPLE_AppDefined,
9856 : "Only constraint of type 'enum' can have multiple rows");
9857 1 : error = true;
9858 4 : break;
9859 : }
9860 :
9861 90 : if (strcmp(pszConstraintType, "enum") == 0)
9862 : {
9863 63 : if (pszValue == nullptr)
9864 : {
9865 1 : CPLError(CE_Failure, CPLE_AppDefined,
9866 : "NULL in 'value' column of enumeration");
9867 1 : error = true;
9868 1 : break;
9869 : }
9870 62 : if (osDescrConstraintName == pszConstraintName)
9871 : {
9872 2 : if (pszDescription)
9873 : {
9874 2 : osConstraintDescription = pszDescription;
9875 : }
9876 2 : continue;
9877 : }
9878 60 : if (asValues.empty())
9879 : {
9880 30 : asValues.reserve(oResultTable->RowCount() + 1);
9881 : }
9882 : OGRCodedValue cv;
9883 : // intended: the 'value' column in GPKG is actually the code
9884 60 : cv.pszCode = VSI_STRDUP_VERBOSE(pszValue);
9885 60 : if (cv.pszCode == nullptr)
9886 : {
9887 0 : error = true;
9888 0 : break;
9889 : }
9890 60 : if (pszDescription)
9891 : {
9892 48 : cv.pszValue = VSI_STRDUP_VERBOSE(pszDescription);
9893 48 : if (cv.pszValue == nullptr)
9894 : {
9895 0 : VSIFree(cv.pszCode);
9896 0 : error = true;
9897 0 : break;
9898 : }
9899 : }
9900 : else
9901 : {
9902 12 : cv.pszValue = nullptr;
9903 : }
9904 :
9905 : // If we can't get the data type from field definition, guess it
9906 : // from code.
9907 60 : if (nFieldType < 0 && nFieldTypeFromEnumCode != OFTString)
9908 : {
9909 36 : switch (CPLGetValueType(cv.pszCode))
9910 : {
9911 26 : case CPL_VALUE_INTEGER:
9912 : {
9913 26 : if (nFieldTypeFromEnumCode != OFTReal &&
9914 : nFieldTypeFromEnumCode != OFTInteger64)
9915 : {
9916 18 : const auto nVal = CPLAtoGIntBig(cv.pszCode);
9917 34 : if (nVal < std::numeric_limits<int>::min() ||
9918 16 : nVal > std::numeric_limits<int>::max())
9919 : {
9920 6 : nFieldTypeFromEnumCode = OFTInteger64;
9921 : }
9922 : else
9923 : {
9924 12 : nFieldTypeFromEnumCode = OFTInteger;
9925 : }
9926 : }
9927 26 : break;
9928 : }
9929 :
9930 6 : case CPL_VALUE_REAL:
9931 6 : nFieldTypeFromEnumCode = OFTReal;
9932 6 : break;
9933 :
9934 4 : case CPL_VALUE_STRING:
9935 4 : nFieldTypeFromEnumCode = OFTString;
9936 4 : break;
9937 : }
9938 : }
9939 :
9940 60 : asValues.emplace_back(cv);
9941 : }
9942 27 : else if (strcmp(pszConstraintType, "range") == 0)
9943 : {
9944 : OGRField sMin;
9945 : OGRField sMax;
9946 20 : OGR_RawField_SetUnset(&sMin);
9947 20 : OGR_RawField_SetUnset(&sMax);
9948 20 : if (nFieldType != OFTInteger && nFieldType != OFTInteger64)
9949 11 : nFieldType = OFTReal;
9950 39 : if (pszMin != nullptr &&
9951 19 : CPLAtof(pszMin) != -std::numeric_limits<double>::infinity())
9952 : {
9953 15 : if (nFieldType == OFTInteger)
9954 6 : sMin.Integer = atoi(pszMin);
9955 9 : else if (nFieldType == OFTInteger64)
9956 3 : sMin.Integer64 = CPLAtoGIntBig(pszMin);
9957 : else /* if( nFieldType == OFTReal ) */
9958 6 : sMin.Real = CPLAtof(pszMin);
9959 : }
9960 39 : if (pszMax != nullptr &&
9961 19 : CPLAtof(pszMax) != std::numeric_limits<double>::infinity())
9962 : {
9963 15 : if (nFieldType == OFTInteger)
9964 6 : sMax.Integer = atoi(pszMax);
9965 9 : else if (nFieldType == OFTInteger64)
9966 3 : sMax.Integer64 = CPLAtoGIntBig(pszMax);
9967 : else /* if( nFieldType == OFTReal ) */
9968 6 : sMax.Real = CPLAtof(pszMax);
9969 : }
9970 20 : poDomain = std::make_unique<OGRRangeFieldDomain>(
9971 20 : name, pszDescription ? pszDescription : "",
9972 40 : static_cast<OGRFieldType>(nFieldType), eSubType, sMin,
9973 20 : bIsMinIncluded, sMax, bIsMaxIncluded);
9974 : }
9975 7 : else if (strcmp(pszConstraintType, "glob") == 0)
9976 : {
9977 6 : if (pszValue == nullptr)
9978 : {
9979 1 : CPLError(CE_Failure, CPLE_AppDefined,
9980 : "NULL in 'value' column of glob");
9981 1 : error = true;
9982 1 : break;
9983 : }
9984 5 : if (nFieldType < 0)
9985 1 : nFieldType = OFTString;
9986 5 : poDomain = std::make_unique<OGRGlobFieldDomain>(
9987 5 : name, pszDescription ? pszDescription : "",
9988 15 : static_cast<OGRFieldType>(nFieldType), eSubType, pszValue);
9989 : }
9990 : else
9991 : {
9992 1 : CPLError(CE_Failure, CPLE_AppDefined,
9993 : "Unhandled constraint_type: %s", pszConstraintType);
9994 1 : error = true;
9995 1 : break;
9996 : }
9997 :
9998 85 : osLastConstraintType = pszConstraintType;
9999 : }
10000 :
10001 58 : if (!asValues.empty())
10002 : {
10003 30 : if (nFieldType < 0)
10004 18 : nFieldType = nFieldTypeFromEnumCode;
10005 30 : poDomain = std::make_unique<OGRCodedFieldDomain>(
10006 : name, osConstraintDescription,
10007 60 : static_cast<OGRFieldType>(nFieldType), eSubType,
10008 60 : std::move(asValues));
10009 : }
10010 :
10011 58 : if (error)
10012 : {
10013 4 : return nullptr;
10014 : }
10015 :
10016 54 : m_oMapFieldDomains[name] = std::move(poDomain);
10017 54 : return GDALDataset::GetFieldDomain(name);
10018 : }
10019 :
10020 : /************************************************************************/
10021 : /* AddFieldDomain() */
10022 : /************************************************************************/
10023 :
10024 19 : bool GDALGeoPackageDataset::AddFieldDomain(
10025 : std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
10026 : {
10027 38 : const std::string domainName(domain->GetName());
10028 19 : if (!GetUpdate())
10029 : {
10030 0 : CPLError(CE_Failure, CPLE_NotSupported,
10031 : "AddFieldDomain() not supported on read-only dataset");
10032 0 : return false;
10033 : }
10034 19 : if (GetFieldDomain(domainName) != nullptr)
10035 : {
10036 1 : failureReason = "A domain of identical name already exists";
10037 1 : return false;
10038 : }
10039 18 : if (!CreateColumnsTableAndColumnConstraintsTablesIfNecessary())
10040 0 : return false;
10041 :
10042 18 : const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
10043 18 : const char *min_is_inclusive =
10044 18 : bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
10045 18 : const char *max_is_inclusive =
10046 18 : bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
10047 :
10048 18 : const auto &osDescription = domain->GetDescription();
10049 18 : switch (domain->GetDomainType())
10050 : {
10051 11 : case OFDT_CODED:
10052 : {
10053 : const auto poCodedDomain =
10054 11 : cpl::down_cast<const OGRCodedFieldDomain *>(domain.get());
10055 11 : if (!osDescription.empty())
10056 : {
10057 : // We use a little trick by using a dummy
10058 : // _{domainname}_domain_description enum that has a single
10059 : // entry whose description is the description of the main
10060 : // domain.
10061 1 : char *pszSQL = sqlite3_mprintf(
10062 : "INSERT INTO gpkg_data_column_constraints ("
10063 : "constraint_name, constraint_type, value, "
10064 : "min, %s, max, %s, "
10065 : "description) VALUES ("
10066 : "'_%q_domain_description', 'enum', '', NULL, NULL, NULL, "
10067 : "NULL, %Q)",
10068 : min_is_inclusive, max_is_inclusive, domainName.c_str(),
10069 : osDescription.c_str());
10070 1 : CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
10071 1 : sqlite3_free(pszSQL);
10072 : }
10073 11 : const auto &enumeration = poCodedDomain->GetEnumeration();
10074 33 : for (int i = 0; enumeration[i].pszCode != nullptr; ++i)
10075 : {
10076 22 : char *pszSQL = sqlite3_mprintf(
10077 : "INSERT INTO gpkg_data_column_constraints ("
10078 : "constraint_name, constraint_type, value, "
10079 : "min, %s, max, %s, "
10080 : "description) VALUES ("
10081 : "'%q', 'enum', '%q', NULL, NULL, NULL, NULL, %Q)",
10082 : min_is_inclusive, max_is_inclusive, domainName.c_str(),
10083 22 : enumeration[i].pszCode, enumeration[i].pszValue);
10084 22 : bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
10085 22 : sqlite3_free(pszSQL);
10086 22 : if (!ok)
10087 0 : return false;
10088 : }
10089 11 : break;
10090 : }
10091 :
10092 6 : case OFDT_RANGE:
10093 : {
10094 : const auto poRangeDomain =
10095 6 : cpl::down_cast<const OGRRangeFieldDomain *>(domain.get());
10096 6 : const auto eFieldType = poRangeDomain->GetFieldType();
10097 6 : if (eFieldType != OFTInteger && eFieldType != OFTInteger64 &&
10098 : eFieldType != OFTReal)
10099 : {
10100 : failureReason = "Only range domains of numeric type are "
10101 0 : "supported in GeoPackage";
10102 0 : return false;
10103 : }
10104 :
10105 6 : double dfMin = -std::numeric_limits<double>::infinity();
10106 6 : double dfMax = std::numeric_limits<double>::infinity();
10107 6 : bool bMinIsInclusive = true;
10108 6 : const auto &sMin = poRangeDomain->GetMin(bMinIsInclusive);
10109 6 : bool bMaxIsInclusive = true;
10110 6 : const auto &sMax = poRangeDomain->GetMax(bMaxIsInclusive);
10111 6 : if (eFieldType == OFTInteger)
10112 : {
10113 2 : if (!OGR_RawField_IsUnset(&sMin))
10114 2 : dfMin = sMin.Integer;
10115 2 : if (!OGR_RawField_IsUnset(&sMax))
10116 2 : dfMax = sMax.Integer;
10117 : }
10118 4 : else if (eFieldType == OFTInteger64)
10119 : {
10120 1 : if (!OGR_RawField_IsUnset(&sMin))
10121 1 : dfMin = static_cast<double>(sMin.Integer64);
10122 1 : if (!OGR_RawField_IsUnset(&sMax))
10123 1 : dfMax = static_cast<double>(sMax.Integer64);
10124 : }
10125 : else /* if( eFieldType == OFTReal ) */
10126 : {
10127 3 : if (!OGR_RawField_IsUnset(&sMin))
10128 3 : dfMin = sMin.Real;
10129 3 : if (!OGR_RawField_IsUnset(&sMax))
10130 3 : dfMax = sMax.Real;
10131 : }
10132 :
10133 6 : sqlite3_stmt *hInsertStmt = nullptr;
10134 : const char *pszSQL =
10135 6 : CPLSPrintf("INSERT INTO gpkg_data_column_constraints ("
10136 : "constraint_name, constraint_type, value, "
10137 : "min, %s, max, %s, "
10138 : "description) VALUES ("
10139 : "?, 'range', NULL, ?, ?, ?, ?, ?)",
10140 : min_is_inclusive, max_is_inclusive);
10141 6 : if (SQLPrepareWithError(hDB, pszSQL, -1, &hInsertStmt, nullptr) !=
10142 : SQLITE_OK)
10143 : {
10144 0 : return false;
10145 : }
10146 6 : sqlite3_bind_text(hInsertStmt, 1, domainName.c_str(),
10147 6 : static_cast<int>(domainName.size()),
10148 : SQLITE_TRANSIENT);
10149 6 : sqlite3_bind_double(hInsertStmt, 2, dfMin);
10150 6 : sqlite3_bind_int(hInsertStmt, 3, bMinIsInclusive ? 1 : 0);
10151 6 : sqlite3_bind_double(hInsertStmt, 4, dfMax);
10152 6 : sqlite3_bind_int(hInsertStmt, 5, bMaxIsInclusive ? 1 : 0);
10153 6 : if (osDescription.empty())
10154 : {
10155 3 : sqlite3_bind_null(hInsertStmt, 6);
10156 : }
10157 : else
10158 : {
10159 3 : sqlite3_bind_text(hInsertStmt, 6, osDescription.c_str(),
10160 3 : static_cast<int>(osDescription.size()),
10161 : SQLITE_TRANSIENT);
10162 : }
10163 6 : const int sqlite_err = sqlite3_step(hInsertStmt);
10164 6 : sqlite3_finalize(hInsertStmt);
10165 6 : if (sqlite_err != SQLITE_OK && sqlite_err != SQLITE_DONE)
10166 : {
10167 0 : CPLError(CE_Failure, CPLE_AppDefined,
10168 : "failed to execute insertion '%s': %s", pszSQL,
10169 : sqlite3_errmsg(hDB));
10170 0 : return false;
10171 : }
10172 :
10173 6 : break;
10174 : }
10175 :
10176 1 : case OFDT_GLOB:
10177 : {
10178 : const auto poGlobDomain =
10179 1 : cpl::down_cast<const OGRGlobFieldDomain *>(domain.get());
10180 2 : char *pszSQL = sqlite3_mprintf(
10181 : "INSERT INTO gpkg_data_column_constraints ("
10182 : "constraint_name, constraint_type, value, "
10183 : "min, %s, max, %s, "
10184 : "description) VALUES ("
10185 : "'%q', 'glob', '%q', NULL, NULL, NULL, NULL, %Q)",
10186 : min_is_inclusive, max_is_inclusive, domainName.c_str(),
10187 1 : poGlobDomain->GetGlob().c_str(),
10188 2 : osDescription.empty() ? nullptr : osDescription.c_str());
10189 1 : bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
10190 1 : sqlite3_free(pszSQL);
10191 1 : if (!ok)
10192 0 : return false;
10193 :
10194 1 : break;
10195 : }
10196 : }
10197 :
10198 18 : m_oMapFieldDomains[domainName] = std::move(domain);
10199 18 : return true;
10200 : }
10201 :
10202 : /************************************************************************/
10203 : /* UpdateFieldDomain() */
10204 : /************************************************************************/
10205 :
10206 3 : bool GDALGeoPackageDataset::UpdateFieldDomain(
10207 : std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
10208 : {
10209 6 : const std::string domainName(domain->GetName());
10210 3 : if (eAccess != GA_Update)
10211 : {
10212 1 : CPLError(CE_Failure, CPLE_NotSupported,
10213 : "UpdateFieldDomain() not supported on read-only dataset");
10214 1 : return false;
10215 : }
10216 :
10217 2 : if (GetFieldDomain(domainName) == nullptr)
10218 : {
10219 1 : failureReason = "The domain should already exist to be updated";
10220 1 : return false;
10221 : }
10222 :
10223 1 : bool bRet = SoftStartTransaction() == OGRERR_NONE;
10224 1 : if (bRet)
10225 : {
10226 2 : bRet = DeleteFieldDomain(domainName, failureReason) &&
10227 1 : AddFieldDomain(std::move(domain), failureReason);
10228 1 : if (bRet)
10229 1 : bRet = SoftCommitTransaction() == OGRERR_NONE;
10230 : else
10231 0 : SoftRollbackTransaction();
10232 : }
10233 1 : return bRet;
10234 : }
10235 :
10236 : /************************************************************************/
10237 : /* DeleteFieldDomain() */
10238 : /************************************************************************/
10239 :
10240 18 : bool GDALGeoPackageDataset::DeleteFieldDomain(const std::string &name,
10241 : std::string &failureReason)
10242 : {
10243 18 : if (eAccess != GA_Update)
10244 : {
10245 1 : CPLError(CE_Failure, CPLE_NotSupported,
10246 : "DeleteFieldDomain() not supported on read-only dataset");
10247 1 : return false;
10248 : }
10249 17 : if (GetFieldDomain(name) == nullptr)
10250 : {
10251 1 : failureReason = "Domain does not exist";
10252 1 : return false;
10253 : }
10254 :
10255 : char *pszSQL =
10256 16 : sqlite3_mprintf("DELETE FROM gpkg_data_column_constraints WHERE "
10257 : "constraint_name IN ('%q', '_%q_domain_description')",
10258 : name.c_str(), name.c_str());
10259 16 : const bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
10260 16 : sqlite3_free(pszSQL);
10261 16 : if (ok)
10262 16 : m_oMapFieldDomains.erase(name);
10263 16 : return ok;
10264 : }
10265 :
10266 : /************************************************************************/
10267 : /* AddRelationship() */
10268 : /************************************************************************/
10269 :
10270 24 : bool GDALGeoPackageDataset::AddRelationship(
10271 : std::unique_ptr<GDALRelationship> &&relationship,
10272 : std::string &failureReason)
10273 : {
10274 24 : if (!GetUpdate())
10275 : {
10276 0 : CPLError(CE_Failure, CPLE_NotSupported,
10277 : "AddRelationship() not supported on read-only dataset");
10278 0 : return false;
10279 : }
10280 :
10281 : const std::string osRelationshipName = GenerateNameForRelationship(
10282 24 : relationship->GetLeftTableName().c_str(),
10283 24 : relationship->GetRightTableName().c_str(),
10284 96 : relationship->GetRelatedTableType().c_str());
10285 : // sanity checks
10286 24 : if (GetRelationship(osRelationshipName) != nullptr)
10287 : {
10288 1 : failureReason = "A relationship of identical name already exists";
10289 1 : return false;
10290 : }
10291 :
10292 23 : if (!ValidateRelationship(relationship.get(), failureReason))
10293 : {
10294 14 : return false;
10295 : }
10296 :
10297 9 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
10298 : {
10299 0 : return false;
10300 : }
10301 9 : if (!CreateRelationsTableIfNecessary())
10302 : {
10303 0 : failureReason = "Could not create gpkgext_relations table";
10304 0 : return false;
10305 : }
10306 9 : if (SQLGetInteger(GetDB(),
10307 : "SELECT 1 FROM gpkg_extensions WHERE "
10308 : "table_name = 'gpkgext_relations'",
10309 9 : nullptr) != 1)
10310 : {
10311 4 : if (OGRERR_NONE !=
10312 4 : SQLCommand(
10313 : GetDB(),
10314 : "INSERT INTO gpkg_extensions "
10315 : "(table_name,column_name,extension_name,definition,scope) "
10316 : "VALUES ('gpkgext_relations', NULL, 'gpkg_related_tables', "
10317 : "'http://www.geopackage.org/18-000.html', "
10318 : "'read-write')"))
10319 : {
10320 : failureReason =
10321 0 : "Could not create gpkg_extensions entry for gpkgext_relations";
10322 0 : return false;
10323 : }
10324 : }
10325 :
10326 9 : const std::string &osLeftTableName = relationship->GetLeftTableName();
10327 9 : const std::string &osRightTableName = relationship->GetRightTableName();
10328 9 : const auto &aosLeftTableFields = relationship->GetLeftTableFields();
10329 9 : const auto &aosRightTableFields = relationship->GetRightTableFields();
10330 :
10331 18 : std::string osRelatedTableType = relationship->GetRelatedTableType();
10332 9 : if (osRelatedTableType.empty())
10333 : {
10334 5 : osRelatedTableType = "features";
10335 : }
10336 :
10337 : // generate mapping table if not set
10338 18 : CPLString osMappingTableName = relationship->GetMappingTableName();
10339 9 : if (osMappingTableName.empty())
10340 : {
10341 3 : int nIndex = 1;
10342 3 : osMappingTableName = osLeftTableName + "_" + osRightTableName;
10343 3 : while (FindLayerIndex(osMappingTableName.c_str()) >= 0)
10344 : {
10345 0 : nIndex += 1;
10346 : osMappingTableName.Printf("%s_%s_%d", osLeftTableName.c_str(),
10347 0 : osRightTableName.c_str(), nIndex);
10348 : }
10349 :
10350 : // determine whether base/related keys are unique
10351 3 : bool bBaseKeyIsUnique = false;
10352 : {
10353 : const std::set<std::string> uniqueBaseFieldsUC =
10354 : SQLGetUniqueFieldUCConstraints(GetDB(),
10355 6 : osLeftTableName.c_str());
10356 6 : if (uniqueBaseFieldsUC.find(
10357 3 : CPLString(aosLeftTableFields[0]).toupper()) !=
10358 6 : uniqueBaseFieldsUC.end())
10359 : {
10360 2 : bBaseKeyIsUnique = true;
10361 : }
10362 : }
10363 3 : bool bRelatedKeyIsUnique = false;
10364 : {
10365 : const std::set<std::string> uniqueRelatedFieldsUC =
10366 : SQLGetUniqueFieldUCConstraints(GetDB(),
10367 6 : osRightTableName.c_str());
10368 6 : if (uniqueRelatedFieldsUC.find(
10369 3 : CPLString(aosRightTableFields[0]).toupper()) !=
10370 6 : uniqueRelatedFieldsUC.end())
10371 : {
10372 2 : bRelatedKeyIsUnique = true;
10373 : }
10374 : }
10375 :
10376 : // create mapping table
10377 :
10378 3 : std::string osBaseIdDefinition = "base_id INTEGER";
10379 3 : if (bBaseKeyIsUnique)
10380 : {
10381 2 : char *pszSQL = sqlite3_mprintf(
10382 : " CONSTRAINT 'fk_base_id_%q' REFERENCES \"%w\"(\"%w\") ON "
10383 : "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
10384 : "DEFERRED",
10385 : osMappingTableName.c_str(), osLeftTableName.c_str(),
10386 2 : aosLeftTableFields[0].c_str());
10387 2 : osBaseIdDefinition += pszSQL;
10388 2 : sqlite3_free(pszSQL);
10389 : }
10390 :
10391 3 : std::string osRelatedIdDefinition = "related_id INTEGER";
10392 3 : if (bRelatedKeyIsUnique)
10393 : {
10394 2 : char *pszSQL = sqlite3_mprintf(
10395 : " CONSTRAINT 'fk_related_id_%q' REFERENCES \"%w\"(\"%w\") ON "
10396 : "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
10397 : "DEFERRED",
10398 : osMappingTableName.c_str(), osRightTableName.c_str(),
10399 2 : aosRightTableFields[0].c_str());
10400 2 : osRelatedIdDefinition += pszSQL;
10401 2 : sqlite3_free(pszSQL);
10402 : }
10403 :
10404 3 : char *pszSQL = sqlite3_mprintf("CREATE TABLE \"%w\" ("
10405 : "id INTEGER PRIMARY KEY AUTOINCREMENT, "
10406 : "%s, %s);",
10407 : osMappingTableName.c_str(),
10408 : osBaseIdDefinition.c_str(),
10409 : osRelatedIdDefinition.c_str());
10410 3 : OGRErr eErr = SQLCommand(hDB, pszSQL);
10411 3 : sqlite3_free(pszSQL);
10412 3 : if (eErr != OGRERR_NONE)
10413 : {
10414 : failureReason =
10415 0 : ("Could not create mapping table " + osMappingTableName)
10416 0 : .c_str();
10417 0 : return false;
10418 : }
10419 :
10420 : /*
10421 : * Strictly speaking we should NOT be inserting the mapping table into gpkg_contents.
10422 : * The related tables extension explicitly states that the mapping table should only be
10423 : * in the gpkgext_relations table and not in gpkg_contents. (See also discussion at
10424 : * https://github.com/opengeospatial/geopackage/issues/679).
10425 : *
10426 : * However, if we don't insert the mapping table into gpkg_contents then it is no longer
10427 : * visible to some clients (eg ESRI software only allows opening tables that are present
10428 : * in gpkg_contents). So we'll do this anyway, for maximum compatibility and flexibility.
10429 : *
10430 : * More related discussion is at https://github.com/OSGeo/gdal/pull/9258
10431 : */
10432 3 : pszSQL = sqlite3_mprintf(
10433 : "INSERT INTO gpkg_contents "
10434 : "(table_name,data_type,identifier,description,last_change,srs_id) "
10435 : "VALUES "
10436 : "('%q','attributes','%q','Mapping table for relationship between "
10437 : "%q and %q',%s,0)",
10438 : osMappingTableName.c_str(), /*table_name*/
10439 : osMappingTableName.c_str(), /*identifier*/
10440 : osLeftTableName.c_str(), /*description left table name*/
10441 : osRightTableName.c_str(), /*description right table name*/
10442 6 : GDALGeoPackageDataset::GetCurrentDateEscapedSQL().c_str());
10443 :
10444 : // Note -- we explicitly ignore failures here, because hey, we aren't really
10445 : // supposed to be adding this table to gpkg_contents anyway!
10446 3 : (void)SQLCommand(hDB, pszSQL);
10447 3 : sqlite3_free(pszSQL);
10448 :
10449 3 : pszSQL = sqlite3_mprintf(
10450 : "CREATE INDEX \"idx_%w_base_id\" ON \"%w\" (base_id);",
10451 : osMappingTableName.c_str(), osMappingTableName.c_str());
10452 3 : eErr = SQLCommand(hDB, pszSQL);
10453 3 : sqlite3_free(pszSQL);
10454 3 : if (eErr != OGRERR_NONE)
10455 : {
10456 0 : failureReason = ("Could not create index for " +
10457 0 : osMappingTableName + " (base_id)")
10458 0 : .c_str();
10459 0 : return false;
10460 : }
10461 :
10462 3 : pszSQL = sqlite3_mprintf(
10463 : "CREATE INDEX \"idx_%qw_related_id\" ON \"%w\" (related_id);",
10464 : osMappingTableName.c_str(), osMappingTableName.c_str());
10465 3 : eErr = SQLCommand(hDB, pszSQL);
10466 3 : sqlite3_free(pszSQL);
10467 3 : if (eErr != OGRERR_NONE)
10468 : {
10469 0 : failureReason = ("Could not create index for " +
10470 0 : osMappingTableName + " (related_id)")
10471 0 : .c_str();
10472 0 : return false;
10473 : }
10474 : }
10475 : else
10476 : {
10477 : // validate mapping table structure
10478 6 : if (OGRGeoPackageTableLayer *poLayer =
10479 6 : cpl::down_cast<OGRGeoPackageTableLayer *>(
10480 6 : GetLayerByName(osMappingTableName)))
10481 : {
10482 4 : if (poLayer->GetLayerDefn()->GetFieldIndex("base_id") < 0)
10483 : {
10484 : failureReason =
10485 2 : ("Field base_id must exist in " + osMappingTableName)
10486 1 : .c_str();
10487 1 : return false;
10488 : }
10489 3 : if (poLayer->GetLayerDefn()->GetFieldIndex("related_id") < 0)
10490 : {
10491 : failureReason =
10492 2 : ("Field related_id must exist in " + osMappingTableName)
10493 1 : .c_str();
10494 1 : return false;
10495 : }
10496 : }
10497 : else
10498 : {
10499 : failureReason =
10500 2 : ("Could not retrieve table " + osMappingTableName).c_str();
10501 2 : return false;
10502 : }
10503 : }
10504 :
10505 5 : char *pszSQL = sqlite3_mprintf(
10506 : "INSERT INTO gpkg_extensions "
10507 : "(table_name,column_name,extension_name,definition,scope) "
10508 : "VALUES ('%q', NULL, 'gpkg_related_tables', "
10509 : "'http://www.geopackage.org/18-000.html', "
10510 : "'read-write')",
10511 : osMappingTableName.c_str());
10512 5 : OGRErr eErr = SQLCommand(hDB, pszSQL);
10513 5 : sqlite3_free(pszSQL);
10514 5 : if (eErr != OGRERR_NONE)
10515 : {
10516 0 : failureReason = ("Could not insert mapping table " +
10517 0 : osMappingTableName + " into gpkg_extensions")
10518 0 : .c_str();
10519 0 : return false;
10520 : }
10521 :
10522 15 : pszSQL = sqlite3_mprintf(
10523 : "INSERT INTO gpkgext_relations "
10524 : "(base_table_name,base_primary_column,related_table_name,related_"
10525 : "primary_column,relation_name,mapping_table_name) "
10526 : "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
10527 5 : osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
10528 5 : osRightTableName.c_str(), aosRightTableFields[0].c_str(),
10529 : osRelatedTableType.c_str(), osMappingTableName.c_str());
10530 5 : eErr = SQLCommand(hDB, pszSQL);
10531 5 : sqlite3_free(pszSQL);
10532 5 : if (eErr != OGRERR_NONE)
10533 : {
10534 0 : failureReason = "Could not insert relationship into gpkgext_relations";
10535 0 : return false;
10536 : }
10537 :
10538 5 : ClearCachedRelationships();
10539 5 : LoadRelationships();
10540 5 : return true;
10541 : }
10542 :
10543 : /************************************************************************/
10544 : /* DeleteRelationship() */
10545 : /************************************************************************/
10546 :
10547 4 : bool GDALGeoPackageDataset::DeleteRelationship(const std::string &name,
10548 : std::string &failureReason)
10549 : {
10550 4 : if (eAccess != GA_Update)
10551 : {
10552 0 : CPLError(CE_Failure, CPLE_NotSupported,
10553 : "DeleteRelationship() not supported on read-only dataset");
10554 0 : return false;
10555 : }
10556 :
10557 : // ensure relationships are up to date before we try to remove one
10558 4 : ClearCachedRelationships();
10559 4 : LoadRelationships();
10560 :
10561 8 : std::string osMappingTableName;
10562 : {
10563 4 : const GDALRelationship *poRelationship = GetRelationship(name);
10564 4 : if (poRelationship == nullptr)
10565 : {
10566 1 : failureReason = "Could not find relationship with name " + name;
10567 1 : return false;
10568 : }
10569 :
10570 3 : osMappingTableName = poRelationship->GetMappingTableName();
10571 : }
10572 :
10573 : // DeleteLayerCommon will delete existing relationship objects, so we can't
10574 : // refer to poRelationship or any of its members previously obtained here
10575 3 : if (DeleteLayerCommon(osMappingTableName.c_str()) != OGRERR_NONE)
10576 : {
10577 : failureReason =
10578 0 : "Could not remove mapping layer name " + osMappingTableName;
10579 :
10580 : // relationships may have been left in an inconsistent state -- reload
10581 : // them now
10582 0 : ClearCachedRelationships();
10583 0 : LoadRelationships();
10584 0 : return false;
10585 : }
10586 :
10587 3 : ClearCachedRelationships();
10588 3 : LoadRelationships();
10589 3 : return true;
10590 : }
10591 :
10592 : /************************************************************************/
10593 : /* UpdateRelationship() */
10594 : /************************************************************************/
10595 :
10596 6 : bool GDALGeoPackageDataset::UpdateRelationship(
10597 : std::unique_ptr<GDALRelationship> &&relationship,
10598 : std::string &failureReason)
10599 : {
10600 6 : if (eAccess != GA_Update)
10601 : {
10602 0 : CPLError(CE_Failure, CPLE_NotSupported,
10603 : "UpdateRelationship() not supported on read-only dataset");
10604 0 : return false;
10605 : }
10606 :
10607 : // ensure relationships are up to date before we try to update one
10608 6 : ClearCachedRelationships();
10609 6 : LoadRelationships();
10610 :
10611 6 : const std::string &osRelationshipName = relationship->GetName();
10612 6 : const std::string &osLeftTableName = relationship->GetLeftTableName();
10613 6 : const std::string &osRightTableName = relationship->GetRightTableName();
10614 6 : const std::string &osMappingTableName = relationship->GetMappingTableName();
10615 6 : const auto &aosLeftTableFields = relationship->GetLeftTableFields();
10616 6 : const auto &aosRightTableFields = relationship->GetRightTableFields();
10617 :
10618 : // sanity checks
10619 : {
10620 : const GDALRelationship *poExistingRelationship =
10621 6 : GetRelationship(osRelationshipName);
10622 6 : if (poExistingRelationship == nullptr)
10623 : {
10624 : failureReason =
10625 1 : "The relationship should already exist to be updated";
10626 1 : return false;
10627 : }
10628 :
10629 5 : if (!ValidateRelationship(relationship.get(), failureReason))
10630 : {
10631 2 : return false;
10632 : }
10633 :
10634 : // we don't permit changes to the participating tables
10635 3 : if (osLeftTableName != poExistingRelationship->GetLeftTableName())
10636 : {
10637 0 : failureReason = ("Cannot change base table from " +
10638 0 : poExistingRelationship->GetLeftTableName() +
10639 0 : " to " + osLeftTableName)
10640 0 : .c_str();
10641 0 : return false;
10642 : }
10643 3 : if (osRightTableName != poExistingRelationship->GetRightTableName())
10644 : {
10645 0 : failureReason = ("Cannot change related table from " +
10646 0 : poExistingRelationship->GetRightTableName() +
10647 0 : " to " + osRightTableName)
10648 0 : .c_str();
10649 0 : return false;
10650 : }
10651 3 : if (osMappingTableName != poExistingRelationship->GetMappingTableName())
10652 : {
10653 0 : failureReason = ("Cannot change mapping table from " +
10654 0 : poExistingRelationship->GetMappingTableName() +
10655 0 : " to " + osMappingTableName)
10656 0 : .c_str();
10657 0 : return false;
10658 : }
10659 : }
10660 :
10661 6 : std::string osRelatedTableType = relationship->GetRelatedTableType();
10662 3 : if (osRelatedTableType.empty())
10663 : {
10664 0 : osRelatedTableType = "features";
10665 : }
10666 :
10667 3 : char *pszSQL = sqlite3_mprintf(
10668 : "DELETE FROM gpkgext_relations WHERE mapping_table_name='%q'",
10669 : osMappingTableName.c_str());
10670 3 : OGRErr eErr = SQLCommand(hDB, pszSQL);
10671 3 : sqlite3_free(pszSQL);
10672 3 : if (eErr != OGRERR_NONE)
10673 : {
10674 : failureReason =
10675 0 : "Could not delete old relationship from gpkgext_relations";
10676 0 : return false;
10677 : }
10678 :
10679 9 : pszSQL = sqlite3_mprintf(
10680 : "INSERT INTO gpkgext_relations "
10681 : "(base_table_name,base_primary_column,related_table_name,related_"
10682 : "primary_column,relation_name,mapping_table_name) "
10683 : "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
10684 3 : osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
10685 3 : osRightTableName.c_str(), aosRightTableFields[0].c_str(),
10686 : osRelatedTableType.c_str(), osMappingTableName.c_str());
10687 3 : eErr = SQLCommand(hDB, pszSQL);
10688 3 : sqlite3_free(pszSQL);
10689 3 : if (eErr != OGRERR_NONE)
10690 : {
10691 : failureReason =
10692 0 : "Could not insert updated relationship into gpkgext_relations";
10693 0 : return false;
10694 : }
10695 :
10696 3 : ClearCachedRelationships();
10697 3 : LoadRelationships();
10698 3 : return true;
10699 : }
10700 :
10701 : /************************************************************************/
10702 : /* GetSqliteMasterContent() */
10703 : /************************************************************************/
10704 :
10705 : const std::vector<SQLSqliteMasterContent> &
10706 2 : GDALGeoPackageDataset::GetSqliteMasterContent()
10707 : {
10708 2 : if (m_aoSqliteMasterContent.empty())
10709 : {
10710 : auto oResultTable =
10711 2 : SQLQuery(hDB, "SELECT sql, type, tbl_name FROM sqlite_master");
10712 1 : if (oResultTable)
10713 : {
10714 58 : for (int rowCnt = 0; rowCnt < oResultTable->RowCount(); ++rowCnt)
10715 : {
10716 114 : SQLSqliteMasterContent row;
10717 57 : const char *pszSQL = oResultTable->GetValue(0, rowCnt);
10718 57 : row.osSQL = pszSQL ? pszSQL : "";
10719 57 : const char *pszType = oResultTable->GetValue(1, rowCnt);
10720 57 : row.osType = pszType ? pszType : "";
10721 57 : const char *pszTableName = oResultTable->GetValue(2, rowCnt);
10722 57 : row.osTableName = pszTableName ? pszTableName : "";
10723 57 : m_aoSqliteMasterContent.emplace_back(std::move(row));
10724 : }
10725 : }
10726 : }
10727 2 : return m_aoSqliteMasterContent;
10728 : }
|