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 : * Permission is hereby granted, free of charge, to any person obtaining a
12 : * copy of this software and associated documentation files (the "Software"),
13 : * to deal in the Software without restriction, including without limitation
14 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 : * and/or sell copies of the Software, and to permit persons to whom the
16 : * Software is furnished to do so, subject to the following conditions:
17 : *
18 : * The above copyright notice and this permission notice shall be included
19 : * in all copies or substantial portions of the Software.
20 : *
21 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 : * DEALINGS IN THE SOFTWARE.
28 : ****************************************************************************/
29 :
30 : #include "ogr_geopackage.h"
31 : #include "ogr_p.h"
32 : #include "ogr_swq.h"
33 : #include "gdalwarper.h"
34 : #include "gdal_utils.h"
35 : #include "ogrgeopackageutility.h"
36 : #include "ogrsqliteutility.h"
37 : #include "ogr_wkb.h"
38 : #include "vrt/vrtdataset.h"
39 :
40 : #include "tilematrixset.hpp"
41 :
42 : #include <cstdlib>
43 :
44 : #include <algorithm>
45 : #include <limits>
46 : #include <sstream>
47 :
48 : #define COMPILATION_ALLOWED
49 : #define DEFINE_OGRSQLiteSQLFunctionsSetCaseSensitiveLike
50 : #include "ogrsqlitesqlfunctionscommon.cpp"
51 :
52 : // Keep in sync prototype of those 2 functions between gdalopeninfo.cpp,
53 : // ogrsqlitedatasource.cpp and ogrgeopackagedatasource.cpp
54 : void GDALOpenInfoDeclareFileNotToOpen(const char *pszFilename,
55 : const GByte *pabyHeader,
56 : int nHeaderBytes);
57 : void GDALOpenInfoUnDeclareFileNotToOpen(const char *pszFilename);
58 :
59 : /************************************************************************/
60 : /* Tiling schemes */
61 : /************************************************************************/
62 :
63 : typedef struct
64 : {
65 : const char *pszName;
66 : int nEPSGCode;
67 : double dfMinX;
68 : double dfMaxY;
69 : int nTileXCountZoomLevel0;
70 : int nTileYCountZoomLevel0;
71 : int nTileWidth;
72 : int nTileHeight;
73 : double dfPixelXSizeZoomLevel0;
74 : double dfPixelYSizeZoomLevel0;
75 : } TilingSchemeDefinition;
76 :
77 : static const TilingSchemeDefinition asTilingSchemes[] = {
78 : /* See http://portal.opengeospatial.org/files/?artifact_id=35326 (WMTS 1.0),
79 : Annex E.3 */
80 : {"GoogleCRS84Quad", 4326, -180.0, 180.0, 1, 1, 256, 256, 360.0 / 256,
81 : 360.0 / 256},
82 :
83 : /* See global-mercator at
84 : http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
85 : {"PseudoTMS_GlobalMercator", 3857, -20037508.34, 20037508.34, 2, 2, 256,
86 : 256, 78271.516, 78271.516},
87 : };
88 :
89 : // Setting it above 30 would lead to integer overflow ((1 << 31) > INT_MAX)
90 : constexpr int MAX_ZOOM_LEVEL = 30;
91 :
92 : /************************************************************************/
93 : /* GetTilingScheme() */
94 : /************************************************************************/
95 :
96 : static std::unique_ptr<TilingSchemeDefinition>
97 500 : GetTilingScheme(const char *pszName)
98 : {
99 500 : if (EQUAL(pszName, "CUSTOM"))
100 372 : return nullptr;
101 :
102 256 : for (const auto &tilingScheme : asTilingSchemes)
103 : {
104 195 : if (EQUAL(pszName, tilingScheme.pszName))
105 : {
106 : return std::unique_ptr<TilingSchemeDefinition>(
107 67 : new TilingSchemeDefinition(tilingScheme));
108 : }
109 : }
110 :
111 61 : if (EQUAL(pszName, "PseudoTMS_GlobalGeodetic"))
112 6 : pszName = "InspireCRS84Quad";
113 :
114 122 : auto poTM = gdal::TileMatrixSet::parse(pszName);
115 61 : if (poTM == nullptr)
116 1 : return nullptr;
117 60 : if (!poTM->haveAllLevelsSameTopLeft())
118 : {
119 0 : CPLError(CE_Failure, CPLE_NotSupported,
120 : "Unsupported tiling scheme: not all zoom levels have same top "
121 : "left corner");
122 0 : return nullptr;
123 : }
124 60 : if (!poTM->haveAllLevelsSameTileSize())
125 : {
126 0 : CPLError(CE_Failure, CPLE_NotSupported,
127 : "Unsupported tiling scheme: not all zoom levels have same "
128 : "tile size");
129 0 : return nullptr;
130 : }
131 60 : if (!poTM->hasOnlyPowerOfTwoVaryingScales())
132 : {
133 1 : CPLError(CE_Failure, CPLE_NotSupported,
134 : "Unsupported tiling scheme: resolution of consecutive zoom "
135 : "levels is not always 2");
136 1 : return nullptr;
137 : }
138 59 : if (poTM->hasVariableMatrixWidth())
139 : {
140 0 : CPLError(CE_Failure, CPLE_NotSupported,
141 : "Unsupported tiling scheme: some levels have variable matrix "
142 : "width");
143 0 : return nullptr;
144 : }
145 118 : auto poTilingScheme = std::make_unique<TilingSchemeDefinition>();
146 59 : poTilingScheme->pszName = pszName;
147 :
148 118 : OGRSpatialReference oSRS;
149 59 : if (oSRS.SetFromUserInput(poTM->crs().c_str()) != OGRERR_NONE)
150 : {
151 0 : return nullptr;
152 : }
153 59 : if (poTM->crs() == "http://www.opengis.net/def/crs/OGC/1.3/CRS84")
154 : {
155 6 : poTilingScheme->nEPSGCode = 4326;
156 : }
157 : else
158 : {
159 53 : const char *pszAuthName = oSRS.GetAuthorityName(nullptr);
160 53 : const char *pszAuthCode = oSRS.GetAuthorityCode(nullptr);
161 53 : if (pszAuthName == nullptr || !EQUAL(pszAuthName, "EPSG") ||
162 : pszAuthCode == nullptr)
163 : {
164 0 : CPLError(CE_Failure, CPLE_NotSupported,
165 : "Unsupported tiling scheme: only EPSG CRS supported");
166 0 : return nullptr;
167 : }
168 53 : poTilingScheme->nEPSGCode = atoi(pszAuthCode);
169 : }
170 59 : const auto &zoomLevel0 = poTM->tileMatrixList()[0];
171 59 : poTilingScheme->dfMinX = zoomLevel0.mTopLeftX;
172 59 : poTilingScheme->dfMaxY = zoomLevel0.mTopLeftY;
173 59 : poTilingScheme->nTileXCountZoomLevel0 = zoomLevel0.mMatrixWidth;
174 59 : poTilingScheme->nTileYCountZoomLevel0 = zoomLevel0.mMatrixHeight;
175 59 : poTilingScheme->nTileWidth = zoomLevel0.mTileWidth;
176 59 : poTilingScheme->nTileHeight = zoomLevel0.mTileHeight;
177 59 : poTilingScheme->dfPixelXSizeZoomLevel0 = zoomLevel0.mResX;
178 59 : poTilingScheme->dfPixelYSizeZoomLevel0 = zoomLevel0.mResY;
179 :
180 118 : const bool bInvertAxis = oSRS.EPSGTreatsAsLatLong() != FALSE ||
181 59 : oSRS.EPSGTreatsAsNorthingEasting() != FALSE;
182 59 : if (bInvertAxis)
183 : {
184 6 : std::swap(poTilingScheme->dfMinX, poTilingScheme->dfMaxY);
185 6 : std::swap(poTilingScheme->dfPixelXSizeZoomLevel0,
186 6 : poTilingScheme->dfPixelYSizeZoomLevel0);
187 : }
188 59 : return poTilingScheme;
189 : }
190 :
191 : static const char *pszCREATE_GPKG_GEOMETRY_COLUMNS =
192 : "CREATE TABLE gpkg_geometry_columns ("
193 : "table_name TEXT NOT NULL,"
194 : "column_name TEXT NOT NULL,"
195 : "geometry_type_name TEXT NOT NULL,"
196 : "srs_id INTEGER NOT NULL,"
197 : "z TINYINT NOT NULL,"
198 : "m TINYINT NOT NULL,"
199 : "CONSTRAINT pk_geom_cols PRIMARY KEY (table_name, column_name),"
200 : "CONSTRAINT uk_gc_table_name UNIQUE (table_name),"
201 : "CONSTRAINT fk_gc_tn FOREIGN KEY (table_name) REFERENCES "
202 : "gpkg_contents(table_name),"
203 : "CONSTRAINT fk_gc_srs FOREIGN KEY (srs_id) REFERENCES gpkg_spatial_ref_sys "
204 : "(srs_id)"
205 : ")";
206 :
207 : /* Only recent versions of SQLite will let us muck with application_id */
208 : /* via a PRAGMA statement, so we have to write directly into the */
209 : /* file header here. */
210 : /* We do this at the *end* of initialization so that there is */
211 : /* data to write down to a file, and we will have a writable file */
212 : /* once we close the SQLite connection */
213 708 : OGRErr GDALGeoPackageDataset::SetApplicationAndUserVersionId()
214 : {
215 708 : CPLAssert(hDB != nullptr);
216 :
217 708 : const CPLString osPragma(CPLString().Printf("PRAGMA application_id = %u;"
218 : "PRAGMA user_version = %u",
219 : m_nApplicationId,
220 1416 : m_nUserVersion));
221 1416 : return SQLCommand(hDB, osPragma.c_str());
222 : }
223 :
224 2029 : bool GDALGeoPackageDataset::CloseDB()
225 : {
226 2029 : OGRSQLiteUnregisterSQLFunctions(m_pSQLFunctionData);
227 2029 : m_pSQLFunctionData = nullptr;
228 2029 : return OGRSQLiteBaseDataSource::CloseDB();
229 : }
230 :
231 11 : bool GDALGeoPackageDataset::ReOpenDB()
232 : {
233 11 : CPLAssert(hDB != nullptr);
234 11 : CPLAssert(m_pszFilename != nullptr);
235 :
236 11 : FinishSpatialite();
237 :
238 11 : CloseDB();
239 :
240 : /* And re-open the file */
241 11 : return OpenOrCreateDB(SQLITE_OPEN_READWRITE);
242 : }
243 :
244 647 : static OGRErr GDALGPKGImportFromEPSG(OGRSpatialReference *poSpatialRef,
245 : int nEPSGCode)
246 : {
247 647 : CPLPushErrorHandler(CPLQuietErrorHandler);
248 647 : const OGRErr eErr = poSpatialRef->importFromEPSG(nEPSGCode);
249 647 : CPLPopErrorHandler();
250 647 : CPLErrorReset();
251 647 : return eErr;
252 : }
253 :
254 : OGRSpatialReference *
255 963 : GDALGeoPackageDataset::GetSpatialRef(int iSrsId, bool bFallbackToEPSG,
256 : bool bEmitErrorIfNotFound)
257 : {
258 : std::map<int, OGRSpatialReference *>::const_iterator oIter =
259 963 : m_oMapSrsIdToSrs.find(iSrsId);
260 963 : if (oIter != m_oMapSrsIdToSrs.end())
261 : {
262 64 : if (oIter->second == nullptr)
263 29 : return nullptr;
264 35 : oIter->second->Reference();
265 35 : return oIter->second;
266 : }
267 :
268 899 : if (iSrsId == 0 || iSrsId == -1)
269 : {
270 117 : OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
271 117 : poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
272 :
273 : // See corresponding tests in GDALGeoPackageDataset::GetSrsId
274 117 : if (iSrsId == 0)
275 : {
276 28 : poSpatialRef->SetGeogCS("Undefined geographic SRS", "unknown",
277 : "unknown", SRS_WGS84_SEMIMAJOR,
278 : SRS_WGS84_INVFLATTENING);
279 : }
280 89 : else if (iSrsId == -1)
281 : {
282 89 : poSpatialRef->SetLocalCS("Undefined Cartesian SRS");
283 89 : poSpatialRef->SetLinearUnits(SRS_UL_METER, 1.0);
284 : }
285 :
286 117 : m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
287 117 : poSpatialRef->Reference();
288 117 : return poSpatialRef;
289 : }
290 :
291 1564 : CPLString oSQL;
292 782 : oSQL.Printf("SELECT srs_name, definition, organization, "
293 : "organization_coordsys_id%s%s "
294 : "FROM gpkg_spatial_ref_sys WHERE "
295 : "srs_id = %d LIMIT 2",
296 782 : m_bHasDefinition12_063 ? ", definition_12_063" : "",
297 782 : m_bHasEpochColumn ? ", epoch" : "", iSrsId);
298 :
299 1564 : auto oResult = SQLQuery(hDB, oSQL.c_str());
300 :
301 782 : if (!oResult || oResult->RowCount() != 1)
302 : {
303 11 : if (bFallbackToEPSG)
304 : {
305 6 : CPLDebug("GPKG",
306 : "unable to read srs_id '%d' from gpkg_spatial_ref_sys",
307 : iSrsId);
308 6 : OGRSpatialReference *poSRS = new OGRSpatialReference();
309 6 : if (poSRS->importFromEPSG(iSrsId) == OGRERR_NONE)
310 : {
311 5 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
312 5 : return poSRS;
313 : }
314 1 : poSRS->Release();
315 : }
316 5 : else if (bEmitErrorIfNotFound)
317 : {
318 2 : CPLError(CE_Warning, CPLE_AppDefined,
319 : "unable to read srs_id '%d' from gpkg_spatial_ref_sys",
320 : iSrsId);
321 2 : m_oMapSrsIdToSrs[iSrsId] = nullptr;
322 : }
323 6 : return nullptr;
324 : }
325 :
326 771 : const char *pszName = oResult->GetValue(0, 0);
327 771 : if (pszName && EQUAL(pszName, "Undefined SRS"))
328 : {
329 295 : m_oMapSrsIdToSrs[iSrsId] = nullptr;
330 295 : return nullptr;
331 : }
332 476 : const char *pszWkt = oResult->GetValue(1, 0);
333 476 : if (pszWkt == nullptr)
334 0 : return nullptr;
335 476 : const char *pszOrganization = oResult->GetValue(2, 0);
336 476 : const char *pszOrganizationCoordsysID = oResult->GetValue(3, 0);
337 : const char *pszWkt2 =
338 476 : m_bHasDefinition12_063 ? oResult->GetValue(4, 0) : nullptr;
339 476 : if (pszWkt2 && !EQUAL(pszWkt2, "undefined"))
340 71 : pszWkt = pszWkt2;
341 : const char *pszCoordinateEpoch =
342 476 : m_bHasEpochColumn ? oResult->GetValue(5, 0) : nullptr;
343 : const double dfCoordinateEpoch =
344 476 : pszCoordinateEpoch ? CPLAtof(pszCoordinateEpoch) : 0.0;
345 :
346 476 : OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
347 476 : poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
348 : // Try to import first from EPSG code, and then from WKT
349 476 : if (!(pszOrganization && pszOrganizationCoordsysID &&
350 476 : EQUAL(pszOrganization, "EPSG") &&
351 457 : (atoi(pszOrganizationCoordsysID) == iSrsId ||
352 4 : (dfCoordinateEpoch > 0 && strstr(pszWkt, "DYNAMIC[") == nullptr)) &&
353 457 : GDALGPKGImportFromEPSG(
354 952 : poSpatialRef, atoi(pszOrganizationCoordsysID)) == OGRERR_NONE) &&
355 19 : poSpatialRef->importFromWkt(pszWkt) != OGRERR_NONE)
356 : {
357 0 : CPLError(CE_Warning, CPLE_AppDefined,
358 : "Unable to parse srs_id '%d' well-known text '%s'", iSrsId,
359 : pszWkt);
360 0 : delete poSpatialRef;
361 0 : m_oMapSrsIdToSrs[iSrsId] = nullptr;
362 0 : return nullptr;
363 : }
364 :
365 476 : poSpatialRef->StripTOWGS84IfKnownDatumAndAllowed();
366 476 : poSpatialRef->SetCoordinateEpoch(dfCoordinateEpoch);
367 476 : m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
368 476 : poSpatialRef->Reference();
369 476 : return poSpatialRef;
370 : }
371 :
372 217 : const char *GDALGeoPackageDataset::GetSrsName(const OGRSpatialReference &oSRS)
373 : {
374 217 : const char *pszName = oSRS.GetName();
375 217 : if (pszName)
376 217 : return pszName;
377 :
378 : // Something odd. Return empty.
379 0 : return "Unnamed SRS";
380 : }
381 :
382 : /* Add the definition_12_063 column to an existing gpkg_spatial_ref_sys table */
383 6 : bool GDALGeoPackageDataset::ConvertGpkgSpatialRefSysToExtensionWkt2(
384 : bool bForceEpoch)
385 : {
386 6 : const bool bAddEpoch = (m_nUserVersion >= GPKG_1_4_VERSION || bForceEpoch);
387 : auto oResultTable = SQLQuery(
388 : hDB, "SELECT srs_name, srs_id, organization, organization_coordsys_id, "
389 12 : "definition, description FROM gpkg_spatial_ref_sys LIMIT 100000");
390 6 : if (!oResultTable)
391 0 : return false;
392 :
393 : // Temporary remove foreign key checks
394 : const GPKGTemporaryForeignKeyCheckDisabler
395 6 : oGPKGTemporaryForeignKeyCheckDisabler(this);
396 :
397 6 : bool bRet = SoftStartTransaction() == OGRERR_NONE;
398 :
399 6 : if (bRet)
400 : {
401 : std::string osSQL("CREATE TABLE gpkg_spatial_ref_sys_temp ("
402 : "srs_name TEXT NOT NULL,"
403 : "srs_id INTEGER NOT NULL PRIMARY KEY,"
404 : "organization TEXT NOT NULL,"
405 : "organization_coordsys_id INTEGER NOT NULL,"
406 : "definition TEXT NOT NULL,"
407 : "description TEXT, "
408 6 : "definition_12_063 TEXT NOT NULL");
409 6 : if (bAddEpoch)
410 3 : osSQL += ", epoch DOUBLE";
411 6 : osSQL += ")";
412 6 : bRet = SQLCommand(hDB, osSQL.c_str()) == OGRERR_NONE;
413 : }
414 :
415 6 : if (bRet)
416 : {
417 28 : for (int i = 0; bRet && i < oResultTable->RowCount(); i++)
418 : {
419 22 : const char *pszSrsName = oResultTable->GetValue(0, i);
420 22 : const char *pszSrsId = oResultTable->GetValue(1, i);
421 22 : const char *pszOrganization = oResultTable->GetValue(2, i);
422 : const char *pszOrganizationCoordsysID =
423 22 : oResultTable->GetValue(3, i);
424 22 : const char *pszDefinition = oResultTable->GetValue(4, i);
425 : if (pszSrsName == nullptr || pszSrsId == nullptr ||
426 : pszOrganization == nullptr ||
427 : pszOrganizationCoordsysID == nullptr)
428 : {
429 : // should not happen as there are NOT NULL constraints
430 : // But a database could lack such NOT NULL constraints or have
431 : // large values that would cause a memory allocation failure.
432 : }
433 22 : const char *pszDescription = oResultTable->GetValue(5, i);
434 : char *pszSQL;
435 :
436 44 : OGRSpatialReference oSRS;
437 22 : if (pszOrganization && pszOrganizationCoordsysID &&
438 22 : EQUAL(pszOrganization, "EPSG"))
439 : {
440 8 : oSRS.importFromEPSG(atoi(pszOrganizationCoordsysID));
441 : }
442 30 : if (!oSRS.IsEmpty() && pszDefinition &&
443 8 : !EQUAL(pszDefinition, "undefined"))
444 : {
445 8 : oSRS.SetFromUserInput(pszDefinition);
446 : }
447 22 : char *pszWKT2 = nullptr;
448 22 : if (!oSRS.IsEmpty())
449 : {
450 8 : const char *const apszOptionsWkt2[] = {"FORMAT=WKT2_2015",
451 : nullptr};
452 8 : oSRS.exportToWkt(&pszWKT2, apszOptionsWkt2);
453 8 : if (pszWKT2 && pszWKT2[0] == '\0')
454 : {
455 0 : CPLFree(pszWKT2);
456 0 : pszWKT2 = nullptr;
457 : }
458 : }
459 22 : if (pszWKT2 == nullptr)
460 : {
461 14 : pszWKT2 = CPLStrdup("undefined");
462 : }
463 :
464 22 : if (pszDescription)
465 : {
466 19 : pszSQL = sqlite3_mprintf(
467 : "INSERT INTO gpkg_spatial_ref_sys_temp(srs_name, srs_id, "
468 : "organization, organization_coordsys_id, definition, "
469 : "description, definition_12_063) VALUES ('%q', '%q', '%q', "
470 : "'%q', '%q', '%q', '%q')",
471 : pszSrsName, pszSrsId, pszOrganization,
472 : pszOrganizationCoordsysID, pszDefinition, pszDescription,
473 : pszWKT2);
474 : }
475 : else
476 : {
477 3 : pszSQL = sqlite3_mprintf(
478 : "INSERT INTO gpkg_spatial_ref_sys_temp(srs_name, srs_id, "
479 : "organization, organization_coordsys_id, definition, "
480 : "description, definition_12_063) VALUES ('%q', '%q', '%q', "
481 : "'%q', '%q', NULL, '%q')",
482 : pszSrsName, pszSrsId, pszOrganization,
483 : pszOrganizationCoordsysID, pszDefinition, pszWKT2);
484 : }
485 :
486 22 : CPLFree(pszWKT2);
487 22 : bRet &= SQLCommand(hDB, pszSQL) == OGRERR_NONE;
488 22 : sqlite3_free(pszSQL);
489 : }
490 : }
491 :
492 6 : if (bRet)
493 : {
494 6 : bRet =
495 6 : SQLCommand(hDB, "DROP TABLE gpkg_spatial_ref_sys") == OGRERR_NONE;
496 : }
497 6 : if (bRet)
498 : {
499 6 : bRet = SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys_temp RENAME "
500 : "TO gpkg_spatial_ref_sys") == OGRERR_NONE;
501 : }
502 6 : if (bRet)
503 : {
504 12 : bRet = OGRERR_NONE == CreateExtensionsTableIfNecessary() &&
505 6 : OGRERR_NONE == SQLCommand(hDB,
506 : "INSERT INTO gpkg_extensions "
507 : "(table_name, column_name, "
508 : "extension_name, definition, scope) "
509 : "VALUES "
510 : "('gpkg_spatial_ref_sys', "
511 : "'definition_12_063', 'gpkg_crs_wkt', "
512 : "'http://www.geopackage.org/spec120/"
513 : "#extension_crs_wkt', 'read-write')");
514 : }
515 6 : if (bRet && bAddEpoch)
516 : {
517 3 : bRet =
518 : OGRERR_NONE ==
519 3 : SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
520 : "'gpkg_crs_wkt_1_1' "
521 6 : "WHERE extension_name = 'gpkg_crs_wkt'") &&
522 : OGRERR_NONE ==
523 3 : SQLCommand(
524 : hDB,
525 : "INSERT INTO gpkg_extensions "
526 : "(table_name, column_name, extension_name, definition, "
527 : "scope) "
528 : "VALUES "
529 : "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
530 : "'http://www.geopackage.org/spec/#extension_crs_wkt', "
531 : "'read-write')");
532 : }
533 6 : if (bRet)
534 : {
535 6 : SoftCommitTransaction();
536 6 : m_bHasDefinition12_063 = true;
537 6 : if (bAddEpoch)
538 3 : m_bHasEpochColumn = true;
539 : }
540 : else
541 : {
542 0 : SoftRollbackTransaction();
543 : }
544 :
545 6 : return bRet;
546 : }
547 :
548 703 : int GDALGeoPackageDataset::GetSrsId(const OGRSpatialReference *poSRSIn)
549 : {
550 703 : const char *pszName = poSRSIn ? poSRSIn->GetName() : nullptr;
551 1020 : if (!poSRSIn || poSRSIn->IsEmpty() ||
552 317 : (pszName && EQUAL(pszName, "Undefined SRS")))
553 : {
554 388 : OGRErr err = OGRERR_NONE;
555 388 : const int nSRSId = SQLGetInteger(
556 : hDB,
557 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE srs_name = "
558 : "'Undefined SRS' AND organization = 'GDAL'",
559 : &err);
560 388 : if (err == OGRERR_NONE)
561 52 : return nSRSId;
562 :
563 : // The below WKT definitions are somehow questionable (using a unknown
564 : // unit). For GDAL >= 3.9, they won't be used. They will only be used
565 : // for earlier versions.
566 : const char *pszSQL;
567 : #define UNDEFINED_CRS_SRS_ID 99999
568 : static_assert(UNDEFINED_CRS_SRS_ID == FIRST_CUSTOM_SRSID - 1);
569 : #define STRINGIFY(x) #x
570 : #define XSTRINGIFY(x) STRINGIFY(x)
571 336 : if (m_bHasDefinition12_063)
572 : {
573 : /* clang-format off */
574 1 : pszSQL =
575 : "INSERT INTO gpkg_spatial_ref_sys "
576 : "(srs_name,srs_id,organization,organization_coordsys_id,"
577 : "definition, definition_12_063, description) VALUES "
578 : "('Undefined SRS'," XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ",'GDAL',"
579 : XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ","
580 : "'LOCAL_CS[\"Undefined SRS\",LOCAL_DATUM[\"unknown\",32767],"
581 : "UNIT[\"unknown\",0],AXIS[\"Easting\",EAST],"
582 : "AXIS[\"Northing\",NORTH]]',"
583 : "'ENGCRS[\"Undefined SRS\",EDATUM[\"unknown\"],CS[Cartesian,2],"
584 : "AXIS[\"easting\",east,ORDER[1],LENGTHUNIT[\"unknown\",0]],"
585 : "AXIS[\"northing\",north,ORDER[2],LENGTHUNIT[\"unknown\",0]]]',"
586 : "'Custom undefined coordinate reference system')";
587 : /* clang-format on */
588 : }
589 : else
590 : {
591 : /* clang-format off */
592 335 : pszSQL =
593 : "INSERT INTO gpkg_spatial_ref_sys "
594 : "(srs_name,srs_id,organization,organization_coordsys_id,"
595 : "definition, description) VALUES "
596 : "('Undefined SRS'," XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ",'GDAL',"
597 : XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ","
598 : "'LOCAL_CS[\"Undefined SRS\",LOCAL_DATUM[\"unknown\",32767],"
599 : "UNIT[\"unknown\",0],AXIS[\"Easting\",EAST],"
600 : "AXIS[\"Northing\",NORTH]]',"
601 : "'Custom undefined coordinate reference system')";
602 : /* clang-format on */
603 : }
604 336 : if (SQLCommand(hDB, pszSQL) == OGRERR_NONE)
605 336 : return UNDEFINED_CRS_SRS_ID;
606 : #undef UNDEFINED_CRS_SRS_ID
607 : #undef XSTRINGIFY
608 : #undef STRINGIFY
609 0 : return -1;
610 : }
611 :
612 630 : std::unique_ptr<OGRSpatialReference> poSRS(poSRSIn->Clone());
613 :
614 315 : if (poSRS->IsGeographic() || poSRS->IsLocal())
615 : {
616 : // See corresponding tests in GDALGeoPackageDataset::GetSpatialRef
617 120 : if (pszName != nullptr && strlen(pszName) > 0)
618 : {
619 120 : if (EQUAL(pszName, "Undefined geographic SRS"))
620 2 : return 0;
621 :
622 118 : if (EQUAL(pszName, "Undefined Cartesian SRS"))
623 1 : return -1;
624 : }
625 : }
626 :
627 312 : const char *pszAuthorityName = poSRS->GetAuthorityName(nullptr);
628 :
629 312 : if (pszAuthorityName == nullptr || strlen(pszAuthorityName) == 0)
630 : {
631 : // Try to force identify an EPSG code.
632 24 : poSRS->AutoIdentifyEPSG();
633 :
634 24 : pszAuthorityName = poSRS->GetAuthorityName(nullptr);
635 24 : if (pszAuthorityName != nullptr && EQUAL(pszAuthorityName, "EPSG"))
636 : {
637 0 : const char *pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
638 0 : if (pszAuthorityCode != nullptr && strlen(pszAuthorityCode) > 0)
639 : {
640 : /* Import 'clean' SRS */
641 0 : poSRS->importFromEPSG(atoi(pszAuthorityCode));
642 :
643 0 : pszAuthorityName = poSRS->GetAuthorityName(nullptr);
644 : }
645 : }
646 :
647 24 : poSRS->SetCoordinateEpoch(poSRSIn->GetCoordinateEpoch());
648 : }
649 :
650 : // Check whether the EPSG authority code is already mapped to a
651 : // SRS ID.
652 312 : char *pszSQL = nullptr;
653 312 : int nSRSId = DEFAULT_SRID;
654 312 : int nAuthorityCode = 0;
655 312 : OGRErr err = OGRERR_NONE;
656 312 : bool bCanUseAuthorityCode = false;
657 312 : const char *const apszIsSameOptions[] = {
658 : "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES",
659 : "IGNORE_COORDINATE_EPOCH=YES", nullptr};
660 312 : if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0)
661 : {
662 288 : const char *pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
663 288 : if (pszAuthorityCode)
664 : {
665 288 : if (CPLGetValueType(pszAuthorityCode) == CPL_VALUE_INTEGER)
666 : {
667 288 : nAuthorityCode = atoi(pszAuthorityCode);
668 : }
669 : else
670 : {
671 0 : CPLDebug("GPKG",
672 : "SRS has %s:%s identification, but the code not "
673 : "being an integer value cannot be stored as such "
674 : "in the database.",
675 : pszAuthorityName, pszAuthorityCode);
676 0 : pszAuthorityName = nullptr;
677 : }
678 : }
679 : }
680 :
681 600 : if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
682 288 : poSRSIn->GetCoordinateEpoch() == 0)
683 : {
684 : pszSQL =
685 283 : sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
686 : "upper(organization) = upper('%q') AND "
687 : "organization_coordsys_id = %d",
688 : pszAuthorityName, nAuthorityCode);
689 :
690 283 : nSRSId = SQLGetInteger(hDB, pszSQL, &err);
691 283 : sqlite3_free(pszSQL);
692 :
693 : // Got a match? Return it!
694 283 : if (OGRERR_NONE == err)
695 : {
696 91 : auto poRefSRS = GetSpatialRef(nSRSId);
697 : bool bOK =
698 91 : (poRefSRS == nullptr ||
699 92 : poSRS->IsSame(poRefSRS, apszIsSameOptions) ||
700 1 : !CPLTestBool(CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")));
701 91 : if (poRefSRS)
702 91 : poRefSRS->Release();
703 91 : if (bOK)
704 : {
705 90 : return nSRSId;
706 : }
707 : else
708 : {
709 1 : CPLError(CE_Warning, CPLE_AppDefined,
710 : "Passed SRS uses %s:%d identification, but its "
711 : "definition is not compatible with the "
712 : "definition of that object already in the database. "
713 : "Registering it as a new entry into the database.",
714 : pszAuthorityName, nAuthorityCode);
715 1 : pszAuthorityName = nullptr;
716 1 : nAuthorityCode = 0;
717 : }
718 : }
719 : }
720 :
721 : // Translate SRS to WKT.
722 222 : CPLCharUniquePtr pszWKT1;
723 222 : CPLCharUniquePtr pszWKT2_2015;
724 222 : CPLCharUniquePtr pszWKT2_2019;
725 222 : const char *const apszOptionsWkt1[] = {"FORMAT=WKT1_GDAL", nullptr};
726 222 : const char *const apszOptionsWkt2_2015[] = {"FORMAT=WKT2_2015", nullptr};
727 222 : const char *const apszOptionsWkt2_2019[] = {"FORMAT=WKT2_2019", nullptr};
728 :
729 444 : std::string osEpochTest;
730 222 : if (poSRSIn->GetCoordinateEpoch() > 0 && m_bHasEpochColumn)
731 : {
732 : osEpochTest =
733 3 : CPLSPrintf(" AND epoch = %.18g", poSRSIn->GetCoordinateEpoch());
734 : }
735 :
736 222 : if (!(poSRS->IsGeographic() && poSRS->GetAxesCount() == 3))
737 : {
738 215 : char *pszTmp = nullptr;
739 215 : poSRS->exportToWkt(&pszTmp, apszOptionsWkt1);
740 215 : pszWKT1.reset(pszTmp);
741 215 : if (pszWKT1 && pszWKT1.get()[0] == '\0')
742 : {
743 0 : pszWKT1.reset();
744 : }
745 : }
746 : {
747 222 : char *pszTmp = nullptr;
748 222 : poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2015);
749 222 : pszWKT2_2015.reset(pszTmp);
750 222 : if (pszWKT2_2015 && pszWKT2_2015.get()[0] == '\0')
751 : {
752 0 : pszWKT2_2015.reset();
753 : }
754 : }
755 : {
756 222 : char *pszTmp = nullptr;
757 222 : poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2019);
758 222 : pszWKT2_2019.reset(pszTmp);
759 222 : if (pszWKT2_2019 && pszWKT2_2019.get()[0] == '\0')
760 : {
761 0 : pszWKT2_2019.reset();
762 : }
763 : }
764 :
765 222 : if (!pszWKT1 && !pszWKT2_2015 && !pszWKT2_2019)
766 : {
767 0 : return DEFAULT_SRID;
768 : }
769 :
770 222 : if (poSRSIn->GetCoordinateEpoch() == 0 || m_bHasEpochColumn)
771 : {
772 : // Search if there is already an existing entry with this WKT
773 219 : if (m_bHasDefinition12_063 && (pszWKT2_2015 || pszWKT2_2019))
774 : {
775 39 : if (pszWKT1)
776 : {
777 136 : pszSQL = sqlite3_mprintf(
778 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
779 : "(definition = '%q' OR definition_12_063 IN ('%q','%q'))%s",
780 : pszWKT1.get(),
781 68 : pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
782 68 : pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
783 : osEpochTest.c_str());
784 : }
785 : else
786 : {
787 20 : pszSQL = sqlite3_mprintf(
788 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
789 : "definition_12_063 IN ('%q', '%q')%s",
790 10 : pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
791 10 : pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
792 : osEpochTest.c_str());
793 : }
794 : }
795 180 : else if (pszWKT1)
796 : {
797 : pszSQL =
798 178 : sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
799 : "definition = '%q'%s",
800 : pszWKT1.get(), osEpochTest.c_str());
801 : }
802 : else
803 : {
804 2 : pszSQL = nullptr;
805 : }
806 219 : if (pszSQL)
807 : {
808 217 : nSRSId = SQLGetInteger(hDB, pszSQL, &err);
809 217 : sqlite3_free(pszSQL);
810 217 : if (OGRERR_NONE == err)
811 : {
812 5 : return nSRSId;
813 : }
814 : }
815 : }
816 :
817 412 : if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
818 195 : poSRSIn->GetCoordinateEpoch() == 0)
819 : {
820 191 : bool bTryToReuseSRSId = true;
821 191 : if (EQUAL(pszAuthorityName, "EPSG"))
822 : {
823 380 : OGRSpatialReference oSRS_EPSG;
824 190 : if (GDALGPKGImportFromEPSG(&oSRS_EPSG, nAuthorityCode) ==
825 : OGRERR_NONE)
826 : {
827 191 : if (!poSRS->IsSame(&oSRS_EPSG, apszIsSameOptions) &&
828 1 : CPLTestBool(
829 : CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")))
830 : {
831 1 : bTryToReuseSRSId = false;
832 1 : CPLError(
833 : CE_Warning, CPLE_AppDefined,
834 : "Passed SRS uses %s:%d identification, but its "
835 : "definition is not compatible with the "
836 : "official definition of the object. "
837 : "Registering it as a non-%s entry into the database.",
838 : pszAuthorityName, nAuthorityCode, pszAuthorityName);
839 1 : pszAuthorityName = nullptr;
840 1 : nAuthorityCode = 0;
841 : }
842 : }
843 : }
844 191 : if (bTryToReuseSRSId)
845 : {
846 : // No match, but maybe we can use the nAuthorityCode as the nSRSId?
847 190 : pszSQL = sqlite3_mprintf(
848 : "SELECT Count(*) FROM gpkg_spatial_ref_sys WHERE "
849 : "srs_id = %d",
850 : nAuthorityCode);
851 :
852 : // Yep, we can!
853 190 : if (SQLGetInteger(hDB, pszSQL, nullptr) == 0)
854 189 : bCanUseAuthorityCode = true;
855 190 : sqlite3_free(pszSQL);
856 : }
857 : }
858 :
859 217 : bool bConvertGpkgSpatialRefSysToExtensionWkt2 = false;
860 217 : bool bForceEpoch = false;
861 219 : if (!m_bHasDefinition12_063 && pszWKT1 == nullptr &&
862 2 : (pszWKT2_2015 != nullptr || pszWKT2_2019 != nullptr))
863 : {
864 2 : bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
865 : }
866 :
867 : // Add epoch column if needed
868 217 : if (poSRSIn->GetCoordinateEpoch() > 0 && !m_bHasEpochColumn)
869 : {
870 3 : if (m_bHasDefinition12_063)
871 : {
872 0 : if (SoftStartTransaction() != OGRERR_NONE)
873 0 : return DEFAULT_SRID;
874 0 : if (SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys "
875 0 : "ADD COLUMN epoch DOUBLE") != OGRERR_NONE ||
876 0 : SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
877 : "'gpkg_crs_wkt_1_1' "
878 : "WHERE extension_name = 'gpkg_crs_wkt'") !=
879 0 : OGRERR_NONE ||
880 0 : SQLCommand(
881 : hDB,
882 : "INSERT INTO gpkg_extensions "
883 : "(table_name, column_name, extension_name, definition, "
884 : "scope) "
885 : "VALUES "
886 : "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
887 : "'http://www.geopackage.org/spec/#extension_crs_wkt', "
888 : "'read-write')") != OGRERR_NONE)
889 : {
890 0 : SoftRollbackTransaction();
891 0 : return DEFAULT_SRID;
892 : }
893 :
894 0 : if (SoftCommitTransaction() != OGRERR_NONE)
895 0 : return DEFAULT_SRID;
896 :
897 0 : m_bHasEpochColumn = true;
898 : }
899 : else
900 : {
901 3 : bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
902 3 : bForceEpoch = true;
903 : }
904 : }
905 :
906 222 : if (bConvertGpkgSpatialRefSysToExtensionWkt2 &&
907 5 : !ConvertGpkgSpatialRefSysToExtensionWkt2(bForceEpoch))
908 : {
909 0 : return DEFAULT_SRID;
910 : }
911 :
912 : // Reuse the authority code number as SRS_ID if we can
913 217 : if (bCanUseAuthorityCode)
914 : {
915 189 : nSRSId = nAuthorityCode;
916 : }
917 : // Otherwise, generate a new SRS_ID number (max + 1)
918 : else
919 : {
920 : // Get the current maximum srid in the srs table.
921 28 : const int nMaxSRSId = SQLGetInteger(
922 : hDB, "SELECT MAX(srs_id) FROM gpkg_spatial_ref_sys", nullptr);
923 28 : nSRSId = std::max(FIRST_CUSTOM_SRSID, nMaxSRSId + 1);
924 : }
925 :
926 434 : std::string osEpochColumn;
927 217 : std::string osEpochVal;
928 217 : if (poSRSIn->GetCoordinateEpoch() > 0)
929 : {
930 5 : osEpochColumn = ", epoch";
931 5 : osEpochVal = CPLSPrintf(", %.18g", poSRSIn->GetCoordinateEpoch());
932 : }
933 :
934 : // Add new SRS row to gpkg_spatial_ref_sys.
935 217 : if (m_bHasDefinition12_063)
936 : {
937 : // Force WKT2_2019 when we have a dynamic CRS and coordinate epoch
938 41 : const char *pszWKT2 = poSRSIn->IsDynamic() &&
939 8 : poSRSIn->GetCoordinateEpoch() > 0 &&
940 1 : pszWKT2_2019
941 1 : ? pszWKT2_2019.get()
942 40 : : pszWKT2_2015 ? pszWKT2_2015.get()
943 87 : : pszWKT2_2019.get();
944 :
945 41 : if (pszAuthorityName != nullptr && nAuthorityCode > 0)
946 : {
947 90 : pszSQL = sqlite3_mprintf(
948 : "INSERT INTO gpkg_spatial_ref_sys "
949 : "(srs_name,srs_id,organization,organization_coordsys_id,"
950 : "definition, definition_12_063%s) VALUES "
951 : "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
952 30 : osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId,
953 : pszAuthorityName, nAuthorityCode,
954 57 : pszWKT1 ? pszWKT1.get() : "undefined",
955 : pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
956 : }
957 : else
958 : {
959 33 : pszSQL = sqlite3_mprintf(
960 : "INSERT INTO gpkg_spatial_ref_sys "
961 : "(srs_name,srs_id,organization,organization_coordsys_id,"
962 : "definition, definition_12_063%s) VALUES "
963 : "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
964 11 : osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId, "NONE",
965 20 : nSRSId, pszWKT1 ? pszWKT1.get() : "undefined",
966 : pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
967 : }
968 : }
969 : else
970 : {
971 176 : if (pszAuthorityName != nullptr && nAuthorityCode > 0)
972 : {
973 328 : pszSQL = sqlite3_mprintf(
974 : "INSERT INTO gpkg_spatial_ref_sys "
975 : "(srs_name,srs_id,organization,organization_coordsys_id,"
976 : "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
977 164 : GetSrsName(*poSRS), nSRSId, pszAuthorityName, nAuthorityCode,
978 328 : pszWKT1 ? pszWKT1.get() : "undefined");
979 : }
980 : else
981 : {
982 24 : pszSQL = sqlite3_mprintf(
983 : "INSERT INTO gpkg_spatial_ref_sys "
984 : "(srs_name,srs_id,organization,organization_coordsys_id,"
985 : "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
986 12 : GetSrsName(*poSRS), nSRSId, "NONE", nSRSId,
987 24 : pszWKT1 ? pszWKT1.get() : "undefined");
988 : }
989 : }
990 :
991 : // Add new row to gpkg_spatial_ref_sys.
992 217 : CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
993 :
994 : // Free everything that was allocated.
995 217 : sqlite3_free(pszSQL);
996 :
997 217 : return nSRSId;
998 : }
999 :
1000 : /************************************************************************/
1001 : /* GDALGeoPackageDataset() */
1002 : /************************************************************************/
1003 :
1004 2018 : GDALGeoPackageDataset::GDALGeoPackageDataset()
1005 : : m_nApplicationId(GPKG_APPLICATION_ID), m_nUserVersion(GPKG_1_2_VERSION),
1006 : m_papoLayers(nullptr), m_nLayers(0),
1007 : #ifdef ENABLE_GPKG_OGR_CONTENTS
1008 : m_bHasGPKGOGRContents(false),
1009 : #endif
1010 : m_bHasGPKGGeometryColumns(false), m_bHasDefinition12_063(false),
1011 : m_bIdentifierAsCO(false), m_bDescriptionAsCO(false),
1012 : m_bHasReadMetadataFromStorage(false), m_bMetadataDirty(false),
1013 : m_bRecordInsertedInGPKGContent(false), m_bGeoTransformValid(false),
1014 : m_nSRID(-1), // Unknown Cartesian.
1015 : m_dfTMSMinX(0.0), m_dfTMSMaxY(0.0), m_nOverviewCount(0),
1016 : m_papoOverviewDS(nullptr), m_bZoomOther(false), m_bInFlushCache(false),
1017 : m_osTilingScheme("CUSTOM"), m_bMapTableToExtensionsBuilt(false),
1018 2018 : m_bMapTableToContentsBuilt(false)
1019 : {
1020 2018 : m_adfGeoTransform[0] = 0.0;
1021 2018 : m_adfGeoTransform[1] = 1.0;
1022 2018 : m_adfGeoTransform[2] = 0.0;
1023 2018 : m_adfGeoTransform[3] = 0.0;
1024 2018 : m_adfGeoTransform[4] = 0.0;
1025 2018 : m_adfGeoTransform[5] = 1.0;
1026 2018 : }
1027 :
1028 : /************************************************************************/
1029 : /* ~GDALGeoPackageDataset() */
1030 : /************************************************************************/
1031 :
1032 4036 : GDALGeoPackageDataset::~GDALGeoPackageDataset()
1033 : {
1034 2018 : GDALGeoPackageDataset::Close();
1035 4036 : }
1036 :
1037 : /************************************************************************/
1038 : /* Close() */
1039 : /************************************************************************/
1040 :
1041 3380 : CPLErr GDALGeoPackageDataset::Close()
1042 : {
1043 3380 : CPLErr eErr = CE_None;
1044 3380 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
1045 : {
1046 1209 : if (eAccess == GA_Update && m_poParentDS == nullptr &&
1047 3227 : !m_osRasterTable.empty() && !m_bGeoTransformValid)
1048 : {
1049 3 : CPLError(CE_Failure, CPLE_AppDefined,
1050 : "Raster table %s not correctly initialized due to missing "
1051 : "call to SetGeoTransform()",
1052 : m_osRasterTable.c_str());
1053 : }
1054 :
1055 2018 : if (GDALGeoPackageDataset::FlushCache(true) != CE_None)
1056 7 : eErr = CE_Failure;
1057 :
1058 : // Destroy bands now since we don't want
1059 : // GDALGPKGMBTilesLikeRasterBand::FlushCache() to run after dataset
1060 : // destruction
1061 3769 : for (int i = 0; i < nBands; i++)
1062 1751 : delete papoBands[i];
1063 2018 : nBands = 0;
1064 2018 : CPLFree(papoBands);
1065 2018 : papoBands = nullptr;
1066 :
1067 : // Destroy overviews before cleaning m_hTempDB as they could still
1068 : // need it
1069 2339 : for (int i = 0; i < m_nOverviewCount; i++)
1070 321 : delete m_papoOverviewDS[i];
1071 :
1072 2018 : if (m_poParentDS)
1073 : {
1074 321 : hDB = nullptr;
1075 : }
1076 :
1077 5417 : for (int i = 0; i < m_nLayers; i++)
1078 3399 : delete m_papoLayers[i];
1079 :
1080 2018 : CPLFree(m_papoLayers);
1081 2018 : CPLFree(m_papoOverviewDS);
1082 :
1083 : std::map<int, OGRSpatialReference *>::iterator oIter =
1084 2018 : m_oMapSrsIdToSrs.begin();
1085 2908 : for (; oIter != m_oMapSrsIdToSrs.end(); ++oIter)
1086 : {
1087 890 : OGRSpatialReference *poSRS = oIter->second;
1088 890 : if (poSRS)
1089 593 : poSRS->Release();
1090 : }
1091 :
1092 2018 : if (!CloseDB())
1093 0 : eErr = CE_Failure;
1094 :
1095 2018 : if (OGRSQLiteBaseDataSource::Close() != CE_None)
1096 0 : eErr = CE_Failure;
1097 : }
1098 3380 : return eErr;
1099 : }
1100 :
1101 : /************************************************************************/
1102 : /* ICanIWriteBlock() */
1103 : /************************************************************************/
1104 :
1105 5664 : bool GDALGeoPackageDataset::ICanIWriteBlock()
1106 : {
1107 5664 : if (!GetUpdate())
1108 : {
1109 0 : CPLError(
1110 : CE_Failure, CPLE_NotSupported,
1111 : "IWriteBlock() not supported on dataset opened in read-only mode");
1112 0 : return false;
1113 : }
1114 :
1115 5664 : if (m_pabyCachedTiles == nullptr)
1116 : {
1117 0 : return false;
1118 : }
1119 :
1120 5664 : if (!m_bGeoTransformValid || m_nSRID == UNKNOWN_SRID)
1121 : {
1122 0 : CPLError(CE_Failure, CPLE_NotSupported,
1123 : "IWriteBlock() not supported if georeferencing not set");
1124 0 : return false;
1125 : }
1126 5664 : return true;
1127 : }
1128 :
1129 : /************************************************************************/
1130 : /* IRasterIO() */
1131 : /************************************************************************/
1132 :
1133 112 : CPLErr GDALGeoPackageDataset::IRasterIO(
1134 : GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
1135 : void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
1136 : int nBandCount, int *panBandMap, GSpacing nPixelSpace, GSpacing nLineSpace,
1137 : GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
1138 :
1139 : {
1140 112 : CPLErr eErr = OGRSQLiteBaseDataSource::IRasterIO(
1141 : eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
1142 : eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace, nBandSpace,
1143 : psExtraArg);
1144 :
1145 : // If writing all bands, in non-shifted mode, flush all entirely written
1146 : // tiles This can avoid "stressing" the block cache with too many dirty
1147 : // blocks. Note: this logic would be useless with a per-dataset block cache.
1148 112 : if (eErr == CE_None && eRWFlag == GF_Write && nXSize == nBufXSize &&
1149 103 : nYSize == nBufYSize && nBandCount == nBands &&
1150 100 : m_nShiftXPixelsMod == 0 && m_nShiftYPixelsMod == 0)
1151 : {
1152 : auto poBand =
1153 96 : cpl::down_cast<GDALGPKGMBTilesLikeRasterBand *>(GetRasterBand(1));
1154 : int nBlockXSize, nBlockYSize;
1155 96 : poBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
1156 96 : const int nBlockXStart = DIV_ROUND_UP(nXOff, nBlockXSize);
1157 96 : const int nBlockYStart = DIV_ROUND_UP(nYOff, nBlockYSize);
1158 96 : const int nBlockXEnd = (nXOff + nXSize) / nBlockXSize;
1159 96 : const int nBlockYEnd = (nYOff + nYSize) / nBlockYSize;
1160 248 : for (int nBlockY = nBlockXStart; nBlockY < nBlockYEnd; nBlockY++)
1161 : {
1162 4361 : for (int nBlockX = nBlockYStart; nBlockX < nBlockXEnd; nBlockX++)
1163 : {
1164 : GDALRasterBlock *poBlock =
1165 4209 : poBand->AccessibleTryGetLockedBlockRef(nBlockX, nBlockY);
1166 4209 : if (poBlock)
1167 : {
1168 : // GetDirty() should be true in most situation (otherwise
1169 : // it means the block cache is under extreme pressure!)
1170 4203 : if (poBlock->GetDirty())
1171 : {
1172 : // IWriteBlock() on one band will check the dirty state
1173 : // of the corresponding blocks in other bands, to decide
1174 : // if it can call WriteTile(), so we have only to do
1175 : // that on one of the bands
1176 4203 : if (poBlock->Write() != CE_None)
1177 250 : eErr = CE_Failure;
1178 : }
1179 4203 : poBlock->DropLock();
1180 : }
1181 : }
1182 : }
1183 : }
1184 :
1185 112 : return eErr;
1186 : }
1187 :
1188 : /************************************************************************/
1189 : /* GetOGRTableLimit() */
1190 : /************************************************************************/
1191 :
1192 2967 : static int GetOGRTableLimit()
1193 : {
1194 2967 : return atoi(CPLGetConfigOption("OGR_TABLE_LIMIT", "10000"));
1195 : }
1196 :
1197 : /************************************************************************/
1198 : /* GetNameTypeMapFromSQliteMaster() */
1199 : /************************************************************************/
1200 :
1201 : const std::map<CPLString, CPLString> &
1202 945 : GDALGeoPackageDataset::GetNameTypeMapFromSQliteMaster()
1203 : {
1204 945 : if (!m_oMapNameToType.empty())
1205 282 : return m_oMapNameToType;
1206 :
1207 : CPLString osSQL(
1208 : "SELECT name, type FROM sqlite_master WHERE "
1209 : "type IN ('view', 'table') OR "
1210 1326 : "(name LIKE 'trigger_%_feature_count_%' AND type = 'trigger')");
1211 663 : const int nTableLimit = GetOGRTableLimit();
1212 663 : if (nTableLimit > 0)
1213 : {
1214 663 : osSQL += " LIMIT ";
1215 663 : osSQL += CPLSPrintf("%d", 1 + 3 * nTableLimit);
1216 : }
1217 :
1218 663 : auto oResult = SQLQuery(hDB, osSQL);
1219 663 : if (oResult)
1220 : {
1221 10958 : for (int i = 0; i < oResult->RowCount(); i++)
1222 : {
1223 10295 : const char *pszName = oResult->GetValue(0, i);
1224 10295 : const char *pszType = oResult->GetValue(1, i);
1225 10295 : m_oMapNameToType[CPLString(pszName).toupper()] = pszType;
1226 : }
1227 : }
1228 :
1229 663 : return m_oMapNameToType;
1230 : }
1231 :
1232 : /************************************************************************/
1233 : /* RemoveTableFromSQLiteMasterCache() */
1234 : /************************************************************************/
1235 :
1236 51 : void GDALGeoPackageDataset::RemoveTableFromSQLiteMasterCache(
1237 : const char *pszTableName)
1238 : {
1239 51 : m_oMapNameToType.erase(CPLString(pszTableName).toupper());
1240 51 : }
1241 :
1242 : /************************************************************************/
1243 : /* GetUnknownExtensionsTableSpecific() */
1244 : /************************************************************************/
1245 :
1246 : const std::map<CPLString, std::vector<GPKGExtensionDesc>> &
1247 630 : GDALGeoPackageDataset::GetUnknownExtensionsTableSpecific()
1248 : {
1249 630 : if (m_bMapTableToExtensionsBuilt)
1250 67 : return m_oMapTableToExtensions;
1251 563 : m_bMapTableToExtensionsBuilt = true;
1252 :
1253 563 : if (!HasExtensionsTable())
1254 33 : return m_oMapTableToExtensions;
1255 :
1256 : CPLString osSQL(
1257 : "SELECT table_name, extension_name, definition, scope "
1258 : "FROM gpkg_extensions WHERE "
1259 : "table_name IS NOT NULL "
1260 : "AND extension_name IS NOT NULL "
1261 : "AND definition IS NOT NULL "
1262 : "AND scope IS NOT NULL "
1263 : "AND extension_name NOT IN ('gpkg_geom_CIRCULARSTRING', "
1264 : "'gpkg_geom_COMPOUNDCURVE', 'gpkg_geom_CURVEPOLYGON', "
1265 : "'gpkg_geom_MULTICURVE', "
1266 : "'gpkg_geom_MULTISURFACE', 'gpkg_geom_CURVE', 'gpkg_geom_SURFACE', "
1267 : "'gpkg_geom_POLYHEDRALSURFACE', 'gpkg_geom_TIN', 'gpkg_geom_TRIANGLE', "
1268 : "'gpkg_rtree_index', 'gpkg_geometry_type_trigger', "
1269 : "'gpkg_srs_id_trigger', "
1270 : "'gpkg_crs_wkt', 'gpkg_crs_wkt_1_1', 'gpkg_schema', "
1271 : "'gpkg_related_tables', 'related_tables'"
1272 : #ifdef HAVE_SPATIALITE
1273 : ", 'gdal_spatialite_computed_geom_column'"
1274 : #endif
1275 1060 : ")");
1276 530 : const int nTableLimit = GetOGRTableLimit();
1277 530 : if (nTableLimit > 0)
1278 : {
1279 530 : osSQL += " LIMIT ";
1280 530 : osSQL += CPLSPrintf("%d", 1 + 10 * nTableLimit);
1281 : }
1282 :
1283 530 : auto oResult = SQLQuery(hDB, osSQL);
1284 530 : if (oResult)
1285 : {
1286 987 : for (int i = 0; i < oResult->RowCount(); i++)
1287 : {
1288 457 : const char *pszTableName = oResult->GetValue(0, i);
1289 457 : const char *pszExtensionName = oResult->GetValue(1, i);
1290 457 : const char *pszDefinition = oResult->GetValue(2, i);
1291 457 : const char *pszScope = oResult->GetValue(3, i);
1292 457 : if (pszTableName && pszExtensionName && pszDefinition && pszScope)
1293 : {
1294 457 : GPKGExtensionDesc oDesc;
1295 457 : oDesc.osExtensionName = pszExtensionName;
1296 457 : oDesc.osDefinition = pszDefinition;
1297 457 : oDesc.osScope = pszScope;
1298 914 : m_oMapTableToExtensions[CPLString(pszTableName).toupper()]
1299 457 : .push_back(oDesc);
1300 : }
1301 : }
1302 : }
1303 :
1304 530 : return m_oMapTableToExtensions;
1305 : }
1306 :
1307 : /************************************************************************/
1308 : /* GetContents() */
1309 : /************************************************************************/
1310 :
1311 : const std::map<CPLString, GPKGContentsDesc> &
1312 617 : GDALGeoPackageDataset::GetContents()
1313 : {
1314 617 : if (m_bMapTableToContentsBuilt)
1315 56 : return m_oMapTableToContents;
1316 561 : m_bMapTableToContentsBuilt = true;
1317 :
1318 : CPLString osSQL("SELECT table_name, data_type, identifier, "
1319 : "description, min_x, min_y, max_x, max_y "
1320 1122 : "FROM gpkg_contents");
1321 561 : const int nTableLimit = GetOGRTableLimit();
1322 561 : if (nTableLimit > 0)
1323 : {
1324 561 : osSQL += " LIMIT ";
1325 561 : osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1326 : }
1327 :
1328 561 : auto oResult = SQLQuery(hDB, osSQL);
1329 561 : if (oResult)
1330 : {
1331 1209 : for (int i = 0; i < oResult->RowCount(); i++)
1332 : {
1333 648 : const char *pszTableName = oResult->GetValue(0, i);
1334 648 : if (pszTableName == nullptr)
1335 0 : continue;
1336 648 : const char *pszDataType = oResult->GetValue(1, i);
1337 648 : const char *pszIdentifier = oResult->GetValue(2, i);
1338 648 : const char *pszDescription = oResult->GetValue(3, i);
1339 648 : const char *pszMinX = oResult->GetValue(4, i);
1340 648 : const char *pszMinY = oResult->GetValue(5, i);
1341 648 : const char *pszMaxX = oResult->GetValue(6, i);
1342 648 : const char *pszMaxY = oResult->GetValue(7, i);
1343 648 : GPKGContentsDesc oDesc;
1344 648 : if (pszDataType)
1345 648 : oDesc.osDataType = pszDataType;
1346 648 : if (pszIdentifier)
1347 648 : oDesc.osIdentifier = pszIdentifier;
1348 648 : if (pszDescription)
1349 647 : oDesc.osDescription = pszDescription;
1350 648 : if (pszMinX)
1351 430 : oDesc.osMinX = pszMinX;
1352 648 : if (pszMinY)
1353 430 : oDesc.osMinY = pszMinY;
1354 648 : if (pszMaxX)
1355 430 : oDesc.osMaxX = pszMaxX;
1356 648 : if (pszMaxY)
1357 430 : oDesc.osMaxY = pszMaxY;
1358 1296 : m_oMapTableToContents[CPLString(pszTableName).toupper()] =
1359 1296 : std::move(oDesc);
1360 : }
1361 : }
1362 :
1363 561 : return m_oMapTableToContents;
1364 : }
1365 :
1366 : /************************************************************************/
1367 : /* Open() */
1368 : /************************************************************************/
1369 :
1370 962 : int GDALGeoPackageDataset::Open(GDALOpenInfo *poOpenInfo,
1371 : const std::string &osFilenameInZip)
1372 : {
1373 962 : m_osFilenameInZip = osFilenameInZip;
1374 962 : CPLAssert(m_nLayers == 0);
1375 962 : CPLAssert(hDB == nullptr);
1376 :
1377 962 : SetDescription(poOpenInfo->pszFilename);
1378 1924 : CPLString osFilename(poOpenInfo->pszFilename);
1379 1924 : CPLString osSubdatasetTableName;
1380 : GByte abyHeaderLetMeHerePlease[100];
1381 962 : const GByte *pabyHeader = poOpenInfo->pabyHeader;
1382 962 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GPKG:"))
1383 : {
1384 166 : char **papszTokens = CSLTokenizeString2(poOpenInfo->pszFilename, ":",
1385 : CSLT_HONOURSTRINGS);
1386 166 : int nCount = CSLCount(papszTokens);
1387 166 : if (nCount < 2)
1388 : {
1389 0 : CSLDestroy(papszTokens);
1390 0 : return FALSE;
1391 : }
1392 :
1393 166 : if (nCount <= 3)
1394 : {
1395 164 : osFilename = papszTokens[1];
1396 : }
1397 : /* GPKG:C:\BLA.GPKG:foo */
1398 2 : else if (nCount == 4 && strlen(papszTokens[1]) == 1 &&
1399 2 : (papszTokens[2][0] == '/' || papszTokens[2][0] == '\\'))
1400 : {
1401 2 : osFilename = CPLString(papszTokens[1]) + ":" + papszTokens[2];
1402 : }
1403 : // GPKG:/vsicurl/http[s]://[user:passwd@]example.com[:8080]/foo.gpkg:bar
1404 0 : else if (/*nCount >= 4 && */
1405 0 : (EQUAL(papszTokens[1], "/vsicurl/http") ||
1406 0 : EQUAL(papszTokens[1], "/vsicurl/https")))
1407 : {
1408 0 : osFilename = CPLString(papszTokens[1]);
1409 0 : for (int i = 2; i < nCount - 1; i++)
1410 : {
1411 0 : osFilename += ':';
1412 0 : osFilename += papszTokens[i];
1413 : }
1414 : }
1415 166 : if (nCount >= 3)
1416 12 : osSubdatasetTableName = papszTokens[nCount - 1];
1417 :
1418 166 : CSLDestroy(papszTokens);
1419 166 : VSILFILE *fp = VSIFOpenL(osFilename, "rb");
1420 166 : if (fp != nullptr)
1421 : {
1422 166 : VSIFReadL(abyHeaderLetMeHerePlease, 1, 100, fp);
1423 166 : VSIFCloseL(fp);
1424 : }
1425 166 : pabyHeader = abyHeaderLetMeHerePlease;
1426 : }
1427 796 : else if (poOpenInfo->pabyHeader &&
1428 796 : STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
1429 : "SQLite format 3"))
1430 : {
1431 790 : m_bCallUndeclareFileNotToOpen = true;
1432 790 : GDALOpenInfoDeclareFileNotToOpen(osFilename, poOpenInfo->pabyHeader,
1433 : poOpenInfo->nHeaderBytes);
1434 : }
1435 :
1436 962 : eAccess = poOpenInfo->eAccess;
1437 962 : if (!m_osFilenameInZip.empty())
1438 : {
1439 1 : m_pszFilename = CPLStrdup(CPLSPrintf(
1440 : "/vsizip/{%s}/%s", osFilename.c_str(), m_osFilenameInZip.c_str()));
1441 : }
1442 : else
1443 : {
1444 961 : m_pszFilename = CPLStrdup(osFilename);
1445 : }
1446 :
1447 962 : if (poOpenInfo->papszOpenOptions)
1448 : {
1449 96 : CSLDestroy(papszOpenOptions);
1450 96 : papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
1451 : }
1452 :
1453 : #ifdef ENABLE_SQL_GPKG_FORMAT
1454 962 : if (poOpenInfo->pabyHeader &&
1455 796 : STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
1456 5 : "-- SQL GPKG") &&
1457 5 : poOpenInfo->fpL != nullptr)
1458 : {
1459 5 : if (sqlite3_open_v2(":memory:", &hDB, SQLITE_OPEN_READWRITE, nullptr) !=
1460 : SQLITE_OK)
1461 : {
1462 0 : return FALSE;
1463 : }
1464 :
1465 5 : InstallSQLFunctions();
1466 :
1467 : // Ingest the lines of the dump
1468 5 : VSIFSeekL(poOpenInfo->fpL, 0, SEEK_SET);
1469 : const char *pszLine;
1470 76 : while ((pszLine = CPLReadLineL(poOpenInfo->fpL)) != nullptr)
1471 : {
1472 71 : if (STARTS_WITH(pszLine, "--"))
1473 5 : continue;
1474 :
1475 : // Reject a few words tat might have security implications
1476 : // Basically we just want to allow CREATE TABLE and INSERT INTO
1477 66 : if (CPLString(pszLine).ifind("ATTACH") != std::string::npos ||
1478 132 : CPLString(pszLine).ifind("DETACH") != std::string::npos ||
1479 132 : CPLString(pszLine).ifind("PRAGMA") != std::string::npos ||
1480 132 : CPLString(pszLine).ifind("SELECT") != std::string::npos ||
1481 128 : CPLString(pszLine).ifind("UPDATE") != std::string::npos ||
1482 128 : CPLString(pszLine).ifind("REPLACE") != std::string::npos ||
1483 128 : CPLString(pszLine).ifind("DELETE") != std::string::npos ||
1484 128 : CPLString(pszLine).ifind("DROP") != std::string::npos ||
1485 260 : CPLString(pszLine).ifind("ALTER") != std::string::npos ||
1486 128 : CPLString(pszLine).ifind("VIRTUAL") != std::string::npos)
1487 : {
1488 8 : bool bOK = false;
1489 : // Accept creation of spatial index
1490 8 : if (STARTS_WITH_CI(pszLine, "CREATE VIRTUAL TABLE "))
1491 : {
1492 4 : const char *pszStr =
1493 : pszLine + strlen("CREATE VIRTUAL TABLE ");
1494 4 : if (*pszStr == '"')
1495 0 : pszStr++;
1496 52 : while ((*pszStr >= 'a' && *pszStr <= 'z') ||
1497 64 : (*pszStr >= 'A' && *pszStr <= 'Z') || *pszStr == '_')
1498 : {
1499 60 : pszStr++;
1500 : }
1501 4 : if (*pszStr == '"')
1502 0 : pszStr++;
1503 4 : if (EQUAL(pszStr,
1504 : " USING rtree(id, minx, maxx, miny, maxy);"))
1505 : {
1506 4 : bOK = true;
1507 : }
1508 : }
1509 : // Accept INSERT INTO rtree_poly_geom SELECT fid, ST_MinX(geom),
1510 : // ST_MaxX(geom), ST_MinY(geom), ST_MaxY(geom) FROM poly;
1511 8 : else if (STARTS_WITH_CI(pszLine, "INSERT INTO rtree_") &&
1512 8 : CPLString(pszLine).ifind("SELECT") !=
1513 : std::string::npos)
1514 : {
1515 : char **papszTokens =
1516 4 : CSLTokenizeString2(pszLine, " (),,", 0);
1517 4 : if (CSLCount(papszTokens) == 15 &&
1518 4 : EQUAL(papszTokens[3], "SELECT") &&
1519 4 : EQUAL(papszTokens[5], "ST_MinX") &&
1520 4 : EQUAL(papszTokens[7], "ST_MaxX") &&
1521 4 : EQUAL(papszTokens[9], "ST_MinY") &&
1522 12 : EQUAL(papszTokens[11], "ST_MaxY") &&
1523 4 : EQUAL(papszTokens[13], "FROM"))
1524 : {
1525 4 : bOK = TRUE;
1526 : }
1527 4 : CSLDestroy(papszTokens);
1528 : }
1529 :
1530 8 : if (!bOK)
1531 : {
1532 0 : CPLError(CE_Failure, CPLE_NotSupported,
1533 : "Rejected statement: %s", pszLine);
1534 0 : return FALSE;
1535 : }
1536 : }
1537 66 : char *pszErrMsg = nullptr;
1538 66 : if (sqlite3_exec(hDB, pszLine, nullptr, nullptr, &pszErrMsg) !=
1539 : SQLITE_OK)
1540 : {
1541 0 : if (pszErrMsg)
1542 0 : CPLDebug("SQLITE", "Error %s", pszErrMsg);
1543 : }
1544 66 : sqlite3_free(pszErrMsg);
1545 5 : }
1546 : }
1547 :
1548 957 : else if (pabyHeader != nullptr)
1549 : #endif
1550 : {
1551 957 : if (poOpenInfo->fpL)
1552 : {
1553 : // See above comment about -wal locking for the importance of
1554 : // closing that file, prior to calling sqlite3_open()
1555 692 : VSIFCloseL(poOpenInfo->fpL);
1556 692 : poOpenInfo->fpL = nullptr;
1557 : }
1558 :
1559 : /* See if we can open the SQLite database */
1560 957 : if (!OpenOrCreateDB(GetUpdate() ? SQLITE_OPEN_READWRITE
1561 : : SQLITE_OPEN_READONLY))
1562 2 : return FALSE;
1563 :
1564 955 : memcpy(&m_nApplicationId, pabyHeader + knApplicationIdPos, 4);
1565 955 : m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
1566 955 : memcpy(&m_nUserVersion, pabyHeader + knUserVersionPos, 4);
1567 955 : m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
1568 955 : if (m_nApplicationId == GP10_APPLICATION_ID)
1569 : {
1570 7 : CPLDebug("GPKG", "GeoPackage v1.0");
1571 : }
1572 948 : else if (m_nApplicationId == GP11_APPLICATION_ID)
1573 : {
1574 2 : CPLDebug("GPKG", "GeoPackage v1.1");
1575 : }
1576 946 : else if (m_nApplicationId == GPKG_APPLICATION_ID &&
1577 943 : m_nUserVersion >= GPKG_1_2_VERSION)
1578 : {
1579 941 : CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
1580 941 : (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
1581 : }
1582 : }
1583 :
1584 : /* Requirement 6: The SQLite PRAGMA integrity_check SQL command SHALL return
1585 : * “ok” */
1586 : /* http://opengis.github.io/geopackage/#_file_integrity */
1587 : /* Disable integrity check by default, since it is expensive on big files */
1588 960 : if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_INTEGRITY_CHECK", "NO")) &&
1589 0 : OGRERR_NONE != PragmaCheck("integrity_check", "ok", 1))
1590 : {
1591 0 : CPLError(CE_Failure, CPLE_AppDefined,
1592 : "pragma integrity_check on '%s' failed", m_pszFilename);
1593 0 : return FALSE;
1594 : }
1595 :
1596 : /* Requirement 7: The SQLite PRAGMA foreign_key_check() SQL with no */
1597 : /* parameter value SHALL return an empty result set */
1598 : /* http://opengis.github.io/geopackage/#_file_integrity */
1599 : /* Disable the check by default, since it is to corrupt databases, and */
1600 : /* that causes issues to downstream software that can't open them. */
1601 960 : if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_FOREIGN_KEY_CHECK", "NO")) &&
1602 0 : OGRERR_NONE != PragmaCheck("foreign_key_check", "", 0))
1603 : {
1604 0 : CPLError(CE_Failure, CPLE_AppDefined,
1605 : "pragma foreign_key_check on '%s' failed.", m_pszFilename);
1606 0 : return FALSE;
1607 : }
1608 :
1609 : /* Check for requirement metadata tables */
1610 : /* Requirement 10: gpkg_spatial_ref_sys must exist */
1611 : /* Requirement 13: gpkg_contents must exist */
1612 960 : if (SQLGetInteger(hDB,
1613 : "SELECT COUNT(*) FROM sqlite_master WHERE "
1614 : "name IN ('gpkg_spatial_ref_sys', 'gpkg_contents') AND "
1615 : "type IN ('table', 'view')",
1616 960 : nullptr) != 2)
1617 : {
1618 0 : CPLError(CE_Failure, CPLE_AppDefined,
1619 : "At least one of the required GeoPackage tables, "
1620 : "gpkg_spatial_ref_sys or gpkg_contents, is missing");
1621 0 : return FALSE;
1622 : }
1623 :
1624 960 : DetectSpatialRefSysColumns();
1625 :
1626 : #ifdef ENABLE_GPKG_OGR_CONTENTS
1627 960 : if (SQLGetInteger(hDB,
1628 : "SELECT 1 FROM sqlite_master WHERE "
1629 : "name = 'gpkg_ogr_contents' AND type = 'table'",
1630 960 : nullptr) == 1)
1631 : {
1632 955 : m_bHasGPKGOGRContents = true;
1633 : }
1634 : #endif
1635 :
1636 960 : CheckUnknownExtensions();
1637 :
1638 960 : int bRet = FALSE;
1639 960 : bool bHasGPKGExtRelations = false;
1640 960 : if (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR)
1641 : {
1642 783 : m_bHasGPKGGeometryColumns =
1643 783 : SQLGetInteger(hDB,
1644 : "SELECT 1 FROM sqlite_master WHERE "
1645 : "name = 'gpkg_geometry_columns' AND "
1646 : "type IN ('table', 'view')",
1647 783 : nullptr) == 1;
1648 783 : bHasGPKGExtRelations = HasGpkgextRelationsTable();
1649 : }
1650 960 : if (m_bHasGPKGGeometryColumns)
1651 : {
1652 : /* Load layer definitions for all tables in gpkg_contents &
1653 : * gpkg_geometry_columns */
1654 : /* and non-spatial tables as well */
1655 : std::string osSQL =
1656 : "SELECT c.table_name, c.identifier, 1 as is_spatial, "
1657 : "g.column_name, g.geometry_type_name, g.z, g.m, c.min_x, c.min_y, "
1658 : "c.max_x, c.max_y, 1 AS is_in_gpkg_contents, "
1659 : "(SELECT type FROM sqlite_master WHERE lower(name) = "
1660 : "lower(c.table_name) AND type IN ('table', 'view')) AS object_type "
1661 : " FROM gpkg_geometry_columns g "
1662 : " JOIN gpkg_contents c ON (g.table_name = c.table_name)"
1663 : " WHERE "
1664 : " c.table_name <> 'ogr_empty_table' AND"
1665 : " c.data_type = 'features' "
1666 : // aspatial: Was the only method available in OGR 2.0 and 2.1
1667 : // attributes: GPKG 1.2 or later
1668 : "UNION ALL "
1669 : "SELECT table_name, identifier, 0 as is_spatial, NULL, NULL, 0, 0, "
1670 : "0 AS xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 1 AS "
1671 : "is_in_gpkg_contents, "
1672 : "(SELECT type FROM sqlite_master WHERE lower(name) = "
1673 : "lower(table_name) AND type IN ('table', 'view')) AS object_type "
1674 : " FROM gpkg_contents"
1675 782 : " WHERE data_type IN ('aspatial', 'attributes') ";
1676 :
1677 1564 : const char *pszListAllTables = CSLFetchNameValueDef(
1678 782 : poOpenInfo->papszOpenOptions, "LIST_ALL_TABLES", "AUTO");
1679 782 : bool bHasASpatialOrAttributes = HasGDALAspatialExtension();
1680 782 : if (!bHasASpatialOrAttributes)
1681 : {
1682 : auto oResultTable =
1683 : SQLQuery(hDB, "SELECT * FROM gpkg_contents WHERE "
1684 781 : "data_type = 'attributes' LIMIT 1");
1685 781 : bHasASpatialOrAttributes =
1686 781 : (oResultTable && oResultTable->RowCount() == 1);
1687 : }
1688 782 : if (bHasGPKGExtRelations)
1689 : {
1690 : osSQL += "UNION ALL "
1691 : "SELECT mapping_table_name, mapping_table_name, 0 as "
1692 : "is_spatial, NULL, NULL, 0, 0, 0 AS "
1693 : "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
1694 : "is_in_gpkg_contents, 'table' AS object_type "
1695 : "FROM gpkgext_relations WHERE "
1696 : "lower(mapping_table_name) NOT IN (SELECT "
1697 : "lower(table_name) FROM "
1698 12 : "gpkg_contents)";
1699 : }
1700 782 : if (EQUAL(pszListAllTables, "YES") ||
1701 782 : (!bHasASpatialOrAttributes && EQUAL(pszListAllTables, "AUTO")))
1702 : {
1703 : // vgpkg_ is Spatialite virtual table
1704 : osSQL +=
1705 : "UNION ALL "
1706 : "SELECT name, name, 0 as is_spatial, NULL, NULL, 0, 0, 0 AS "
1707 : "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
1708 : "is_in_gpkg_contents, type AS object_type "
1709 : "FROM sqlite_master WHERE type IN ('table', 'view') "
1710 : "AND name NOT LIKE 'gpkg_%' "
1711 : "AND name NOT LIKE 'vgpkg_%' "
1712 : "AND name NOT LIKE 'rtree_%' AND name NOT LIKE 'sqlite_%' "
1713 : // Avoid reading those views from simple_sewer_features.gpkg
1714 : "AND name NOT IN ('st_spatial_ref_sys', 'spatial_ref_sys', "
1715 : "'st_geometry_columns', 'geometry_columns') "
1716 : "AND lower(name) NOT IN (SELECT lower(table_name) FROM "
1717 738 : "gpkg_contents)";
1718 738 : if (bHasGPKGExtRelations)
1719 : {
1720 : osSQL += " AND lower(name) NOT IN (SELECT "
1721 : "lower(mapping_table_name) FROM "
1722 8 : "gpkgext_relations)";
1723 : }
1724 : }
1725 782 : const int nTableLimit = GetOGRTableLimit();
1726 782 : if (nTableLimit > 0)
1727 : {
1728 782 : osSQL += " LIMIT ";
1729 782 : osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1730 : }
1731 :
1732 782 : auto oResult = SQLQuery(hDB, osSQL.c_str());
1733 782 : if (!oResult)
1734 : {
1735 0 : return FALSE;
1736 : }
1737 :
1738 782 : if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
1739 : {
1740 1 : CPLError(CE_Warning, CPLE_AppDefined,
1741 : "File has more than %d vector tables. "
1742 : "Limiting to first %d (can be overridden with "
1743 : "OGR_TABLE_LIMIT config option)",
1744 : nTableLimit, nTableLimit);
1745 1 : oResult->LimitRowCount(nTableLimit);
1746 : }
1747 :
1748 782 : if (oResult->RowCount() > 0)
1749 : {
1750 673 : bRet = TRUE;
1751 :
1752 1346 : m_papoLayers = static_cast<OGRGeoPackageTableLayer **>(CPLMalloc(
1753 673 : sizeof(OGRGeoPackageTableLayer *) * oResult->RowCount()));
1754 :
1755 1346 : std::map<std::string, int> oMapTableRefCount;
1756 3496 : for (int i = 0; i < oResult->RowCount(); i++)
1757 : {
1758 2823 : const char *pszTableName = oResult->GetValue(0, i);
1759 2823 : if (pszTableName == nullptr)
1760 0 : continue;
1761 2823 : if (++oMapTableRefCount[pszTableName] == 2)
1762 : {
1763 : // This should normally not happen if all constraints are
1764 : // properly set
1765 2 : CPLError(CE_Warning, CPLE_AppDefined,
1766 : "Table %s appearing several times in "
1767 : "gpkg_contents and/or gpkg_geometry_columns",
1768 : pszTableName);
1769 : }
1770 : }
1771 :
1772 1346 : std::set<std::string> oExistingLayers;
1773 3496 : for (int i = 0; i < oResult->RowCount(); i++)
1774 : {
1775 2823 : const char *pszTableName = oResult->GetValue(0, i);
1776 2823 : if (pszTableName == nullptr)
1777 2 : continue;
1778 : const bool bTableHasSeveralGeomColumns =
1779 2823 : oMapTableRefCount[pszTableName] > 1;
1780 2823 : bool bIsSpatial = CPL_TO_BOOL(oResult->GetValueAsInteger(2, i));
1781 2823 : const char *pszGeomColName = oResult->GetValue(3, i);
1782 2823 : const char *pszGeomType = oResult->GetValue(4, i);
1783 2823 : const char *pszZ = oResult->GetValue(5, i);
1784 2823 : const char *pszM = oResult->GetValue(6, i);
1785 : bool bIsInGpkgContents =
1786 2823 : CPL_TO_BOOL(oResult->GetValueAsInteger(11, i));
1787 2823 : if (!bIsInGpkgContents)
1788 40 : m_bNonSpatialTablesNonRegisteredInGpkgContentsFound = true;
1789 2823 : const char *pszObjectType = oResult->GetValue(12, i);
1790 2823 : if (pszObjectType == nullptr ||
1791 2822 : !(EQUAL(pszObjectType, "table") ||
1792 21 : EQUAL(pszObjectType, "view")))
1793 : {
1794 1 : CPLError(CE_Warning, CPLE_AppDefined,
1795 : "Table/view %s is referenced in gpkg_contents, "
1796 : "but does not exist",
1797 : pszTableName);
1798 1 : continue;
1799 : }
1800 : // Non-standard and undocumented behavior:
1801 : // if the same table appears to have several geometry columns,
1802 : // handle it for now as multiple layers named
1803 : // "table_name (geom_col_name)"
1804 : // The way we handle that might change in the future (e.g
1805 : // could be a single layer with multiple geometry columns)
1806 : const std::string osLayerNameWithGeomColName =
1807 5021 : pszGeomColName ? std::string(pszTableName) + " (" +
1808 : pszGeomColName + ')'
1809 5644 : : std::string(pszTableName);
1810 2822 : if (oExistingLayers.find(osLayerNameWithGeomColName) !=
1811 5644 : oExistingLayers.end())
1812 1 : continue;
1813 2821 : oExistingLayers.insert(osLayerNameWithGeomColName);
1814 : const std::string osLayerName = bTableHasSeveralGeomColumns
1815 : ? osLayerNameWithGeomColName
1816 2821 : : std::string(pszTableName);
1817 : OGRGeoPackageTableLayer *poLayer =
1818 2821 : new OGRGeoPackageTableLayer(this, osLayerName.c_str());
1819 2821 : bool bHasZ = pszZ && atoi(pszZ) > 0;
1820 2821 : bool bHasM = pszM && atoi(pszM) > 0;
1821 2821 : if (pszGeomType && EQUAL(pszGeomType, "GEOMETRY"))
1822 : {
1823 457 : if (pszZ && atoi(pszZ) == 2)
1824 7 : bHasZ = false;
1825 457 : if (pszM && atoi(pszM) == 2)
1826 6 : bHasM = false;
1827 : }
1828 2821 : poLayer->SetOpeningParameters(
1829 : pszTableName, pszObjectType, bIsInGpkgContents, bIsSpatial,
1830 : pszGeomColName, pszGeomType, bHasZ, bHasM);
1831 2821 : m_papoLayers[m_nLayers++] = poLayer;
1832 : }
1833 : }
1834 : }
1835 :
1836 960 : bool bHasTileMatrixSet = false;
1837 960 : if (poOpenInfo->nOpenFlags & GDAL_OF_RASTER)
1838 : {
1839 432 : bHasTileMatrixSet = SQLGetInteger(hDB,
1840 : "SELECT 1 FROM sqlite_master WHERE "
1841 : "name = 'gpkg_tile_matrix_set' AND "
1842 : "type IN ('table', 'view')",
1843 : nullptr) == 1;
1844 : }
1845 960 : if (bHasTileMatrixSet)
1846 : {
1847 : std::string osSQL =
1848 : "SELECT c.table_name, c.identifier, c.description, c.srs_id, "
1849 : "c.min_x, c.min_y, c.max_x, c.max_y, "
1850 : "tms.min_x, tms.min_y, tms.max_x, tms.max_y, c.data_type "
1851 : "FROM gpkg_contents c JOIN gpkg_tile_matrix_set tms ON "
1852 : "c.table_name = tms.table_name WHERE "
1853 431 : "data_type IN ('tiles', '2d-gridded-coverage')";
1854 431 : if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE"))
1855 : osSubdatasetTableName =
1856 2 : CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE");
1857 431 : if (!osSubdatasetTableName.empty())
1858 : {
1859 14 : char *pszTmp = sqlite3_mprintf(" AND c.table_name='%q'",
1860 : osSubdatasetTableName.c_str());
1861 14 : osSQL += pszTmp;
1862 14 : sqlite3_free(pszTmp);
1863 14 : SetPhysicalFilename(osFilename.c_str());
1864 : }
1865 431 : const int nTableLimit = GetOGRTableLimit();
1866 431 : if (nTableLimit > 0)
1867 : {
1868 431 : osSQL += " LIMIT ";
1869 431 : osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1870 : }
1871 :
1872 431 : auto oResult = SQLQuery(hDB, osSQL.c_str());
1873 431 : if (!oResult)
1874 : {
1875 0 : return FALSE;
1876 : }
1877 :
1878 431 : if (oResult->RowCount() == 0 && !osSubdatasetTableName.empty())
1879 : {
1880 1 : CPLError(CE_Failure, CPLE_AppDefined,
1881 : "Cannot find table '%s' in GeoPackage dataset",
1882 : osSubdatasetTableName.c_str());
1883 : }
1884 430 : else if (oResult->RowCount() == 1)
1885 : {
1886 258 : const char *pszTableName = oResult->GetValue(0, 0);
1887 258 : const char *pszIdentifier = oResult->GetValue(1, 0);
1888 258 : const char *pszDescription = oResult->GetValue(2, 0);
1889 258 : const char *pszSRSId = oResult->GetValue(3, 0);
1890 258 : const char *pszMinX = oResult->GetValue(4, 0);
1891 258 : const char *pszMinY = oResult->GetValue(5, 0);
1892 258 : const char *pszMaxX = oResult->GetValue(6, 0);
1893 258 : const char *pszMaxY = oResult->GetValue(7, 0);
1894 258 : const char *pszTMSMinX = oResult->GetValue(8, 0);
1895 258 : const char *pszTMSMinY = oResult->GetValue(9, 0);
1896 258 : const char *pszTMSMaxX = oResult->GetValue(10, 0);
1897 258 : const char *pszTMSMaxY = oResult->GetValue(11, 0);
1898 258 : const char *pszDataType = oResult->GetValue(12, 0);
1899 258 : if (pszTableName && pszTMSMinX && pszTMSMinY && pszTMSMaxX &&
1900 : pszTMSMaxY)
1901 : {
1902 516 : bRet = OpenRaster(
1903 : pszTableName, pszIdentifier, pszDescription,
1904 258 : pszSRSId ? atoi(pszSRSId) : 0, CPLAtof(pszTMSMinX),
1905 : CPLAtof(pszTMSMinY), CPLAtof(pszTMSMaxX),
1906 : CPLAtof(pszTMSMaxY), pszMinX, pszMinY, pszMaxX, pszMaxY,
1907 258 : EQUAL(pszDataType, "tiles"), poOpenInfo->papszOpenOptions);
1908 : }
1909 : }
1910 172 : else if (oResult->RowCount() >= 1)
1911 : {
1912 4 : bRet = TRUE;
1913 :
1914 4 : if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
1915 : {
1916 1 : CPLError(CE_Warning, CPLE_AppDefined,
1917 : "File has more than %d raster tables. "
1918 : "Limiting to first %d (can be overridden with "
1919 : "OGR_TABLE_LIMIT config option)",
1920 : nTableLimit, nTableLimit);
1921 1 : oResult->LimitRowCount(nTableLimit);
1922 : }
1923 :
1924 4 : int nSDSCount = 0;
1925 2010 : for (int i = 0; i < oResult->RowCount(); i++)
1926 : {
1927 2006 : const char *pszTableName = oResult->GetValue(0, i);
1928 2006 : const char *pszIdentifier = oResult->GetValue(1, i);
1929 2006 : if (pszTableName == nullptr)
1930 0 : continue;
1931 : m_aosSubDatasets.AddNameValue(
1932 : CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
1933 2006 : CPLSPrintf("GPKG:%s:%s", m_pszFilename, pszTableName));
1934 : m_aosSubDatasets.AddNameValue(
1935 : CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
1936 : pszIdentifier
1937 2006 : ? CPLSPrintf("%s - %s", pszTableName, pszIdentifier)
1938 4012 : : pszTableName);
1939 2006 : nSDSCount++;
1940 : }
1941 : }
1942 : }
1943 :
1944 960 : if (!bRet && (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR))
1945 : {
1946 30 : if ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE))
1947 : {
1948 20 : bRet = TRUE;
1949 : }
1950 : else
1951 : {
1952 10 : CPLDebug("GPKG",
1953 : "This GeoPackage has no vector content and is opened "
1954 : "in read-only mode. If you open it in update mode, "
1955 : "opening will be successful.");
1956 : }
1957 : }
1958 :
1959 960 : if (eAccess == GA_Update)
1960 : {
1961 195 : FixupWrongRTreeTrigger();
1962 195 : FixupWrongMedataReferenceColumnNameUpdate();
1963 : }
1964 :
1965 960 : SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
1966 :
1967 960 : return bRet;
1968 : }
1969 :
1970 : /************************************************************************/
1971 : /* DetectSpatialRefSysColumns() */
1972 : /************************************************************************/
1973 :
1974 967 : void GDALGeoPackageDataset::DetectSpatialRefSysColumns()
1975 : {
1976 : // Detect definition_12_063 column
1977 : {
1978 967 : sqlite3_stmt *hSQLStmt = nullptr;
1979 967 : int rc = sqlite3_prepare_v2(
1980 : hDB, "SELECT definition_12_063 FROM gpkg_spatial_ref_sys ", -1,
1981 : &hSQLStmt, nullptr);
1982 967 : if (rc == SQLITE_OK)
1983 : {
1984 78 : m_bHasDefinition12_063 = true;
1985 78 : sqlite3_finalize(hSQLStmt);
1986 : }
1987 : }
1988 :
1989 : // Detect epoch column
1990 967 : if (m_bHasDefinition12_063)
1991 : {
1992 78 : sqlite3_stmt *hSQLStmt = nullptr;
1993 : int rc =
1994 78 : sqlite3_prepare_v2(hDB, "SELECT epoch FROM gpkg_spatial_ref_sys ",
1995 : -1, &hSQLStmt, nullptr);
1996 78 : if (rc == SQLITE_OK)
1997 : {
1998 5 : m_bHasEpochColumn = true;
1999 5 : sqlite3_finalize(hSQLStmt);
2000 : }
2001 : }
2002 967 : }
2003 :
2004 : /************************************************************************/
2005 : /* FixupWrongRTreeTrigger() */
2006 : /************************************************************************/
2007 :
2008 195 : void GDALGeoPackageDataset::FixupWrongRTreeTrigger()
2009 : {
2010 : auto oResult = SQLQuery(
2011 : hDB,
2012 : "SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND "
2013 195 : "NAME LIKE 'rtree_%_update3' AND sql LIKE '% AFTER UPDATE OF % ON %'");
2014 195 : if (oResult == nullptr)
2015 0 : return;
2016 195 : if (oResult->RowCount() > 0)
2017 : {
2018 1 : CPLDebug("GPKG", "Fixing incorrect trigger(s) related to RTree");
2019 : }
2020 197 : for (int i = 0; i < oResult->RowCount(); i++)
2021 : {
2022 2 : const char *pszName = oResult->GetValue(0, i);
2023 2 : const char *pszSQL = oResult->GetValue(1, i);
2024 2 : const char *pszPtr1 = strstr(pszSQL, " AFTER UPDATE OF ");
2025 2 : if (pszPtr1)
2026 : {
2027 2 : const char *pszPtr = pszPtr1 + strlen(" AFTER UPDATE OF ");
2028 : // Skipping over geometry column name
2029 4 : while (*pszPtr == ' ')
2030 2 : pszPtr++;
2031 2 : if (pszPtr[0] == '"' || pszPtr[0] == '\'')
2032 : {
2033 1 : char chStringDelim = pszPtr[0];
2034 1 : pszPtr++;
2035 9 : while (*pszPtr != '\0' && *pszPtr != chStringDelim)
2036 : {
2037 8 : if (*pszPtr == '\\' && pszPtr[1] == chStringDelim)
2038 0 : pszPtr += 2;
2039 : else
2040 8 : pszPtr += 1;
2041 : }
2042 1 : if (*pszPtr == chStringDelim)
2043 1 : pszPtr++;
2044 : }
2045 : else
2046 : {
2047 1 : pszPtr++;
2048 8 : while (*pszPtr != ' ')
2049 7 : pszPtr++;
2050 : }
2051 2 : if (*pszPtr == ' ')
2052 : {
2053 2 : SQLCommand(hDB,
2054 4 : ("DROP TRIGGER \"" + SQLEscapeName(pszName) + "\"")
2055 : .c_str());
2056 4 : CPLString newSQL;
2057 2 : newSQL.assign(pszSQL, pszPtr1 - pszSQL);
2058 2 : newSQL += " AFTER UPDATE";
2059 2 : newSQL += pszPtr;
2060 2 : SQLCommand(hDB, newSQL);
2061 : }
2062 : }
2063 : }
2064 : }
2065 :
2066 : /************************************************************************/
2067 : /* FixupWrongMedataReferenceColumnNameUpdate() */
2068 : /************************************************************************/
2069 :
2070 195 : void GDALGeoPackageDataset::FixupWrongMedataReferenceColumnNameUpdate()
2071 : {
2072 : // Fix wrong trigger that was generated by GDAL < 2.4.0
2073 : // See https://github.com/qgis/QGIS/issues/42768
2074 : auto oResult = SQLQuery(
2075 : hDB, "SELECT sql FROM sqlite_master WHERE type = 'trigger' AND "
2076 : "NAME ='gpkg_metadata_reference_column_name_update' AND "
2077 195 : "sql LIKE '%column_nameIS%'");
2078 195 : if (oResult == nullptr)
2079 0 : return;
2080 195 : if (oResult->RowCount() == 1)
2081 : {
2082 1 : CPLDebug("GPKG", "Fixing incorrect trigger "
2083 : "gpkg_metadata_reference_column_name_update");
2084 1 : const char *pszSQL = oResult->GetValue(0, 0);
2085 : std::string osNewSQL(
2086 3 : CPLString(pszSQL).replaceAll("column_nameIS", "column_name IS"));
2087 :
2088 1 : SQLCommand(hDB,
2089 : "DROP TRIGGER gpkg_metadata_reference_column_name_update");
2090 1 : SQLCommand(hDB, osNewSQL.c_str());
2091 : }
2092 : }
2093 :
2094 : /************************************************************************/
2095 : /* ClearCachedRelationships() */
2096 : /************************************************************************/
2097 :
2098 35 : void GDALGeoPackageDataset::ClearCachedRelationships()
2099 : {
2100 35 : m_bHasPopulatedRelationships = false;
2101 35 : m_osMapRelationships.clear();
2102 35 : }
2103 :
2104 : /************************************************************************/
2105 : /* LoadRelationships() */
2106 : /************************************************************************/
2107 :
2108 49 : void GDALGeoPackageDataset::LoadRelationships() const
2109 : {
2110 49 : m_osMapRelationships.clear();
2111 :
2112 49 : std::vector<std::string> oExcludedTables;
2113 49 : if (HasGpkgextRelationsTable())
2114 : {
2115 30 : LoadRelationshipsUsingRelatedTablesExtension();
2116 :
2117 76 : for (const auto &oRelationship : m_osMapRelationships)
2118 : {
2119 : oExcludedTables.emplace_back(
2120 46 : oRelationship.second->GetMappingTableName());
2121 : }
2122 : }
2123 :
2124 : // Also load relationships defined using foreign keys (i.e. one-to-many
2125 : // relationships). Here we must exclude any relationships defined from the
2126 : // related tables extension, we don't want them included twice.
2127 49 : LoadRelationshipsFromForeignKeys(oExcludedTables);
2128 49 : m_bHasPopulatedRelationships = true;
2129 49 : }
2130 :
2131 : /************************************************************************/
2132 : /* LoadRelationshipsUsingRelatedTablesExtension() */
2133 : /************************************************************************/
2134 :
2135 30 : void GDALGeoPackageDataset::LoadRelationshipsUsingRelatedTablesExtension() const
2136 : {
2137 30 : m_osMapRelationships.clear();
2138 :
2139 : auto oResultTable = SQLQuery(
2140 30 : hDB, "SELECT base_table_name, base_primary_column, "
2141 : "related_table_name, related_primary_column, relation_name, "
2142 60 : "mapping_table_name FROM gpkgext_relations");
2143 30 : if (oResultTable && oResultTable->RowCount() > 0)
2144 : {
2145 74 : for (int i = 0; i < oResultTable->RowCount(); i++)
2146 : {
2147 47 : const char *pszBaseTableName = oResultTable->GetValue(0, i);
2148 47 : if (!pszBaseTableName)
2149 : {
2150 0 : CPLError(CE_Warning, CPLE_AppDefined,
2151 : "Could not retrieve base_table_name from "
2152 : "gpkgext_relations");
2153 1 : continue;
2154 : }
2155 47 : const char *pszBasePrimaryColumn = oResultTable->GetValue(1, i);
2156 47 : if (!pszBasePrimaryColumn)
2157 : {
2158 0 : CPLError(CE_Warning, CPLE_AppDefined,
2159 : "Could not retrieve base_primary_column from "
2160 : "gpkgext_relations");
2161 0 : continue;
2162 : }
2163 47 : const char *pszRelatedTableName = oResultTable->GetValue(2, i);
2164 47 : if (!pszRelatedTableName)
2165 : {
2166 0 : CPLError(CE_Warning, CPLE_AppDefined,
2167 : "Could not retrieve related_table_name from "
2168 : "gpkgext_relations");
2169 0 : continue;
2170 : }
2171 47 : const char *pszRelatedPrimaryColumn = oResultTable->GetValue(3, i);
2172 47 : if (!pszRelatedPrimaryColumn)
2173 : {
2174 0 : CPLError(CE_Warning, CPLE_AppDefined,
2175 : "Could not retrieve related_primary_column from "
2176 : "gpkgext_relations");
2177 0 : continue;
2178 : }
2179 47 : const char *pszRelationName = oResultTable->GetValue(4, i);
2180 47 : if (!pszRelationName)
2181 : {
2182 0 : CPLError(
2183 : CE_Warning, CPLE_AppDefined,
2184 : "Could not retrieve relation_name from gpkgext_relations");
2185 0 : continue;
2186 : }
2187 47 : const char *pszMappingTableName = oResultTable->GetValue(5, i);
2188 47 : if (!pszMappingTableName)
2189 : {
2190 0 : CPLError(CE_Warning, CPLE_AppDefined,
2191 : "Could not retrieve mapping_table_name from "
2192 : "gpkgext_relations");
2193 0 : continue;
2194 : }
2195 :
2196 : // confirm that mapping table exists
2197 : char *pszSQL =
2198 47 : sqlite3_mprintf("SELECT 1 FROM sqlite_master WHERE "
2199 : "name='%q' AND type IN ('table', 'view')",
2200 : pszMappingTableName);
2201 47 : const int nMappingTableCount = SQLGetInteger(hDB, pszSQL, nullptr);
2202 47 : sqlite3_free(pszSQL);
2203 :
2204 47 : if (nMappingTableCount < 1)
2205 : {
2206 1 : CPLError(CE_Warning, CPLE_AppDefined,
2207 : "Relationship mapping table %s does not exist",
2208 : pszMappingTableName);
2209 1 : continue;
2210 : }
2211 :
2212 : const std::string osRelationName = GenerateNameForRelationship(
2213 92 : pszBaseTableName, pszRelatedTableName, pszRelationName);
2214 :
2215 92 : std::string osType{};
2216 : // defined requirement classes -- for these types the relation name
2217 : // will be specific string value from the related tables extension.
2218 : // In this case we need to construct a unique relationship name
2219 : // based on the related tables
2220 46 : if (EQUAL(pszRelationName, "media") ||
2221 34 : EQUAL(pszRelationName, "simple_attributes") ||
2222 34 : EQUAL(pszRelationName, "features") ||
2223 12 : EQUAL(pszRelationName, "attributes") ||
2224 2 : EQUAL(pszRelationName, "tiles"))
2225 : {
2226 44 : osType = pszRelationName;
2227 : }
2228 : else
2229 : {
2230 : // user defined types default to features
2231 2 : osType = "features";
2232 : }
2233 :
2234 : std::unique_ptr<GDALRelationship> poRelationship(
2235 : new GDALRelationship(osRelationName, pszBaseTableName,
2236 138 : pszRelatedTableName, GRC_MANY_TO_MANY));
2237 :
2238 92 : poRelationship->SetLeftTableFields({pszBasePrimaryColumn});
2239 92 : poRelationship->SetRightTableFields({pszRelatedPrimaryColumn});
2240 92 : poRelationship->SetLeftMappingTableFields({"base_id"});
2241 92 : poRelationship->SetRightMappingTableFields({"related_id"});
2242 46 : poRelationship->SetMappingTableName(pszMappingTableName);
2243 46 : poRelationship->SetRelatedTableType(osType);
2244 :
2245 46 : m_osMapRelationships[osRelationName] = std::move(poRelationship);
2246 : }
2247 : }
2248 30 : }
2249 :
2250 : /************************************************************************/
2251 : /* GenerateNameForRelationship() */
2252 : /************************************************************************/
2253 :
2254 66 : std::string GDALGeoPackageDataset::GenerateNameForRelationship(
2255 : const char *pszBaseTableName, const char *pszRelatedTableName,
2256 : const char *pszType)
2257 : {
2258 : // defined requirement classes -- for these types the relation name will be
2259 : // specific string value from the related tables extension. In this case we
2260 : // need to construct a unique relationship name based on the related tables
2261 66 : if (EQUAL(pszType, "media") || EQUAL(pszType, "simple_attributes") ||
2262 43 : EQUAL(pszType, "features") || EQUAL(pszType, "attributes") ||
2263 8 : EQUAL(pszType, "tiles"))
2264 : {
2265 116 : std::ostringstream stream;
2266 : stream << pszBaseTableName << '_' << pszRelatedTableName << '_'
2267 58 : << pszType;
2268 58 : return stream.str();
2269 : }
2270 : else
2271 : {
2272 : // user defined types default to features
2273 8 : return pszType;
2274 : }
2275 : }
2276 :
2277 : /************************************************************************/
2278 : /* ValidateRelationship() */
2279 : /************************************************************************/
2280 :
2281 24 : bool GDALGeoPackageDataset::ValidateRelationship(
2282 : const GDALRelationship *poRelationship, std::string &failureReason)
2283 : {
2284 :
2285 24 : if (poRelationship->GetCardinality() !=
2286 : GDALRelationshipCardinality::GRC_MANY_TO_MANY)
2287 : {
2288 3 : failureReason = "Only many to many relationships are supported";
2289 3 : return false;
2290 : }
2291 :
2292 42 : std::string osRelatedTableType = poRelationship->GetRelatedTableType();
2293 53 : if (!osRelatedTableType.empty() && osRelatedTableType != "features" &&
2294 22 : osRelatedTableType != "media" &&
2295 12 : osRelatedTableType != "simple_attributes" &&
2296 43 : osRelatedTableType != "attributes" && osRelatedTableType != "tiles")
2297 : {
2298 : failureReason =
2299 4 : ("Related table type " + osRelatedTableType +
2300 : " is not a valid value for the GeoPackage specification. "
2301 : "Valid values are: features, media, simple_attributes, "
2302 : "attributes, tiles.")
2303 2 : .c_str();
2304 2 : return false;
2305 : }
2306 :
2307 19 : const std::string &osLeftTableName = poRelationship->GetLeftTableName();
2308 19 : OGRGeoPackageLayer *poLeftTable = cpl::down_cast<OGRGeoPackageLayer *>(
2309 19 : GetLayerByName(osLeftTableName.c_str()));
2310 19 : if (!poLeftTable)
2311 : {
2312 2 : failureReason = ("Left table " + osLeftTableName +
2313 : " is not an existing layer in the dataset")
2314 1 : .c_str();
2315 1 : return false;
2316 : }
2317 18 : const std::string &osRightTableName = poRelationship->GetRightTableName();
2318 18 : OGRGeoPackageLayer *poRightTable = cpl::down_cast<OGRGeoPackageLayer *>(
2319 18 : GetLayerByName(osRightTableName.c_str()));
2320 18 : if (!poRightTable)
2321 : {
2322 2 : failureReason = ("Right table " + osRightTableName +
2323 : " is not an existing layer in the dataset")
2324 1 : .c_str();
2325 1 : return false;
2326 : }
2327 :
2328 17 : const auto &aosLeftTableFields = poRelationship->GetLeftTableFields();
2329 17 : if (aosLeftTableFields.empty())
2330 : {
2331 1 : failureReason = "No left table fields were specified";
2332 1 : return false;
2333 : }
2334 16 : else if (aosLeftTableFields.size() > 1)
2335 : {
2336 : failureReason = "Only a single left table field is permitted for the "
2337 1 : "GeoPackage specification";
2338 1 : return false;
2339 : }
2340 : else
2341 : {
2342 : // validate left field exists
2343 30 : if (poLeftTable->GetLayerDefn()->GetFieldIndex(
2344 33 : aosLeftTableFields[0].c_str()) < 0 &&
2345 3 : !EQUAL(poLeftTable->GetFIDColumn(), aosLeftTableFields[0].c_str()))
2346 : {
2347 2 : failureReason = ("Left table field " + aosLeftTableFields[0] +
2348 2 : " does not exist in " + osLeftTableName)
2349 1 : .c_str();
2350 1 : return false;
2351 : }
2352 : }
2353 :
2354 14 : const auto &aosRightTableFields = poRelationship->GetRightTableFields();
2355 14 : if (aosRightTableFields.empty())
2356 : {
2357 1 : failureReason = "No right table fields were specified";
2358 1 : return false;
2359 : }
2360 13 : else if (aosRightTableFields.size() > 1)
2361 : {
2362 : failureReason = "Only a single right table field is permitted for the "
2363 1 : "GeoPackage specification";
2364 1 : return false;
2365 : }
2366 : else
2367 : {
2368 : // validate right field exists
2369 24 : if (poRightTable->GetLayerDefn()->GetFieldIndex(
2370 28 : aosRightTableFields[0].c_str()) < 0 &&
2371 4 : !EQUAL(poRightTable->GetFIDColumn(),
2372 : aosRightTableFields[0].c_str()))
2373 : {
2374 4 : failureReason = ("Right table field " + aosRightTableFields[0] +
2375 4 : " does not exist in " + osRightTableName)
2376 2 : .c_str();
2377 2 : return false;
2378 : }
2379 : }
2380 :
2381 10 : return true;
2382 : }
2383 :
2384 : /************************************************************************/
2385 : /* InitRaster() */
2386 : /************************************************************************/
2387 :
2388 341 : bool GDALGeoPackageDataset::InitRaster(
2389 : GDALGeoPackageDataset *poParentDS, const char *pszTableName, double dfMinX,
2390 : double dfMinY, double dfMaxX, double dfMaxY, const char *pszContentsMinX,
2391 : const char *pszContentsMinY, const char *pszContentsMaxX,
2392 : const char *pszContentsMaxY, char **papszOpenOptionsIn,
2393 : const SQLResult &oResult, int nIdxInResult)
2394 : {
2395 341 : m_osRasterTable = pszTableName;
2396 341 : m_dfTMSMinX = dfMinX;
2397 341 : m_dfTMSMaxY = dfMaxY;
2398 :
2399 : // Despite prior checking, the type might be Binary and
2400 : // SQLResultGetValue() not working properly on it
2401 341 : int nZoomLevel = atoi(oResult.GetValue(0, nIdxInResult));
2402 341 : if (nZoomLevel < 0 || nZoomLevel > 65536)
2403 : {
2404 0 : return false;
2405 : }
2406 341 : double dfPixelXSize = CPLAtof(oResult.GetValue(1, nIdxInResult));
2407 341 : double dfPixelYSize = CPLAtof(oResult.GetValue(2, nIdxInResult));
2408 341 : if (dfPixelXSize <= 0 || dfPixelYSize <= 0)
2409 : {
2410 0 : return false;
2411 : }
2412 341 : int nTileWidth = atoi(oResult.GetValue(3, nIdxInResult));
2413 341 : int nTileHeight = atoi(oResult.GetValue(4, nIdxInResult));
2414 341 : if (nTileWidth <= 0 || nTileWidth > 65536 || nTileHeight <= 0 ||
2415 : nTileHeight > 65536)
2416 : {
2417 0 : return false;
2418 : }
2419 : int nTileMatrixWidth = static_cast<int>(
2420 682 : std::min(static_cast<GIntBig>(INT_MAX),
2421 341 : CPLAtoGIntBig(oResult.GetValue(5, nIdxInResult))));
2422 : int nTileMatrixHeight = static_cast<int>(
2423 682 : std::min(static_cast<GIntBig>(INT_MAX),
2424 341 : CPLAtoGIntBig(oResult.GetValue(6, nIdxInResult))));
2425 341 : if (nTileMatrixWidth <= 0 || nTileMatrixHeight <= 0)
2426 : {
2427 0 : return false;
2428 : }
2429 :
2430 : /* Use content bounds in priority over tile_matrix_set bounds */
2431 341 : double dfGDALMinX = dfMinX;
2432 341 : double dfGDALMinY = dfMinY;
2433 341 : double dfGDALMaxX = dfMaxX;
2434 341 : double dfGDALMaxY = dfMaxY;
2435 : pszContentsMinX =
2436 341 : CSLFetchNameValueDef(papszOpenOptionsIn, "MINX", pszContentsMinX);
2437 : pszContentsMinY =
2438 341 : CSLFetchNameValueDef(papszOpenOptionsIn, "MINY", pszContentsMinY);
2439 : pszContentsMaxX =
2440 341 : CSLFetchNameValueDef(papszOpenOptionsIn, "MAXX", pszContentsMaxX);
2441 : pszContentsMaxY =
2442 341 : CSLFetchNameValueDef(papszOpenOptionsIn, "MAXY", pszContentsMaxY);
2443 341 : if (pszContentsMinX != nullptr && pszContentsMinY != nullptr &&
2444 341 : pszContentsMaxX != nullptr && pszContentsMaxY != nullptr)
2445 : {
2446 681 : if (CPLAtof(pszContentsMinX) < CPLAtof(pszContentsMaxX) &&
2447 340 : CPLAtof(pszContentsMinY) < CPLAtof(pszContentsMaxY))
2448 : {
2449 340 : dfGDALMinX = CPLAtof(pszContentsMinX);
2450 340 : dfGDALMinY = CPLAtof(pszContentsMinY);
2451 340 : dfGDALMaxX = CPLAtof(pszContentsMaxX);
2452 340 : dfGDALMaxY = CPLAtof(pszContentsMaxY);
2453 : }
2454 : else
2455 : {
2456 1 : CPLError(CE_Warning, CPLE_AppDefined,
2457 : "Illegal min_x/min_y/max_x/max_y values for %s in open "
2458 : "options and/or gpkg_contents. Using bounds of "
2459 : "gpkg_tile_matrix_set instead",
2460 : pszTableName);
2461 : }
2462 : }
2463 341 : if (dfGDALMinX >= dfGDALMaxX || dfGDALMinY >= dfGDALMaxY)
2464 : {
2465 0 : CPLError(CE_Failure, CPLE_AppDefined,
2466 : "Illegal min_x/min_y/max_x/max_y values for %s", pszTableName);
2467 0 : return false;
2468 : }
2469 :
2470 341 : int nBandCount = 0;
2471 : const char *pszBAND_COUNT =
2472 341 : CSLFetchNameValue(papszOpenOptionsIn, "BAND_COUNT");
2473 341 : if (poParentDS)
2474 : {
2475 85 : nBandCount = poParentDS->GetRasterCount();
2476 : }
2477 256 : else if (m_eDT != GDT_Byte)
2478 : {
2479 63 : if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO") &&
2480 0 : !EQUAL(pszBAND_COUNT, "1"))
2481 : {
2482 0 : CPLError(CE_Warning, CPLE_AppDefined,
2483 : "BAND_COUNT ignored for non-Byte data");
2484 : }
2485 63 : nBandCount = 1;
2486 : }
2487 : else
2488 : {
2489 193 : if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO"))
2490 : {
2491 69 : nBandCount = atoi(pszBAND_COUNT);
2492 69 : if (nBandCount == 1)
2493 5 : GetMetadata("IMAGE_STRUCTURE");
2494 : }
2495 : else
2496 : {
2497 124 : GetMetadata("IMAGE_STRUCTURE");
2498 124 : nBandCount = m_nBandCountFromMetadata;
2499 124 : if (nBandCount == 1)
2500 30 : m_eTF = GPKG_TF_PNG;
2501 : }
2502 193 : if (nBandCount == 1 && !m_osTFFromMetadata.empty())
2503 : {
2504 2 : m_eTF = GDALGPKGMBTilesGetTileFormat(m_osTFFromMetadata.c_str());
2505 : }
2506 193 : if (nBandCount <= 0 || nBandCount > 4)
2507 82 : nBandCount = 4;
2508 : }
2509 :
2510 341 : return InitRaster(poParentDS, pszTableName, nZoomLevel, nBandCount, dfMinX,
2511 : dfMaxY, dfPixelXSize, dfPixelYSize, nTileWidth,
2512 : nTileHeight, nTileMatrixWidth, nTileMatrixHeight,
2513 341 : dfGDALMinX, dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
2514 : }
2515 :
2516 : /************************************************************************/
2517 : /* ComputeTileAndPixelShifts() */
2518 : /************************************************************************/
2519 :
2520 737 : bool GDALGeoPackageDataset::ComputeTileAndPixelShifts()
2521 : {
2522 : int nTileWidth, nTileHeight;
2523 737 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
2524 :
2525 : // Compute shift between GDAL origin and TileMatrixSet origin
2526 737 : const double dfShiftXPixels =
2527 737 : (m_adfGeoTransform[0] - m_dfTMSMinX) / m_adfGeoTransform[1];
2528 737 : if (dfShiftXPixels / nTileWidth <= INT_MIN ||
2529 735 : dfShiftXPixels / nTileWidth > INT_MAX)
2530 2 : return false;
2531 735 : const int64_t nShiftXPixels =
2532 735 : static_cast<int64_t>(floor(0.5 + dfShiftXPixels));
2533 735 : m_nShiftXTiles = static_cast<int>(nShiftXPixels / nTileWidth);
2534 735 : if (nShiftXPixels < 0 && (nShiftXPixels % nTileWidth) != 0)
2535 11 : m_nShiftXTiles--;
2536 735 : m_nShiftXPixelsMod =
2537 735 : (static_cast<int>(nShiftXPixels % nTileWidth) + nTileWidth) %
2538 : nTileWidth;
2539 :
2540 735 : const double dfShiftYPixels =
2541 735 : (m_adfGeoTransform[3] - m_dfTMSMaxY) / m_adfGeoTransform[5];
2542 735 : if (dfShiftYPixels / nTileHeight <= INT_MIN ||
2543 735 : dfShiftYPixels / nTileHeight > INT_MAX)
2544 1 : return false;
2545 734 : const int64_t nShiftYPixels =
2546 734 : static_cast<int64_t>(floor(0.5 + dfShiftYPixels));
2547 734 : m_nShiftYTiles = static_cast<int>(nShiftYPixels / nTileHeight);
2548 734 : if (nShiftYPixels < 0 && (nShiftYPixels % nTileHeight) != 0)
2549 11 : m_nShiftYTiles--;
2550 734 : m_nShiftYPixelsMod =
2551 734 : (static_cast<int>(nShiftYPixels % nTileHeight) + nTileHeight) %
2552 : nTileHeight;
2553 734 : return true;
2554 : }
2555 :
2556 : /************************************************************************/
2557 : /* AllocCachedTiles() */
2558 : /************************************************************************/
2559 :
2560 734 : bool GDALGeoPackageDataset::AllocCachedTiles()
2561 : {
2562 : int nTileWidth, nTileHeight;
2563 734 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
2564 :
2565 : // We currently need 4 caches because of
2566 : // GDALGPKGMBTilesLikePseudoDataset::ReadTile(int nRow, int nCol)
2567 734 : const int nCacheCount = 4;
2568 : /*
2569 : (m_nShiftXPixelsMod != 0 || m_nShiftYPixelsMod != 0) ? 4 :
2570 : (GetUpdate() && m_eDT == GDT_Byte) ? 2 : 1;
2571 : */
2572 734 : m_pabyCachedTiles = static_cast<GByte *>(VSI_MALLOC3_VERBOSE(
2573 : cpl::fits_on<int>(nCacheCount * (m_eDT == GDT_Byte ? 4 : 1) *
2574 : m_nDTSize),
2575 : nTileWidth, nTileHeight));
2576 734 : if (m_pabyCachedTiles == nullptr)
2577 : {
2578 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too big tiles: %d x %d",
2579 : nTileWidth, nTileHeight);
2580 0 : return false;
2581 : }
2582 :
2583 734 : return true;
2584 : }
2585 :
2586 : /************************************************************************/
2587 : /* InitRaster() */
2588 : /************************************************************************/
2589 :
2590 577 : bool GDALGeoPackageDataset::InitRaster(
2591 : GDALGeoPackageDataset *poParentDS, const char *pszTableName, int nZoomLevel,
2592 : int nBandCount, double dfTMSMinX, double dfTMSMaxY, double dfPixelXSize,
2593 : double dfPixelYSize, int nTileWidth, int nTileHeight, int nTileMatrixWidth,
2594 : int nTileMatrixHeight, double dfGDALMinX, double dfGDALMinY,
2595 : double dfGDALMaxX, double dfGDALMaxY)
2596 : {
2597 577 : m_osRasterTable = pszTableName;
2598 577 : m_dfTMSMinX = dfTMSMinX;
2599 577 : m_dfTMSMaxY = dfTMSMaxY;
2600 577 : m_nZoomLevel = nZoomLevel;
2601 577 : m_nTileMatrixWidth = nTileMatrixWidth;
2602 577 : m_nTileMatrixHeight = nTileMatrixHeight;
2603 :
2604 577 : m_bGeoTransformValid = true;
2605 577 : m_adfGeoTransform[0] = dfGDALMinX;
2606 577 : m_adfGeoTransform[1] = dfPixelXSize;
2607 577 : m_adfGeoTransform[3] = dfGDALMaxY;
2608 577 : m_adfGeoTransform[5] = -dfPixelYSize;
2609 577 : double dfRasterXSize = 0.5 + (dfGDALMaxX - dfGDALMinX) / dfPixelXSize;
2610 577 : double dfRasterYSize = 0.5 + (dfGDALMaxY - dfGDALMinY) / dfPixelYSize;
2611 577 : if (dfRasterXSize > INT_MAX || dfRasterYSize > INT_MAX)
2612 : {
2613 0 : CPLError(CE_Failure, CPLE_NotSupported, "Too big raster: %f x %f",
2614 : dfRasterXSize, dfRasterYSize);
2615 0 : return false;
2616 : }
2617 577 : nRasterXSize = std::max(1, static_cast<int>(dfRasterXSize));
2618 577 : nRasterYSize = std::max(1, static_cast<int>(dfRasterYSize));
2619 :
2620 577 : if (poParentDS)
2621 : {
2622 321 : m_poParentDS = poParentDS;
2623 321 : eAccess = poParentDS->eAccess;
2624 321 : hDB = poParentDS->hDB;
2625 321 : m_eTF = poParentDS->m_eTF;
2626 321 : m_eDT = poParentDS->m_eDT;
2627 321 : m_nDTSize = poParentDS->m_nDTSize;
2628 321 : m_dfScale = poParentDS->m_dfScale;
2629 321 : m_dfOffset = poParentDS->m_dfOffset;
2630 321 : m_dfPrecision = poParentDS->m_dfPrecision;
2631 321 : m_usGPKGNull = poParentDS->m_usGPKGNull;
2632 321 : m_nQuality = poParentDS->m_nQuality;
2633 321 : m_nZLevel = poParentDS->m_nZLevel;
2634 321 : m_bDither = poParentDS->m_bDither;
2635 : /*m_nSRID = poParentDS->m_nSRID;*/
2636 321 : m_osWHERE = poParentDS->m_osWHERE;
2637 321 : SetDescription(CPLSPrintf("%s - zoom_level=%d",
2638 321 : poParentDS->GetDescription(), m_nZoomLevel));
2639 : }
2640 :
2641 2034 : for (int i = 1; i <= nBandCount; i++)
2642 : {
2643 : GDALGeoPackageRasterBand *poNewBand =
2644 1457 : new GDALGeoPackageRasterBand(this, nTileWidth, nTileHeight);
2645 1457 : if (poParentDS)
2646 : {
2647 753 : int bHasNoData = FALSE;
2648 : double dfNoDataValue =
2649 753 : poParentDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
2650 753 : if (bHasNoData)
2651 24 : poNewBand->SetNoDataValueInternal(dfNoDataValue);
2652 : }
2653 1457 : SetBand(i, poNewBand);
2654 :
2655 1457 : if (nBandCount == 1 && m_poCTFromMetadata)
2656 : {
2657 3 : poNewBand->AssignColorTable(m_poCTFromMetadata.get());
2658 : }
2659 1457 : if (!m_osNodataValueFromMetadata.empty())
2660 : {
2661 8 : poNewBand->SetNoDataValueInternal(
2662 : CPLAtof(m_osNodataValueFromMetadata.c_str()));
2663 : }
2664 : }
2665 :
2666 577 : if (!ComputeTileAndPixelShifts())
2667 : {
2668 3 : CPLError(CE_Failure, CPLE_AppDefined,
2669 : "Overflow occurred in ComputeTileAndPixelShifts()");
2670 3 : return false;
2671 : }
2672 :
2673 574 : GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
2674 574 : GDALPamDataset::SetMetadataItem("ZOOM_LEVEL",
2675 : CPLSPrintf("%d", m_nZoomLevel));
2676 :
2677 574 : return AllocCachedTiles();
2678 : }
2679 :
2680 : /************************************************************************/
2681 : /* GDALGPKGMBTilesGetTileFormat() */
2682 : /************************************************************************/
2683 :
2684 79 : GPKGTileFormat GDALGPKGMBTilesGetTileFormat(const char *pszTF)
2685 : {
2686 79 : GPKGTileFormat eTF = GPKG_TF_PNG_JPEG;
2687 79 : if (pszTF)
2688 : {
2689 79 : if (EQUAL(pszTF, "PNG_JPEG") || EQUAL(pszTF, "AUTO"))
2690 1 : eTF = GPKG_TF_PNG_JPEG;
2691 78 : else if (EQUAL(pszTF, "PNG"))
2692 45 : eTF = GPKG_TF_PNG;
2693 33 : else if (EQUAL(pszTF, "PNG8"))
2694 6 : eTF = GPKG_TF_PNG8;
2695 27 : else if (EQUAL(pszTF, "JPEG"))
2696 14 : eTF = GPKG_TF_JPEG;
2697 13 : else if (EQUAL(pszTF, "WEBP"))
2698 13 : eTF = GPKG_TF_WEBP;
2699 : else
2700 : {
2701 0 : CPLError(CE_Failure, CPLE_NotSupported,
2702 : "Unsuppoted value for TILE_FORMAT: %s", pszTF);
2703 : }
2704 : }
2705 79 : return eTF;
2706 : }
2707 :
2708 28 : const char *GDALMBTilesGetTileFormatName(GPKGTileFormat eTF)
2709 : {
2710 28 : switch (eTF)
2711 : {
2712 26 : case GPKG_TF_PNG:
2713 : case GPKG_TF_PNG8:
2714 26 : return "png";
2715 1 : case GPKG_TF_JPEG:
2716 1 : return "jpg";
2717 1 : case GPKG_TF_WEBP:
2718 1 : return "webp";
2719 0 : default:
2720 0 : break;
2721 : }
2722 0 : CPLError(CE_Failure, CPLE_NotSupported,
2723 : "Unsuppoted value for TILE_FORMAT: %d", static_cast<int>(eTF));
2724 0 : return nullptr;
2725 : }
2726 :
2727 : /************************************************************************/
2728 : /* OpenRaster() */
2729 : /************************************************************************/
2730 :
2731 258 : bool GDALGeoPackageDataset::OpenRaster(
2732 : const char *pszTableName, const char *pszIdentifier,
2733 : const char *pszDescription, int nSRSId, double dfMinX, double dfMinY,
2734 : double dfMaxX, double dfMaxY, const char *pszContentsMinX,
2735 : const char *pszContentsMinY, const char *pszContentsMaxX,
2736 : const char *pszContentsMaxY, bool bIsTiles, char **papszOpenOptionsIn)
2737 : {
2738 258 : if (dfMinX >= dfMaxX || dfMinY >= dfMaxY)
2739 0 : return false;
2740 :
2741 : // Config option just for debug, and for example force set to NaN
2742 : // which is not supported
2743 516 : CPLString osDataNull = CPLGetConfigOption("GPKG_NODATA", "");
2744 516 : CPLString osUom;
2745 516 : CPLString osFieldName;
2746 516 : CPLString osGridCellEncoding;
2747 258 : if (!bIsTiles)
2748 : {
2749 63 : char *pszSQL = sqlite3_mprintf(
2750 : "SELECT datatype, scale, offset, data_null, precision FROM "
2751 : "gpkg_2d_gridded_coverage_ancillary "
2752 : "WHERE tile_matrix_set_name = '%q' "
2753 : "AND datatype IN ('integer', 'float')"
2754 : "AND (scale > 0 OR scale IS NULL)",
2755 : pszTableName);
2756 63 : auto oResult = SQLQuery(hDB, pszSQL);
2757 63 : sqlite3_free(pszSQL);
2758 63 : if (!oResult || oResult->RowCount() == 0)
2759 : {
2760 0 : return false;
2761 : }
2762 63 : const char *pszDataType = oResult->GetValue(0, 0);
2763 63 : const char *pszScale = oResult->GetValue(1, 0);
2764 63 : const char *pszOffset = oResult->GetValue(2, 0);
2765 63 : const char *pszDataNull = oResult->GetValue(3, 0);
2766 63 : const char *pszPrecision = oResult->GetValue(4, 0);
2767 63 : if (pszDataNull)
2768 23 : osDataNull = pszDataNull;
2769 63 : if (EQUAL(pszDataType, "float"))
2770 : {
2771 6 : SetDataType(GDT_Float32);
2772 6 : m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
2773 : }
2774 : else
2775 : {
2776 57 : SetDataType(GDT_Float32);
2777 57 : m_eTF = GPKG_TF_PNG_16BIT;
2778 57 : const double dfScale = pszScale ? CPLAtof(pszScale) : 1.0;
2779 57 : const double dfOffset = pszOffset ? CPLAtof(pszOffset) : 0.0;
2780 57 : if (dfScale == 1.0)
2781 : {
2782 57 : if (dfOffset == 0.0)
2783 : {
2784 24 : SetDataType(GDT_UInt16);
2785 : }
2786 33 : else if (dfOffset == -32768.0)
2787 : {
2788 33 : SetDataType(GDT_Int16);
2789 : }
2790 : // coverity[tainted_data]
2791 0 : else if (dfOffset == -32767.0 && !osDataNull.empty() &&
2792 0 : CPLAtof(osDataNull) == 65535.0)
2793 : // Given that we will map the nodata value to -32768
2794 : {
2795 0 : SetDataType(GDT_Int16);
2796 : }
2797 : }
2798 :
2799 : // Check that the tile offset and scales are compatible of a
2800 : // final integer result.
2801 57 : if (m_eDT != GDT_Float32)
2802 : {
2803 : // coverity[tainted_data]
2804 57 : if (dfScale == 1.0 && dfOffset == -32768.0 &&
2805 114 : !osDataNull.empty() && CPLAtof(osDataNull) == 65535.0)
2806 : {
2807 : // Given that we will map the nodata value to -32768
2808 9 : pszSQL = sqlite3_mprintf(
2809 : "SELECT 1 FROM "
2810 : "gpkg_2d_gridded_tile_ancillary WHERE "
2811 : "tpudt_name = '%q' "
2812 : "AND NOT ((offset = 0.0 or offset = 1.0) "
2813 : "AND scale = 1.0) "
2814 : "LIMIT 1",
2815 : pszTableName);
2816 : }
2817 : else
2818 : {
2819 48 : pszSQL = sqlite3_mprintf(
2820 : "SELECT 1 FROM "
2821 : "gpkg_2d_gridded_tile_ancillary WHERE "
2822 : "tpudt_name = '%q' "
2823 : "AND NOT (offset = 0.0 AND scale = 1.0) LIMIT 1",
2824 : pszTableName);
2825 : }
2826 57 : sqlite3_stmt *hSQLStmt = nullptr;
2827 : int rc =
2828 57 : sqlite3_prepare_v2(hDB, pszSQL, -1, &hSQLStmt, nullptr);
2829 :
2830 57 : if (rc == SQLITE_OK)
2831 : {
2832 57 : if (sqlite3_step(hSQLStmt) == SQLITE_ROW)
2833 : {
2834 8 : SetDataType(GDT_Float32);
2835 : }
2836 57 : sqlite3_finalize(hSQLStmt);
2837 : }
2838 : else
2839 : {
2840 0 : CPLError(CE_Failure, CPLE_AppDefined,
2841 : "Error when running %s", pszSQL);
2842 : }
2843 57 : sqlite3_free(pszSQL);
2844 : }
2845 :
2846 57 : SetGlobalOffsetScale(dfOffset, dfScale);
2847 : }
2848 63 : if (pszPrecision)
2849 63 : m_dfPrecision = CPLAtof(pszPrecision);
2850 :
2851 : // Request those columns in a separate query, so as to keep
2852 : // compatibility with pre OGC 17-066r1 databases
2853 : pszSQL =
2854 63 : sqlite3_mprintf("SELECT uom, field_name, grid_cell_encoding FROM "
2855 : "gpkg_2d_gridded_coverage_ancillary "
2856 : "WHERE tile_matrix_set_name = '%q'",
2857 : pszTableName);
2858 63 : CPLPushErrorHandler(CPLQuietErrorHandler);
2859 63 : oResult = SQLQuery(hDB, pszSQL);
2860 63 : CPLPopErrorHandler();
2861 63 : sqlite3_free(pszSQL);
2862 63 : if (oResult && oResult->RowCount() == 1)
2863 : {
2864 62 : const char *pszUom = oResult->GetValue(0, 0);
2865 62 : if (pszUom)
2866 2 : osUom = pszUom;
2867 62 : const char *pszFieldName = oResult->GetValue(1, 0);
2868 62 : if (pszFieldName)
2869 62 : osFieldName = pszFieldName;
2870 62 : const char *pszGridCellEncoding = oResult->GetValue(2, 0);
2871 62 : if (pszGridCellEncoding)
2872 62 : osGridCellEncoding = pszGridCellEncoding;
2873 : }
2874 : }
2875 :
2876 258 : m_bRecordInsertedInGPKGContent = true;
2877 258 : m_nSRID = nSRSId;
2878 :
2879 258 : OGRSpatialReference *poSRS = GetSpatialRef(nSRSId);
2880 258 : if (poSRS)
2881 : {
2882 257 : m_oSRS = *poSRS;
2883 257 : poSRS->Release();
2884 : }
2885 :
2886 : /* Various sanity checks added in the SELECT */
2887 258 : char *pszQuotedTableName = sqlite3_mprintf("'%q'", pszTableName);
2888 516 : CPLString osQuotedTableName(pszQuotedTableName);
2889 258 : sqlite3_free(pszQuotedTableName);
2890 258 : char *pszSQL = sqlite3_mprintf(
2891 : "SELECT zoom_level, pixel_x_size, pixel_y_size, tile_width, "
2892 : "tile_height, matrix_width, matrix_height "
2893 : "FROM gpkg_tile_matrix tm "
2894 : "WHERE table_name = %s "
2895 : // INT_MAX would be the theoretical maximum value to avoid
2896 : // overflows, but that's already a insane value.
2897 : "AND zoom_level >= 0 AND zoom_level <= 65536 "
2898 : "AND pixel_x_size > 0 AND pixel_y_size > 0 "
2899 : "AND tile_width >= 1 AND tile_width <= 65536 "
2900 : "AND tile_height >= 1 AND tile_height <= 65536 "
2901 : "AND matrix_width >= 1 AND matrix_height >= 1",
2902 : osQuotedTableName.c_str());
2903 516 : CPLString osSQL(pszSQL);
2904 : const char *pszZoomLevel =
2905 258 : CSLFetchNameValue(papszOpenOptionsIn, "ZOOM_LEVEL");
2906 258 : if (pszZoomLevel)
2907 : {
2908 5 : if (GetUpdate())
2909 1 : osSQL += CPLSPrintf(" AND zoom_level <= %d", atoi(pszZoomLevel));
2910 : else
2911 : {
2912 : osSQL += CPLSPrintf(
2913 : " AND (zoom_level = %d OR (zoom_level < %d AND EXISTS(SELECT 1 "
2914 : "FROM %s WHERE zoom_level = tm.zoom_level LIMIT 1)))",
2915 : atoi(pszZoomLevel), atoi(pszZoomLevel),
2916 4 : osQuotedTableName.c_str());
2917 : }
2918 : }
2919 : // In read-only mode, only lists non empty zoom levels
2920 253 : else if (!GetUpdate())
2921 : {
2922 : osSQL += CPLSPrintf(" AND EXISTS(SELECT 1 FROM %s WHERE zoom_level = "
2923 : "tm.zoom_level LIMIT 1)",
2924 204 : osQuotedTableName.c_str());
2925 : }
2926 : else // if( pszZoomLevel == nullptr )
2927 : {
2928 : osSQL +=
2929 : CPLSPrintf(" AND zoom_level <= (SELECT MAX(zoom_level) FROM %s)",
2930 49 : osQuotedTableName.c_str());
2931 : }
2932 258 : osSQL += " ORDER BY zoom_level DESC";
2933 : // To avoid denial of service.
2934 258 : osSQL += " LIMIT 100";
2935 :
2936 516 : auto oResult = SQLQuery(hDB, osSQL.c_str());
2937 258 : if (!oResult || oResult->RowCount() == 0)
2938 : {
2939 100 : if (oResult && oResult->RowCount() == 0 && pszContentsMinX != nullptr &&
2940 100 : pszContentsMinY != nullptr && pszContentsMaxX != nullptr &&
2941 : pszContentsMaxY != nullptr)
2942 : {
2943 49 : osSQL = pszSQL;
2944 49 : osSQL += " ORDER BY zoom_level DESC";
2945 49 : if (!GetUpdate())
2946 24 : osSQL += " LIMIT 1";
2947 49 : oResult = SQLQuery(hDB, osSQL.c_str());
2948 : }
2949 50 : if (!oResult || oResult->RowCount() == 0)
2950 : {
2951 1 : if (oResult && pszZoomLevel != nullptr)
2952 : {
2953 1 : CPLError(CE_Failure, CPLE_AppDefined,
2954 : "ZOOM_LEVEL is probably not valid w.r.t tile "
2955 : "table content");
2956 : }
2957 1 : sqlite3_free(pszSQL);
2958 1 : return false;
2959 : }
2960 : }
2961 257 : sqlite3_free(pszSQL);
2962 :
2963 : // If USE_TILE_EXTENT=YES, then query the tile table to find which tiles
2964 : // actually exist.
2965 :
2966 : // CAUTION: Do not move those variables inside inner scope !
2967 514 : CPLString osContentsMinX, osContentsMinY, osContentsMaxX, osContentsMaxY;
2968 :
2969 257 : if (CPLTestBool(
2970 : CSLFetchNameValueDef(papszOpenOptionsIn, "USE_TILE_EXTENT", "NO")))
2971 : {
2972 13 : pszSQL = sqlite3_mprintf(
2973 : "SELECT MIN(tile_column), MIN(tile_row), MAX(tile_column), "
2974 : "MAX(tile_row) FROM \"%w\" WHERE zoom_level = %d",
2975 : pszTableName, atoi(oResult->GetValue(0, 0)));
2976 13 : auto oResult2 = SQLQuery(hDB, pszSQL);
2977 13 : sqlite3_free(pszSQL);
2978 26 : if (!oResult2 || oResult2->RowCount() == 0 ||
2979 : // Can happen if table is empty
2980 38 : oResult2->GetValue(0, 0) == nullptr ||
2981 : // Can happen if table has no NOT NULL constraint on tile_row
2982 : // and that all tile_row are NULL
2983 12 : oResult2->GetValue(1, 0) == nullptr)
2984 : {
2985 1 : return false;
2986 : }
2987 12 : const double dfPixelXSize = CPLAtof(oResult->GetValue(1, 0));
2988 12 : const double dfPixelYSize = CPLAtof(oResult->GetValue(2, 0));
2989 12 : const int nTileWidth = atoi(oResult->GetValue(3, 0));
2990 12 : const int nTileHeight = atoi(oResult->GetValue(4, 0));
2991 : osContentsMinX =
2992 24 : CPLSPrintf("%.18g", dfMinX + dfPixelXSize * nTileWidth *
2993 12 : atoi(oResult2->GetValue(0, 0)));
2994 : osContentsMaxY =
2995 24 : CPLSPrintf("%.18g", dfMaxY - dfPixelYSize * nTileHeight *
2996 12 : atoi(oResult2->GetValue(1, 0)));
2997 : osContentsMaxX = CPLSPrintf(
2998 24 : "%.18g", dfMinX + dfPixelXSize * nTileWidth *
2999 12 : (1 + atoi(oResult2->GetValue(2, 0))));
3000 : osContentsMinY = CPLSPrintf(
3001 24 : "%.18g", dfMaxY - dfPixelYSize * nTileHeight *
3002 12 : (1 + atoi(oResult2->GetValue(3, 0))));
3003 12 : pszContentsMinX = osContentsMinX.c_str();
3004 12 : pszContentsMinY = osContentsMinY.c_str();
3005 12 : pszContentsMaxX = osContentsMaxX.c_str();
3006 12 : pszContentsMaxY = osContentsMaxY.c_str();
3007 : }
3008 :
3009 256 : if (!InitRaster(nullptr, pszTableName, dfMinX, dfMinY, dfMaxX, dfMaxY,
3010 : pszContentsMinX, pszContentsMinY, pszContentsMaxX,
3011 256 : pszContentsMaxY, papszOpenOptionsIn, *oResult, 0))
3012 : {
3013 3 : return false;
3014 : }
3015 :
3016 : auto poBand =
3017 253 : reinterpret_cast<GDALGeoPackageRasterBand *>(GetRasterBand(1));
3018 253 : if (!osDataNull.empty())
3019 : {
3020 23 : double dfGPKGNoDataValue = CPLAtof(osDataNull);
3021 23 : if (m_eTF == GPKG_TF_PNG_16BIT)
3022 : {
3023 21 : if (dfGPKGNoDataValue < 0 || dfGPKGNoDataValue > 65535 ||
3024 21 : static_cast<int>(dfGPKGNoDataValue) != dfGPKGNoDataValue)
3025 : {
3026 0 : CPLError(CE_Warning, CPLE_AppDefined,
3027 : "data_null = %.18g is invalid for integer data_type",
3028 : dfGPKGNoDataValue);
3029 : }
3030 : else
3031 : {
3032 21 : m_usGPKGNull = static_cast<GUInt16>(dfGPKGNoDataValue);
3033 21 : if (m_eDT == GDT_Int16 && m_usGPKGNull > 32767)
3034 9 : dfGPKGNoDataValue = -32768.0;
3035 12 : else if (m_eDT == GDT_Float32)
3036 : {
3037 : // Pick a value that is unlikely to be hit with offset &
3038 : // scale
3039 4 : dfGPKGNoDataValue = -std::numeric_limits<float>::max();
3040 : }
3041 21 : poBand->SetNoDataValueInternal(dfGPKGNoDataValue);
3042 : }
3043 : }
3044 : else
3045 : {
3046 2 : poBand->SetNoDataValueInternal(
3047 2 : static_cast<float>(dfGPKGNoDataValue));
3048 : }
3049 : }
3050 253 : if (!osUom.empty())
3051 : {
3052 2 : poBand->SetUnitTypeInternal(osUom);
3053 : }
3054 253 : if (!osFieldName.empty())
3055 : {
3056 62 : GetRasterBand(1)->GDALRasterBand::SetDescription(osFieldName);
3057 : }
3058 253 : if (!osGridCellEncoding.empty())
3059 : {
3060 62 : if (osGridCellEncoding == "grid-value-is-center")
3061 : {
3062 15 : GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
3063 : GDALMD_AOP_POINT);
3064 : }
3065 47 : else if (osGridCellEncoding == "grid-value-is-area")
3066 : {
3067 43 : GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
3068 : GDALMD_AOP_AREA);
3069 : }
3070 : else
3071 : {
3072 4 : GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
3073 : GDALMD_AOP_POINT);
3074 4 : GetRasterBand(1)->GDALRasterBand::SetMetadataItem(
3075 : "GRID_CELL_ENCODING", osGridCellEncoding);
3076 : }
3077 : }
3078 :
3079 253 : CheckUnknownExtensions(true);
3080 :
3081 : // Do this after CheckUnknownExtensions() so that m_eTF is set to
3082 : // GPKG_TF_WEBP if the table already registers the gpkg_webp extension
3083 253 : const char *pszTF = CSLFetchNameValue(papszOpenOptionsIn, "TILE_FORMAT");
3084 253 : if (pszTF)
3085 : {
3086 4 : if (!GetUpdate())
3087 : {
3088 0 : CPLError(CE_Warning, CPLE_AppDefined,
3089 : "TILE_FORMAT open option ignored in read-only mode");
3090 : }
3091 4 : else if (m_eTF == GPKG_TF_PNG_16BIT ||
3092 4 : m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
3093 : {
3094 0 : CPLError(CE_Warning, CPLE_AppDefined,
3095 : "TILE_FORMAT open option ignored on gridded coverages");
3096 : }
3097 : else
3098 : {
3099 4 : GPKGTileFormat eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
3100 4 : if (eTF == GPKG_TF_WEBP && m_eTF != eTF)
3101 : {
3102 1 : if (!RegisterWebPExtension())
3103 0 : return false;
3104 : }
3105 4 : m_eTF = eTF;
3106 : }
3107 : }
3108 :
3109 253 : ParseCompressionOptions(papszOpenOptionsIn);
3110 :
3111 253 : m_osWHERE = CSLFetchNameValueDef(papszOpenOptionsIn, "WHERE", "");
3112 :
3113 : // Set metadata
3114 253 : if (pszIdentifier && pszIdentifier[0])
3115 253 : GDALPamDataset::SetMetadataItem("IDENTIFIER", pszIdentifier);
3116 253 : if (pszDescription && pszDescription[0])
3117 21 : GDALPamDataset::SetMetadataItem("DESCRIPTION", pszDescription);
3118 :
3119 : // Add overviews
3120 337 : for (int i = 1; i < oResult->RowCount(); i++)
3121 : {
3122 85 : GDALGeoPackageDataset *poOvrDS = new GDALGeoPackageDataset();
3123 85 : poOvrDS->ShareLockWithParentDataset(this);
3124 85 : if (!poOvrDS->InitRaster(this, pszTableName, dfMinX, dfMinY, dfMaxX,
3125 : dfMaxY, pszContentsMinX, pszContentsMinY,
3126 : pszContentsMaxX, pszContentsMaxY,
3127 85 : papszOpenOptionsIn, *oResult, i))
3128 : {
3129 0 : delete poOvrDS;
3130 1 : break;
3131 : }
3132 :
3133 85 : m_papoOverviewDS = static_cast<GDALGeoPackageDataset **>(
3134 170 : CPLRealloc(m_papoOverviewDS, sizeof(GDALGeoPackageDataset *) *
3135 85 : (m_nOverviewCount + 1)));
3136 85 : m_papoOverviewDS[m_nOverviewCount++] = poOvrDS;
3137 :
3138 : int nTileWidth, nTileHeight;
3139 85 : poOvrDS->GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3140 86 : if (eAccess == GA_ReadOnly && poOvrDS->GetRasterXSize() < nTileWidth &&
3141 1 : poOvrDS->GetRasterYSize() < nTileHeight)
3142 : {
3143 1 : break;
3144 : }
3145 : }
3146 :
3147 253 : return true;
3148 : }
3149 :
3150 : /************************************************************************/
3151 : /* GetSpatialRef() */
3152 : /************************************************************************/
3153 :
3154 13 : const OGRSpatialReference *GDALGeoPackageDataset::GetSpatialRef() const
3155 : {
3156 13 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
3157 : }
3158 :
3159 : /************************************************************************/
3160 : /* SetSpatialRef() */
3161 : /************************************************************************/
3162 :
3163 136 : CPLErr GDALGeoPackageDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
3164 : {
3165 136 : if (nBands == 0)
3166 : {
3167 1 : CPLError(CE_Failure, CPLE_NotSupported,
3168 : "SetProjection() not supported on a dataset with 0 band");
3169 1 : return CE_Failure;
3170 : }
3171 135 : if (eAccess != GA_Update)
3172 : {
3173 1 : CPLError(CE_Failure, CPLE_NotSupported,
3174 : "SetProjection() not supported on read-only dataset");
3175 1 : return CE_Failure;
3176 : }
3177 :
3178 134 : const int nSRID = GetSrsId(poSRS);
3179 268 : const auto poTS = GetTilingScheme(m_osTilingScheme);
3180 134 : if (poTS && nSRID != poTS->nEPSGCode)
3181 : {
3182 2 : CPLError(CE_Failure, CPLE_NotSupported,
3183 : "Projection should be EPSG:%d for %s tiling scheme",
3184 1 : poTS->nEPSGCode, m_osTilingScheme.c_str());
3185 1 : return CE_Failure;
3186 : }
3187 :
3188 133 : m_nSRID = nSRID;
3189 133 : m_oSRS.Clear();
3190 133 : if (poSRS)
3191 132 : m_oSRS = *poSRS;
3192 :
3193 133 : if (m_bRecordInsertedInGPKGContent)
3194 : {
3195 108 : char *pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET srs_id = %d "
3196 : "WHERE lower(table_name) = lower('%q')",
3197 : m_nSRID, m_osRasterTable.c_str());
3198 108 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3199 108 : sqlite3_free(pszSQL);
3200 108 : if (eErr != OGRERR_NONE)
3201 0 : return CE_Failure;
3202 :
3203 108 : pszSQL = sqlite3_mprintf("UPDATE gpkg_tile_matrix_set SET srs_id = %d "
3204 : "WHERE lower(table_name) = lower('%q')",
3205 : m_nSRID, m_osRasterTable.c_str());
3206 108 : eErr = SQLCommand(hDB, pszSQL);
3207 108 : sqlite3_free(pszSQL);
3208 108 : if (eErr != OGRERR_NONE)
3209 0 : return CE_Failure;
3210 : }
3211 :
3212 133 : return CE_None;
3213 : }
3214 :
3215 : /************************************************************************/
3216 : /* GetGeoTransform() */
3217 : /************************************************************************/
3218 :
3219 30 : CPLErr GDALGeoPackageDataset::GetGeoTransform(double *padfGeoTransform)
3220 : {
3221 30 : memcpy(padfGeoTransform, m_adfGeoTransform, 6 * sizeof(double));
3222 30 : if (!m_bGeoTransformValid)
3223 1 : return CE_Failure;
3224 : else
3225 29 : return CE_None;
3226 : }
3227 :
3228 : /************************************************************************/
3229 : /* SetGeoTransform() */
3230 : /************************************************************************/
3231 :
3232 164 : CPLErr GDALGeoPackageDataset::SetGeoTransform(double *padfGeoTransform)
3233 : {
3234 164 : if (nBands == 0)
3235 : {
3236 1 : CPLError(CE_Failure, CPLE_NotSupported,
3237 : "SetGeoTransform() not supported on a dataset with 0 band");
3238 1 : return CE_Failure;
3239 : }
3240 163 : if (eAccess != GA_Update)
3241 : {
3242 1 : CPLError(CE_Failure, CPLE_NotSupported,
3243 : "SetGeoTransform() not supported on read-only dataset");
3244 1 : return CE_Failure;
3245 : }
3246 162 : if (m_bGeoTransformValid)
3247 : {
3248 1 : CPLError(CE_Failure, CPLE_NotSupported,
3249 : "Cannot modify geotransform once set");
3250 1 : return CE_Failure;
3251 : }
3252 161 : if (padfGeoTransform[2] != 0.0 || padfGeoTransform[4] != 0 ||
3253 161 : padfGeoTransform[5] > 0.0)
3254 : {
3255 0 : CPLError(CE_Failure, CPLE_NotSupported,
3256 : "Only north-up non rotated geotransform supported");
3257 0 : return CE_Failure;
3258 : }
3259 :
3260 161 : if (m_nZoomLevel < 0)
3261 : {
3262 160 : const auto poTS = GetTilingScheme(m_osTilingScheme);
3263 160 : if (poTS)
3264 : {
3265 20 : double dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
3266 20 : double dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
3267 199 : for (m_nZoomLevel = 0; m_nZoomLevel < MAX_ZOOM_LEVEL;
3268 179 : m_nZoomLevel++)
3269 : {
3270 198 : double dfExpectedPixelXSize =
3271 198 : dfPixelXSizeZoomLevel0 / (1 << m_nZoomLevel);
3272 198 : double dfExpectedPixelYSize =
3273 198 : dfPixelYSizeZoomLevel0 / (1 << m_nZoomLevel);
3274 198 : if (fabs(padfGeoTransform[1] - dfExpectedPixelXSize) <
3275 198 : 1e-8 * dfExpectedPixelXSize &&
3276 19 : fabs(fabs(padfGeoTransform[5]) - dfExpectedPixelYSize) <
3277 19 : 1e-8 * dfExpectedPixelYSize)
3278 : {
3279 19 : break;
3280 : }
3281 : }
3282 20 : if (m_nZoomLevel == MAX_ZOOM_LEVEL)
3283 : {
3284 1 : m_nZoomLevel = -1;
3285 1 : CPLError(
3286 : CE_Failure, CPLE_NotSupported,
3287 : "Could not find an appropriate zoom level of %s tiling "
3288 : "scheme that matches raster pixel size",
3289 : m_osTilingScheme.c_str());
3290 1 : return CE_Failure;
3291 : }
3292 : }
3293 : }
3294 :
3295 160 : memcpy(m_adfGeoTransform, padfGeoTransform, 6 * sizeof(double));
3296 160 : m_bGeoTransformValid = true;
3297 :
3298 160 : return FinalizeRasterRegistration();
3299 : }
3300 :
3301 : /************************************************************************/
3302 : /* FinalizeRasterRegistration() */
3303 : /************************************************************************/
3304 :
3305 160 : CPLErr GDALGeoPackageDataset::FinalizeRasterRegistration()
3306 : {
3307 : OGRErr eErr;
3308 :
3309 160 : m_dfTMSMinX = m_adfGeoTransform[0];
3310 160 : m_dfTMSMaxY = m_adfGeoTransform[3];
3311 :
3312 : int nTileWidth, nTileHeight;
3313 160 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3314 :
3315 160 : if (m_nZoomLevel < 0)
3316 : {
3317 140 : m_nZoomLevel = 0;
3318 211 : while ((nRasterXSize >> m_nZoomLevel) > nTileWidth ||
3319 140 : (nRasterYSize >> m_nZoomLevel) > nTileHeight)
3320 71 : m_nZoomLevel++;
3321 : }
3322 :
3323 160 : double dfPixelXSizeZoomLevel0 = m_adfGeoTransform[1] * (1 << m_nZoomLevel);
3324 160 : double dfPixelYSizeZoomLevel0 =
3325 160 : fabs(m_adfGeoTransform[5]) * (1 << m_nZoomLevel);
3326 : int nTileXCountZoomLevel0 =
3327 160 : std::max(1, DIV_ROUND_UP((nRasterXSize >> m_nZoomLevel), nTileWidth));
3328 : int nTileYCountZoomLevel0 =
3329 160 : std::max(1, DIV_ROUND_UP((nRasterYSize >> m_nZoomLevel), nTileHeight));
3330 :
3331 320 : const auto poTS = GetTilingScheme(m_osTilingScheme);
3332 160 : if (poTS)
3333 : {
3334 20 : CPLAssert(m_nZoomLevel >= 0);
3335 20 : m_dfTMSMinX = poTS->dfMinX;
3336 20 : m_dfTMSMaxY = poTS->dfMaxY;
3337 20 : dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
3338 20 : dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
3339 20 : nTileXCountZoomLevel0 = poTS->nTileXCountZoomLevel0;
3340 20 : nTileYCountZoomLevel0 = poTS->nTileYCountZoomLevel0;
3341 : }
3342 160 : m_nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << m_nZoomLevel);
3343 160 : m_nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << m_nZoomLevel);
3344 :
3345 160 : if (!ComputeTileAndPixelShifts())
3346 : {
3347 0 : CPLError(CE_Failure, CPLE_AppDefined,
3348 : "Overflow occurred in ComputeTileAndPixelShifts()");
3349 0 : return CE_Failure;
3350 : }
3351 :
3352 160 : if (!AllocCachedTiles())
3353 : {
3354 0 : return CE_Failure;
3355 : }
3356 :
3357 160 : double dfGDALMinX = m_adfGeoTransform[0];
3358 160 : double dfGDALMinY =
3359 160 : m_adfGeoTransform[3] + nRasterYSize * m_adfGeoTransform[5];
3360 160 : double dfGDALMaxX =
3361 160 : m_adfGeoTransform[0] + nRasterXSize * m_adfGeoTransform[1];
3362 160 : double dfGDALMaxY = m_adfGeoTransform[3];
3363 :
3364 160 : if (SoftStartTransaction() != OGRERR_NONE)
3365 0 : return CE_Failure;
3366 :
3367 : const char *pszCurrentDate =
3368 160 : CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
3369 : CPLString osInsertGpkgContentsFormatting(
3370 : "INSERT INTO gpkg_contents "
3371 : "(table_name,data_type,identifier,description,min_x,min_y,max_x,max_y,"
3372 : "last_change,srs_id) VALUES "
3373 320 : "('%q','%q','%q','%q',%.18g,%.18g,%.18g,%.18g,");
3374 160 : osInsertGpkgContentsFormatting += (pszCurrentDate) ? "'%q'" : "%s";
3375 160 : osInsertGpkgContentsFormatting += ",%d)";
3376 320 : char *pszSQL = sqlite3_mprintf(
3377 : osInsertGpkgContentsFormatting.c_str(), m_osRasterTable.c_str(),
3378 160 : (m_eDT == GDT_Byte) ? "tiles" : "2d-gridded-coverage",
3379 : m_osIdentifier.c_str(), m_osDescription.c_str(), dfGDALMinX, dfGDALMinY,
3380 : dfGDALMaxX, dfGDALMaxY,
3381 : pszCurrentDate ? pszCurrentDate
3382 : : "strftime('%Y-%m-%dT%H:%M:%fZ','now')",
3383 : m_nSRID);
3384 :
3385 160 : eErr = SQLCommand(hDB, pszSQL);
3386 160 : sqlite3_free(pszSQL);
3387 160 : if (eErr != OGRERR_NONE)
3388 : {
3389 0 : SoftRollbackTransaction();
3390 0 : return CE_Failure;
3391 : }
3392 :
3393 160 : double dfTMSMaxX = m_dfTMSMinX + nTileXCountZoomLevel0 * nTileWidth *
3394 : dfPixelXSizeZoomLevel0;
3395 160 : double dfTMSMinY = m_dfTMSMaxY - nTileYCountZoomLevel0 * nTileHeight *
3396 : dfPixelYSizeZoomLevel0;
3397 :
3398 : pszSQL =
3399 160 : sqlite3_mprintf("INSERT INTO gpkg_tile_matrix_set "
3400 : "(table_name,srs_id,min_x,min_y,max_x,max_y) VALUES "
3401 : "('%q',%d,%.18g,%.18g,%.18g,%.18g)",
3402 : m_osRasterTable.c_str(), m_nSRID, m_dfTMSMinX,
3403 : dfTMSMinY, dfTMSMaxX, m_dfTMSMaxY);
3404 160 : eErr = SQLCommand(hDB, pszSQL);
3405 160 : sqlite3_free(pszSQL);
3406 160 : if (eErr != OGRERR_NONE)
3407 : {
3408 0 : SoftRollbackTransaction();
3409 0 : return CE_Failure;
3410 : }
3411 :
3412 160 : m_papoOverviewDS = static_cast<GDALGeoPackageDataset **>(
3413 160 : CPLCalloc(sizeof(GDALGeoPackageDataset *), m_nZoomLevel));
3414 :
3415 552 : for (int i = 0; i <= m_nZoomLevel; i++)
3416 : {
3417 392 : double dfPixelXSizeZoomLevel = 0.0;
3418 392 : double dfPixelYSizeZoomLevel = 0.0;
3419 392 : int nTileMatrixWidth = 0;
3420 392 : int nTileMatrixHeight = 0;
3421 392 : if (EQUAL(m_osTilingScheme, "CUSTOM"))
3422 : {
3423 211 : dfPixelXSizeZoomLevel =
3424 211 : m_adfGeoTransform[1] * (1 << (m_nZoomLevel - i));
3425 211 : dfPixelYSizeZoomLevel =
3426 211 : fabs(m_adfGeoTransform[5]) * (1 << (m_nZoomLevel - i));
3427 : }
3428 : else
3429 : {
3430 181 : dfPixelXSizeZoomLevel = dfPixelXSizeZoomLevel0 / (1 << i);
3431 181 : dfPixelYSizeZoomLevel = dfPixelYSizeZoomLevel0 / (1 << i);
3432 : }
3433 392 : nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << i);
3434 392 : nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << i);
3435 :
3436 392 : pszSQL = sqlite3_mprintf(
3437 : "INSERT INTO gpkg_tile_matrix "
3438 : "(table_name,zoom_level,matrix_width,matrix_height,tile_width,tile_"
3439 : "height,pixel_x_size,pixel_y_size) VALUES "
3440 : "('%q',%d,%d,%d,%d,%d,%.18g,%.18g)",
3441 : m_osRasterTable.c_str(), i, nTileMatrixWidth, nTileMatrixHeight,
3442 : nTileWidth, nTileHeight, dfPixelXSizeZoomLevel,
3443 : dfPixelYSizeZoomLevel);
3444 392 : eErr = SQLCommand(hDB, pszSQL);
3445 392 : sqlite3_free(pszSQL);
3446 392 : if (eErr != OGRERR_NONE)
3447 : {
3448 0 : SoftRollbackTransaction();
3449 0 : return CE_Failure;
3450 : }
3451 :
3452 392 : if (i < m_nZoomLevel)
3453 : {
3454 232 : GDALGeoPackageDataset *poOvrDS = new GDALGeoPackageDataset();
3455 232 : poOvrDS->ShareLockWithParentDataset(this);
3456 232 : poOvrDS->InitRaster(this, m_osRasterTable, i, nBands, m_dfTMSMinX,
3457 : m_dfTMSMaxY, dfPixelXSizeZoomLevel,
3458 : dfPixelYSizeZoomLevel, nTileWidth, nTileHeight,
3459 : nTileMatrixWidth, nTileMatrixHeight, dfGDALMinX,
3460 : dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
3461 :
3462 232 : m_papoOverviewDS[m_nZoomLevel - 1 - i] = poOvrDS;
3463 : }
3464 : }
3465 :
3466 160 : if (!m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.empty())
3467 : {
3468 39 : eErr = SQLCommand(
3469 : hDB, m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.c_str());
3470 39 : m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.clear();
3471 39 : if (eErr != OGRERR_NONE)
3472 : {
3473 0 : SoftRollbackTransaction();
3474 0 : return CE_Failure;
3475 : }
3476 : }
3477 :
3478 160 : SoftCommitTransaction();
3479 :
3480 160 : m_nOverviewCount = m_nZoomLevel;
3481 160 : m_bRecordInsertedInGPKGContent = true;
3482 :
3483 160 : return CE_None;
3484 : }
3485 :
3486 : /************************************************************************/
3487 : /* FlushCache() */
3488 : /************************************************************************/
3489 :
3490 2151 : CPLErr GDALGeoPackageDataset::FlushCache(bool bAtClosing)
3491 : {
3492 2151 : if (m_bInFlushCache)
3493 0 : return CE_None;
3494 :
3495 2151 : if (eAccess == GA_Update || !m_bMetadataDirty)
3496 : {
3497 2148 : SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
3498 : }
3499 :
3500 2151 : if (m_bRemoveOGREmptyTable)
3501 : {
3502 520 : m_bRemoveOGREmptyTable = false;
3503 520 : RemoveOGREmptyTable();
3504 : }
3505 :
3506 2151 : CPLErr eErr = IFlushCacheWithErrCode(bAtClosing);
3507 :
3508 2151 : FlushMetadata();
3509 :
3510 2151 : if (eAccess == GA_Update || !m_bMetadataDirty)
3511 : {
3512 : // Needed again as above IFlushCacheWithErrCode()
3513 : // may have call GDALGeoPackageRasterBand::InvalidateStatistics()
3514 : // which modifies metadata
3515 2151 : SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
3516 : }
3517 :
3518 2151 : return eErr;
3519 : }
3520 :
3521 4310 : CPLErr GDALGeoPackageDataset::IFlushCacheWithErrCode(bool bAtClosing)
3522 :
3523 : {
3524 4310 : if (m_bInFlushCache)
3525 2092 : return CE_None;
3526 2218 : m_bInFlushCache = true;
3527 2218 : if (hDB && eAccess == GA_ReadOnly && bAtClosing)
3528 : {
3529 : // Clean-up metadata that will go to PAM by removing items that
3530 : // are reconstructed.
3531 1556 : CPLStringList aosMD;
3532 1372 : for (CSLConstList papszIter = GetMetadata(); papszIter && *papszIter;
3533 : ++papszIter)
3534 : {
3535 594 : char *pszKey = nullptr;
3536 594 : CPLParseNameValue(*papszIter, &pszKey);
3537 1188 : if (pszKey &&
3538 594 : (EQUAL(pszKey, "AREA_OR_POINT") ||
3539 455 : EQUAL(pszKey, "IDENTIFIER") || EQUAL(pszKey, "DESCRIPTION") ||
3540 245 : EQUAL(pszKey, "ZOOM_LEVEL") ||
3541 624 : STARTS_WITH(pszKey, "GPKG_METADATA_ITEM_")))
3542 : {
3543 : // remove it
3544 : }
3545 : else
3546 : {
3547 30 : aosMD.AddString(*papszIter);
3548 : }
3549 594 : CPLFree(pszKey);
3550 : }
3551 778 : oMDMD.SetMetadata(aosMD.List());
3552 778 : oMDMD.SetMetadata(nullptr, "IMAGE_STRUCTURE");
3553 :
3554 1556 : GDALPamDataset::FlushCache(bAtClosing);
3555 : }
3556 : else
3557 : {
3558 : // Short circuit GDALPamDataset to avoid serialization to .aux.xml
3559 1440 : GDALDataset::FlushCache(bAtClosing);
3560 : }
3561 :
3562 5688 : for (int i = 0; i < m_nLayers; i++)
3563 : {
3564 3470 : m_papoLayers[i]->RunDeferredCreationIfNecessary();
3565 3470 : m_papoLayers[i]->CreateSpatialIndexIfNecessary();
3566 : }
3567 :
3568 : // Update raster table last_change column in gpkg_contents if needed
3569 2218 : if (m_bHasModifiedTiles)
3570 : {
3571 490 : for (int i = 1; i <= nBands; ++i)
3572 : {
3573 : auto poBand =
3574 330 : cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
3575 330 : if (!poBand->HaveStatsMetadataBeenSetInThisSession())
3576 : {
3577 327 : poBand->InvalidateStatistics();
3578 327 : if (psPam && psPam->pszPamFilename)
3579 327 : VSIUnlink(psPam->pszPamFilename);
3580 : }
3581 : }
3582 :
3583 160 : UpdateGpkgContentsLastChange(m_osRasterTable);
3584 :
3585 160 : m_bHasModifiedTiles = false;
3586 : }
3587 :
3588 2218 : CPLErr eErr = FlushTiles();
3589 :
3590 2218 : m_bInFlushCache = false;
3591 2218 : return eErr;
3592 : }
3593 :
3594 : /************************************************************************/
3595 : /* GetCurrentDateEscapedSQL() */
3596 : /************************************************************************/
3597 :
3598 1563 : std::string GDALGeoPackageDataset::GetCurrentDateEscapedSQL()
3599 : {
3600 : const char *pszCurrentDate =
3601 1563 : CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
3602 1563 : if (pszCurrentDate)
3603 2 : return '\'' + SQLEscapeLiteral(pszCurrentDate) + '\'';
3604 1562 : return "strftime('%Y-%m-%dT%H:%M:%fZ','now')";
3605 : }
3606 :
3607 : /************************************************************************/
3608 : /* UpdateGpkgContentsLastChange() */
3609 : /************************************************************************/
3610 :
3611 : OGRErr
3612 678 : GDALGeoPackageDataset::UpdateGpkgContentsLastChange(const char *pszTableName)
3613 : {
3614 : char *pszSQL =
3615 678 : sqlite3_mprintf("UPDATE gpkg_contents SET "
3616 : "last_change = %s "
3617 : "WHERE lower(table_name) = lower('%q')",
3618 1356 : GetCurrentDateEscapedSQL().c_str(), pszTableName);
3619 678 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3620 678 : sqlite3_free(pszSQL);
3621 678 : return eErr;
3622 : }
3623 :
3624 : /************************************************************************/
3625 : /* IBuildOverviews() */
3626 : /************************************************************************/
3627 :
3628 20 : CPLErr GDALGeoPackageDataset::IBuildOverviews(
3629 : const char *pszResampling, int nOverviews, const int *panOverviewList,
3630 : int nBandsIn, const int * /*panBandList*/, GDALProgressFunc pfnProgress,
3631 : void *pProgressData, CSLConstList papszOptions)
3632 : {
3633 20 : if (GetAccess() != GA_Update)
3634 : {
3635 1 : CPLError(CE_Failure, CPLE_NotSupported,
3636 : "Overview building not supported on a database opened in "
3637 : "read-only mode");
3638 1 : return CE_Failure;
3639 : }
3640 19 : if (m_poParentDS != nullptr)
3641 : {
3642 1 : CPLError(CE_Failure, CPLE_NotSupported,
3643 : "Overview building not supported on overview dataset");
3644 1 : return CE_Failure;
3645 : }
3646 :
3647 18 : if (nOverviews == 0)
3648 : {
3649 5 : for (int i = 0; i < m_nOverviewCount; i++)
3650 3 : m_papoOverviewDS[i]->FlushCache(false);
3651 :
3652 2 : SoftStartTransaction();
3653 :
3654 2 : if (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
3655 : {
3656 1 : char *pszSQL = sqlite3_mprintf(
3657 : "DELETE FROM gpkg_2d_gridded_tile_ancillary WHERE id IN "
3658 : "(SELECT y.id FROM \"%w\" x "
3659 : "JOIN gpkg_2d_gridded_tile_ancillary y "
3660 : "ON x.id = y.tpudt_id AND y.tpudt_name = '%q' AND "
3661 : "x.zoom_level < %d)",
3662 : m_osRasterTable.c_str(), m_osRasterTable.c_str(), m_nZoomLevel);
3663 1 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3664 1 : sqlite3_free(pszSQL);
3665 1 : if (eErr != OGRERR_NONE)
3666 : {
3667 0 : SoftRollbackTransaction();
3668 0 : return CE_Failure;
3669 : }
3670 : }
3671 :
3672 : char *pszSQL =
3673 2 : sqlite3_mprintf("DELETE FROM \"%w\" WHERE zoom_level < %d",
3674 : m_osRasterTable.c_str(), m_nZoomLevel);
3675 2 : OGRErr eErr = SQLCommand(hDB, pszSQL);
3676 2 : sqlite3_free(pszSQL);
3677 2 : if (eErr != OGRERR_NONE)
3678 : {
3679 0 : SoftRollbackTransaction();
3680 0 : return CE_Failure;
3681 : }
3682 :
3683 2 : SoftCommitTransaction();
3684 :
3685 2 : return CE_None;
3686 : }
3687 :
3688 16 : if (nBandsIn != nBands)
3689 : {
3690 0 : CPLError(CE_Failure, CPLE_NotSupported,
3691 : "Generation of overviews in GPKG only"
3692 : "supported when operating on all bands.");
3693 0 : return CE_Failure;
3694 : }
3695 :
3696 16 : if (m_nOverviewCount == 0)
3697 : {
3698 0 : CPLError(CE_Failure, CPLE_AppDefined,
3699 : "Image too small to support overviews");
3700 0 : return CE_Failure;
3701 : }
3702 :
3703 16 : FlushCache(false);
3704 60 : for (int i = 0; i < nOverviews; i++)
3705 : {
3706 47 : if (panOverviewList[i] < 2)
3707 : {
3708 1 : CPLError(CE_Failure, CPLE_IllegalArg,
3709 : "Overview factor must be >= 2");
3710 1 : return CE_Failure;
3711 : }
3712 :
3713 46 : bool bFound = false;
3714 46 : int jCandidate = -1;
3715 46 : int nMaxOvFactor = 0;
3716 196 : for (int j = 0; j < m_nOverviewCount; j++)
3717 : {
3718 190 : auto poODS = m_papoOverviewDS[j];
3719 190 : const int nOvFactor = static_cast<int>(
3720 190 : 0.5 + poODS->m_adfGeoTransform[1] / m_adfGeoTransform[1]);
3721 :
3722 190 : nMaxOvFactor = nOvFactor;
3723 :
3724 190 : if (nOvFactor == panOverviewList[i])
3725 : {
3726 40 : bFound = true;
3727 40 : break;
3728 : }
3729 :
3730 150 : if (jCandidate < 0 && nOvFactor > panOverviewList[i])
3731 1 : jCandidate = j;
3732 : }
3733 :
3734 46 : if (!bFound)
3735 : {
3736 : /* Mostly for debug */
3737 6 : if (!CPLTestBool(CPLGetConfigOption(
3738 : "ALLOW_GPKG_ZOOM_OTHER_EXTENSION", "YES")))
3739 : {
3740 2 : CPLString osOvrList;
3741 4 : for (int j = 0; j < m_nOverviewCount; j++)
3742 : {
3743 2 : auto poODS = m_papoOverviewDS[j];
3744 2 : const int nOvFactor =
3745 2 : static_cast<int>(0.5 + poODS->m_adfGeoTransform[1] /
3746 2 : m_adfGeoTransform[1]);
3747 :
3748 2 : if (j != 0)
3749 0 : osOvrList += " ";
3750 2 : osOvrList += CPLSPrintf("%d", nOvFactor);
3751 : }
3752 2 : CPLError(CE_Failure, CPLE_NotSupported,
3753 : "Only overviews %s can be computed",
3754 : osOvrList.c_str());
3755 2 : return CE_Failure;
3756 : }
3757 : else
3758 : {
3759 4 : int nOvFactor = panOverviewList[i];
3760 4 : if (jCandidate < 0)
3761 3 : jCandidate = m_nOverviewCount;
3762 :
3763 4 : int nOvXSize = std::max(1, GetRasterXSize() / nOvFactor);
3764 4 : int nOvYSize = std::max(1, GetRasterYSize() / nOvFactor);
3765 4 : if (!(jCandidate == m_nOverviewCount &&
3766 3 : nOvFactor == 2 * nMaxOvFactor) &&
3767 1 : !m_bZoomOther)
3768 : {
3769 1 : CPLError(CE_Warning, CPLE_AppDefined,
3770 : "Use of overview factor %d causes gpkg_zoom_other "
3771 : "extension to be needed",
3772 : nOvFactor);
3773 1 : RegisterZoomOtherExtension();
3774 1 : m_bZoomOther = true;
3775 : }
3776 :
3777 4 : SoftStartTransaction();
3778 :
3779 4 : CPLAssert(jCandidate > 0);
3780 4 : int nNewZoomLevel =
3781 4 : m_papoOverviewDS[jCandidate - 1]->m_nZoomLevel;
3782 :
3783 : char *pszSQL;
3784 : OGRErr eErr;
3785 24 : for (int k = 0; k <= jCandidate; k++)
3786 : {
3787 60 : pszSQL = sqlite3_mprintf(
3788 : "UPDATE gpkg_tile_matrix SET zoom_level = %d "
3789 : "WHERE lower(table_name) = lower('%q') AND zoom_level "
3790 : "= %d",
3791 20 : m_nZoomLevel - k + 1, m_osRasterTable.c_str(),
3792 20 : m_nZoomLevel - k);
3793 20 : eErr = SQLCommand(hDB, pszSQL);
3794 20 : sqlite3_free(pszSQL);
3795 20 : if (eErr != OGRERR_NONE)
3796 : {
3797 0 : SoftRollbackTransaction();
3798 0 : return CE_Failure;
3799 : }
3800 :
3801 : pszSQL =
3802 20 : sqlite3_mprintf("UPDATE \"%w\" SET zoom_level = %d "
3803 : "WHERE zoom_level = %d",
3804 : m_osRasterTable.c_str(),
3805 20 : m_nZoomLevel - k + 1, m_nZoomLevel - k);
3806 20 : eErr = SQLCommand(hDB, pszSQL);
3807 20 : sqlite3_free(pszSQL);
3808 20 : if (eErr != OGRERR_NONE)
3809 : {
3810 0 : SoftRollbackTransaction();
3811 0 : return CE_Failure;
3812 : }
3813 : }
3814 :
3815 4 : double dfGDALMinX = m_adfGeoTransform[0];
3816 4 : double dfGDALMinY =
3817 4 : m_adfGeoTransform[3] + nRasterYSize * m_adfGeoTransform[5];
3818 4 : double dfGDALMaxX =
3819 4 : m_adfGeoTransform[0] + nRasterXSize * m_adfGeoTransform[1];
3820 4 : double dfGDALMaxY = m_adfGeoTransform[3];
3821 4 : double dfPixelXSizeZoomLevel = m_adfGeoTransform[1] * nOvFactor;
3822 4 : double dfPixelYSizeZoomLevel =
3823 4 : fabs(m_adfGeoTransform[5]) * nOvFactor;
3824 : int nTileWidth, nTileHeight;
3825 4 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3826 4 : int nTileMatrixWidth = (nOvXSize + nTileWidth - 1) / nTileWidth;
3827 4 : int nTileMatrixHeight =
3828 4 : (nOvYSize + nTileHeight - 1) / nTileHeight;
3829 4 : pszSQL = sqlite3_mprintf(
3830 : "INSERT INTO gpkg_tile_matrix "
3831 : "(table_name,zoom_level,matrix_width,matrix_height,tile_"
3832 : "width,tile_height,pixel_x_size,pixel_y_size) VALUES "
3833 : "('%q',%d,%d,%d,%d,%d,%.18g,%.18g)",
3834 : m_osRasterTable.c_str(), nNewZoomLevel, nTileMatrixWidth,
3835 : nTileMatrixHeight, nTileWidth, nTileHeight,
3836 : dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel);
3837 4 : eErr = SQLCommand(hDB, pszSQL);
3838 4 : sqlite3_free(pszSQL);
3839 4 : if (eErr != OGRERR_NONE)
3840 : {
3841 0 : SoftRollbackTransaction();
3842 0 : return CE_Failure;
3843 : }
3844 :
3845 4 : SoftCommitTransaction();
3846 :
3847 4 : m_nZoomLevel++; /* this change our zoom level as well as
3848 : previous overviews */
3849 20 : for (int k = 0; k < jCandidate; k++)
3850 16 : m_papoOverviewDS[k]->m_nZoomLevel++;
3851 :
3852 4 : GDALGeoPackageDataset *poOvrDS = new GDALGeoPackageDataset();
3853 4 : poOvrDS->ShareLockWithParentDataset(this);
3854 4 : poOvrDS->InitRaster(
3855 : this, m_osRasterTable, nNewZoomLevel, nBands, m_dfTMSMinX,
3856 : m_dfTMSMaxY, dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel,
3857 : nTileWidth, nTileHeight, nTileMatrixWidth,
3858 : nTileMatrixHeight, dfGDALMinX, dfGDALMinY, dfGDALMaxX,
3859 : dfGDALMaxY);
3860 4 : m_papoOverviewDS =
3861 8 : static_cast<GDALGeoPackageDataset **>(CPLRealloc(
3862 4 : m_papoOverviewDS, sizeof(GDALGeoPackageDataset *) *
3863 4 : (m_nOverviewCount + 1)));
3864 :
3865 4 : if (jCandidate < m_nOverviewCount)
3866 : {
3867 1 : memmove(m_papoOverviewDS + jCandidate + 1,
3868 1 : m_papoOverviewDS + jCandidate,
3869 : sizeof(GDALGeoPackageDataset *) *
3870 1 : (m_nOverviewCount - jCandidate));
3871 : }
3872 4 : m_papoOverviewDS[jCandidate] = poOvrDS;
3873 4 : m_nOverviewCount++;
3874 : }
3875 : }
3876 : }
3877 :
3878 : GDALRasterBand ***papapoOverviewBands = static_cast<GDALRasterBand ***>(
3879 13 : CPLCalloc(sizeof(GDALRasterBand **), nBands));
3880 13 : CPLErr eErr = CE_None;
3881 49 : for (int iBand = 0; eErr == CE_None && iBand < nBands; iBand++)
3882 : {
3883 72 : papapoOverviewBands[iBand] = static_cast<GDALRasterBand **>(
3884 36 : CPLCalloc(sizeof(GDALRasterBand *), nOverviews));
3885 36 : int iCurOverview = 0;
3886 185 : for (int i = 0; i < nOverviews; i++)
3887 : {
3888 149 : int j = 0; // Used after for.
3889 724 : for (; j < m_nOverviewCount; j++)
3890 : {
3891 724 : auto poODS = m_papoOverviewDS[j];
3892 724 : const int nOvFactor = static_cast<int>(
3893 724 : 0.5 + poODS->m_adfGeoTransform[1] / m_adfGeoTransform[1]);
3894 :
3895 724 : if (nOvFactor == panOverviewList[i])
3896 : {
3897 298 : papapoOverviewBands[iBand][iCurOverview] =
3898 149 : poODS->GetRasterBand(iBand + 1);
3899 149 : iCurOverview++;
3900 149 : break;
3901 : }
3902 : }
3903 149 : if (j == m_nOverviewCount)
3904 : {
3905 0 : CPLError(CE_Failure, CPLE_AppDefined,
3906 : "Could not find dataset corresponding to ov factor %d",
3907 0 : panOverviewList[i]);
3908 0 : eErr = CE_Failure;
3909 : }
3910 : }
3911 36 : if (eErr == CE_None)
3912 : {
3913 36 : CPLAssert(iCurOverview == nOverviews);
3914 : }
3915 : }
3916 :
3917 13 : if (eErr == CE_None)
3918 13 : eErr = GDALRegenerateOverviewsMultiBand(
3919 13 : nBands, papoBands, nOverviews, papapoOverviewBands, pszResampling,
3920 : pfnProgress, pProgressData, papszOptions);
3921 :
3922 49 : for (int iBand = 0; iBand < nBands; iBand++)
3923 : {
3924 36 : CPLFree(papapoOverviewBands[iBand]);
3925 : }
3926 13 : CPLFree(papapoOverviewBands);
3927 :
3928 13 : return eErr;
3929 : }
3930 :
3931 : /************************************************************************/
3932 : /* GetFileList() */
3933 : /************************************************************************/
3934 :
3935 36 : char **GDALGeoPackageDataset::GetFileList()
3936 : {
3937 36 : TryLoadXML();
3938 36 : return GDALPamDataset::GetFileList();
3939 : }
3940 :
3941 : /************************************************************************/
3942 : /* GetMetadataDomainList() */
3943 : /************************************************************************/
3944 :
3945 26 : char **GDALGeoPackageDataset::GetMetadataDomainList()
3946 : {
3947 26 : GetMetadata();
3948 26 : if (!m_osRasterTable.empty())
3949 5 : GetMetadata("GEOPACKAGE");
3950 26 : return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
3951 26 : TRUE, "SUBDATASETS", nullptr);
3952 : }
3953 :
3954 : /************************************************************************/
3955 : /* CheckMetadataDomain() */
3956 : /************************************************************************/
3957 :
3958 4216 : const char *GDALGeoPackageDataset::CheckMetadataDomain(const char *pszDomain)
3959 : {
3960 4378 : if (pszDomain != nullptr && EQUAL(pszDomain, "GEOPACKAGE") &&
3961 162 : m_osRasterTable.empty())
3962 : {
3963 4 : CPLError(
3964 : CE_Warning, CPLE_IllegalArg,
3965 : "Using GEOPACKAGE for a non-raster geopackage is not supported. "
3966 : "Using default domain instead");
3967 4 : return nullptr;
3968 : }
3969 4212 : return pszDomain;
3970 : }
3971 :
3972 : /************************************************************************/
3973 : /* HasMetadataTables() */
3974 : /************************************************************************/
3975 :
3976 4112 : bool GDALGeoPackageDataset::HasMetadataTables() const
3977 : {
3978 4112 : if (m_nHasMetadataTables < 0)
3979 : {
3980 : const int nCount =
3981 1582 : SQLGetInteger(hDB,
3982 : "SELECT COUNT(*) FROM sqlite_master WHERE name IN "
3983 : "('gpkg_metadata', 'gpkg_metadata_reference') "
3984 : "AND type IN ('table', 'view')",
3985 : nullptr);
3986 1582 : m_nHasMetadataTables = nCount == 2;
3987 : }
3988 4112 : return CPL_TO_BOOL(m_nHasMetadataTables);
3989 : }
3990 :
3991 : /************************************************************************/
3992 : /* HasDataColumnsTable() */
3993 : /************************************************************************/
3994 :
3995 796 : bool GDALGeoPackageDataset::HasDataColumnsTable() const
3996 : {
3997 1592 : const int nCount = SQLGetInteger(
3998 796 : hDB,
3999 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_data_columns'"
4000 : "AND type IN ('table', 'view')",
4001 : nullptr);
4002 796 : return nCount == 1;
4003 : }
4004 :
4005 : /************************************************************************/
4006 : /* HasDataColumnConstraintsTable() */
4007 : /************************************************************************/
4008 :
4009 119 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTable() const
4010 : {
4011 119 : const int nCount = SQLGetInteger(hDB,
4012 : "SELECT 1 FROM sqlite_master WHERE name = "
4013 : "'gpkg_data_column_constraints'"
4014 : "AND type IN ('table', 'view')",
4015 : nullptr);
4016 119 : return nCount == 1;
4017 : }
4018 :
4019 : /************************************************************************/
4020 : /* HasDataColumnConstraintsTableGPKG_1_0() */
4021 : /************************************************************************/
4022 :
4023 73 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTableGPKG_1_0() const
4024 : {
4025 73 : if (m_nApplicationId != GP10_APPLICATION_ID)
4026 71 : return false;
4027 : // In GPKG 1.0, the columns were named minIsInclusive, maxIsInclusive
4028 : // They were changed in 1.1 to min_is_inclusive, max_is_inclusive
4029 2 : bool bRet = false;
4030 2 : sqlite3_stmt *hSQLStmt = nullptr;
4031 2 : int rc = sqlite3_prepare_v2(hDB,
4032 : "SELECT minIsInclusive, maxIsInclusive FROM "
4033 : "gpkg_data_column_constraints",
4034 : -1, &hSQLStmt, nullptr);
4035 2 : if (rc == SQLITE_OK)
4036 : {
4037 2 : bRet = true;
4038 2 : sqlite3_finalize(hSQLStmt);
4039 : }
4040 2 : return bRet;
4041 : }
4042 :
4043 : /************************************************************************/
4044 : /* CreateColumnsTableAndColumnConstraintsTablesIfNecessary() */
4045 : /************************************************************************/
4046 :
4047 49 : bool GDALGeoPackageDataset::
4048 : CreateColumnsTableAndColumnConstraintsTablesIfNecessary()
4049 : {
4050 49 : if (!HasDataColumnsTable())
4051 : {
4052 : // Geopackage < 1.3 had
4053 : // CONSTRAINT fk_gdc_tn FOREIGN KEY (table_name) REFERENCES
4054 : // gpkg_contents(table_name) instead of the unique constraint.
4055 10 : if (OGRERR_NONE !=
4056 10 : SQLCommand(
4057 : GetDB(),
4058 : "CREATE TABLE gpkg_data_columns ("
4059 : "table_name TEXT NOT NULL,"
4060 : "column_name TEXT NOT NULL,"
4061 : "name TEXT,"
4062 : "title TEXT,"
4063 : "description TEXT,"
4064 : "mime_type TEXT,"
4065 : "constraint_name TEXT,"
4066 : "CONSTRAINT pk_gdc PRIMARY KEY (table_name, column_name),"
4067 : "CONSTRAINT gdc_tn UNIQUE (table_name, name));"))
4068 : {
4069 0 : return false;
4070 : }
4071 : }
4072 49 : if (!HasDataColumnConstraintsTable())
4073 : {
4074 22 : const char *min_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
4075 11 : ? "min_is_inclusive"
4076 : : "minIsInclusive";
4077 22 : const char *max_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
4078 11 : ? "max_is_inclusive"
4079 : : "maxIsInclusive";
4080 :
4081 : const std::string osSQL(
4082 : CPLSPrintf("CREATE TABLE gpkg_data_column_constraints ("
4083 : "constraint_name TEXT NOT NULL,"
4084 : "constraint_type TEXT NOT NULL,"
4085 : "value TEXT,"
4086 : "min NUMERIC,"
4087 : "%s BOOLEAN,"
4088 : "max NUMERIC,"
4089 : "%s BOOLEAN,"
4090 : "description TEXT,"
4091 : "CONSTRAINT gdcc_ntv UNIQUE (constraint_name, "
4092 : "constraint_type, value));",
4093 11 : min_is_inclusive, max_is_inclusive));
4094 11 : if (OGRERR_NONE != SQLCommand(GetDB(), osSQL.c_str()))
4095 : {
4096 0 : return false;
4097 : }
4098 : }
4099 49 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
4100 : {
4101 0 : return false;
4102 : }
4103 49 : if (SQLGetInteger(GetDB(),
4104 : "SELECT 1 FROM gpkg_extensions WHERE "
4105 : "table_name = 'gpkg_data_columns'",
4106 49 : nullptr) != 1)
4107 : {
4108 11 : if (OGRERR_NONE !=
4109 11 : SQLCommand(
4110 : GetDB(),
4111 : "INSERT INTO gpkg_extensions "
4112 : "(table_name,column_name,extension_name,definition,scope) "
4113 : "VALUES ('gpkg_data_columns', NULL, 'gpkg_schema', "
4114 : "'http://www.geopackage.org/spec121/#extension_schema', "
4115 : "'read-write')"))
4116 : {
4117 0 : return false;
4118 : }
4119 : }
4120 49 : if (SQLGetInteger(GetDB(),
4121 : "SELECT 1 FROM gpkg_extensions WHERE "
4122 : "table_name = 'gpkg_data_column_constraints'",
4123 49 : nullptr) != 1)
4124 : {
4125 11 : if (OGRERR_NONE !=
4126 11 : SQLCommand(
4127 : GetDB(),
4128 : "INSERT INTO gpkg_extensions "
4129 : "(table_name,column_name,extension_name,definition,scope) "
4130 : "VALUES ('gpkg_data_column_constraints', NULL, 'gpkg_schema', "
4131 : "'http://www.geopackage.org/spec121/#extension_schema', "
4132 : "'read-write')"))
4133 : {
4134 0 : return false;
4135 : }
4136 : }
4137 :
4138 49 : return true;
4139 : }
4140 :
4141 : /************************************************************************/
4142 : /* HasGpkgextRelationsTable() */
4143 : /************************************************************************/
4144 :
4145 892 : bool GDALGeoPackageDataset::HasGpkgextRelationsTable() const
4146 : {
4147 1784 : const int nCount = SQLGetInteger(
4148 892 : hDB,
4149 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkgext_relations'"
4150 : "AND type IN ('table', 'view')",
4151 : nullptr);
4152 892 : return nCount == 1;
4153 : }
4154 :
4155 : /************************************************************************/
4156 : /* CreateRelationsTableIfNecessary() */
4157 : /************************************************************************/
4158 :
4159 7 : bool GDALGeoPackageDataset::CreateRelationsTableIfNecessary()
4160 : {
4161 7 : if (HasGpkgextRelationsTable())
4162 : {
4163 5 : return true;
4164 : }
4165 :
4166 2 : if (OGRERR_NONE !=
4167 2 : SQLCommand(GetDB(), "CREATE TABLE gpkgext_relations ("
4168 : "id INTEGER PRIMARY KEY AUTOINCREMENT,"
4169 : "base_table_name TEXT NOT NULL,"
4170 : "base_primary_column TEXT NOT NULL DEFAULT 'id',"
4171 : "related_table_name TEXT NOT NULL,"
4172 : "related_primary_column TEXT NOT NULL DEFAULT 'id',"
4173 : "relation_name TEXT NOT NULL,"
4174 : "mapping_table_name TEXT NOT NULL UNIQUE);"))
4175 : {
4176 0 : return false;
4177 : }
4178 :
4179 2 : return true;
4180 : }
4181 :
4182 : /************************************************************************/
4183 : /* HasQGISLayerStyles() */
4184 : /************************************************************************/
4185 :
4186 9 : bool GDALGeoPackageDataset::HasQGISLayerStyles() const
4187 : {
4188 : // QGIS layer_styles extension:
4189 : // https://github.com/pka/qgpkg/blob/master/qgis_geopackage_extension.md
4190 9 : bool bRet = false;
4191 : const int nCount =
4192 9 : SQLGetInteger(hDB,
4193 : "SELECT 1 FROM sqlite_master WHERE name = 'layer_styles'"
4194 : "AND type = 'table'",
4195 : nullptr);
4196 9 : if (nCount == 1)
4197 : {
4198 1 : sqlite3_stmt *hSQLStmt = nullptr;
4199 2 : int rc = sqlite3_prepare_v2(
4200 1 : hDB, "SELECT f_table_name, f_geometry_column FROM layer_styles", -1,
4201 : &hSQLStmt, nullptr);
4202 1 : if (rc == SQLITE_OK)
4203 : {
4204 1 : bRet = true;
4205 1 : sqlite3_finalize(hSQLStmt);
4206 : }
4207 : }
4208 9 : return bRet;
4209 : }
4210 :
4211 : /************************************************************************/
4212 : /* GetMetadata() */
4213 : /************************************************************************/
4214 :
4215 2832 : char **GDALGeoPackageDataset::GetMetadata(const char *pszDomain)
4216 :
4217 : {
4218 2832 : pszDomain = CheckMetadataDomain(pszDomain);
4219 2832 : if (pszDomain != nullptr && EQUAL(pszDomain, "SUBDATASETS"))
4220 44 : return m_aosSubDatasets.List();
4221 :
4222 2788 : if (m_bHasReadMetadataFromStorage)
4223 1230 : return GDALPamDataset::GetMetadata(pszDomain);
4224 :
4225 1558 : m_bHasReadMetadataFromStorage = true;
4226 :
4227 1558 : TryLoadXML();
4228 :
4229 1558 : if (!HasMetadataTables())
4230 1170 : return GDALPamDataset::GetMetadata(pszDomain);
4231 :
4232 388 : char *pszSQL = nullptr;
4233 388 : if (!m_osRasterTable.empty())
4234 : {
4235 156 : pszSQL = sqlite3_mprintf(
4236 : "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
4237 : "mdr.reference_scope FROM gpkg_metadata md "
4238 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4239 : "WHERE "
4240 : "(mdr.reference_scope = 'geopackage' OR "
4241 : "(mdr.reference_scope = 'table' AND lower(mdr.table_name) = "
4242 : "lower('%q'))) ORDER BY md.id "
4243 : "LIMIT 1000", // to avoid denial of service
4244 : m_osRasterTable.c_str());
4245 : }
4246 : else
4247 : {
4248 232 : pszSQL = sqlite3_mprintf(
4249 : "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
4250 : "mdr.reference_scope FROM gpkg_metadata md "
4251 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4252 : "WHERE "
4253 : "mdr.reference_scope = 'geopackage' ORDER BY md.id "
4254 : "LIMIT 1000" // to avoid denial of service
4255 : );
4256 : }
4257 :
4258 776 : auto oResult = SQLQuery(hDB, pszSQL);
4259 388 : sqlite3_free(pszSQL);
4260 388 : if (!oResult)
4261 : {
4262 0 : return GDALPamDataset::GetMetadata(pszDomain);
4263 : }
4264 :
4265 388 : char **papszMetadata = CSLDuplicate(GDALPamDataset::GetMetadata());
4266 :
4267 : /* GDAL metadata */
4268 565 : for (int i = 0; i < oResult->RowCount(); i++)
4269 : {
4270 177 : const char *pszMetadata = oResult->GetValue(0, i);
4271 177 : const char *pszMDStandardURI = oResult->GetValue(1, i);
4272 177 : const char *pszMimeType = oResult->GetValue(2, i);
4273 177 : const char *pszReferenceScope = oResult->GetValue(3, i);
4274 177 : if (pszMetadata && pszMDStandardURI && pszMimeType &&
4275 177 : pszReferenceScope && EQUAL(pszMDStandardURI, "http://gdal.org") &&
4276 161 : EQUAL(pszMimeType, "text/xml"))
4277 : {
4278 161 : CPLXMLNode *psXMLNode = CPLParseXMLString(pszMetadata);
4279 161 : if (psXMLNode)
4280 : {
4281 322 : GDALMultiDomainMetadata oLocalMDMD;
4282 161 : oLocalMDMD.XMLInit(psXMLNode, FALSE);
4283 307 : if (!m_osRasterTable.empty() &&
4284 146 : EQUAL(pszReferenceScope, "geopackage"))
4285 : {
4286 6 : oMDMD.SetMetadata(oLocalMDMD.GetMetadata(), "GEOPACKAGE");
4287 : }
4288 : else
4289 : {
4290 : papszMetadata =
4291 155 : CSLMerge(papszMetadata, oLocalMDMD.GetMetadata());
4292 155 : CSLConstList papszDomainList = oLocalMDMD.GetDomainList();
4293 155 : CSLConstList papszIter = papszDomainList;
4294 407 : while (papszIter && *papszIter)
4295 : {
4296 252 : if (EQUAL(*papszIter, "IMAGE_STRUCTURE"))
4297 : {
4298 : CSLConstList papszMD =
4299 113 : oLocalMDMD.GetMetadata(*papszIter);
4300 : const char *pszBAND_COUNT =
4301 113 : CSLFetchNameValue(papszMD, "BAND_COUNT");
4302 113 : if (pszBAND_COUNT)
4303 111 : m_nBandCountFromMetadata = atoi(pszBAND_COUNT);
4304 :
4305 : const char *pszCOLOR_TABLE =
4306 113 : CSLFetchNameValue(papszMD, "COLOR_TABLE");
4307 113 : if (pszCOLOR_TABLE)
4308 : {
4309 : const CPLStringList aosTokens(
4310 : CSLTokenizeString2(pszCOLOR_TABLE, "{,",
4311 26 : 0));
4312 13 : if ((aosTokens.size() % 4) == 0)
4313 : {
4314 13 : const int nColors = aosTokens.size() / 4;
4315 : m_poCTFromMetadata =
4316 13 : std::make_unique<GDALColorTable>();
4317 3341 : for (int iColor = 0; iColor < nColors;
4318 : ++iColor)
4319 : {
4320 : GDALColorEntry sEntry;
4321 3328 : sEntry.c1 = static_cast<short>(
4322 3328 : atoi(aosTokens[4 * iColor + 0]));
4323 3328 : sEntry.c2 = static_cast<short>(
4324 3328 : atoi(aosTokens[4 * iColor + 1]));
4325 3328 : sEntry.c3 = static_cast<short>(
4326 3328 : atoi(aosTokens[4 * iColor + 2]));
4327 3328 : sEntry.c4 = static_cast<short>(
4328 3328 : atoi(aosTokens[4 * iColor + 3]));
4329 3328 : m_poCTFromMetadata->SetColorEntry(
4330 : iColor, &sEntry);
4331 : }
4332 : }
4333 : }
4334 :
4335 : const char *pszTILE_FORMAT =
4336 113 : CSLFetchNameValue(papszMD, "TILE_FORMAT");
4337 113 : if (pszTILE_FORMAT)
4338 : {
4339 8 : m_osTFFromMetadata = pszTILE_FORMAT;
4340 8 : oMDMD.SetMetadataItem("TILE_FORMAT",
4341 : pszTILE_FORMAT,
4342 : "IMAGE_STRUCTURE");
4343 : }
4344 :
4345 : const char *pszNodataValue =
4346 113 : CSLFetchNameValue(papszMD, "NODATA_VALUE");
4347 113 : if (pszNodataValue)
4348 : {
4349 2 : m_osNodataValueFromMetadata = pszNodataValue;
4350 : }
4351 : }
4352 :
4353 139 : else if (!EQUAL(*papszIter, "") &&
4354 12 : !STARTS_WITH(*papszIter, "BAND_"))
4355 : {
4356 12 : oMDMD.SetMetadata(
4357 6 : oLocalMDMD.GetMetadata(*papszIter), *papszIter);
4358 : }
4359 252 : papszIter++;
4360 : }
4361 : }
4362 161 : CPLDestroyXMLNode(psXMLNode);
4363 : }
4364 : }
4365 : }
4366 :
4367 388 : GDALPamDataset::SetMetadata(papszMetadata);
4368 388 : CSLDestroy(papszMetadata);
4369 388 : papszMetadata = nullptr;
4370 :
4371 : /* Add non-GDAL metadata now */
4372 388 : int nNonGDALMDILocal = 1;
4373 388 : int nNonGDALMDIGeopackage = 1;
4374 565 : for (int i = 0; i < oResult->RowCount(); i++)
4375 : {
4376 177 : const char *pszMetadata = oResult->GetValue(0, i);
4377 177 : const char *pszMDStandardURI = oResult->GetValue(1, i);
4378 177 : const char *pszMimeType = oResult->GetValue(2, i);
4379 177 : const char *pszReferenceScope = oResult->GetValue(3, i);
4380 177 : if (pszMetadata == nullptr || pszMDStandardURI == nullptr ||
4381 177 : pszMimeType == nullptr || pszReferenceScope == nullptr)
4382 : {
4383 : // should not happen as there are NOT NULL constraints
4384 : // But a database could lack such NOT NULL constraints or have
4385 : // large values that would cause a memory allocation failure.
4386 0 : continue;
4387 : }
4388 177 : int bIsGPKGScope = EQUAL(pszReferenceScope, "geopackage");
4389 177 : if (EQUAL(pszMDStandardURI, "http://gdal.org") &&
4390 161 : EQUAL(pszMimeType, "text/xml"))
4391 161 : continue;
4392 :
4393 16 : if (!m_osRasterTable.empty() && bIsGPKGScope)
4394 : {
4395 8 : oMDMD.SetMetadataItem(
4396 : CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDIGeopackage),
4397 : pszMetadata, "GEOPACKAGE");
4398 8 : nNonGDALMDIGeopackage++;
4399 : }
4400 : /*else if( strcmp( pszMDStandardURI, "http://www.isotc211.org/2005/gmd"
4401 : ) == 0 && strcmp( pszMimeType, "text/xml" ) == 0 )
4402 : {
4403 : char* apszMD[2];
4404 : apszMD[0] = (char*)pszMetadata;
4405 : apszMD[1] = NULL;
4406 : oMDMD.SetMetadata(apszMD, "xml:MD_Metadata");
4407 : }*/
4408 : else
4409 : {
4410 8 : oMDMD.SetMetadataItem(
4411 : CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDILocal),
4412 : pszMetadata);
4413 8 : nNonGDALMDILocal++;
4414 : }
4415 : }
4416 :
4417 388 : return GDALPamDataset::GetMetadata(pszDomain);
4418 : }
4419 :
4420 : /************************************************************************/
4421 : /* WriteMetadata() */
4422 : /************************************************************************/
4423 :
4424 607 : void GDALGeoPackageDataset::WriteMetadata(
4425 : CPLXMLNode *psXMLNode, /* will be destroyed by the method */
4426 : const char *pszTableName)
4427 : {
4428 607 : const bool bIsEmpty = (psXMLNode == nullptr);
4429 607 : if (!HasMetadataTables())
4430 : {
4431 446 : if (bIsEmpty || !CreateMetadataTables())
4432 : {
4433 205 : CPLDestroyXMLNode(psXMLNode);
4434 205 : return;
4435 : }
4436 : }
4437 :
4438 402 : char *pszXML = nullptr;
4439 402 : if (!bIsEmpty)
4440 : {
4441 : CPLXMLNode *psMasterXMLNode =
4442 274 : CPLCreateXMLNode(nullptr, CXT_Element, "GDALMultiDomainMetadata");
4443 274 : psMasterXMLNode->psChild = psXMLNode;
4444 274 : pszXML = CPLSerializeXMLTree(psMasterXMLNode);
4445 274 : CPLDestroyXMLNode(psMasterXMLNode);
4446 : }
4447 : // cppcheck-suppress uselessAssignmentPtrArg
4448 402 : psXMLNode = nullptr;
4449 :
4450 402 : char *pszSQL = nullptr;
4451 402 : if (pszTableName && pszTableName[0] != '\0')
4452 : {
4453 283 : pszSQL = sqlite3_mprintf(
4454 : "SELECT md.id FROM gpkg_metadata md "
4455 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4456 : "WHERE md.md_scope = 'dataset' AND "
4457 : "md.md_standard_uri='http://gdal.org' "
4458 : "AND md.mime_type='text/xml' AND mdr.reference_scope = 'table' AND "
4459 : "lower(mdr.table_name) = lower('%q')",
4460 : pszTableName);
4461 : }
4462 : else
4463 : {
4464 119 : pszSQL = sqlite3_mprintf(
4465 : "SELECT md.id FROM gpkg_metadata md "
4466 : "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4467 : "WHERE md.md_scope = 'dataset' AND "
4468 : "md.md_standard_uri='http://gdal.org' "
4469 : "AND md.mime_type='text/xml' AND mdr.reference_scope = "
4470 : "'geopackage'");
4471 : }
4472 : OGRErr err;
4473 402 : int mdId = SQLGetInteger(hDB, pszSQL, &err);
4474 402 : if (err != OGRERR_NONE)
4475 377 : mdId = -1;
4476 402 : sqlite3_free(pszSQL);
4477 :
4478 402 : if (bIsEmpty)
4479 : {
4480 128 : if (mdId >= 0)
4481 : {
4482 6 : SQLCommand(
4483 : hDB,
4484 : CPLSPrintf(
4485 : "DELETE FROM gpkg_metadata_reference WHERE md_file_id = %d",
4486 : mdId));
4487 6 : SQLCommand(
4488 : hDB,
4489 : CPLSPrintf("DELETE FROM gpkg_metadata WHERE id = %d", mdId));
4490 : }
4491 : }
4492 : else
4493 : {
4494 274 : if (mdId >= 0)
4495 : {
4496 19 : pszSQL = sqlite3_mprintf(
4497 : "UPDATE gpkg_metadata SET metadata = '%q' WHERE id = %d",
4498 : pszXML, mdId);
4499 : }
4500 : else
4501 : {
4502 : pszSQL =
4503 255 : sqlite3_mprintf("INSERT INTO gpkg_metadata (md_scope, "
4504 : "md_standard_uri, mime_type, metadata) VALUES "
4505 : "('dataset','http://gdal.org','text/xml','%q')",
4506 : pszXML);
4507 : }
4508 274 : SQLCommand(hDB, pszSQL);
4509 274 : sqlite3_free(pszSQL);
4510 :
4511 274 : CPLFree(pszXML);
4512 :
4513 274 : if (mdId < 0)
4514 : {
4515 255 : const sqlite_int64 nFID = sqlite3_last_insert_rowid(hDB);
4516 255 : if (pszTableName != nullptr && pszTableName[0] != '\0')
4517 : {
4518 244 : pszSQL = sqlite3_mprintf(
4519 : "INSERT INTO gpkg_metadata_reference (reference_scope, "
4520 : "table_name, timestamp, md_file_id) VALUES "
4521 : "('table', '%q', %s, %d)",
4522 488 : pszTableName, GetCurrentDateEscapedSQL().c_str(),
4523 : static_cast<int>(nFID));
4524 : }
4525 : else
4526 : {
4527 11 : pszSQL = sqlite3_mprintf(
4528 : "INSERT INTO gpkg_metadata_reference (reference_scope, "
4529 : "timestamp, md_file_id) VALUES "
4530 : "('geopackage', %s, %d)",
4531 22 : GetCurrentDateEscapedSQL().c_str(), static_cast<int>(nFID));
4532 : }
4533 : }
4534 : else
4535 : {
4536 19 : pszSQL = sqlite3_mprintf("UPDATE gpkg_metadata_reference SET "
4537 : "timestamp = %s WHERE md_file_id = %d",
4538 38 : GetCurrentDateEscapedSQL().c_str(), mdId);
4539 : }
4540 274 : SQLCommand(hDB, pszSQL);
4541 274 : sqlite3_free(pszSQL);
4542 : }
4543 : }
4544 :
4545 : /************************************************************************/
4546 : /* CreateMetadataTables() */
4547 : /************************************************************************/
4548 :
4549 253 : bool GDALGeoPackageDataset::CreateMetadataTables()
4550 : {
4551 : const bool bCreateTriggers =
4552 253 : CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "NO"));
4553 :
4554 : /* From C.10. gpkg_metadata Table 35. gpkg_metadata Table Definition SQL */
4555 : CPLString osSQL = "CREATE TABLE gpkg_metadata ("
4556 : "id INTEGER CONSTRAINT m_pk PRIMARY KEY ASC NOT NULL,"
4557 : "md_scope TEXT NOT NULL DEFAULT 'dataset',"
4558 : "md_standard_uri TEXT NOT NULL,"
4559 : "mime_type TEXT NOT NULL DEFAULT 'text/xml',"
4560 : "metadata TEXT NOT NULL DEFAULT ''"
4561 506 : ")";
4562 :
4563 : /* From D.2. metadata Table 40. metadata Trigger Definition SQL */
4564 253 : const char *pszMetadataTriggers =
4565 : "CREATE TRIGGER 'gpkg_metadata_md_scope_insert' "
4566 : "BEFORE INSERT ON 'gpkg_metadata' "
4567 : "FOR EACH ROW BEGIN "
4568 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata violates "
4569 : "constraint: md_scope must be one of undefined | fieldSession | "
4570 : "collectionSession | series | dataset | featureType | feature | "
4571 : "attributeType | attribute | tile | model | catalogue | schema | "
4572 : "taxonomy software | service | collectionHardware | "
4573 : "nonGeographicDataset | dimensionGroup') "
4574 : "WHERE NOT(NEW.md_scope IN "
4575 : "('undefined','fieldSession','collectionSession','series','dataset', "
4576 : "'featureType','feature','attributeType','attribute','tile','model', "
4577 : "'catalogue','schema','taxonomy','software','service', "
4578 : "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
4579 : "END; "
4580 : "CREATE TRIGGER 'gpkg_metadata_md_scope_update' "
4581 : "BEFORE UPDATE OF 'md_scope' ON 'gpkg_metadata' "
4582 : "FOR EACH ROW BEGIN "
4583 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata violates "
4584 : "constraint: md_scope must be one of undefined | fieldSession | "
4585 : "collectionSession | series | dataset | featureType | feature | "
4586 : "attributeType | attribute | tile | model | catalogue | schema | "
4587 : "taxonomy software | service | collectionHardware | "
4588 : "nonGeographicDataset | dimensionGroup') "
4589 : "WHERE NOT(NEW.md_scope IN "
4590 : "('undefined','fieldSession','collectionSession','series','dataset', "
4591 : "'featureType','feature','attributeType','attribute','tile','model', "
4592 : "'catalogue','schema','taxonomy','software','service', "
4593 : "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
4594 : "END";
4595 253 : if (bCreateTriggers)
4596 : {
4597 0 : osSQL += ";";
4598 0 : osSQL += pszMetadataTriggers;
4599 : }
4600 :
4601 : /* From C.11. gpkg_metadata_reference Table 36. gpkg_metadata_reference
4602 : * Table Definition SQL */
4603 : osSQL += ";"
4604 : "CREATE TABLE gpkg_metadata_reference ("
4605 : "reference_scope TEXT NOT NULL,"
4606 : "table_name TEXT,"
4607 : "column_name TEXT,"
4608 : "row_id_value INTEGER,"
4609 : "timestamp DATETIME NOT NULL DEFAULT "
4610 : "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
4611 : "md_file_id INTEGER NOT NULL,"
4612 : "md_parent_id INTEGER,"
4613 : "CONSTRAINT crmr_mfi_fk FOREIGN KEY (md_file_id) REFERENCES "
4614 : "gpkg_metadata(id),"
4615 : "CONSTRAINT crmr_mpi_fk FOREIGN KEY (md_parent_id) REFERENCES "
4616 : "gpkg_metadata(id)"
4617 253 : ")";
4618 :
4619 : /* From D.3. metadata_reference Table 41. gpkg_metadata_reference Trigger
4620 : * Definition SQL */
4621 253 : const char *pszMetadataReferenceTriggers =
4622 : "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_insert' "
4623 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4624 : "FOR EACH ROW BEGIN "
4625 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4626 : "violates constraint: reference_scope must be one of \"geopackage\", "
4627 : "table\", \"column\", \"row\", \"row/col\"') "
4628 : "WHERE NOT NEW.reference_scope IN "
4629 : "('geopackage','table','column','row','row/col'); "
4630 : "END; "
4631 : "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_update' "
4632 : "BEFORE UPDATE OF 'reference_scope' ON 'gpkg_metadata_reference' "
4633 : "FOR EACH ROW BEGIN "
4634 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4635 : "violates constraint: reference_scope must be one of \"geopackage\", "
4636 : "\"table\", \"column\", \"row\", \"row/col\"') "
4637 : "WHERE NOT NEW.reference_scope IN "
4638 : "('geopackage','table','column','row','row/col'); "
4639 : "END; "
4640 : "CREATE TRIGGER 'gpkg_metadata_reference_column_name_insert' "
4641 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4642 : "FOR EACH ROW BEGIN "
4643 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4644 : "violates constraint: column name must be NULL when reference_scope "
4645 : "is \"geopackage\", \"table\" or \"row\"') "
4646 : "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
4647 : "AND NEW.column_name IS NOT NULL); "
4648 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4649 : "violates constraint: column name must be defined for the specified "
4650 : "table when reference_scope is \"column\" or \"row/col\"') "
4651 : "WHERE (NEW.reference_scope IN ('column','row/col') "
4652 : "AND NOT NEW.table_name IN ( "
4653 : "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
4654 : "AND name = NEW.table_name "
4655 : "AND sql LIKE ('%' || NEW.column_name || '%'))); "
4656 : "END; "
4657 : "CREATE TRIGGER 'gpkg_metadata_reference_column_name_update' "
4658 : "BEFORE UPDATE OF column_name ON 'gpkg_metadata_reference' "
4659 : "FOR EACH ROW BEGIN "
4660 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4661 : "violates constraint: column name must be NULL when reference_scope "
4662 : "is \"geopackage\", \"table\" or \"row\"') "
4663 : "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
4664 : "AND NEW.column_name IS NOT NULL); "
4665 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4666 : "violates constraint: column name must be defined for the specified "
4667 : "table when reference_scope is \"column\" or \"row/col\"') "
4668 : "WHERE (NEW.reference_scope IN ('column','row/col') "
4669 : "AND NOT NEW.table_name IN ( "
4670 : "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
4671 : "AND name = NEW.table_name "
4672 : "AND sql LIKE ('%' || NEW.column_name || '%'))); "
4673 : "END; "
4674 : "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_insert' "
4675 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4676 : "FOR EACH ROW BEGIN "
4677 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4678 : "violates constraint: row_id_value must be NULL when reference_scope "
4679 : "is \"geopackage\", \"table\" or \"column\"') "
4680 : "WHERE NEW.reference_scope IN ('geopackage','table','column') "
4681 : "AND NEW.row_id_value IS NOT NULL; "
4682 : "END; "
4683 : "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_update' "
4684 : "BEFORE UPDATE OF 'row_id_value' ON 'gpkg_metadata_reference' "
4685 : "FOR EACH ROW BEGIN "
4686 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4687 : "violates constraint: row_id_value must be NULL when reference_scope "
4688 : "is \"geopackage\", \"table\" or \"column\"') "
4689 : "WHERE NEW.reference_scope IN ('geopackage','table','column') "
4690 : "AND NEW.row_id_value IS NOT NULL; "
4691 : "END; "
4692 : "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_insert' "
4693 : "BEFORE INSERT ON 'gpkg_metadata_reference' "
4694 : "FOR EACH ROW BEGIN "
4695 : "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4696 : "violates constraint: timestamp must be a valid time in ISO 8601 "
4697 : "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
4698 : "WHERE NOT (NEW.timestamp GLOB "
4699 : "'[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-"
4700 : "5][0-9].[0-9][0-9][0-9]Z' "
4701 : "AND strftime('%s',NEW.timestamp) NOT NULL); "
4702 : "END; "
4703 : "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_update' "
4704 : "BEFORE UPDATE OF 'timestamp' ON 'gpkg_metadata_reference' "
4705 : "FOR EACH ROW BEGIN "
4706 : "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4707 : "violates constraint: timestamp must be a valid time in ISO 8601 "
4708 : "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
4709 : "WHERE NOT (NEW.timestamp GLOB "
4710 : "'[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-"
4711 : "5][0-9].[0-9][0-9][0-9]Z' "
4712 : "AND strftime('%s',NEW.timestamp) NOT NULL); "
4713 : "END";
4714 253 : if (bCreateTriggers)
4715 : {
4716 0 : osSQL += ";";
4717 0 : osSQL += pszMetadataReferenceTriggers;
4718 : }
4719 :
4720 253 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
4721 0 : return false;
4722 :
4723 253 : osSQL += ";";
4724 : osSQL += "INSERT INTO gpkg_extensions "
4725 : "(table_name, column_name, extension_name, definition, scope) "
4726 : "VALUES "
4727 : "('gpkg_metadata', NULL, 'gpkg_metadata', "
4728 : "'http://www.geopackage.org/spec120/#extension_metadata', "
4729 253 : "'read-write')";
4730 :
4731 253 : osSQL += ";";
4732 : osSQL += "INSERT INTO gpkg_extensions "
4733 : "(table_name, column_name, extension_name, definition, scope) "
4734 : "VALUES "
4735 : "('gpkg_metadata_reference', NULL, 'gpkg_metadata', "
4736 : "'http://www.geopackage.org/spec120/#extension_metadata', "
4737 253 : "'read-write')";
4738 :
4739 253 : const bool bOK = SQLCommand(hDB, osSQL) == OGRERR_NONE;
4740 253 : m_nHasMetadataTables = bOK;
4741 253 : return bOK;
4742 : }
4743 :
4744 : /************************************************************************/
4745 : /* FlushMetadata() */
4746 : /************************************************************************/
4747 :
4748 7631 : void GDALGeoPackageDataset::FlushMetadata()
4749 : {
4750 7631 : if (!m_bMetadataDirty || m_poParentDS != nullptr ||
4751 307 : m_nCreateMetadataTables == FALSE)
4752 7330 : return;
4753 301 : m_bMetadataDirty = false;
4754 :
4755 301 : if (eAccess == GA_ReadOnly)
4756 : {
4757 3 : return;
4758 : }
4759 :
4760 298 : bool bCanWriteAreaOrPoint =
4761 594 : !m_bGridCellEncodingAsCO &&
4762 296 : (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT);
4763 298 : if (!m_osRasterTable.empty())
4764 : {
4765 : const char *pszIdentifier =
4766 121 : GDALGeoPackageDataset::GetMetadataItem("IDENTIFIER");
4767 : const char *pszDescription =
4768 121 : GDALGeoPackageDataset::GetMetadataItem("DESCRIPTION");
4769 148 : if (!m_bIdentifierAsCO && pszIdentifier != nullptr &&
4770 27 : pszIdentifier != m_osIdentifier)
4771 : {
4772 14 : m_osIdentifier = pszIdentifier;
4773 : char *pszSQL =
4774 14 : sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
4775 : "WHERE lower(table_name) = lower('%q')",
4776 : pszIdentifier, m_osRasterTable.c_str());
4777 14 : SQLCommand(hDB, pszSQL);
4778 14 : sqlite3_free(pszSQL);
4779 : }
4780 128 : if (!m_bDescriptionAsCO && pszDescription != nullptr &&
4781 7 : pszDescription != m_osDescription)
4782 : {
4783 7 : m_osDescription = pszDescription;
4784 : char *pszSQL =
4785 7 : sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
4786 : "WHERE lower(table_name) = lower('%q')",
4787 : pszDescription, m_osRasterTable.c_str());
4788 7 : SQLCommand(hDB, pszSQL);
4789 7 : sqlite3_free(pszSQL);
4790 : }
4791 121 : if (bCanWriteAreaOrPoint)
4792 : {
4793 : const char *pszAreaOrPoint =
4794 27 : GDALGeoPackageDataset::GetMetadataItem(GDALMD_AREA_OR_POINT);
4795 27 : if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_AREA))
4796 : {
4797 22 : bCanWriteAreaOrPoint = false;
4798 22 : char *pszSQL = sqlite3_mprintf(
4799 : "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
4800 : "grid_cell_encoding = 'grid-value-is-area' WHERE "
4801 : "lower(tile_matrix_set_name) = lower('%q')",
4802 : m_osRasterTable.c_str());
4803 22 : SQLCommand(hDB, pszSQL);
4804 22 : sqlite3_free(pszSQL);
4805 : }
4806 5 : else if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_POINT))
4807 : {
4808 1 : bCanWriteAreaOrPoint = false;
4809 1 : char *pszSQL = sqlite3_mprintf(
4810 : "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
4811 : "grid_cell_encoding = 'grid-value-is-center' WHERE "
4812 : "lower(tile_matrix_set_name) = lower('%q')",
4813 : m_osRasterTable.c_str());
4814 1 : SQLCommand(hDB, pszSQL);
4815 1 : sqlite3_free(pszSQL);
4816 : }
4817 : }
4818 : }
4819 :
4820 298 : char **papszMDDup = nullptr;
4821 479 : for (char **papszIter = GDALGeoPackageDataset::GetMetadata();
4822 479 : papszIter && *papszIter; ++papszIter)
4823 : {
4824 181 : if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
4825 27 : continue;
4826 154 : if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
4827 8 : continue;
4828 146 : if (STARTS_WITH_CI(*papszIter, "ZOOM_LEVEL="))
4829 14 : continue;
4830 132 : if (STARTS_WITH_CI(*papszIter, "GPKG_METADATA_ITEM_"))
4831 4 : continue;
4832 128 : if ((m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT) &&
4833 28 : !bCanWriteAreaOrPoint &&
4834 25 : STARTS_WITH_CI(*papszIter, GDALMD_AREA_OR_POINT))
4835 : {
4836 25 : continue;
4837 : }
4838 103 : papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
4839 : }
4840 :
4841 298 : CPLXMLNode *psXMLNode = nullptr;
4842 : {
4843 298 : GDALMultiDomainMetadata oLocalMDMD;
4844 298 : CSLConstList papszDomainList = oMDMD.GetDomainList();
4845 298 : CSLConstList papszIter = papszDomainList;
4846 298 : oLocalMDMD.SetMetadata(papszMDDup);
4847 574 : while (papszIter && *papszIter)
4848 : {
4849 276 : if (!EQUAL(*papszIter, "") &&
4850 136 : !EQUAL(*papszIter, "IMAGE_STRUCTURE") &&
4851 15 : !EQUAL(*papszIter, "GEOPACKAGE"))
4852 : {
4853 8 : oLocalMDMD.SetMetadata(oMDMD.GetMetadata(*papszIter),
4854 : *papszIter);
4855 : }
4856 276 : papszIter++;
4857 : }
4858 298 : if (m_nBandCountFromMetadata > 0)
4859 : {
4860 57 : oLocalMDMD.SetMetadataItem(
4861 : "BAND_COUNT", CPLSPrintf("%d", m_nBandCountFromMetadata),
4862 : "IMAGE_STRUCTURE");
4863 57 : if (nBands == 1)
4864 : {
4865 36 : const auto poCT = GetRasterBand(1)->GetColorTable();
4866 36 : if (poCT)
4867 : {
4868 16 : std::string osVal("{");
4869 8 : const int nColorCount = poCT->GetColorEntryCount();
4870 2056 : for (int i = 0; i < nColorCount; ++i)
4871 : {
4872 2048 : if (i > 0)
4873 2040 : osVal += ',';
4874 2048 : const GDALColorEntry *psEntry = poCT->GetColorEntry(i);
4875 : osVal +=
4876 2048 : CPLSPrintf("{%d,%d,%d,%d}", psEntry->c1,
4877 2048 : psEntry->c2, psEntry->c3, psEntry->c4);
4878 : }
4879 8 : osVal += '}';
4880 8 : oLocalMDMD.SetMetadataItem("COLOR_TABLE", osVal.c_str(),
4881 : "IMAGE_STRUCTURE");
4882 : }
4883 : }
4884 57 : if (nBands == 1)
4885 : {
4886 36 : const char *pszTILE_FORMAT = nullptr;
4887 36 : switch (m_eTF)
4888 : {
4889 0 : case GPKG_TF_PNG_JPEG:
4890 0 : pszTILE_FORMAT = "JPEG_PNG";
4891 0 : break;
4892 30 : case GPKG_TF_PNG:
4893 30 : break;
4894 0 : case GPKG_TF_PNG8:
4895 0 : pszTILE_FORMAT = "PNG8";
4896 0 : break;
4897 3 : case GPKG_TF_JPEG:
4898 3 : pszTILE_FORMAT = "JPEG";
4899 3 : break;
4900 3 : case GPKG_TF_WEBP:
4901 3 : pszTILE_FORMAT = "WEBP";
4902 3 : break;
4903 0 : case GPKG_TF_PNG_16BIT:
4904 0 : break;
4905 0 : case GPKG_TF_TIFF_32BIT_FLOAT:
4906 0 : break;
4907 : }
4908 36 : if (pszTILE_FORMAT)
4909 6 : oLocalMDMD.SetMetadataItem("TILE_FORMAT", pszTILE_FORMAT,
4910 : "IMAGE_STRUCTURE");
4911 : }
4912 : }
4913 419 : if (GetRasterCount() > 0 &&
4914 121 : GetRasterBand(1)->GetRasterDataType() == GDT_Byte)
4915 : {
4916 92 : int bHasNoData = FALSE;
4917 : const double dfNoDataValue =
4918 92 : GetRasterBand(1)->GetNoDataValue(&bHasNoData);
4919 92 : if (bHasNoData)
4920 : {
4921 3 : oLocalMDMD.SetMetadataItem("NODATA_VALUE",
4922 : CPLSPrintf("%.18g", dfNoDataValue),
4923 : "IMAGE_STRUCTURE");
4924 : }
4925 : }
4926 522 : for (int i = 1; i <= GetRasterCount(); ++i)
4927 : {
4928 : auto poBand =
4929 224 : cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
4930 224 : poBand->AddImplicitStatistics(false);
4931 224 : char **papszMD = GetRasterBand(i)->GetMetadata();
4932 224 : poBand->AddImplicitStatistics(true);
4933 224 : if (papszMD)
4934 : {
4935 8 : oLocalMDMD.SetMetadata(papszMD, CPLSPrintf("BAND_%d", i));
4936 : }
4937 : }
4938 298 : psXMLNode = oLocalMDMD.Serialize();
4939 : }
4940 :
4941 298 : CSLDestroy(papszMDDup);
4942 298 : papszMDDup = nullptr;
4943 :
4944 298 : WriteMetadata(psXMLNode, m_osRasterTable.c_str());
4945 :
4946 298 : if (!m_osRasterTable.empty())
4947 : {
4948 : char **papszGeopackageMD =
4949 121 : GDALGeoPackageDataset::GetMetadata("GEOPACKAGE");
4950 :
4951 121 : papszMDDup = nullptr;
4952 130 : for (char **papszIter = papszGeopackageMD; papszIter && *papszIter;
4953 : ++papszIter)
4954 : {
4955 9 : papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
4956 : }
4957 :
4958 242 : GDALMultiDomainMetadata oLocalMDMD;
4959 121 : oLocalMDMD.SetMetadata(papszMDDup);
4960 121 : CSLDestroy(papszMDDup);
4961 121 : papszMDDup = nullptr;
4962 121 : psXMLNode = oLocalMDMD.Serialize();
4963 :
4964 121 : WriteMetadata(psXMLNode, nullptr);
4965 : }
4966 :
4967 486 : for (int i = 0; i < m_nLayers; i++)
4968 : {
4969 : const char *pszIdentifier =
4970 188 : m_papoLayers[i]->GetMetadataItem("IDENTIFIER");
4971 : const char *pszDescription =
4972 188 : m_papoLayers[i]->GetMetadataItem("DESCRIPTION");
4973 188 : if (pszIdentifier != nullptr)
4974 : {
4975 : char *pszSQL =
4976 3 : sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
4977 : "WHERE lower(table_name) = lower('%q')",
4978 3 : pszIdentifier, m_papoLayers[i]->GetName());
4979 3 : SQLCommand(hDB, pszSQL);
4980 3 : sqlite3_free(pszSQL);
4981 : }
4982 188 : if (pszDescription != nullptr)
4983 : {
4984 : char *pszSQL =
4985 3 : sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
4986 : "WHERE lower(table_name) = lower('%q')",
4987 3 : pszDescription, m_papoLayers[i]->GetName());
4988 3 : SQLCommand(hDB, pszSQL);
4989 3 : sqlite3_free(pszSQL);
4990 : }
4991 :
4992 188 : papszMDDup = nullptr;
4993 538 : for (char **papszIter = m_papoLayers[i]->GetMetadata();
4994 538 : papszIter && *papszIter; ++papszIter)
4995 : {
4996 350 : if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
4997 3 : continue;
4998 347 : if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
4999 3 : continue;
5000 344 : if (STARTS_WITH_CI(*papszIter, "OLMD_FID64="))
5001 0 : continue;
5002 344 : papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
5003 : }
5004 :
5005 : {
5006 188 : GDALMultiDomainMetadata oLocalMDMD;
5007 188 : char **papszDomainList = m_papoLayers[i]->GetMetadataDomainList();
5008 188 : char **papszIter = papszDomainList;
5009 188 : oLocalMDMD.SetMetadata(papszMDDup);
5010 396 : while (papszIter && *papszIter)
5011 : {
5012 208 : if (!EQUAL(*papszIter, ""))
5013 66 : oLocalMDMD.SetMetadata(
5014 33 : m_papoLayers[i]->GetMetadata(*papszIter), *papszIter);
5015 208 : papszIter++;
5016 : }
5017 188 : CSLDestroy(papszDomainList);
5018 188 : psXMLNode = oLocalMDMD.Serialize();
5019 : }
5020 :
5021 188 : CSLDestroy(papszMDDup);
5022 188 : papszMDDup = nullptr;
5023 :
5024 188 : WriteMetadata(psXMLNode, m_papoLayers[i]->GetName());
5025 : }
5026 : }
5027 :
5028 : /************************************************************************/
5029 : /* GetMetadataItem() */
5030 : /************************************************************************/
5031 :
5032 1252 : const char *GDALGeoPackageDataset::GetMetadataItem(const char *pszName,
5033 : const char *pszDomain)
5034 : {
5035 1252 : pszDomain = CheckMetadataDomain(pszDomain);
5036 1252 : return CSLFetchNameValue(GetMetadata(pszDomain), pszName);
5037 : }
5038 :
5039 : /************************************************************************/
5040 : /* SetMetadata() */
5041 : /************************************************************************/
5042 :
5043 111 : CPLErr GDALGeoPackageDataset::SetMetadata(char **papszMetadata,
5044 : const char *pszDomain)
5045 : {
5046 111 : pszDomain = CheckMetadataDomain(pszDomain);
5047 111 : m_bMetadataDirty = true;
5048 111 : GetMetadata(); /* force loading from storage if needed */
5049 111 : return GDALPamDataset::SetMetadata(papszMetadata, pszDomain);
5050 : }
5051 :
5052 : /************************************************************************/
5053 : /* SetMetadataItem() */
5054 : /************************************************************************/
5055 :
5056 21 : CPLErr GDALGeoPackageDataset::SetMetadataItem(const char *pszName,
5057 : const char *pszValue,
5058 : const char *pszDomain)
5059 : {
5060 21 : pszDomain = CheckMetadataDomain(pszDomain);
5061 21 : m_bMetadataDirty = true;
5062 21 : GetMetadata(); /* force loading from storage if needed */
5063 21 : return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
5064 : }
5065 :
5066 : /************************************************************************/
5067 : /* Create() */
5068 : /************************************************************************/
5069 :
5070 735 : int GDALGeoPackageDataset::Create(const char *pszFilename, int nXSize,
5071 : int nYSize, int nBandsIn, GDALDataType eDT,
5072 : char **papszOptions)
5073 : {
5074 1470 : CPLString osCommand;
5075 :
5076 : /* First, ensure there isn't any such file yet. */
5077 : VSIStatBufL sStatBuf;
5078 :
5079 735 : if (nBandsIn != 0)
5080 : {
5081 197 : if (eDT == GDT_Byte)
5082 : {
5083 128 : if (nBandsIn != 1 && nBandsIn != 2 && nBandsIn != 3 &&
5084 : nBandsIn != 4)
5085 : {
5086 1 : CPLError(CE_Failure, CPLE_NotSupported,
5087 : "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), "
5088 : "3 (RGB) or 4 (RGBA) band dataset supported for "
5089 : "Byte datatype");
5090 1 : return FALSE;
5091 : }
5092 : }
5093 69 : else if (eDT == GDT_Int16 || eDT == GDT_UInt16 || eDT == GDT_Float32)
5094 : {
5095 42 : if (nBandsIn != 1)
5096 : {
5097 3 : CPLError(CE_Failure, CPLE_NotSupported,
5098 : "Only single band dataset supported for non Byte "
5099 : "datatype");
5100 3 : return FALSE;
5101 : }
5102 : }
5103 : else
5104 : {
5105 27 : CPLError(CE_Failure, CPLE_NotSupported,
5106 : "Only Byte, Int16, UInt16 or Float32 supported");
5107 27 : return FALSE;
5108 : }
5109 : }
5110 :
5111 704 : const size_t nFilenameLen = strlen(pszFilename);
5112 704 : const bool bGpkgZip =
5113 699 : (nFilenameLen > strlen(".gpkg.zip") &&
5114 1403 : !STARTS_WITH(pszFilename, "/vsizip/") &&
5115 699 : EQUAL(pszFilename + nFilenameLen - strlen(".gpkg.zip"), ".gpkg.zip"));
5116 :
5117 : const bool bUseTempFile =
5118 705 : bGpkgZip || (CPLTestBool(CPLGetConfigOption(
5119 1 : "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", "NO")) &&
5120 1 : (VSIHasOptimizedReadMultiRange(pszFilename) != FALSE ||
5121 1 : EQUAL(CPLGetConfigOption(
5122 : "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", ""),
5123 704 : "FORCED")));
5124 :
5125 704 : bool bFileExists = false;
5126 704 : if (VSIStatL(pszFilename, &sStatBuf) == 0)
5127 : {
5128 7 : bFileExists = true;
5129 14 : if (nBandsIn == 0 || bUseTempFile ||
5130 7 : !CPLTestBool(
5131 : CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")))
5132 : {
5133 0 : CPLError(CE_Failure, CPLE_AppDefined,
5134 : "A file system object called '%s' already exists.",
5135 : pszFilename);
5136 :
5137 0 : return FALSE;
5138 : }
5139 : }
5140 :
5141 704 : if (bUseTempFile)
5142 : {
5143 3 : if (bGpkgZip)
5144 : {
5145 2 : std::string osFilenameInZip(CPLGetFilename(pszFilename));
5146 2 : osFilenameInZip.resize(osFilenameInZip.size() - strlen(".zip"));
5147 : m_osFinalFilename =
5148 2 : std::string("/vsizip/{") + pszFilename + "}/" + osFilenameInZip;
5149 : }
5150 : else
5151 : {
5152 1 : m_osFinalFilename = pszFilename;
5153 : }
5154 3 : m_pszFilename =
5155 3 : CPLStrdup(CPLGenerateTempFilename(CPLGetFilename(pszFilename)));
5156 3 : CPLDebug("GPKG", "Creating temporary file %s", m_pszFilename);
5157 : }
5158 : else
5159 : {
5160 701 : m_pszFilename = CPLStrdup(pszFilename);
5161 : }
5162 704 : m_bNew = true;
5163 704 : eAccess = GA_Update;
5164 704 : m_bDateTimeWithTZ =
5165 704 : EQUAL(CSLFetchNameValueDef(papszOptions, "DATETIME_FORMAT", "WITH_TZ"),
5166 : "WITH_TZ");
5167 :
5168 : // for test/debug purposes only. true is the nominal value
5169 704 : m_bPNGSupports2Bands =
5170 704 : CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_2BANDS", "TRUE"));
5171 704 : m_bPNGSupportsCT =
5172 704 : CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_CT", "TRUE"));
5173 :
5174 704 : if (!OpenOrCreateDB(bFileExists
5175 : ? SQLITE_OPEN_READWRITE
5176 : : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE))
5177 4 : return FALSE;
5178 :
5179 : /* Default to synchronous=off for performance for new file */
5180 1393 : if (!bFileExists &&
5181 693 : CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
5182 : {
5183 268 : SQLCommand(hDB, "PRAGMA synchronous = OFF");
5184 : }
5185 :
5186 : /* OGR UTF-8 support. If we set the UTF-8 Pragma early on, it */
5187 : /* will be written into the main file and supported henceforth */
5188 700 : SQLCommand(hDB, "PRAGMA encoding = \"UTF-8\"");
5189 :
5190 700 : if (bFileExists)
5191 : {
5192 7 : VSILFILE *fp = VSIFOpenL(pszFilename, "rb");
5193 7 : if (fp)
5194 : {
5195 : GByte abyHeader[100];
5196 7 : VSIFReadL(abyHeader, 1, sizeof(abyHeader), fp);
5197 7 : VSIFCloseL(fp);
5198 :
5199 7 : memcpy(&m_nApplicationId, abyHeader + knApplicationIdPos, 4);
5200 7 : m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
5201 7 : memcpy(&m_nUserVersion, abyHeader + knUserVersionPos, 4);
5202 7 : m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
5203 :
5204 7 : if (m_nApplicationId == GP10_APPLICATION_ID)
5205 : {
5206 0 : CPLDebug("GPKG", "GeoPackage v1.0");
5207 : }
5208 7 : else if (m_nApplicationId == GP11_APPLICATION_ID)
5209 : {
5210 0 : CPLDebug("GPKG", "GeoPackage v1.1");
5211 : }
5212 7 : else if (m_nApplicationId == GPKG_APPLICATION_ID &&
5213 7 : m_nUserVersion >= GPKG_1_2_VERSION)
5214 : {
5215 7 : CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
5216 7 : (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
5217 : }
5218 : }
5219 :
5220 7 : DetectSpatialRefSysColumns();
5221 : }
5222 :
5223 700 : const char *pszVersion = CSLFetchNameValue(papszOptions, "VERSION");
5224 700 : if (pszVersion && !EQUAL(pszVersion, "AUTO"))
5225 : {
5226 33 : if (EQUAL(pszVersion, "1.0"))
5227 : {
5228 2 : m_nApplicationId = GP10_APPLICATION_ID;
5229 2 : m_nUserVersion = 0;
5230 : }
5231 31 : else if (EQUAL(pszVersion, "1.1"))
5232 : {
5233 1 : m_nApplicationId = GP11_APPLICATION_ID;
5234 1 : m_nUserVersion = 0;
5235 : }
5236 30 : else if (EQUAL(pszVersion, "1.2"))
5237 : {
5238 12 : m_nApplicationId = GPKG_APPLICATION_ID;
5239 12 : m_nUserVersion = GPKG_1_2_VERSION;
5240 : }
5241 18 : else if (EQUAL(pszVersion, "1.3"))
5242 : {
5243 3 : m_nApplicationId = GPKG_APPLICATION_ID;
5244 3 : m_nUserVersion = GPKG_1_3_VERSION;
5245 : }
5246 15 : else if (EQUAL(pszVersion, "1.4"))
5247 : {
5248 15 : m_nApplicationId = GPKG_APPLICATION_ID;
5249 15 : m_nUserVersion = GPKG_1_4_VERSION;
5250 : }
5251 : }
5252 :
5253 700 : SoftStartTransaction();
5254 :
5255 1400 : CPLString osSQL;
5256 700 : if (!bFileExists)
5257 : {
5258 : /* Requirement 10: A GeoPackage SHALL include a gpkg_spatial_ref_sys
5259 : * table */
5260 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5261 : osSQL = "CREATE TABLE gpkg_spatial_ref_sys ("
5262 : "srs_name TEXT NOT NULL,"
5263 : "srs_id INTEGER NOT NULL PRIMARY KEY,"
5264 : "organization TEXT NOT NULL,"
5265 : "organization_coordsys_id INTEGER NOT NULL,"
5266 : "definition TEXT NOT NULL,"
5267 693 : "description TEXT";
5268 693 : if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "CRS_WKT_EXTENSION",
5269 849 : "NO")) ||
5270 156 : (nBandsIn != 0 && eDT != GDT_Byte))
5271 : {
5272 41 : m_bHasDefinition12_063 = true;
5273 41 : osSQL += ", definition_12_063 TEXT NOT NULL";
5274 41 : if (m_nUserVersion >= GPKG_1_4_VERSION)
5275 : {
5276 1 : osSQL += ", epoch DOUBLE";
5277 1 : m_bHasEpochColumn = true;
5278 : }
5279 : }
5280 : osSQL += ")"
5281 : ";"
5282 : /* Requirement 11: The gpkg_spatial_ref_sys table in a
5283 : GeoPackage SHALL */
5284 : /* contain a record for EPSG:4326, the geodetic WGS84 SRS */
5285 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5286 :
5287 : "INSERT INTO gpkg_spatial_ref_sys ("
5288 : "srs_name, srs_id, organization, organization_coordsys_id, "
5289 693 : "definition, description";
5290 693 : if (m_bHasDefinition12_063)
5291 41 : osSQL += ", definition_12_063";
5292 : osSQL +=
5293 : ") VALUES ("
5294 : "'WGS 84 geodetic', 4326, 'EPSG', 4326, '"
5295 : "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS "
5296 : "84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],"
5297 : "AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY["
5298 : "\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY["
5299 : "\"EPSG\",\"9122\"]],AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\","
5300 : "EAST],AUTHORITY[\"EPSG\",\"4326\"]]"
5301 : "', 'longitude/latitude coordinates in decimal degrees on the WGS "
5302 693 : "84 spheroid'";
5303 693 : if (m_bHasDefinition12_063)
5304 : osSQL +=
5305 : ", 'GEODCRS[\"WGS 84\", DATUM[\"World Geodetic System 1984\", "
5306 : "ELLIPSOID[\"WGS 84\",6378137, 298.257223563, "
5307 : "LENGTHUNIT[\"metre\", 1.0]]], PRIMEM[\"Greenwich\", 0.0, "
5308 : "ANGLEUNIT[\"degree\",0.0174532925199433]], CS[ellipsoidal, "
5309 : "2], AXIS[\"latitude\", north, ORDER[1]], AXIS[\"longitude\", "
5310 : "east, ORDER[2]], ANGLEUNIT[\"degree\", 0.0174532925199433], "
5311 41 : "ID[\"EPSG\", 4326]]'";
5312 : osSQL +=
5313 : ")"
5314 : ";"
5315 : /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
5316 : SHALL */
5317 : /* contain a record with an srs_id of -1, an organization of “NONE”,
5318 : */
5319 : /* an organization_coordsys_id of -1, and definition “undefined” */
5320 : /* for undefined Cartesian coordinate reference systems */
5321 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5322 : "INSERT INTO gpkg_spatial_ref_sys ("
5323 : "srs_name, srs_id, organization, organization_coordsys_id, "
5324 693 : "definition, description";
5325 693 : if (m_bHasDefinition12_063)
5326 41 : osSQL += ", definition_12_063";
5327 : osSQL += ") VALUES ("
5328 : "'Undefined Cartesian SRS', -1, 'NONE', -1, 'undefined', "
5329 693 : "'undefined Cartesian coordinate reference system'";
5330 693 : if (m_bHasDefinition12_063)
5331 41 : osSQL += ", 'undefined'";
5332 : osSQL +=
5333 : ")"
5334 : ";"
5335 : /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
5336 : SHALL */
5337 : /* contain a record with an srs_id of 0, an organization of “NONE”,
5338 : */
5339 : /* an organization_coordsys_id of 0, and definition “undefined” */
5340 : /* for undefined geographic coordinate reference systems */
5341 : /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5342 : "INSERT INTO gpkg_spatial_ref_sys ("
5343 : "srs_name, srs_id, organization, organization_coordsys_id, "
5344 693 : "definition, description";
5345 693 : if (m_bHasDefinition12_063)
5346 41 : osSQL += ", definition_12_063";
5347 : osSQL += ") VALUES ("
5348 : "'Undefined geographic SRS', 0, 'NONE', 0, 'undefined', "
5349 693 : "'undefined geographic coordinate reference system'";
5350 693 : if (m_bHasDefinition12_063)
5351 41 : osSQL += ", 'undefined'";
5352 : osSQL += ")"
5353 : ";"
5354 : /* Requirement 13: A GeoPackage file SHALL include a
5355 : gpkg_contents table */
5356 : /* http://opengis.github.io/geopackage/#_contents */
5357 : "CREATE TABLE gpkg_contents ("
5358 : "table_name TEXT NOT NULL PRIMARY KEY,"
5359 : "data_type TEXT NOT NULL,"
5360 : "identifier TEXT UNIQUE,"
5361 : "description TEXT DEFAULT '',"
5362 : "last_change DATETIME NOT NULL DEFAULT "
5363 : "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
5364 : "min_x DOUBLE, min_y DOUBLE,"
5365 : "max_x DOUBLE, max_y DOUBLE,"
5366 : "srs_id INTEGER,"
5367 : "CONSTRAINT fk_gc_r_srs_id FOREIGN KEY (srs_id) REFERENCES "
5368 : "gpkg_spatial_ref_sys(srs_id)"
5369 693 : ")";
5370 :
5371 : #ifdef ENABLE_GPKG_OGR_CONTENTS
5372 693 : if (CPLFetchBool(papszOptions, "ADD_GPKG_OGR_CONTENTS", true))
5373 : {
5374 691 : m_bHasGPKGOGRContents = true;
5375 : osSQL += ";"
5376 : "CREATE TABLE gpkg_ogr_contents("
5377 : "table_name TEXT NOT NULL PRIMARY KEY,"
5378 : "feature_count INTEGER DEFAULT NULL"
5379 691 : ")";
5380 : }
5381 : #endif
5382 :
5383 : /* Requirement 21: A GeoPackage with a gpkg_contents table row with a
5384 : * “features” */
5385 : /* data_type SHALL contain a gpkg_geometry_columns table or updateable
5386 : * view */
5387 : /* http://opengis.github.io/geopackage/#_geometry_columns */
5388 : const bool bCreateGeometryColumns =
5389 693 : CPLTestBool(CPLGetConfigOption("CREATE_GEOMETRY_COLUMNS", "YES"));
5390 693 : if (bCreateGeometryColumns)
5391 : {
5392 692 : m_bHasGPKGGeometryColumns = true;
5393 692 : osSQL += ";";
5394 692 : osSQL += pszCREATE_GPKG_GEOMETRY_COLUMNS;
5395 : }
5396 : }
5397 :
5398 : const bool bCreateTriggers =
5399 700 : CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "YES"));
5400 7 : if ((bFileExists && nBandsIn != 0 &&
5401 7 : SQLGetInteger(
5402 : hDB,
5403 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_tile_matrix_set' "
5404 : "AND type in ('table', 'view')",
5405 1400 : nullptr) == 0) ||
5406 699 : (!bFileExists &&
5407 693 : CPLTestBool(CPLGetConfigOption("CREATE_RASTER_TABLES", "YES"))))
5408 : {
5409 693 : if (!osSQL.empty())
5410 692 : osSQL += ";";
5411 :
5412 : /* From C.5. gpkg_tile_matrix_set Table 28. gpkg_tile_matrix_set Table
5413 : * Creation SQL */
5414 : osSQL += "CREATE TABLE gpkg_tile_matrix_set ("
5415 : "table_name TEXT NOT NULL PRIMARY KEY,"
5416 : "srs_id INTEGER NOT NULL,"
5417 : "min_x DOUBLE NOT NULL,"
5418 : "min_y DOUBLE NOT NULL,"
5419 : "max_x DOUBLE NOT NULL,"
5420 : "max_y DOUBLE NOT NULL,"
5421 : "CONSTRAINT fk_gtms_table_name FOREIGN KEY (table_name) "
5422 : "REFERENCES gpkg_contents(table_name),"
5423 : "CONSTRAINT fk_gtms_srs FOREIGN KEY (srs_id) REFERENCES "
5424 : "gpkg_spatial_ref_sys (srs_id)"
5425 : ")"
5426 : ";"
5427 :
5428 : /* From C.6. gpkg_tile_matrix Table 29. gpkg_tile_matrix Table
5429 : Creation SQL */
5430 : "CREATE TABLE gpkg_tile_matrix ("
5431 : "table_name TEXT NOT NULL,"
5432 : "zoom_level INTEGER NOT NULL,"
5433 : "matrix_width INTEGER NOT NULL,"
5434 : "matrix_height INTEGER NOT NULL,"
5435 : "tile_width INTEGER NOT NULL,"
5436 : "tile_height INTEGER NOT NULL,"
5437 : "pixel_x_size DOUBLE NOT NULL,"
5438 : "pixel_y_size DOUBLE NOT NULL,"
5439 : "CONSTRAINT pk_ttm PRIMARY KEY (table_name, zoom_level),"
5440 : "CONSTRAINT fk_tmm_table_name FOREIGN KEY (table_name) "
5441 : "REFERENCES gpkg_contents(table_name)"
5442 693 : ")";
5443 :
5444 693 : if (bCreateTriggers)
5445 : {
5446 : /* From D.1. gpkg_tile_matrix Table 39. gpkg_tile_matrix Trigger
5447 : * Definition SQL */
5448 693 : const char *pszTileMatrixTrigger =
5449 : "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_insert' "
5450 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5451 : "FOR EACH ROW BEGIN "
5452 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5453 : "violates constraint: zoom_level cannot be less than 0') "
5454 : "WHERE (NEW.zoom_level < 0); "
5455 : "END; "
5456 : "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_update' "
5457 : "BEFORE UPDATE of zoom_level ON 'gpkg_tile_matrix' "
5458 : "FOR EACH ROW BEGIN "
5459 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5460 : "violates constraint: zoom_level cannot be less than 0') "
5461 : "WHERE (NEW.zoom_level < 0); "
5462 : "END; "
5463 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_insert' "
5464 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5465 : "FOR EACH ROW BEGIN "
5466 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5467 : "violates constraint: matrix_width cannot be less than 1') "
5468 : "WHERE (NEW.matrix_width < 1); "
5469 : "END; "
5470 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_update' "
5471 : "BEFORE UPDATE OF matrix_width ON 'gpkg_tile_matrix' "
5472 : "FOR EACH ROW BEGIN "
5473 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5474 : "violates constraint: matrix_width cannot be less than 1') "
5475 : "WHERE (NEW.matrix_width < 1); "
5476 : "END; "
5477 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_insert' "
5478 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5479 : "FOR EACH ROW BEGIN "
5480 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5481 : "violates constraint: matrix_height cannot be less than 1') "
5482 : "WHERE (NEW.matrix_height < 1); "
5483 : "END; "
5484 : "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_update' "
5485 : "BEFORE UPDATE OF matrix_height ON 'gpkg_tile_matrix' "
5486 : "FOR EACH ROW BEGIN "
5487 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5488 : "violates constraint: matrix_height cannot be less than 1') "
5489 : "WHERE (NEW.matrix_height < 1); "
5490 : "END; "
5491 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_insert' "
5492 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5493 : "FOR EACH ROW BEGIN "
5494 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5495 : "violates constraint: pixel_x_size must be greater than 0') "
5496 : "WHERE NOT (NEW.pixel_x_size > 0); "
5497 : "END; "
5498 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_update' "
5499 : "BEFORE UPDATE OF pixel_x_size ON 'gpkg_tile_matrix' "
5500 : "FOR EACH ROW BEGIN "
5501 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5502 : "violates constraint: pixel_x_size must be greater than 0') "
5503 : "WHERE NOT (NEW.pixel_x_size > 0); "
5504 : "END; "
5505 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_insert' "
5506 : "BEFORE INSERT ON 'gpkg_tile_matrix' "
5507 : "FOR EACH ROW BEGIN "
5508 : "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5509 : "violates constraint: pixel_y_size must be greater than 0') "
5510 : "WHERE NOT (NEW.pixel_y_size > 0); "
5511 : "END; "
5512 : "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_update' "
5513 : "BEFORE UPDATE OF pixel_y_size ON 'gpkg_tile_matrix' "
5514 : "FOR EACH ROW BEGIN "
5515 : "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5516 : "violates constraint: pixel_y_size must be greater than 0') "
5517 : "WHERE NOT (NEW.pixel_y_size > 0); "
5518 : "END;";
5519 693 : osSQL += ";";
5520 693 : osSQL += pszTileMatrixTrigger;
5521 : }
5522 : }
5523 :
5524 700 : if (!osSQL.empty() && OGRERR_NONE != SQLCommand(hDB, osSQL))
5525 0 : return FALSE;
5526 :
5527 700 : if (!bFileExists)
5528 : {
5529 : const char *pszMetadataTables =
5530 693 : CSLFetchNameValue(papszOptions, "METADATA_TABLES");
5531 693 : if (pszMetadataTables)
5532 6 : m_nCreateMetadataTables = int(CPLTestBool(pszMetadataTables));
5533 :
5534 693 : if (m_nCreateMetadataTables == TRUE && !CreateMetadataTables())
5535 0 : return FALSE;
5536 :
5537 693 : if (m_bHasDefinition12_063)
5538 : {
5539 82 : if (OGRERR_NONE != CreateExtensionsTableIfNecessary() ||
5540 : OGRERR_NONE !=
5541 41 : SQLCommand(hDB, "INSERT INTO gpkg_extensions "
5542 : "(table_name, column_name, extension_name, "
5543 : "definition, scope) "
5544 : "VALUES "
5545 : "('gpkg_spatial_ref_sys', "
5546 : "'definition_12_063', 'gpkg_crs_wkt', "
5547 : "'http://www.geopackage.org/spec120/"
5548 : "#extension_crs_wkt', 'read-write')"))
5549 : {
5550 0 : return FALSE;
5551 : }
5552 41 : if (m_bHasEpochColumn)
5553 : {
5554 1 : if (OGRERR_NONE !=
5555 1 : SQLCommand(
5556 : hDB, "UPDATE gpkg_extensions SET extension_name = "
5557 : "'gpkg_crs_wkt_1_1' "
5558 2 : "WHERE extension_name = 'gpkg_crs_wkt'") ||
5559 : OGRERR_NONE !=
5560 1 : SQLCommand(hDB, "INSERT INTO gpkg_extensions "
5561 : "(table_name, column_name, "
5562 : "extension_name, definition, scope) "
5563 : "VALUES "
5564 : "('gpkg_spatial_ref_sys', 'epoch', "
5565 : "'gpkg_crs_wkt_1_1', "
5566 : "'http://www.geopackage.org/spec/"
5567 : "#extension_crs_wkt', "
5568 : "'read-write')"))
5569 : {
5570 0 : return FALSE;
5571 : }
5572 : }
5573 : }
5574 : }
5575 :
5576 700 : if (nBandsIn != 0)
5577 : {
5578 163 : const char *pszTableName = CPLGetBasename(m_pszFilename);
5579 : m_osRasterTable =
5580 163 : CSLFetchNameValueDef(papszOptions, "RASTER_TABLE", pszTableName);
5581 163 : if (m_osRasterTable.empty())
5582 : {
5583 0 : CPLError(CE_Failure, CPLE_AppDefined,
5584 : "RASTER_TABLE must be set to a non empty value");
5585 0 : return FALSE;
5586 : }
5587 163 : m_bIdentifierAsCO =
5588 163 : CSLFetchNameValue(papszOptions, "RASTER_IDENTIFIER") != nullptr;
5589 : m_osIdentifier = CSLFetchNameValueDef(papszOptions, "RASTER_IDENTIFIER",
5590 163 : m_osRasterTable);
5591 163 : m_bDescriptionAsCO =
5592 163 : CSLFetchNameValue(papszOptions, "RASTER_DESCRIPTION") != nullptr;
5593 : m_osDescription =
5594 163 : CSLFetchNameValueDef(papszOptions, "RASTER_DESCRIPTION", "");
5595 163 : SetDataType(eDT);
5596 163 : if (eDT == GDT_Int16)
5597 15 : SetGlobalOffsetScale(-32768.0, 1.0);
5598 :
5599 : /* From C.7. sample_tile_pyramid (Informative) Table 31. EXAMPLE: tiles
5600 : * table Create Table SQL (Informative) */
5601 : char *pszSQL =
5602 163 : sqlite3_mprintf("CREATE TABLE \"%w\" ("
5603 : "id INTEGER PRIMARY KEY AUTOINCREMENT,"
5604 : "zoom_level INTEGER NOT NULL,"
5605 : "tile_column INTEGER NOT NULL,"
5606 : "tile_row INTEGER NOT NULL,"
5607 : "tile_data BLOB NOT NULL,"
5608 : "UNIQUE (zoom_level, tile_column, tile_row)"
5609 : ")",
5610 : m_osRasterTable.c_str());
5611 163 : osSQL = pszSQL;
5612 163 : sqlite3_free(pszSQL);
5613 :
5614 163 : if (bCreateTriggers)
5615 : {
5616 : /* From D.5. sample_tile_pyramid Table 43. tiles table Trigger
5617 : * Definition SQL */
5618 163 : pszSQL = sqlite3_mprintf(
5619 : "CREATE TRIGGER \"%w_zoom_insert\" "
5620 : "BEFORE INSERT ON \"%w\" "
5621 : "FOR EACH ROW BEGIN "
5622 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
5623 : "constraint: zoom_level not specified for table in "
5624 : "gpkg_tile_matrix') "
5625 : "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
5626 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
5627 : "END; "
5628 : "CREATE TRIGGER \"%w_zoom_update\" "
5629 : "BEFORE UPDATE OF zoom_level ON \"%w\" "
5630 : "FOR EACH ROW BEGIN "
5631 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
5632 : "constraint: zoom_level not specified for table in "
5633 : "gpkg_tile_matrix') "
5634 : "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
5635 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
5636 : "END; "
5637 : "CREATE TRIGGER \"%w_tile_column_insert\" "
5638 : "BEFORE INSERT ON \"%w\" "
5639 : "FOR EACH ROW BEGIN "
5640 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
5641 : "constraint: tile_column cannot be < 0') "
5642 : "WHERE (NEW.tile_column < 0) ; "
5643 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
5644 : "constraint: tile_column must by < matrix_width specified for "
5645 : "table and zoom level in gpkg_tile_matrix') "
5646 : "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
5647 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
5648 : "zoom_level = NEW.zoom_level)); "
5649 : "END; "
5650 : "CREATE TRIGGER \"%w_tile_column_update\" "
5651 : "BEFORE UPDATE OF tile_column ON \"%w\" "
5652 : "FOR EACH ROW BEGIN "
5653 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
5654 : "constraint: tile_column cannot be < 0') "
5655 : "WHERE (NEW.tile_column < 0) ; "
5656 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
5657 : "constraint: tile_column must by < matrix_width specified for "
5658 : "table and zoom level in gpkg_tile_matrix') "
5659 : "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
5660 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
5661 : "zoom_level = NEW.zoom_level)); "
5662 : "END; "
5663 : "CREATE TRIGGER \"%w_tile_row_insert\" "
5664 : "BEFORE INSERT ON \"%w\" "
5665 : "FOR EACH ROW BEGIN "
5666 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
5667 : "constraint: tile_row cannot be < 0') "
5668 : "WHERE (NEW.tile_row < 0) ; "
5669 : "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
5670 : "constraint: tile_row must by < matrix_height specified for "
5671 : "table and zoom level in gpkg_tile_matrix') "
5672 : "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
5673 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
5674 : "zoom_level = NEW.zoom_level)); "
5675 : "END; "
5676 : "CREATE TRIGGER \"%w_tile_row_update\" "
5677 : "BEFORE UPDATE OF tile_row ON \"%w\" "
5678 : "FOR EACH ROW BEGIN "
5679 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
5680 : "constraint: tile_row cannot be < 0') "
5681 : "WHERE (NEW.tile_row < 0) ; "
5682 : "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
5683 : "constraint: tile_row must by < matrix_height specified for "
5684 : "table and zoom level in gpkg_tile_matrix') "
5685 : "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
5686 : "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
5687 : "zoom_level = NEW.zoom_level)); "
5688 : "END; ",
5689 : m_osRasterTable.c_str(), m_osRasterTable.c_str(),
5690 : m_osRasterTable.c_str(), m_osRasterTable.c_str(),
5691 : m_osRasterTable.c_str(), m_osRasterTable.c_str(),
5692 : m_osRasterTable.c_str(), m_osRasterTable.c_str(),
5693 : m_osRasterTable.c_str(), m_osRasterTable.c_str(),
5694 : m_osRasterTable.c_str(), m_osRasterTable.c_str(),
5695 : m_osRasterTable.c_str(), m_osRasterTable.c_str(),
5696 : m_osRasterTable.c_str(), m_osRasterTable.c_str(),
5697 : m_osRasterTable.c_str(), m_osRasterTable.c_str(),
5698 : m_osRasterTable.c_str(), m_osRasterTable.c_str(),
5699 : m_osRasterTable.c_str(), m_osRasterTable.c_str(),
5700 : m_osRasterTable.c_str(), m_osRasterTable.c_str(),
5701 : m_osRasterTable.c_str(), m_osRasterTable.c_str(),
5702 : m_osRasterTable.c_str(), m_osRasterTable.c_str());
5703 :
5704 163 : osSQL += ";";
5705 163 : osSQL += pszSQL;
5706 163 : sqlite3_free(pszSQL);
5707 : }
5708 :
5709 163 : OGRErr eErr = SQLCommand(hDB, osSQL);
5710 163 : if (OGRERR_NONE != eErr)
5711 0 : return FALSE;
5712 :
5713 163 : const char *pszTF = CSLFetchNameValue(papszOptions, "TILE_FORMAT");
5714 163 : if (eDT == GDT_Int16 || eDT == GDT_UInt16)
5715 : {
5716 26 : m_eTF = GPKG_TF_PNG_16BIT;
5717 26 : if (pszTF)
5718 : {
5719 0 : if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "PNG"))
5720 : {
5721 0 : CPLError(CE_Warning, CPLE_NotSupported,
5722 : "Only AUTO or PNG supported "
5723 : "as tile format for Int16 / UInt16");
5724 : }
5725 : }
5726 : }
5727 137 : else if (eDT == GDT_Float32)
5728 : {
5729 13 : m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
5730 13 : if (pszTF)
5731 : {
5732 5 : if (EQUAL(pszTF, "PNG"))
5733 5 : m_eTF = GPKG_TF_PNG_16BIT;
5734 0 : else if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "TIFF"))
5735 : {
5736 0 : CPLError(CE_Warning, CPLE_NotSupported,
5737 : "Only AUTO, PNG or TIFF supported "
5738 : "as tile format for Float32");
5739 : }
5740 : }
5741 : }
5742 : else
5743 : {
5744 124 : if (pszTF)
5745 : {
5746 70 : m_eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
5747 70 : if (nBandsIn == 1 && m_eTF != GPKG_TF_PNG)
5748 7 : m_bMetadataDirty = true;
5749 : }
5750 54 : else if (nBandsIn == 1)
5751 43 : m_eTF = GPKG_TF_PNG;
5752 : }
5753 :
5754 163 : if (eDT != GDT_Byte)
5755 : {
5756 39 : if (!CreateTileGriddedTable(papszOptions))
5757 0 : return FALSE;
5758 : }
5759 :
5760 163 : nRasterXSize = nXSize;
5761 163 : nRasterYSize = nYSize;
5762 :
5763 : const char *pszTileSize =
5764 163 : CSLFetchNameValueDef(papszOptions, "BLOCKSIZE", "256");
5765 : const char *pszTileWidth =
5766 163 : CSLFetchNameValueDef(papszOptions, "BLOCKXSIZE", pszTileSize);
5767 : const char *pszTileHeight =
5768 163 : CSLFetchNameValueDef(papszOptions, "BLOCKYSIZE", pszTileSize);
5769 163 : int nTileWidth = atoi(pszTileWidth);
5770 163 : int nTileHeight = atoi(pszTileHeight);
5771 163 : if ((nTileWidth < 8 || nTileWidth > 4096 || nTileHeight < 8 ||
5772 326 : nTileHeight > 4096) &&
5773 1 : !CPLTestBool(CPLGetConfigOption("GPKG_ALLOW_CRAZY_SETTINGS", "NO")))
5774 : {
5775 0 : CPLError(CE_Failure, CPLE_AppDefined,
5776 : "Invalid block dimensions: %dx%d", nTileWidth,
5777 : nTileHeight);
5778 0 : return FALSE;
5779 : }
5780 :
5781 457 : for (int i = 1; i <= nBandsIn; i++)
5782 294 : SetBand(
5783 294 : i, new GDALGeoPackageRasterBand(this, nTileWidth, nTileHeight));
5784 :
5785 163 : GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL",
5786 : "IMAGE_STRUCTURE");
5787 163 : GDALPamDataset::SetMetadataItem("IDENTIFIER", m_osIdentifier);
5788 163 : if (!m_osDescription.empty())
5789 1 : GDALPamDataset::SetMetadataItem("DESCRIPTION", m_osDescription);
5790 :
5791 163 : ParseCompressionOptions(papszOptions);
5792 :
5793 163 : if (m_eTF == GPKG_TF_WEBP)
5794 : {
5795 10 : if (!RegisterWebPExtension())
5796 0 : return FALSE;
5797 : }
5798 :
5799 : m_osTilingScheme =
5800 163 : CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
5801 163 : if (!EQUAL(m_osTilingScheme, "CUSTOM"))
5802 : {
5803 22 : const auto poTS = GetTilingScheme(m_osTilingScheme);
5804 22 : if (!poTS)
5805 0 : return FALSE;
5806 :
5807 43 : if (nTileWidth != poTS->nTileWidth ||
5808 21 : nTileHeight != poTS->nTileHeight)
5809 : {
5810 2 : CPLError(CE_Failure, CPLE_NotSupported,
5811 : "Tile dimension should be %dx%d for %s tiling scheme",
5812 1 : poTS->nTileWidth, poTS->nTileHeight,
5813 : m_osTilingScheme.c_str());
5814 1 : return FALSE;
5815 : }
5816 :
5817 : const char *pszZoomLevel =
5818 21 : CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
5819 21 : if (pszZoomLevel)
5820 : {
5821 1 : m_nZoomLevel = atoi(pszZoomLevel);
5822 1 : int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
5823 1 : while ((1 << nMaxZoomLevelForThisTM) >
5824 2 : INT_MAX / poTS->nTileXCountZoomLevel0 ||
5825 1 : (1 << nMaxZoomLevelForThisTM) >
5826 1 : INT_MAX / poTS->nTileYCountZoomLevel0)
5827 : {
5828 0 : --nMaxZoomLevelForThisTM;
5829 : }
5830 :
5831 1 : if (m_nZoomLevel < 0 || m_nZoomLevel > nMaxZoomLevelForThisTM)
5832 : {
5833 0 : CPLError(CE_Failure, CPLE_AppDefined,
5834 : "ZOOM_LEVEL = %s is invalid. It should be in "
5835 : "[0,%d] range",
5836 : pszZoomLevel, nMaxZoomLevelForThisTM);
5837 0 : return FALSE;
5838 : }
5839 : }
5840 :
5841 : // Implicitly sets SRS.
5842 21 : OGRSpatialReference oSRS;
5843 21 : if (oSRS.importFromEPSG(poTS->nEPSGCode) != OGRERR_NONE)
5844 0 : return FALSE;
5845 21 : char *pszWKT = nullptr;
5846 21 : oSRS.exportToWkt(&pszWKT);
5847 21 : SetProjection(pszWKT);
5848 21 : CPLFree(pszWKT);
5849 : }
5850 : else
5851 : {
5852 141 : if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
5853 : {
5854 0 : CPLError(
5855 : CE_Failure, CPLE_NotSupported,
5856 : "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
5857 0 : return false;
5858 : }
5859 : }
5860 : }
5861 :
5862 699 : if (bFileExists && nBandsIn > 0 && eDT == GDT_Byte)
5863 : {
5864 : // If there was an ogr_empty_table table, we can remove it
5865 6 : RemoveOGREmptyTable();
5866 : }
5867 :
5868 699 : SoftCommitTransaction();
5869 :
5870 : /* Requirement 2 */
5871 : /* We have to do this after there's some content so the database file */
5872 : /* is not zero length */
5873 699 : SetApplicationAndUserVersionId();
5874 :
5875 : /* Default to synchronous=off for performance for new file */
5876 1391 : if (!bFileExists &&
5877 692 : CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
5878 : {
5879 268 : SQLCommand(hDB, "PRAGMA synchronous = OFF");
5880 : }
5881 :
5882 699 : return TRUE;
5883 : }
5884 :
5885 : /************************************************************************/
5886 : /* RemoveOGREmptyTable() */
5887 : /************************************************************************/
5888 :
5889 526 : void GDALGeoPackageDataset::RemoveOGREmptyTable()
5890 : {
5891 : // Run with sqlite3_exec since we don't want errors to be emitted
5892 526 : sqlite3_exec(hDB, "DROP TABLE IF EXISTS ogr_empty_table", nullptr, nullptr,
5893 : nullptr);
5894 526 : sqlite3_exec(
5895 : hDB, "DELETE FROM gpkg_contents WHERE table_name = 'ogr_empty_table'",
5896 : nullptr, nullptr, nullptr);
5897 : #ifdef ENABLE_GPKG_OGR_CONTENTS
5898 526 : if (m_bHasGPKGOGRContents)
5899 : {
5900 517 : sqlite3_exec(hDB,
5901 : "DELETE FROM gpkg_ogr_contents WHERE "
5902 : "table_name = 'ogr_empty_table'",
5903 : nullptr, nullptr, nullptr);
5904 : }
5905 : #endif
5906 526 : sqlite3_exec(hDB,
5907 : "DELETE FROM gpkg_geometry_columns WHERE "
5908 : "table_name = 'ogr_empty_table'",
5909 : nullptr, nullptr, nullptr);
5910 526 : }
5911 :
5912 : /************************************************************************/
5913 : /* CreateTileGriddedTable() */
5914 : /************************************************************************/
5915 :
5916 39 : bool GDALGeoPackageDataset::CreateTileGriddedTable(char **papszOptions)
5917 : {
5918 78 : CPLString osSQL;
5919 39 : if (!HasGriddedCoverageAncillaryTable())
5920 : {
5921 : // It doesn't exist. So create gpkg_extensions table if necessary, and
5922 : // gpkg_2d_gridded_coverage_ancillary & gpkg_2d_gridded_tile_ancillary,
5923 : // and register them as extensions.
5924 39 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
5925 0 : return false;
5926 :
5927 : // Req 1 /table-defs/coverage-ancillary
5928 : osSQL = "CREATE TABLE gpkg_2d_gridded_coverage_ancillary ("
5929 : "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
5930 : "tile_matrix_set_name TEXT NOT NULL UNIQUE,"
5931 : "datatype TEXT NOT NULL DEFAULT 'integer',"
5932 : "scale REAL NOT NULL DEFAULT 1.0,"
5933 : "offset REAL NOT NULL DEFAULT 0.0,"
5934 : "precision REAL DEFAULT 1.0,"
5935 : "data_null REAL,"
5936 : "grid_cell_encoding TEXT DEFAULT 'grid-value-is-center',"
5937 : "uom TEXT,"
5938 : "field_name TEXT DEFAULT 'Height',"
5939 : "quantity_definition TEXT DEFAULT 'Height',"
5940 : "CONSTRAINT fk_g2dgtct_name FOREIGN KEY(tile_matrix_set_name) "
5941 : "REFERENCES gpkg_tile_matrix_set ( table_name ) "
5942 : "CHECK (datatype in ('integer','float')))"
5943 : ";"
5944 : // Requirement 2 /table-defs/tile-ancillary
5945 : "CREATE TABLE gpkg_2d_gridded_tile_ancillary ("
5946 : "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
5947 : "tpudt_name TEXT NOT NULL,"
5948 : "tpudt_id INTEGER NOT NULL,"
5949 : "scale REAL NOT NULL DEFAULT 1.0,"
5950 : "offset REAL NOT NULL DEFAULT 0.0,"
5951 : "min REAL DEFAULT NULL,"
5952 : "max REAL DEFAULT NULL,"
5953 : "mean REAL DEFAULT NULL,"
5954 : "std_dev REAL DEFAULT NULL,"
5955 : "CONSTRAINT fk_g2dgtat_name FOREIGN KEY (tpudt_name) "
5956 : "REFERENCES gpkg_contents(table_name),"
5957 : "UNIQUE (tpudt_name, tpudt_id))"
5958 : ";"
5959 : // Requirement 6 /gpkg-extensions
5960 : "INSERT INTO gpkg_extensions "
5961 : "(table_name, column_name, extension_name, definition, scope) "
5962 : "VALUES ('gpkg_2d_gridded_coverage_ancillary', NULL, "
5963 : "'gpkg_2d_gridded_coverage', "
5964 : "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5965 : "'read-write')"
5966 : ";"
5967 : // Requirement 6 /gpkg-extensions
5968 : "INSERT INTO gpkg_extensions "
5969 : "(table_name, column_name, extension_name, definition, scope) "
5970 : "VALUES ('gpkg_2d_gridded_tile_ancillary', NULL, "
5971 : "'gpkg_2d_gridded_coverage', "
5972 : "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5973 : "'read-write')"
5974 39 : ";";
5975 : }
5976 :
5977 : // Requirement 6 /gpkg-extensions
5978 39 : char *pszSQL = sqlite3_mprintf(
5979 : "INSERT INTO gpkg_extensions "
5980 : "(table_name, column_name, extension_name, definition, scope) "
5981 : "VALUES ('%q', 'tile_data', "
5982 : "'gpkg_2d_gridded_coverage', "
5983 : "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5984 : "'read-write')",
5985 : m_osRasterTable.c_str());
5986 39 : osSQL += pszSQL;
5987 39 : osSQL += ";";
5988 39 : sqlite3_free(pszSQL);
5989 :
5990 : // Requirement 7 /gpkg-2d-gridded-coverage-ancillary
5991 : // Requirement 8 /gpkg-2d-gridded-coverage-ancillary-set-name
5992 : // Requirement 9 /gpkg-2d-gridded-coverage-ancillary-datatype
5993 39 : m_dfPrecision =
5994 39 : CPLAtof(CSLFetchNameValueDef(papszOptions, "PRECISION", "1"));
5995 : CPLString osGridCellEncoding(CSLFetchNameValueDef(
5996 78 : papszOptions, "GRID_CELL_ENCODING", "grid-value-is-center"));
5997 39 : m_bGridCellEncodingAsCO =
5998 39 : CSLFetchNameValue(papszOptions, "GRID_CELL_ENCODING") != nullptr;
5999 78 : CPLString osUom(CSLFetchNameValueDef(papszOptions, "UOM", ""));
6000 : CPLString osFieldName(
6001 78 : CSLFetchNameValueDef(papszOptions, "FIELD_NAME", "Height"));
6002 : CPLString osQuantityDefinition(
6003 78 : CSLFetchNameValueDef(papszOptions, "QUANTITY_DEFINITION", "Height"));
6004 :
6005 118 : pszSQL = sqlite3_mprintf(
6006 : "INSERT INTO gpkg_2d_gridded_coverage_ancillary "
6007 : "(tile_matrix_set_name, datatype, scale, offset, precision, "
6008 : "grid_cell_encoding, uom, field_name, quantity_definition) "
6009 : "VALUES (%Q, '%s', %.18g, %.18g, %.18g, %Q, %Q, %Q, %Q)",
6010 : m_osRasterTable.c_str(),
6011 39 : (m_eTF == GPKG_TF_PNG_16BIT) ? "integer" : "float", m_dfScale,
6012 : m_dfOffset, m_dfPrecision, osGridCellEncoding.c_str(),
6013 40 : osUom.empty() ? nullptr : osUom.c_str(), osFieldName.c_str(),
6014 : osQuantityDefinition.c_str());
6015 39 : m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary = pszSQL;
6016 39 : sqlite3_free(pszSQL);
6017 :
6018 : // Requirement 3 /gpkg-spatial-ref-sys-row
6019 : auto oResultTable = SQLQuery(
6020 78 : hDB, "SELECT * FROM gpkg_spatial_ref_sys WHERE srs_id = 4979 LIMIT 2");
6021 39 : bool bHasEPSG4979 = (oResultTable && oResultTable->RowCount() == 1);
6022 39 : if (!bHasEPSG4979)
6023 : {
6024 40 : if (!m_bHasDefinition12_063 &&
6025 1 : !ConvertGpkgSpatialRefSysToExtensionWkt2(/*bForceEpoch=*/false))
6026 : {
6027 0 : return false;
6028 : }
6029 :
6030 : // This is WKT 2...
6031 39 : const char *pszWKT =
6032 : "GEODCRS[\"WGS 84\","
6033 : "DATUM[\"World Geodetic System 1984\","
6034 : " ELLIPSOID[\"WGS 84\",6378137,298.257223563,"
6035 : "LENGTHUNIT[\"metre\",1.0]]],"
6036 : "CS[ellipsoidal,3],"
6037 : " AXIS[\"latitude\",north,ORDER[1],ANGLEUNIT[\"degree\","
6038 : "0.0174532925199433]],"
6039 : " AXIS[\"longitude\",east,ORDER[2],ANGLEUNIT[\"degree\","
6040 : "0.0174532925199433]],"
6041 : " AXIS[\"ellipsoidal height\",up,ORDER[3],"
6042 : "LENGTHUNIT[\"metre\",1.0]],"
6043 : "ID[\"EPSG\",4979]]";
6044 :
6045 39 : pszSQL = sqlite3_mprintf(
6046 : "INSERT INTO gpkg_spatial_ref_sys "
6047 : "(srs_name,srs_id,organization,organization_coordsys_id,"
6048 : "definition,definition_12_063) VALUES "
6049 : "('WGS 84 3D', 4979, 'EPSG', 4979, 'undefined', '%q')",
6050 : pszWKT);
6051 39 : osSQL += ";";
6052 39 : osSQL += pszSQL;
6053 39 : sqlite3_free(pszSQL);
6054 : }
6055 :
6056 39 : return SQLCommand(hDB, osSQL) == OGRERR_NONE;
6057 : }
6058 :
6059 : /************************************************************************/
6060 : /* HasGriddedCoverageAncillaryTable() */
6061 : /************************************************************************/
6062 :
6063 41 : bool GDALGeoPackageDataset::HasGriddedCoverageAncillaryTable()
6064 : {
6065 : auto oResultTable = SQLQuery(
6066 : hDB, "SELECT * FROM sqlite_master WHERE type IN ('table', 'view') AND "
6067 41 : "name = 'gpkg_2d_gridded_coverage_ancillary'");
6068 41 : bool bHasTable = (oResultTable && oResultTable->RowCount() == 1);
6069 82 : return bHasTable;
6070 : }
6071 :
6072 : /************************************************************************/
6073 : /* GetUnderlyingDataset() */
6074 : /************************************************************************/
6075 :
6076 3 : static GDALDataset *GetUnderlyingDataset(GDALDataset *poSrcDS)
6077 : {
6078 3 : if (auto poVRTDS = dynamic_cast<VRTDataset *>(poSrcDS))
6079 : {
6080 0 : auto poTmpDS = poVRTDS->GetSingleSimpleSource();
6081 0 : if (poTmpDS)
6082 0 : return poTmpDS;
6083 : }
6084 :
6085 3 : return poSrcDS;
6086 : }
6087 :
6088 : /************************************************************************/
6089 : /* CreateCopy() */
6090 : /************************************************************************/
6091 :
6092 : typedef struct
6093 : {
6094 : const char *pszName;
6095 : GDALResampleAlg eResampleAlg;
6096 : } WarpResamplingAlg;
6097 :
6098 : static const WarpResamplingAlg asResamplingAlg[] = {
6099 : {"NEAREST", GRA_NearestNeighbour},
6100 : {"BILINEAR", GRA_Bilinear},
6101 : {"CUBIC", GRA_Cubic},
6102 : {"CUBICSPLINE", GRA_CubicSpline},
6103 : {"LANCZOS", GRA_Lanczos},
6104 : {"MODE", GRA_Mode},
6105 : {"AVERAGE", GRA_Average},
6106 : {"RMS", GRA_RMS},
6107 : };
6108 :
6109 137 : GDALDataset *GDALGeoPackageDataset::CreateCopy(const char *pszFilename,
6110 : GDALDataset *poSrcDS,
6111 : int bStrict, char **papszOptions,
6112 : GDALProgressFunc pfnProgress,
6113 : void *pProgressData)
6114 : {
6115 : const char *pszTilingScheme =
6116 137 : CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
6117 :
6118 274 : CPLStringList apszUpdatedOptions(CSLDuplicate(papszOptions));
6119 137 : if (CPLTestBool(
6120 142 : CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")) &&
6121 5 : CSLFetchNameValue(papszOptions, "RASTER_TABLE") == nullptr)
6122 : {
6123 : CPLString osBasename(
6124 6 : CPLGetBasename(GetUnderlyingDataset(poSrcDS)->GetDescription()));
6125 3 : apszUpdatedOptions.SetNameValue("RASTER_TABLE", osBasename);
6126 : }
6127 :
6128 137 : const int nBands = poSrcDS->GetRasterCount();
6129 137 : if (nBands != 1 && nBands != 2 && nBands != 3 && nBands != 4)
6130 : {
6131 2 : CPLError(CE_Failure, CPLE_NotSupported,
6132 : "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), 3 (RGB) or "
6133 : "4 (RGBA) band dataset supported");
6134 2 : return nullptr;
6135 : }
6136 :
6137 135 : const char *pszUnitType = poSrcDS->GetRasterBand(1)->GetUnitType();
6138 270 : if (CSLFetchNameValue(papszOptions, "UOM") == nullptr && pszUnitType &&
6139 135 : !EQUAL(pszUnitType, ""))
6140 : {
6141 1 : apszUpdatedOptions.SetNameValue("UOM", pszUnitType);
6142 : }
6143 :
6144 135 : if (EQUAL(pszTilingScheme, "CUSTOM"))
6145 : {
6146 111 : if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
6147 : {
6148 0 : CPLError(CE_Failure, CPLE_NotSupported,
6149 : "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
6150 0 : return nullptr;
6151 : }
6152 :
6153 111 : GDALGeoPackageDataset *poDS = nullptr;
6154 : GDALDriver *poThisDriver =
6155 111 : reinterpret_cast<GDALDriver *>(GDALGetDriverByName("GPKG"));
6156 111 : if (poThisDriver != nullptr)
6157 : {
6158 111 : poDS = cpl::down_cast<GDALGeoPackageDataset *>(
6159 : poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
6160 : apszUpdatedOptions, pfnProgress,
6161 111 : pProgressData));
6162 :
6163 205 : if (poDS != nullptr &&
6164 111 : poSrcDS->GetRasterBand(1)->GetRasterDataType() == GDT_Byte &&
6165 : nBands <= 3)
6166 : {
6167 59 : poDS->m_nBandCountFromMetadata = nBands;
6168 59 : poDS->m_bMetadataDirty = true;
6169 : }
6170 : }
6171 111 : if (poDS)
6172 94 : poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
6173 111 : return poDS;
6174 : }
6175 :
6176 48 : const auto poTS = GetTilingScheme(pszTilingScheme);
6177 24 : if (!poTS)
6178 : {
6179 2 : return nullptr;
6180 : }
6181 22 : const int nEPSGCode = poTS->nEPSGCode;
6182 :
6183 44 : OGRSpatialReference oSRS;
6184 22 : if (oSRS.importFromEPSG(nEPSGCode) != OGRERR_NONE)
6185 : {
6186 0 : return nullptr;
6187 : }
6188 22 : char *pszWKT = nullptr;
6189 22 : oSRS.exportToWkt(&pszWKT);
6190 22 : char **papszTO = CSLSetNameValue(nullptr, "DST_SRS", pszWKT);
6191 :
6192 22 : void *hTransformArg = nullptr;
6193 :
6194 : // Hack to compensate for GDALSuggestedWarpOutput2() failure (or not
6195 : // ideal suggestion with PROJ 8) when reprojecting latitude = +/- 90 to
6196 : // EPSG:3857.
6197 : double adfSrcGeoTransform[6];
6198 22 : std::unique_ptr<GDALDataset> poTmpDS;
6199 22 : bool bEPSG3857Adjust = false;
6200 30 : if (nEPSGCode == 3857 &&
6201 8 : poSrcDS->GetGeoTransform(adfSrcGeoTransform) == CE_None &&
6202 38 : adfSrcGeoTransform[2] == 0 && adfSrcGeoTransform[4] == 0 &&
6203 8 : adfSrcGeoTransform[5] < 0)
6204 : {
6205 8 : const auto poSrcSRS = poSrcDS->GetSpatialRef();
6206 8 : if (poSrcSRS && poSrcSRS->IsGeographic())
6207 : {
6208 2 : double maxLat = adfSrcGeoTransform[3];
6209 2 : double minLat = adfSrcGeoTransform[3] +
6210 2 : poSrcDS->GetRasterYSize() * adfSrcGeoTransform[5];
6211 : // Corresponds to the latitude of below MAX_GM
6212 2 : constexpr double MAX_LAT = 85.0511287798066;
6213 2 : bool bModified = false;
6214 2 : if (maxLat > MAX_LAT)
6215 : {
6216 2 : maxLat = MAX_LAT;
6217 2 : bModified = true;
6218 : }
6219 2 : if (minLat < -MAX_LAT)
6220 : {
6221 2 : minLat = -MAX_LAT;
6222 2 : bModified = true;
6223 : }
6224 2 : if (bModified)
6225 : {
6226 4 : CPLStringList aosOptions;
6227 2 : aosOptions.AddString("-of");
6228 2 : aosOptions.AddString("VRT");
6229 2 : aosOptions.AddString("-projwin");
6230 : aosOptions.AddString(
6231 2 : CPLSPrintf("%.18g", adfSrcGeoTransform[0]));
6232 2 : aosOptions.AddString(CPLSPrintf("%.18g", maxLat));
6233 : aosOptions.AddString(
6234 2 : CPLSPrintf("%.18g", adfSrcGeoTransform[0] +
6235 2 : poSrcDS->GetRasterXSize() *
6236 2 : adfSrcGeoTransform[1]));
6237 2 : aosOptions.AddString(CPLSPrintf("%.18g", minLat));
6238 : auto psOptions =
6239 2 : GDALTranslateOptionsNew(aosOptions.List(), nullptr);
6240 2 : poTmpDS.reset(GDALDataset::FromHandle(GDALTranslate(
6241 : "", GDALDataset::ToHandle(poSrcDS), psOptions, nullptr)));
6242 2 : GDALTranslateOptionsFree(psOptions);
6243 2 : if (poTmpDS)
6244 : {
6245 2 : bEPSG3857Adjust = true;
6246 2 : hTransformArg = GDALCreateGenImgProjTransformer2(
6247 2 : GDALDataset::FromHandle(poTmpDS.get()), nullptr,
6248 : papszTO);
6249 : }
6250 : }
6251 : }
6252 : }
6253 22 : if (hTransformArg == nullptr)
6254 : {
6255 : hTransformArg =
6256 20 : GDALCreateGenImgProjTransformer2(poSrcDS, nullptr, papszTO);
6257 : }
6258 :
6259 22 : if (hTransformArg == nullptr)
6260 : {
6261 1 : CPLFree(pszWKT);
6262 1 : CSLDestroy(papszTO);
6263 1 : return nullptr;
6264 : }
6265 :
6266 21 : GDALTransformerInfo *psInfo =
6267 : static_cast<GDALTransformerInfo *>(hTransformArg);
6268 : double adfGeoTransform[6];
6269 : double adfExtent[4];
6270 : int nXSize, nYSize;
6271 :
6272 21 : if (GDALSuggestedWarpOutput2(poSrcDS, psInfo->pfnTransform, hTransformArg,
6273 : adfGeoTransform, &nXSize, &nYSize, adfExtent,
6274 21 : 0) != CE_None)
6275 : {
6276 0 : CPLFree(pszWKT);
6277 0 : CSLDestroy(papszTO);
6278 0 : GDALDestroyGenImgProjTransformer(hTransformArg);
6279 0 : return nullptr;
6280 : }
6281 :
6282 21 : GDALDestroyGenImgProjTransformer(hTransformArg);
6283 21 : hTransformArg = nullptr;
6284 21 : poTmpDS.reset();
6285 :
6286 21 : if (bEPSG3857Adjust)
6287 : {
6288 2 : constexpr double SPHERICAL_RADIUS = 6378137.0;
6289 2 : constexpr double MAX_GM =
6290 : SPHERICAL_RADIUS * M_PI; // 20037508.342789244
6291 2 : double maxNorthing = adfGeoTransform[3];
6292 2 : double minNorthing = adfGeoTransform[3] + adfGeoTransform[5] * nYSize;
6293 2 : bool bChanged = false;
6294 2 : if (maxNorthing > MAX_GM)
6295 : {
6296 2 : bChanged = true;
6297 2 : maxNorthing = MAX_GM;
6298 : }
6299 2 : if (minNorthing < -MAX_GM)
6300 : {
6301 2 : bChanged = true;
6302 2 : minNorthing = -MAX_GM;
6303 : }
6304 2 : if (bChanged)
6305 : {
6306 2 : adfGeoTransform[3] = maxNorthing;
6307 2 : nYSize =
6308 2 : int((maxNorthing - minNorthing) / (-adfGeoTransform[5]) + 0.5);
6309 2 : adfExtent[1] = maxNorthing + nYSize * adfGeoTransform[5];
6310 2 : adfExtent[3] = maxNorthing;
6311 : }
6312 : }
6313 :
6314 21 : double dfComputedRes = adfGeoTransform[1];
6315 21 : double dfPrevRes = 0.0;
6316 21 : double dfRes = 0.0;
6317 21 : int nZoomLevel = 0; // Used after for.
6318 21 : const char *pszZoomLevel = CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
6319 21 : if (pszZoomLevel)
6320 : {
6321 2 : nZoomLevel = atoi(pszZoomLevel);
6322 :
6323 2 : int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
6324 2 : while ((1 << nMaxZoomLevelForThisTM) >
6325 4 : INT_MAX / poTS->nTileXCountZoomLevel0 ||
6326 2 : (1 << nMaxZoomLevelForThisTM) >
6327 2 : INT_MAX / poTS->nTileYCountZoomLevel0)
6328 : {
6329 0 : --nMaxZoomLevelForThisTM;
6330 : }
6331 :
6332 2 : if (nZoomLevel < 0 || nZoomLevel > nMaxZoomLevelForThisTM)
6333 : {
6334 1 : CPLError(CE_Failure, CPLE_AppDefined,
6335 : "ZOOM_LEVEL = %s is invalid. It should be in [0,%d] range",
6336 : pszZoomLevel, nMaxZoomLevelForThisTM);
6337 1 : CPLFree(pszWKT);
6338 1 : CSLDestroy(papszTO);
6339 1 : return nullptr;
6340 : }
6341 : }
6342 : else
6343 : {
6344 171 : for (; nZoomLevel < MAX_ZOOM_LEVEL; nZoomLevel++)
6345 : {
6346 171 : dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
6347 171 : if (dfComputedRes > dfRes ||
6348 152 : fabs(dfComputedRes - dfRes) / dfRes <= 1e-8)
6349 : break;
6350 152 : dfPrevRes = dfRes;
6351 : }
6352 38 : if (nZoomLevel == MAX_ZOOM_LEVEL ||
6353 38 : (1 << nZoomLevel) > INT_MAX / poTS->nTileXCountZoomLevel0 ||
6354 19 : (1 << nZoomLevel) > INT_MAX / poTS->nTileYCountZoomLevel0)
6355 : {
6356 0 : CPLError(CE_Failure, CPLE_AppDefined,
6357 : "Could not find an appropriate zoom level");
6358 0 : CPLFree(pszWKT);
6359 0 : CSLDestroy(papszTO);
6360 0 : return nullptr;
6361 : }
6362 :
6363 19 : if (nZoomLevel > 0 && fabs(dfComputedRes - dfRes) / dfRes > 1e-8)
6364 : {
6365 17 : const char *pszZoomLevelStrategy = CSLFetchNameValueDef(
6366 : papszOptions, "ZOOM_LEVEL_STRATEGY", "AUTO");
6367 17 : if (EQUAL(pszZoomLevelStrategy, "LOWER"))
6368 : {
6369 1 : nZoomLevel--;
6370 : }
6371 16 : else if (EQUAL(pszZoomLevelStrategy, "UPPER"))
6372 : {
6373 : /* do nothing */
6374 : }
6375 : else
6376 : {
6377 15 : if (dfPrevRes / dfComputedRes < dfComputedRes / dfRes)
6378 13 : nZoomLevel--;
6379 : }
6380 : }
6381 : }
6382 :
6383 20 : dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
6384 :
6385 20 : double dfMinX = adfExtent[0];
6386 20 : double dfMinY = adfExtent[1];
6387 20 : double dfMaxX = adfExtent[2];
6388 20 : double dfMaxY = adfExtent[3];
6389 :
6390 20 : nXSize = static_cast<int>(0.5 + (dfMaxX - dfMinX) / dfRes);
6391 20 : nYSize = static_cast<int>(0.5 + (dfMaxY - dfMinY) / dfRes);
6392 20 : adfGeoTransform[1] = dfRes;
6393 20 : adfGeoTransform[5] = -dfRes;
6394 :
6395 20 : const GDALDataType eDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
6396 20 : int nTargetBands = nBands;
6397 : /* For grey level or RGB, if there's reprojection involved, add an alpha */
6398 : /* channel */
6399 37 : if (eDT == GDT_Byte &&
6400 13 : ((nBands == 1 &&
6401 17 : poSrcDS->GetRasterBand(1)->GetColorTable() == nullptr) ||
6402 : nBands == 3))
6403 : {
6404 30 : OGRSpatialReference oSrcSRS;
6405 15 : oSrcSRS.SetFromUserInput(poSrcDS->GetProjectionRef());
6406 15 : oSrcSRS.AutoIdentifyEPSG();
6407 30 : if (oSrcSRS.GetAuthorityCode(nullptr) == nullptr ||
6408 15 : atoi(oSrcSRS.GetAuthorityCode(nullptr)) != nEPSGCode)
6409 : {
6410 13 : nTargetBands++;
6411 : }
6412 : }
6413 :
6414 20 : GDALResampleAlg eResampleAlg = GRA_Bilinear;
6415 20 : const char *pszResampling = CSLFetchNameValue(papszOptions, "RESAMPLING");
6416 20 : if (pszResampling)
6417 : {
6418 6 : for (size_t iAlg = 0;
6419 6 : iAlg < sizeof(asResamplingAlg) / sizeof(asResamplingAlg[0]);
6420 : iAlg++)
6421 : {
6422 6 : if (EQUAL(pszResampling, asResamplingAlg[iAlg].pszName))
6423 : {
6424 3 : eResampleAlg = asResamplingAlg[iAlg].eResampleAlg;
6425 3 : break;
6426 : }
6427 : }
6428 : }
6429 :
6430 16 : if (nBands == 1 && poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr &&
6431 36 : eResampleAlg != GRA_NearestNeighbour && eResampleAlg != GRA_Mode)
6432 : {
6433 0 : CPLError(
6434 : CE_Warning, CPLE_AppDefined,
6435 : "Input dataset has a color table, which will likely lead to "
6436 : "bad results when using a resampling method other than "
6437 : "nearest neighbour or mode. Converting the dataset to 24/32 bit "
6438 : "(e.g. with gdal_translate -expand rgb/rgba) is advised.");
6439 : }
6440 :
6441 20 : GDALGeoPackageDataset *poDS = new GDALGeoPackageDataset();
6442 20 : if (!(poDS->Create(pszFilename, nXSize, nYSize, nTargetBands, eDT,
6443 : apszUpdatedOptions)))
6444 : {
6445 1 : delete poDS;
6446 1 : CPLFree(pszWKT);
6447 1 : CSLDestroy(papszTO);
6448 1 : return nullptr;
6449 : }
6450 :
6451 : // Assign nodata values before the SetGeoTransform call.
6452 : // SetGeoTransform will trigger creation of the overview datasets for each
6453 : // zoom level and at that point the nodata value needs to be known.
6454 19 : int bHasNoData = FALSE;
6455 : double dfNoDataValue =
6456 19 : poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
6457 19 : if (eDT != GDT_Byte && bHasNoData)
6458 : {
6459 3 : poDS->GetRasterBand(1)->SetNoDataValue(dfNoDataValue);
6460 : }
6461 :
6462 19 : poDS->SetGeoTransform(adfGeoTransform);
6463 19 : poDS->SetProjection(pszWKT);
6464 19 : CPLFree(pszWKT);
6465 19 : pszWKT = nullptr;
6466 24 : if (nTargetBands == 1 && nBands == 1 &&
6467 5 : poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr)
6468 : {
6469 2 : poDS->GetRasterBand(1)->SetColorTable(
6470 1 : poSrcDS->GetRasterBand(1)->GetColorTable());
6471 : }
6472 :
6473 19 : hTransformArg = GDALCreateGenImgProjTransformer2(poSrcDS, poDS, papszTO);
6474 19 : CSLDestroy(papszTO);
6475 19 : if (hTransformArg == nullptr)
6476 : {
6477 0 : delete poDS;
6478 0 : return nullptr;
6479 : }
6480 :
6481 19 : poDS->SetMetadata(poSrcDS->GetMetadata());
6482 :
6483 : /* -------------------------------------------------------------------- */
6484 : /* Warp the transformer with a linear approximator */
6485 : /* -------------------------------------------------------------------- */
6486 19 : hTransformArg = GDALCreateApproxTransformer(GDALGenImgProjTransform,
6487 : hTransformArg, 0.125);
6488 19 : GDALApproxTransformerOwnsSubtransformer(hTransformArg, TRUE);
6489 :
6490 : /* -------------------------------------------------------------------- */
6491 : /* Setup warp options. */
6492 : /* -------------------------------------------------------------------- */
6493 19 : GDALWarpOptions *psWO = GDALCreateWarpOptions();
6494 :
6495 19 : psWO->papszWarpOptions = CSLSetNameValue(nullptr, "OPTIMIZE_SIZE", "YES");
6496 19 : psWO->papszWarpOptions =
6497 19 : CSLSetNameValue(psWO->papszWarpOptions, "SAMPLE_GRID", "YES");
6498 19 : if (bHasNoData)
6499 : {
6500 3 : if (dfNoDataValue == 0.0)
6501 : {
6502 : // Do not initialize in the case where nodata != 0, since we
6503 : // want the GeoPackage driver to return empty tiles at the nodata
6504 : // value instead of 0 as GDAL core would
6505 0 : psWO->papszWarpOptions =
6506 0 : CSLSetNameValue(psWO->papszWarpOptions, "INIT_DEST", "0");
6507 : }
6508 :
6509 3 : psWO->padfSrcNoDataReal =
6510 3 : static_cast<double *>(CPLMalloc(sizeof(double)));
6511 3 : psWO->padfSrcNoDataReal[0] = dfNoDataValue;
6512 :
6513 3 : psWO->padfDstNoDataReal =
6514 3 : static_cast<double *>(CPLMalloc(sizeof(double)));
6515 3 : psWO->padfDstNoDataReal[0] = dfNoDataValue;
6516 : }
6517 19 : psWO->eWorkingDataType = eDT;
6518 19 : psWO->eResampleAlg = eResampleAlg;
6519 :
6520 19 : psWO->hSrcDS = poSrcDS;
6521 19 : psWO->hDstDS = poDS;
6522 :
6523 19 : psWO->pfnTransformer = GDALApproxTransform;
6524 19 : psWO->pTransformerArg = hTransformArg;
6525 :
6526 19 : psWO->pfnProgress = pfnProgress;
6527 19 : psWO->pProgressArg = pProgressData;
6528 :
6529 : /* -------------------------------------------------------------------- */
6530 : /* Setup band mapping. */
6531 : /* -------------------------------------------------------------------- */
6532 :
6533 19 : if (nBands == 2 || nBands == 4)
6534 1 : psWO->nBandCount = nBands - 1;
6535 : else
6536 18 : psWO->nBandCount = nBands;
6537 :
6538 19 : psWO->panSrcBands =
6539 19 : static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
6540 19 : psWO->panDstBands =
6541 19 : static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
6542 :
6543 46 : for (int i = 0; i < psWO->nBandCount; i++)
6544 : {
6545 27 : psWO->panSrcBands[i] = i + 1;
6546 27 : psWO->panDstBands[i] = i + 1;
6547 : }
6548 :
6549 19 : if (nBands == 2 || nBands == 4)
6550 : {
6551 1 : psWO->nSrcAlphaBand = nBands;
6552 : }
6553 19 : if (nTargetBands == 2 || nTargetBands == 4)
6554 : {
6555 13 : psWO->nDstAlphaBand = nTargetBands;
6556 : }
6557 :
6558 : /* -------------------------------------------------------------------- */
6559 : /* Initialize and execute the warp. */
6560 : /* -------------------------------------------------------------------- */
6561 19 : GDALWarpOperation oWO;
6562 :
6563 19 : CPLErr eErr = oWO.Initialize(psWO);
6564 19 : if (eErr == CE_None)
6565 : {
6566 : /*if( bMulti )
6567 : eErr = oWO.ChunkAndWarpMulti( 0, 0, nXSize, nYSize );
6568 : else*/
6569 19 : eErr = oWO.ChunkAndWarpImage(0, 0, nXSize, nYSize);
6570 : }
6571 19 : if (eErr != CE_None)
6572 : {
6573 0 : delete poDS;
6574 0 : poDS = nullptr;
6575 : }
6576 :
6577 19 : GDALDestroyTransformer(hTransformArg);
6578 19 : GDALDestroyWarpOptions(psWO);
6579 :
6580 19 : if (poDS)
6581 19 : poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
6582 :
6583 19 : return poDS;
6584 : }
6585 :
6586 : /************************************************************************/
6587 : /* ParseCompressionOptions() */
6588 : /************************************************************************/
6589 :
6590 416 : void GDALGeoPackageDataset::ParseCompressionOptions(char **papszOptions)
6591 : {
6592 416 : const char *pszZLevel = CSLFetchNameValue(papszOptions, "ZLEVEL");
6593 416 : if (pszZLevel)
6594 0 : m_nZLevel = atoi(pszZLevel);
6595 :
6596 416 : const char *pszQuality = CSLFetchNameValue(papszOptions, "QUALITY");
6597 416 : if (pszQuality)
6598 0 : m_nQuality = atoi(pszQuality);
6599 :
6600 416 : const char *pszDither = CSLFetchNameValue(papszOptions, "DITHER");
6601 416 : if (pszDither)
6602 0 : m_bDither = CPLTestBool(pszDither);
6603 416 : }
6604 :
6605 : /************************************************************************/
6606 : /* RegisterWebPExtension() */
6607 : /************************************************************************/
6608 :
6609 11 : bool GDALGeoPackageDataset::RegisterWebPExtension()
6610 : {
6611 11 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
6612 0 : return false;
6613 :
6614 11 : char *pszSQL = sqlite3_mprintf(
6615 : "INSERT INTO gpkg_extensions "
6616 : "(table_name, column_name, extension_name, definition, scope) "
6617 : "VALUES "
6618 : "('%q', 'tile_data', 'gpkg_webp', "
6619 : "'http://www.geopackage.org/spec120/#extension_tiles_webp', "
6620 : "'read-write')",
6621 : m_osRasterTable.c_str());
6622 11 : const OGRErr eErr = SQLCommand(hDB, pszSQL);
6623 11 : sqlite3_free(pszSQL);
6624 :
6625 11 : return OGRERR_NONE == eErr;
6626 : }
6627 :
6628 : /************************************************************************/
6629 : /* RegisterZoomOtherExtension() */
6630 : /************************************************************************/
6631 :
6632 1 : bool GDALGeoPackageDataset::RegisterZoomOtherExtension()
6633 : {
6634 1 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
6635 0 : return false;
6636 :
6637 1 : char *pszSQL = sqlite3_mprintf(
6638 : "INSERT INTO gpkg_extensions "
6639 : "(table_name, column_name, extension_name, definition, scope) "
6640 : "VALUES "
6641 : "('%q', 'tile_data', 'gpkg_zoom_other', "
6642 : "'http://www.geopackage.org/spec120/#extension_zoom_other_intervals', "
6643 : "'read-write')",
6644 : m_osRasterTable.c_str());
6645 1 : const OGRErr eErr = SQLCommand(hDB, pszSQL);
6646 1 : sqlite3_free(pszSQL);
6647 1 : return OGRERR_NONE == eErr;
6648 : }
6649 :
6650 : /************************************************************************/
6651 : /* GetLayer() */
6652 : /************************************************************************/
6653 :
6654 13126 : OGRLayer *GDALGeoPackageDataset::GetLayer(int iLayer)
6655 :
6656 : {
6657 13126 : if (iLayer < 0 || iLayer >= m_nLayers)
6658 6 : return nullptr;
6659 : else
6660 13120 : return m_papoLayers[iLayer];
6661 : }
6662 :
6663 : /************************************************************************/
6664 : /* LaunderName() */
6665 : /************************************************************************/
6666 :
6667 : /** Launder identifiers (table, column names) according to guidance at
6668 : * https://www.geopackage.org/guidance/getting-started.html:
6669 : * "For maximum interoperability, start your database identifiers (table names,
6670 : * column names, etc.) with a lowercase character and only use lowercase
6671 : * characters, numbers 0-9, and underscores (_)."
6672 : */
6673 :
6674 : /* static */
6675 5 : std::string GDALGeoPackageDataset::LaunderName(const std::string &osStr)
6676 : {
6677 5 : char *pszASCII = CPLUTF8ForceToASCII(osStr.c_str(), '_');
6678 10 : const std::string osStrASCII(pszASCII);
6679 5 : CPLFree(pszASCII);
6680 :
6681 10 : std::string osRet;
6682 5 : osRet.reserve(osStrASCII.size());
6683 :
6684 29 : for (size_t i = 0; i < osStrASCII.size(); ++i)
6685 : {
6686 24 : if (osRet.empty())
6687 : {
6688 5 : if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
6689 : {
6690 2 : osRet += (osStrASCII[i] - 'A' + 'a');
6691 : }
6692 3 : else if (osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z')
6693 : {
6694 2 : osRet += osStrASCII[i];
6695 : }
6696 : else
6697 : {
6698 1 : continue;
6699 : }
6700 : }
6701 19 : else if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
6702 : {
6703 11 : osRet += (osStrASCII[i] - 'A' + 'a');
6704 : }
6705 9 : else if ((osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z') ||
6706 14 : (osStrASCII[i] >= '0' && osStrASCII[i] <= '9') ||
6707 5 : osStrASCII[i] == '_')
6708 : {
6709 7 : osRet += osStrASCII[i];
6710 : }
6711 : else
6712 : {
6713 1 : osRet += '_';
6714 : }
6715 : }
6716 :
6717 5 : if (osRet.empty() && !osStrASCII.empty())
6718 2 : return LaunderName(std::string("x").append(osStrASCII));
6719 :
6720 4 : if (osRet != osStr)
6721 : {
6722 3 : CPLDebug("PG", "LaunderName('%s') -> '%s'", osStr.c_str(),
6723 : osRet.c_str());
6724 : }
6725 :
6726 4 : return osRet;
6727 : }
6728 :
6729 : /************************************************************************/
6730 : /* ICreateLayer() */
6731 : /************************************************************************/
6732 :
6733 : OGRLayer *
6734 614 : GDALGeoPackageDataset::ICreateLayer(const char *pszLayerName,
6735 : const OGRGeomFieldDefn *poSrcGeomFieldDefn,
6736 : CSLConstList papszOptions)
6737 : {
6738 : /* -------------------------------------------------------------------- */
6739 : /* Verify we are in update mode. */
6740 : /* -------------------------------------------------------------------- */
6741 614 : if (!GetUpdate())
6742 : {
6743 0 : CPLError(CE_Failure, CPLE_NoWriteAccess,
6744 : "Data source %s opened read-only.\n"
6745 : "New layer %s cannot be created.\n",
6746 : m_pszFilename, pszLayerName);
6747 :
6748 0 : return nullptr;
6749 : }
6750 :
6751 : const bool bLaunder =
6752 614 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "LAUNDER", "NO"));
6753 : const std::string osTableName(bLaunder ? LaunderName(pszLayerName)
6754 1842 : : std::string(pszLayerName));
6755 :
6756 : const auto eGType =
6757 614 : poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone;
6758 : const auto poSpatialRef =
6759 614 : poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr;
6760 :
6761 614 : if (!m_bHasGPKGGeometryColumns)
6762 : {
6763 1 : if (SQLCommand(hDB, pszCREATE_GPKG_GEOMETRY_COLUMNS) != OGRERR_NONE)
6764 : {
6765 0 : return nullptr;
6766 : }
6767 1 : m_bHasGPKGGeometryColumns = true;
6768 : }
6769 :
6770 : // Check identifier unicity
6771 614 : const char *pszIdentifier = CSLFetchNameValue(papszOptions, "IDENTIFIER");
6772 614 : if (pszIdentifier != nullptr && pszIdentifier[0] == '\0')
6773 0 : pszIdentifier = nullptr;
6774 614 : if (pszIdentifier != nullptr)
6775 : {
6776 13 : for (int i = 0; i < m_nLayers; ++i)
6777 : {
6778 : const char *pszOtherIdentifier =
6779 9 : m_papoLayers[i]->GetMetadataItem("IDENTIFIER");
6780 9 : if (pszOtherIdentifier == nullptr)
6781 6 : pszOtherIdentifier = m_papoLayers[i]->GetName();
6782 18 : if (pszOtherIdentifier != nullptr &&
6783 12 : EQUAL(pszOtherIdentifier, pszIdentifier) &&
6784 3 : !EQUAL(m_papoLayers[i]->GetName(), osTableName.c_str()))
6785 : {
6786 2 : CPLError(CE_Failure, CPLE_AppDefined,
6787 : "Identifier %s is already used by table %s",
6788 2 : pszIdentifier, m_papoLayers[i]->GetName());
6789 3 : return nullptr;
6790 : }
6791 : }
6792 :
6793 : // In case there would be table in gpkg_contents not listed as a
6794 : // vector layer
6795 4 : char *pszSQL = sqlite3_mprintf(
6796 : "SELECT table_name FROM gpkg_contents WHERE identifier = '%q' "
6797 : "LIMIT 2",
6798 : pszIdentifier);
6799 4 : auto oResult = SQLQuery(hDB, pszSQL);
6800 4 : sqlite3_free(pszSQL);
6801 8 : if (oResult && oResult->RowCount() > 0 &&
6802 9 : oResult->GetValue(0, 0) != nullptr &&
6803 1 : !EQUAL(oResult->GetValue(0, 0), osTableName.c_str()))
6804 : {
6805 1 : CPLError(CE_Failure, CPLE_AppDefined,
6806 : "Identifier %s is already used by table %s", pszIdentifier,
6807 : oResult->GetValue(0, 0));
6808 1 : return nullptr;
6809 : }
6810 : }
6811 :
6812 : /* Read GEOMETRY_NAME option */
6813 : const char *pszGeomColumnName =
6814 611 : CSLFetchNameValue(papszOptions, "GEOMETRY_NAME");
6815 611 : if (pszGeomColumnName == nullptr) /* deprecated name */
6816 577 : pszGeomColumnName = CSLFetchNameValue(papszOptions, "GEOMETRY_COLUMN");
6817 611 : if (pszGeomColumnName == nullptr && poSrcGeomFieldDefn)
6818 : {
6819 537 : pszGeomColumnName = poSrcGeomFieldDefn->GetNameRef();
6820 537 : if (pszGeomColumnName && pszGeomColumnName[0] == 0)
6821 534 : pszGeomColumnName = nullptr;
6822 : }
6823 611 : if (pszGeomColumnName == nullptr)
6824 574 : pszGeomColumnName = "geom";
6825 : const bool bGeomNullable =
6826 611 : CPLFetchBool(papszOptions, "GEOMETRY_NULLABLE", true);
6827 :
6828 : /* Read FID option */
6829 611 : const char *pszFIDColumnName = CSLFetchNameValue(papszOptions, "FID");
6830 611 : if (pszFIDColumnName == nullptr)
6831 589 : pszFIDColumnName = "fid";
6832 :
6833 611 : if (CPLTestBool(CPLGetConfigOption("GPKG_NAME_CHECK", "YES")))
6834 : {
6835 611 : if (strspn(pszFIDColumnName, "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") > 0)
6836 : {
6837 0 : CPLError(CE_Failure, CPLE_AppDefined,
6838 : "The primary key (%s) name may not contain special "
6839 : "characters or spaces",
6840 : pszFIDColumnName);
6841 0 : return nullptr;
6842 : }
6843 :
6844 : /* Avoiding gpkg prefixes is not an official requirement, but seems wise
6845 : */
6846 611 : if (STARTS_WITH(osTableName.c_str(), "gpkg"))
6847 : {
6848 0 : CPLError(CE_Failure, CPLE_AppDefined,
6849 : "The layer name may not begin with 'gpkg' as it is a "
6850 : "reserved geopackage prefix");
6851 0 : return nullptr;
6852 : }
6853 :
6854 : /* Preemptively try and avoid sqlite3 syntax errors due to */
6855 : /* illegal characters. */
6856 611 : if (strspn(osTableName.c_str(), "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") >
6857 : 0)
6858 : {
6859 0 : CPLError(
6860 : CE_Failure, CPLE_AppDefined,
6861 : "The layer name may not contain special characters or spaces");
6862 0 : return nullptr;
6863 : }
6864 : }
6865 :
6866 : /* Check for any existing layers that already use this name */
6867 799 : for (int iLayer = 0; iLayer < m_nLayers; iLayer++)
6868 : {
6869 189 : if (EQUAL(osTableName.c_str(), m_papoLayers[iLayer]->GetName()))
6870 : {
6871 : const char *pszOverwrite =
6872 2 : CSLFetchNameValue(papszOptions, "OVERWRITE");
6873 2 : if (pszOverwrite != nullptr && CPLTestBool(pszOverwrite))
6874 : {
6875 1 : DeleteLayer(iLayer);
6876 : }
6877 : else
6878 : {
6879 1 : CPLError(CE_Failure, CPLE_AppDefined,
6880 : "Layer %s already exists, CreateLayer failed.\n"
6881 : "Use the layer creation option OVERWRITE=YES to "
6882 : "replace it.",
6883 : osTableName.c_str());
6884 1 : return nullptr;
6885 : }
6886 : }
6887 : }
6888 :
6889 610 : if (m_nLayers == 1)
6890 : {
6891 : // Async RTree building doesn't play well with multiple layer:
6892 : // SQLite3 locks being hold for a long time, random failed commits,
6893 : // etc.
6894 64 : m_papoLayers[0]->FinishOrDisableThreadedRTree();
6895 : }
6896 :
6897 : /* Create a blank layer. */
6898 : auto poLayer = std::unique_ptr<OGRGeoPackageTableLayer>(
6899 1220 : new OGRGeoPackageTableLayer(this, osTableName.c_str()));
6900 :
6901 610 : OGRSpatialReference *poSRS = nullptr;
6902 610 : if (poSpatialRef)
6903 : {
6904 178 : poSRS = poSpatialRef->Clone();
6905 178 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
6906 : }
6907 1221 : poLayer->SetCreationParameters(
6908 : eGType,
6909 611 : bLaunder ? LaunderName(pszGeomColumnName).c_str() : pszGeomColumnName,
6910 : bGeomNullable, poSRS, CSLFetchNameValue(papszOptions, "SRID"),
6911 1220 : poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetCoordinatePrecision()
6912 : : OGRGeomCoordinatePrecision(),
6913 610 : CPLTestBool(
6914 : CSLFetchNameValueDef(papszOptions, "DISCARD_COORD_LSB", "NO")),
6915 610 : CPLTestBool(CSLFetchNameValueDef(
6916 : papszOptions, "UNDO_DISCARD_COORD_LSB_ON_READING", "NO")),
6917 611 : bLaunder ? LaunderName(pszFIDColumnName).c_str() : pszFIDColumnName,
6918 : pszIdentifier, CSLFetchNameValue(papszOptions, "DESCRIPTION"));
6919 610 : if (poSRS)
6920 : {
6921 178 : poSRS->Release();
6922 : }
6923 :
6924 610 : poLayer->SetLaunder(bLaunder);
6925 :
6926 : /* Should we create a spatial index ? */
6927 610 : const char *pszSI = CSLFetchNameValue(papszOptions, "SPATIAL_INDEX");
6928 610 : int bCreateSpatialIndex = (pszSI == nullptr || CPLTestBool(pszSI));
6929 610 : if (eGType != wkbNone && bCreateSpatialIndex)
6930 : {
6931 545 : poLayer->SetDeferredSpatialIndexCreation(true);
6932 : }
6933 :
6934 610 : poLayer->SetPrecisionFlag(CPLFetchBool(papszOptions, "PRECISION", true));
6935 610 : poLayer->SetTruncateFieldsFlag(
6936 610 : CPLFetchBool(papszOptions, "TRUNCATE_FIELDS", false));
6937 610 : if (eGType == wkbNone)
6938 : {
6939 43 : const char *pszASpatialVariant = CSLFetchNameValueDef(
6940 : papszOptions, "ASPATIAL_VARIANT",
6941 43 : m_bNonSpatialTablesNonRegisteredInGpkgContentsFound
6942 : ? "NOT_REGISTERED"
6943 : : "GPKG_ATTRIBUTES");
6944 43 : GPKGASpatialVariant eASpatialVariant = GPKG_ATTRIBUTES;
6945 43 : if (EQUAL(pszASpatialVariant, "GPKG_ATTRIBUTES"))
6946 31 : eASpatialVariant = GPKG_ATTRIBUTES;
6947 12 : else if (EQUAL(pszASpatialVariant, "OGR_ASPATIAL"))
6948 : {
6949 0 : CPLError(CE_Failure, CPLE_NotSupported,
6950 : "ASPATIAL_VARIANT=OGR_ASPATIAL is no longer supported");
6951 0 : return nullptr;
6952 : }
6953 12 : else if (EQUAL(pszASpatialVariant, "NOT_REGISTERED"))
6954 12 : eASpatialVariant = NOT_REGISTERED;
6955 : else
6956 : {
6957 0 : CPLError(CE_Failure, CPLE_NotSupported,
6958 : "Unsupported value for ASPATIAL_VARIANT: %s",
6959 : pszASpatialVariant);
6960 0 : return nullptr;
6961 : }
6962 43 : poLayer->SetASpatialVariant(eASpatialVariant);
6963 : }
6964 :
6965 : const char *pszDateTimePrecision =
6966 610 : CSLFetchNameValueDef(papszOptions, "DATETIME_PRECISION", "AUTO");
6967 610 : if (EQUAL(pszDateTimePrecision, "MILLISECOND"))
6968 : {
6969 2 : poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
6970 : }
6971 608 : else if (EQUAL(pszDateTimePrecision, "SECOND"))
6972 : {
6973 1 : if (m_nUserVersion < GPKG_1_4_VERSION)
6974 0 : CPLError(
6975 : CE_Warning, CPLE_AppDefined,
6976 : "DATETIME_PRECISION=SECOND is only valid since GeoPackage 1.4");
6977 1 : poLayer->SetDateTimePrecision(OGRISO8601Precision::SECOND);
6978 : }
6979 607 : else if (EQUAL(pszDateTimePrecision, "MINUTE"))
6980 : {
6981 1 : if (m_nUserVersion < GPKG_1_4_VERSION)
6982 0 : CPLError(
6983 : CE_Warning, CPLE_AppDefined,
6984 : "DATETIME_PRECISION=MINUTE is only valid since GeoPackage 1.4");
6985 1 : poLayer->SetDateTimePrecision(OGRISO8601Precision::MINUTE);
6986 : }
6987 606 : else if (EQUAL(pszDateTimePrecision, "AUTO"))
6988 : {
6989 605 : if (m_nUserVersion < GPKG_1_4_VERSION)
6990 594 : poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
6991 : }
6992 : else
6993 : {
6994 1 : CPLError(CE_Failure, CPLE_NotSupported,
6995 : "Unsupported value for DATETIME_PRECISION: %s",
6996 : pszDateTimePrecision);
6997 1 : return nullptr;
6998 : }
6999 :
7000 : // If there was an ogr_empty_table table, we can remove it
7001 : // But do it at dataset closing, otherwise locking performance issues
7002 : // can arise (probably when transactions are used).
7003 609 : m_bRemoveOGREmptyTable = true;
7004 :
7005 1218 : m_papoLayers = static_cast<OGRGeoPackageTableLayer **>(CPLRealloc(
7006 609 : m_papoLayers, sizeof(OGRGeoPackageTableLayer *) * (m_nLayers + 1)));
7007 609 : auto poRet = poLayer.release();
7008 609 : m_papoLayers[m_nLayers] = poRet;
7009 609 : m_nLayers++;
7010 609 : return poRet;
7011 : }
7012 :
7013 : /************************************************************************/
7014 : /* FindLayerIndex() */
7015 : /************************************************************************/
7016 :
7017 20 : int GDALGeoPackageDataset::FindLayerIndex(const char *pszLayerName)
7018 :
7019 : {
7020 34 : for (int iLayer = 0; iLayer < m_nLayers; iLayer++)
7021 : {
7022 23 : if (EQUAL(pszLayerName, m_papoLayers[iLayer]->GetName()))
7023 9 : return iLayer;
7024 : }
7025 11 : return -1;
7026 : }
7027 :
7028 : /************************************************************************/
7029 : /* DeleteLayerCommon() */
7030 : /************************************************************************/
7031 :
7032 36 : OGRErr GDALGeoPackageDataset::DeleteLayerCommon(const char *pszLayerName)
7033 : {
7034 : // Temporary remove foreign key checks
7035 : const GPKGTemporaryForeignKeyCheckDisabler
7036 36 : oGPKGTemporaryForeignKeyCheckDisabler(this);
7037 :
7038 36 : char *pszSQL = sqlite3_mprintf(
7039 : "DELETE FROM gpkg_contents WHERE lower(table_name) = lower('%q')",
7040 : pszLayerName);
7041 36 : OGRErr eErr = SQLCommand(hDB, pszSQL);
7042 36 : sqlite3_free(pszSQL);
7043 :
7044 36 : if (eErr == OGRERR_NONE && HasExtensionsTable())
7045 : {
7046 34 : pszSQL = sqlite3_mprintf(
7047 : "DELETE FROM gpkg_extensions WHERE lower(table_name) = lower('%q')",
7048 : pszLayerName);
7049 34 : eErr = SQLCommand(hDB, pszSQL);
7050 34 : sqlite3_free(pszSQL);
7051 : }
7052 :
7053 36 : if (eErr == OGRERR_NONE && HasMetadataTables())
7054 : {
7055 : // Delete from gpkg_metadata metadata records that are only referenced
7056 : // by the table we are about to drop
7057 8 : pszSQL = sqlite3_mprintf(
7058 : "DELETE FROM gpkg_metadata WHERE id IN ("
7059 : "SELECT DISTINCT md_file_id FROM "
7060 : "gpkg_metadata_reference WHERE "
7061 : "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
7062 : "AND id NOT IN ("
7063 : "SELECT DISTINCT md_file_id FROM gpkg_metadata_reference WHERE "
7064 : "md_file_id IN (SELECT DISTINCT md_file_id FROM "
7065 : "gpkg_metadata_reference WHERE "
7066 : "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
7067 : "AND lower(table_name) <> lower('%q'))",
7068 : pszLayerName, pszLayerName, pszLayerName);
7069 8 : eErr = SQLCommand(hDB, pszSQL);
7070 8 : sqlite3_free(pszSQL);
7071 :
7072 8 : if (eErr == OGRERR_NONE)
7073 : {
7074 : pszSQL =
7075 8 : sqlite3_mprintf("DELETE FROM gpkg_metadata_reference WHERE "
7076 : "lower(table_name) = lower('%q')",
7077 : pszLayerName);
7078 8 : eErr = SQLCommand(hDB, pszSQL);
7079 8 : sqlite3_free(pszSQL);
7080 : }
7081 : }
7082 :
7083 36 : if (eErr == OGRERR_NONE && HasGpkgextRelationsTable())
7084 : {
7085 : // Remove reference to potential corresponding mapping table in
7086 : // gpkg_extensions
7087 4 : pszSQL = sqlite3_mprintf(
7088 : "DELETE FROM gpkg_extensions WHERE "
7089 : "extension_name IN ('related_tables', "
7090 : "'gpkg_related_tables') AND lower(table_name) = "
7091 : "(SELECT lower(mapping_table_name) FROM gpkgext_relations WHERE "
7092 : "lower(base_table_name) = lower('%q') OR "
7093 : "lower(related_table_name) = lower('%q') OR "
7094 : "lower(mapping_table_name) = lower('%q'))",
7095 : pszLayerName, pszLayerName, pszLayerName);
7096 4 : eErr = SQLCommand(hDB, pszSQL);
7097 4 : sqlite3_free(pszSQL);
7098 :
7099 4 : if (eErr == OGRERR_NONE)
7100 : {
7101 : // Remove reference to potential corresponding mapping table in
7102 : // gpkgext_relations
7103 : pszSQL =
7104 4 : sqlite3_mprintf("DELETE FROM gpkgext_relations WHERE "
7105 : "lower(base_table_name) = lower('%q') OR "
7106 : "lower(related_table_name) = lower('%q') OR "
7107 : "lower(mapping_table_name) = lower('%q')",
7108 : pszLayerName, pszLayerName, pszLayerName);
7109 4 : eErr = SQLCommand(hDB, pszSQL);
7110 4 : sqlite3_free(pszSQL);
7111 : }
7112 :
7113 4 : if (eErr == OGRERR_NONE && HasExtensionsTable())
7114 : {
7115 : // If there is no longer any mapping table, then completely
7116 : // remove any reference to the extension in gpkg_extensions
7117 : // as mandated per the related table specification.
7118 : OGRErr err;
7119 4 : if (SQLGetInteger(hDB,
7120 : "SELECT COUNT(*) FROM gpkg_extensions WHERE "
7121 : "extension_name IN ('related_tables', "
7122 : "'gpkg_related_tables') AND "
7123 : "lower(table_name) != 'gpkgext_relations'",
7124 4 : &err) == 0)
7125 : {
7126 2 : eErr = SQLCommand(hDB, "DELETE FROM gpkg_extensions WHERE "
7127 : "extension_name IN ('related_tables', "
7128 : "'gpkg_related_tables')");
7129 : }
7130 :
7131 4 : ClearCachedRelationships();
7132 : }
7133 : }
7134 :
7135 36 : if (eErr == OGRERR_NONE)
7136 : {
7137 36 : pszSQL = sqlite3_mprintf("DROP TABLE \"%w\"", pszLayerName);
7138 36 : eErr = SQLCommand(hDB, pszSQL);
7139 36 : sqlite3_free(pszSQL);
7140 : }
7141 :
7142 : // Check foreign key integrity
7143 36 : if (eErr == OGRERR_NONE)
7144 : {
7145 36 : eErr = PragmaCheck("foreign_key_check", "", 0);
7146 : }
7147 :
7148 72 : return eErr;
7149 : }
7150 :
7151 : /************************************************************************/
7152 : /* DeleteLayer() */
7153 : /************************************************************************/
7154 :
7155 33 : OGRErr GDALGeoPackageDataset::DeleteLayer(int iLayer)
7156 : {
7157 33 : if (!GetUpdate() || iLayer < 0 || iLayer >= m_nLayers)
7158 2 : return OGRERR_FAILURE;
7159 :
7160 31 : m_papoLayers[iLayer]->ResetReading();
7161 31 : m_papoLayers[iLayer]->SyncToDisk();
7162 :
7163 62 : CPLString osLayerName = m_papoLayers[iLayer]->GetName();
7164 :
7165 31 : CPLDebug("GPKG", "DeleteLayer(%s)", osLayerName.c_str());
7166 :
7167 : // Temporary remove foreign key checks
7168 : const GPKGTemporaryForeignKeyCheckDisabler
7169 31 : oGPKGTemporaryForeignKeyCheckDisabler(this);
7170 :
7171 31 : OGRErr eErr = SoftStartTransaction();
7172 :
7173 31 : if (eErr == OGRERR_NONE)
7174 : {
7175 31 : if (m_papoLayers[iLayer]->HasSpatialIndex())
7176 28 : m_papoLayers[iLayer]->DropSpatialIndex();
7177 :
7178 : char *pszSQL =
7179 31 : sqlite3_mprintf("DELETE FROM gpkg_geometry_columns WHERE "
7180 : "lower(table_name) = lower('%q')",
7181 : osLayerName.c_str());
7182 31 : eErr = SQLCommand(hDB, pszSQL);
7183 31 : sqlite3_free(pszSQL);
7184 : }
7185 :
7186 31 : if (eErr == OGRERR_NONE && HasDataColumnsTable())
7187 : {
7188 1 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_data_columns WHERE "
7189 : "lower(table_name) = lower('%q')",
7190 : osLayerName.c_str());
7191 1 : eErr = SQLCommand(hDB, pszSQL);
7192 1 : sqlite3_free(pszSQL);
7193 : }
7194 :
7195 : #ifdef ENABLE_GPKG_OGR_CONTENTS
7196 31 : if (eErr == OGRERR_NONE && m_bHasGPKGOGRContents)
7197 : {
7198 31 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_ogr_contents WHERE "
7199 : "lower(table_name) = lower('%q')",
7200 : osLayerName.c_str());
7201 31 : eErr = SQLCommand(hDB, pszSQL);
7202 31 : sqlite3_free(pszSQL);
7203 : }
7204 : #endif
7205 :
7206 31 : if (eErr == OGRERR_NONE)
7207 : {
7208 31 : eErr = DeleteLayerCommon(osLayerName.c_str());
7209 : }
7210 :
7211 31 : if (eErr == OGRERR_NONE)
7212 : {
7213 31 : eErr = SoftCommitTransaction();
7214 31 : if (eErr == OGRERR_NONE)
7215 : {
7216 : /* Delete the layer object and remove the gap in the layers list */
7217 31 : delete m_papoLayers[iLayer];
7218 31 : memmove(m_papoLayers + iLayer, m_papoLayers + iLayer + 1,
7219 31 : sizeof(void *) * (m_nLayers - iLayer - 1));
7220 31 : m_nLayers--;
7221 : }
7222 : }
7223 : else
7224 : {
7225 0 : SoftRollbackTransaction();
7226 : }
7227 :
7228 31 : return eErr;
7229 : }
7230 :
7231 : /************************************************************************/
7232 : /* DeleteRasterLayer() */
7233 : /************************************************************************/
7234 :
7235 2 : OGRErr GDALGeoPackageDataset::DeleteRasterLayer(const char *pszLayerName)
7236 : {
7237 : // Temporary remove foreign key checks
7238 : const GPKGTemporaryForeignKeyCheckDisabler
7239 2 : oGPKGTemporaryForeignKeyCheckDisabler(this);
7240 :
7241 2 : OGRErr eErr = SoftStartTransaction();
7242 :
7243 2 : if (eErr == OGRERR_NONE)
7244 : {
7245 2 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix WHERE "
7246 : "lower(table_name) = lower('%q')",
7247 : pszLayerName);
7248 2 : eErr = SQLCommand(hDB, pszSQL);
7249 2 : sqlite3_free(pszSQL);
7250 : }
7251 :
7252 2 : if (eErr == OGRERR_NONE)
7253 : {
7254 2 : char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix_set WHERE "
7255 : "lower(table_name) = lower('%q')",
7256 : pszLayerName);
7257 2 : eErr = SQLCommand(hDB, pszSQL);
7258 2 : sqlite3_free(pszSQL);
7259 : }
7260 :
7261 2 : if (eErr == OGRERR_NONE && HasGriddedCoverageAncillaryTable())
7262 : {
7263 : char *pszSQL =
7264 1 : sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_coverage_ancillary "
7265 : "WHERE lower(tile_matrix_set_name) = lower('%q')",
7266 : pszLayerName);
7267 1 : eErr = SQLCommand(hDB, pszSQL);
7268 1 : sqlite3_free(pszSQL);
7269 :
7270 1 : if (eErr == OGRERR_NONE)
7271 : {
7272 : pszSQL =
7273 1 : sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_tile_ancillary "
7274 : "WHERE lower(tpudt_name) = lower('%q')",
7275 : pszLayerName);
7276 1 : eErr = SQLCommand(hDB, pszSQL);
7277 1 : sqlite3_free(pszSQL);
7278 : }
7279 : }
7280 :
7281 2 : if (eErr == OGRERR_NONE)
7282 : {
7283 2 : eErr = DeleteLayerCommon(pszLayerName);
7284 : }
7285 :
7286 2 : if (eErr == OGRERR_NONE)
7287 : {
7288 2 : eErr = SoftCommitTransaction();
7289 : }
7290 : else
7291 : {
7292 0 : SoftRollbackTransaction();
7293 : }
7294 :
7295 4 : return eErr;
7296 : }
7297 :
7298 : /************************************************************************/
7299 : /* DeleteVectorOrRasterLayer() */
7300 : /************************************************************************/
7301 :
7302 13 : bool GDALGeoPackageDataset::DeleteVectorOrRasterLayer(const char *pszLayerName)
7303 : {
7304 :
7305 13 : int idx = FindLayerIndex(pszLayerName);
7306 13 : if (idx >= 0)
7307 : {
7308 5 : DeleteLayer(idx);
7309 5 : return true;
7310 : }
7311 :
7312 : char *pszSQL =
7313 8 : sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
7314 : "lower(table_name) = lower('%q') "
7315 : "AND data_type IN ('tiles', '2d-gridded-coverage')",
7316 : pszLayerName);
7317 8 : bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
7318 8 : sqlite3_free(pszSQL);
7319 8 : if (bIsRasterTable)
7320 : {
7321 2 : DeleteRasterLayer(pszLayerName);
7322 2 : return true;
7323 : }
7324 6 : return false;
7325 : }
7326 :
7327 : /************************************************************************/
7328 : /* TestCapability() */
7329 : /************************************************************************/
7330 :
7331 303 : int GDALGeoPackageDataset::TestCapability(const char *pszCap)
7332 : {
7333 303 : if (EQUAL(pszCap, ODsCCreateLayer) || EQUAL(pszCap, ODsCDeleteLayer) ||
7334 181 : EQUAL(pszCap, "RenameLayer"))
7335 : {
7336 122 : return GetUpdate();
7337 : }
7338 181 : else if (EQUAL(pszCap, ODsCCurveGeometries))
7339 12 : return TRUE;
7340 169 : else if (EQUAL(pszCap, ODsCMeasuredGeometries))
7341 8 : return TRUE;
7342 161 : else if (EQUAL(pszCap, ODsCZGeometries))
7343 8 : return TRUE;
7344 153 : else if (EQUAL(pszCap, ODsCRandomLayerWrite) ||
7345 153 : EQUAL(pszCap, GDsCAddRelationship) ||
7346 153 : EQUAL(pszCap, GDsCDeleteRelationship) ||
7347 153 : EQUAL(pszCap, GDsCUpdateRelationship) ||
7348 153 : EQUAL(pszCap, ODsCAddFieldDomain))
7349 1 : return GetUpdate();
7350 :
7351 152 : return OGRSQLiteBaseDataSource::TestCapability(pszCap);
7352 : }
7353 :
7354 : /************************************************************************/
7355 : /* ResetReadingAllLayers() */
7356 : /************************************************************************/
7357 :
7358 44 : void GDALGeoPackageDataset::ResetReadingAllLayers()
7359 : {
7360 95 : for (int i = 0; i < m_nLayers; i++)
7361 : {
7362 51 : m_papoLayers[i]->ResetReading();
7363 : }
7364 44 : }
7365 :
7366 : /************************************************************************/
7367 : /* ExecuteSQL() */
7368 : /************************************************************************/
7369 :
7370 : static const char *const apszFuncsWithSideEffects[] = {
7371 : "CreateSpatialIndex",
7372 : "DisableSpatialIndex",
7373 : "HasSpatialIndex",
7374 : "RegisterGeometryExtension",
7375 : };
7376 :
7377 5310 : OGRLayer *GDALGeoPackageDataset::ExecuteSQL(const char *pszSQLCommand,
7378 : OGRGeometry *poSpatialFilter,
7379 : const char *pszDialect)
7380 :
7381 : {
7382 5310 : m_bHasReadMetadataFromStorage = false;
7383 :
7384 5310 : FlushMetadata();
7385 :
7386 5328 : while (*pszSQLCommand != '\0' &&
7387 5328 : isspace(static_cast<unsigned char>(*pszSQLCommand)))
7388 18 : pszSQLCommand++;
7389 :
7390 10620 : CPLString osSQLCommand(pszSQLCommand);
7391 5310 : if (!osSQLCommand.empty() && osSQLCommand.back() == ';')
7392 47 : osSQLCommand.resize(osSQLCommand.size() - 1);
7393 :
7394 5310 : if (pszDialect == nullptr || !EQUAL(pszDialect, "DEBUG"))
7395 : {
7396 : // Some SQL commands will influence the feature count behind our
7397 : // back, so disable it in that case.
7398 : #ifdef ENABLE_GPKG_OGR_CONTENTS
7399 : const bool bInsertOrDelete =
7400 5241 : osSQLCommand.ifind("insert into ") != std::string::npos ||
7401 2131 : osSQLCommand.ifind("insert or replace into ") !=
7402 7372 : std::string::npos ||
7403 2085 : osSQLCommand.ifind("delete from ") != std::string::npos;
7404 : const bool bRollback =
7405 5241 : osSQLCommand.ifind("rollback ") != std::string::npos;
7406 : #endif
7407 :
7408 6748 : for (int i = 0; i < m_nLayers; i++)
7409 : {
7410 1507 : if (m_papoLayers[i]->SyncToDisk() != OGRERR_NONE)
7411 0 : return nullptr;
7412 : #ifdef ENABLE_GPKG_OGR_CONTENTS
7413 1700 : if (bRollback || (bInsertOrDelete &&
7414 193 : osSQLCommand.ifind(m_papoLayers[i]->GetName()) !=
7415 : std::string::npos))
7416 : {
7417 148 : m_papoLayers[i]->DisableFeatureCount();
7418 : }
7419 : #endif
7420 : }
7421 : }
7422 :
7423 5310 : if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 0") ||
7424 5309 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=0") ||
7425 5309 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =0") ||
7426 5309 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 0"))
7427 : {
7428 1 : OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, false);
7429 : }
7430 5309 : else if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 1") ||
7431 5308 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=1") ||
7432 5308 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =1") ||
7433 5308 : EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 1"))
7434 : {
7435 1 : OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, true);
7436 : }
7437 :
7438 : /* -------------------------------------------------------------------- */
7439 : /* DEBUG "SELECT nolock" command. */
7440 : /* -------------------------------------------------------------------- */
7441 5379 : if (pszDialect != nullptr && EQUAL(pszDialect, "DEBUG") &&
7442 69 : EQUAL(osSQLCommand, "SELECT nolock"))
7443 : {
7444 3 : return new OGRSQLiteSingleFeatureLayer(osSQLCommand, m_bNoLock ? 1 : 0);
7445 : }
7446 :
7447 : /* -------------------------------------------------------------------- */
7448 : /* Special case DELLAYER: command. */
7449 : /* -------------------------------------------------------------------- */
7450 5307 : if (STARTS_WITH_CI(osSQLCommand, "DELLAYER:"))
7451 : {
7452 4 : const char *pszLayerName = osSQLCommand.c_str() + strlen("DELLAYER:");
7453 :
7454 4 : while (*pszLayerName == ' ')
7455 0 : pszLayerName++;
7456 :
7457 4 : if (!DeleteVectorOrRasterLayer(pszLayerName))
7458 : {
7459 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
7460 : pszLayerName);
7461 : }
7462 4 : return nullptr;
7463 : }
7464 :
7465 : /* -------------------------------------------------------------------- */
7466 : /* Special case RECOMPUTE EXTENT ON command. */
7467 : /* -------------------------------------------------------------------- */
7468 5303 : if (STARTS_WITH_CI(osSQLCommand, "RECOMPUTE EXTENT ON "))
7469 : {
7470 : const char *pszLayerName =
7471 4 : osSQLCommand.c_str() + strlen("RECOMPUTE EXTENT ON ");
7472 :
7473 4 : while (*pszLayerName == ' ')
7474 0 : pszLayerName++;
7475 :
7476 4 : int idx = FindLayerIndex(pszLayerName);
7477 4 : if (idx >= 0)
7478 : {
7479 4 : m_papoLayers[idx]->RecomputeExtent();
7480 : }
7481 : else
7482 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
7483 : pszLayerName);
7484 4 : return nullptr;
7485 : }
7486 :
7487 : /* -------------------------------------------------------------------- */
7488 : /* Intercept DROP TABLE */
7489 : /* -------------------------------------------------------------------- */
7490 5299 : if (STARTS_WITH_CI(osSQLCommand, "DROP TABLE "))
7491 : {
7492 9 : const char *pszLayerName = osSQLCommand.c_str() + strlen("DROP TABLE ");
7493 :
7494 9 : while (*pszLayerName == ' ')
7495 0 : pszLayerName++;
7496 :
7497 9 : if (DeleteVectorOrRasterLayer(SQLUnescape(pszLayerName)))
7498 4 : return nullptr;
7499 : }
7500 :
7501 : /* -------------------------------------------------------------------- */
7502 : /* Intercept ALTER TABLE src_table RENAME TO dst_table */
7503 : /* and ALTER TABLE table RENAME COLUMN src_name TO dst_name */
7504 : /* and ALTER TABLE table DROP COLUMN col_name */
7505 : /* */
7506 : /* We do this because SQLite mechanisms can't deal with updating */
7507 : /* literal values in gpkg_ tables that refer to table and column */
7508 : /* names. */
7509 : /* -------------------------------------------------------------------- */
7510 5295 : if (STARTS_WITH_CI(osSQLCommand, "ALTER TABLE "))
7511 : {
7512 6 : char **papszTokens = SQLTokenize(osSQLCommand);
7513 : /* ALTER TABLE src_table RENAME TO dst_table */
7514 10 : if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "RENAME") &&
7515 4 : EQUAL(papszTokens[4], "TO"))
7516 : {
7517 4 : const char *pszSrcTableName = papszTokens[2];
7518 4 : const char *pszDstTableName = papszTokens[5];
7519 : OGRGeoPackageTableLayer *poSrcLayer =
7520 0 : dynamic_cast<OGRGeoPackageTableLayer *>(
7521 4 : GetLayerByName(SQLUnescape(pszSrcTableName)));
7522 4 : if (poSrcLayer)
7523 : {
7524 4 : poSrcLayer->Rename(SQLUnescape(pszDstTableName));
7525 4 : CSLDestroy(papszTokens);
7526 4 : return nullptr;
7527 : }
7528 : }
7529 : /* ALTER TABLE table RENAME COLUMN src_name TO dst_name */
7530 2 : else if (CSLCount(papszTokens) == 8 &&
7531 1 : EQUAL(papszTokens[3], "RENAME") &&
7532 3 : EQUAL(papszTokens[4], "COLUMN") && EQUAL(papszTokens[6], "TO"))
7533 : {
7534 1 : const char *pszTableName = papszTokens[2];
7535 1 : const char *pszSrcColumn = papszTokens[5];
7536 1 : const char *pszDstColumn = papszTokens[7];
7537 : OGRGeoPackageTableLayer *poLayer =
7538 0 : dynamic_cast<OGRGeoPackageTableLayer *>(
7539 1 : GetLayerByName(SQLUnescape(pszTableName)));
7540 1 : if (poLayer)
7541 : {
7542 2 : int nSrcFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
7543 2 : SQLUnescape(pszSrcColumn));
7544 1 : if (nSrcFieldIdx >= 0)
7545 : {
7546 : // OFTString or any type will do as we just alter the name
7547 : // so it will be ignored.
7548 1 : OGRFieldDefn oFieldDefn(SQLUnescape(pszDstColumn),
7549 1 : OFTString);
7550 1 : poLayer->AlterFieldDefn(nSrcFieldIdx, &oFieldDefn,
7551 : ALTER_NAME_FLAG);
7552 1 : CSLDestroy(papszTokens);
7553 1 : return nullptr;
7554 : }
7555 : }
7556 : }
7557 : /* ALTER TABLE table DROP COLUMN col_name */
7558 2 : else if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "DROP") &&
7559 1 : EQUAL(papszTokens[4], "COLUMN"))
7560 : {
7561 1 : const char *pszTableName = papszTokens[2];
7562 1 : const char *pszColumnName = papszTokens[5];
7563 : OGRGeoPackageTableLayer *poLayer =
7564 0 : dynamic_cast<OGRGeoPackageTableLayer *>(
7565 1 : GetLayerByName(SQLUnescape(pszTableName)));
7566 1 : if (poLayer)
7567 : {
7568 2 : int nFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
7569 2 : SQLUnescape(pszColumnName));
7570 1 : if (nFieldIdx >= 0)
7571 : {
7572 1 : poLayer->DeleteField(nFieldIdx);
7573 1 : CSLDestroy(papszTokens);
7574 1 : return nullptr;
7575 : }
7576 : }
7577 : }
7578 0 : CSLDestroy(papszTokens);
7579 : }
7580 :
7581 5289 : if (EQUAL(osSQLCommand, "VACUUM"))
7582 : {
7583 9 : ResetReadingAllLayers();
7584 : }
7585 :
7586 5289 : if (EQUAL(osSQLCommand, "BEGIN"))
7587 : {
7588 0 : SoftStartTransaction();
7589 0 : return nullptr;
7590 : }
7591 5289 : else if (EQUAL(osSQLCommand, "COMMIT"))
7592 : {
7593 0 : SoftCommitTransaction();
7594 0 : return nullptr;
7595 : }
7596 5289 : else if (EQUAL(osSQLCommand, "ROLLBACK"))
7597 : {
7598 0 : SoftRollbackTransaction();
7599 0 : return nullptr;
7600 : }
7601 :
7602 5289 : else if (pszDialect != nullptr && EQUAL(pszDialect, "INDIRECT_SQLITE"))
7603 1 : return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter, "SQLITE");
7604 5288 : else if (pszDialect != nullptr && !EQUAL(pszDialect, "") &&
7605 66 : !EQUAL(pszDialect, "NATIVE") && !EQUAL(pszDialect, "SQLITE") &&
7606 66 : !EQUAL(pszDialect, "DEBUG"))
7607 0 : return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter,
7608 0 : pszDialect);
7609 :
7610 : /* -------------------------------------------------------------------- */
7611 : /* Prepare statement. */
7612 : /* -------------------------------------------------------------------- */
7613 5288 : sqlite3_stmt *hSQLStmt = nullptr;
7614 :
7615 : /* This will speed-up layer creation */
7616 : /* ORDER BY are costly to evaluate and are not necessary to establish */
7617 : /* the layer definition. */
7618 5288 : bool bUseStatementForGetNextFeature = true;
7619 5288 : bool bEmptyLayer = false;
7620 10576 : CPLString osSQLCommandTruncated(osSQLCommand);
7621 :
7622 17394 : if (osSQLCommand.ifind("SELECT ") == 0 &&
7623 6053 : CPLString(osSQLCommand.substr(1)).ifind("SELECT ") ==
7624 731 : std::string::npos &&
7625 731 : osSQLCommand.ifind(" UNION ") == std::string::npos &&
7626 6784 : osSQLCommand.ifind(" INTERSECT ") == std::string::npos &&
7627 731 : osSQLCommand.ifind(" EXCEPT ") == std::string::npos)
7628 : {
7629 731 : size_t nOrderByPos = osSQLCommand.ifind(" ORDER BY ");
7630 731 : if (nOrderByPos != std::string::npos)
7631 : {
7632 5 : osSQLCommandTruncated.resize(nOrderByPos);
7633 5 : bUseStatementForGetNextFeature = false;
7634 : }
7635 : }
7636 :
7637 5288 : int rc = prepareSql(hDB, osSQLCommandTruncated.c_str(),
7638 5288 : static_cast<int>(osSQLCommandTruncated.size()),
7639 : &hSQLStmt, nullptr);
7640 :
7641 5288 : if (rc != SQLITE_OK)
7642 : {
7643 8 : CPLError(CE_Failure, CPLE_AppDefined,
7644 : "In ExecuteSQL(): sqlite3_prepare_v2(%s):\n %s",
7645 : osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
7646 :
7647 8 : if (hSQLStmt != nullptr)
7648 : {
7649 0 : sqlite3_finalize(hSQLStmt);
7650 : }
7651 :
7652 8 : return nullptr;
7653 : }
7654 :
7655 : /* -------------------------------------------------------------------- */
7656 : /* Do we get a resultset? */
7657 : /* -------------------------------------------------------------------- */
7658 5280 : rc = sqlite3_step(hSQLStmt);
7659 :
7660 6816 : for (int i = 0; i < m_nLayers; i++)
7661 : {
7662 1536 : m_papoLayers[i]->RunDeferredDropRTreeTableIfNecessary();
7663 : }
7664 :
7665 5280 : if (rc != SQLITE_ROW)
7666 : {
7667 4595 : if (rc != SQLITE_DONE)
7668 : {
7669 7 : CPLError(CE_Failure, CPLE_AppDefined,
7670 : "In ExecuteSQL(): sqlite3_step(%s):\n %s",
7671 : osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
7672 :
7673 7 : sqlite3_finalize(hSQLStmt);
7674 7 : return nullptr;
7675 : }
7676 :
7677 4588 : if (EQUAL(osSQLCommand, "VACUUM"))
7678 : {
7679 9 : sqlite3_finalize(hSQLStmt);
7680 : /* VACUUM rewrites the DB, so we need to reset the application id */
7681 9 : SetApplicationAndUserVersionId();
7682 9 : return nullptr;
7683 : }
7684 :
7685 4579 : if (!STARTS_WITH_CI(osSQLCommand, "SELECT "))
7686 : {
7687 4458 : sqlite3_finalize(hSQLStmt);
7688 4458 : return nullptr;
7689 : }
7690 :
7691 121 : bUseStatementForGetNextFeature = false;
7692 121 : bEmptyLayer = true;
7693 : }
7694 :
7695 : /* -------------------------------------------------------------------- */
7696 : /* Special case for some functions which must be run */
7697 : /* only once */
7698 : /* -------------------------------------------------------------------- */
7699 806 : if (STARTS_WITH_CI(osSQLCommand, "SELECT "))
7700 : {
7701 3669 : for (unsigned int i = 0; i < sizeof(apszFuncsWithSideEffects) /
7702 : sizeof(apszFuncsWithSideEffects[0]);
7703 : i++)
7704 : {
7705 2961 : if (EQUALN(apszFuncsWithSideEffects[i], osSQLCommand.c_str() + 7,
7706 : strlen(apszFuncsWithSideEffects[i])))
7707 : {
7708 112 : if (sqlite3_column_count(hSQLStmt) == 1 &&
7709 56 : sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
7710 : {
7711 56 : int ret = sqlite3_column_int(hSQLStmt, 0);
7712 :
7713 56 : sqlite3_finalize(hSQLStmt);
7714 :
7715 : return new OGRSQLiteSingleFeatureLayer(
7716 56 : apszFuncsWithSideEffects[i], ret);
7717 : }
7718 : }
7719 : }
7720 : }
7721 42 : else if (STARTS_WITH_CI(osSQLCommand, "PRAGMA "))
7722 : {
7723 57 : if (sqlite3_column_count(hSQLStmt) == 1 &&
7724 15 : sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
7725 : {
7726 12 : int ret = sqlite3_column_int(hSQLStmt, 0);
7727 :
7728 12 : sqlite3_finalize(hSQLStmt);
7729 :
7730 12 : return new OGRSQLiteSingleFeatureLayer(osSQLCommand.c_str() + 7,
7731 12 : ret);
7732 : }
7733 33 : else if (sqlite3_column_count(hSQLStmt) == 1 &&
7734 3 : sqlite3_column_type(hSQLStmt, 0) == SQLITE_TEXT)
7735 : {
7736 : const char *pszRet = reinterpret_cast<const char *>(
7737 3 : sqlite3_column_text(hSQLStmt, 0));
7738 :
7739 : OGRLayer *poRet = new OGRSQLiteSingleFeatureLayer(
7740 3 : osSQLCommand.c_str() + 7, pszRet);
7741 :
7742 3 : sqlite3_finalize(hSQLStmt);
7743 :
7744 3 : return poRet;
7745 : }
7746 : }
7747 :
7748 : /* -------------------------------------------------------------------- */
7749 : /* Create layer. */
7750 : /* -------------------------------------------------------------------- */
7751 :
7752 : OGRLayer *poLayer = new OGRGeoPackageSelectLayer(
7753 : this, osSQLCommand, hSQLStmt, bUseStatementForGetNextFeature,
7754 735 : bEmptyLayer);
7755 :
7756 738 : if (poSpatialFilter != nullptr &&
7757 3 : poLayer->GetLayerDefn()->GetGeomFieldCount() > 0)
7758 3 : poLayer->SetSpatialFilter(0, poSpatialFilter);
7759 :
7760 735 : return poLayer;
7761 : }
7762 :
7763 : /************************************************************************/
7764 : /* ReleaseResultSet() */
7765 : /************************************************************************/
7766 :
7767 761 : void GDALGeoPackageDataset::ReleaseResultSet(OGRLayer *poLayer)
7768 :
7769 : {
7770 761 : delete poLayer;
7771 761 : }
7772 :
7773 : /************************************************************************/
7774 : /* HasExtensionsTable() */
7775 : /************************************************************************/
7776 :
7777 5116 : bool GDALGeoPackageDataset::HasExtensionsTable()
7778 : {
7779 5116 : return SQLGetInteger(
7780 : hDB,
7781 : "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_extensions' "
7782 : "AND type IN ('table', 'view')",
7783 5116 : nullptr) == 1;
7784 : }
7785 :
7786 : /************************************************************************/
7787 : /* CheckUnknownExtensions() */
7788 : /************************************************************************/
7789 :
7790 1213 : void GDALGeoPackageDataset::CheckUnknownExtensions(bool bCheckRasterTable)
7791 : {
7792 1213 : if (!HasExtensionsTable())
7793 191 : return;
7794 :
7795 1022 : char *pszSQL = nullptr;
7796 1022 : if (!bCheckRasterTable)
7797 829 : pszSQL = sqlite3_mprintf(
7798 : "SELECT extension_name, definition, scope FROM gpkg_extensions "
7799 : "WHERE (table_name IS NULL "
7800 : "AND extension_name IS NOT NULL "
7801 : "AND definition IS NOT NULL "
7802 : "AND scope IS NOT NULL "
7803 : "AND extension_name NOT IN ("
7804 : "'gdal_aspatial', "
7805 : "'gpkg_elevation_tiles', " // Old name before GPKG 1.2 approval
7806 : "'2d_gridded_coverage', " // Old name after GPKG 1.2 and before OGC
7807 : // 17-066r1 finalization
7808 : "'gpkg_2d_gridded_coverage', " // Name in OGC 17-066r1 final
7809 : "'gpkg_metadata', "
7810 : "'gpkg_schema', "
7811 : "'gpkg_crs_wkt', "
7812 : "'gpkg_crs_wkt_1_1', "
7813 : "'related_tables', 'gpkg_related_tables')) "
7814 : #ifdef WORKAROUND_SQLITE3_BUGS
7815 : "OR 0 "
7816 : #endif
7817 : "LIMIT 1000");
7818 : else
7819 193 : pszSQL = sqlite3_mprintf(
7820 : "SELECT extension_name, definition, scope FROM gpkg_extensions "
7821 : "WHERE (lower(table_name) = lower('%q') "
7822 : "AND extension_name IS NOT NULL "
7823 : "AND definition IS NOT NULL "
7824 : "AND scope IS NOT NULL "
7825 : "AND extension_name NOT IN ("
7826 : "'gpkg_elevation_tiles', " // Old name before GPKG 1.2 approval
7827 : "'2d_gridded_coverage', " // Old name after GPKG 1.2 and before OGC
7828 : // 17-066r1 finalization
7829 : "'gpkg_2d_gridded_coverage', " // Name in OGC 17-066r1 final
7830 : "'gpkg_metadata', "
7831 : "'gpkg_schema', "
7832 : "'gpkg_crs_wkt', "
7833 : "'gpkg_crs_wkt_1_1', "
7834 : "'related_tables', 'gpkg_related_tables')) "
7835 : #ifdef WORKAROUND_SQLITE3_BUGS
7836 : "OR 0 "
7837 : #endif
7838 : "LIMIT 1000",
7839 : m_osRasterTable.c_str());
7840 :
7841 2044 : auto oResultTable = SQLQuery(GetDB(), pszSQL);
7842 1022 : sqlite3_free(pszSQL);
7843 1022 : if (oResultTable && oResultTable->RowCount() > 0)
7844 : {
7845 44 : for (int i = 0; i < oResultTable->RowCount(); i++)
7846 : {
7847 22 : const char *pszExtName = oResultTable->GetValue(0, i);
7848 22 : const char *pszDefinition = oResultTable->GetValue(1, i);
7849 22 : const char *pszScope = oResultTable->GetValue(2, i);
7850 22 : if (pszExtName == nullptr || pszDefinition == nullptr ||
7851 : pszScope == nullptr)
7852 : {
7853 0 : continue;
7854 : }
7855 :
7856 22 : if (EQUAL(pszExtName, "gpkg_webp"))
7857 : {
7858 16 : if (GDALGetDriverByName("WEBP") == nullptr)
7859 : {
7860 1 : CPLError(
7861 : CE_Warning, CPLE_AppDefined,
7862 : "Table %s contains WEBP tiles, but GDAL configured "
7863 : "without WEBP support. Data will be missing",
7864 : m_osRasterTable.c_str());
7865 : }
7866 16 : m_eTF = GPKG_TF_WEBP;
7867 16 : continue;
7868 : }
7869 6 : if (EQUAL(pszExtName, "gpkg_zoom_other"))
7870 : {
7871 2 : m_bZoomOther = true;
7872 2 : continue;
7873 : }
7874 :
7875 4 : if (GetUpdate() && EQUAL(pszScope, "write-only"))
7876 : {
7877 1 : CPLError(
7878 : CE_Warning, CPLE_AppDefined,
7879 : "Database relies on the '%s' (%s) extension that should "
7880 : "be implemented for safe write-support, but is not "
7881 : "currently. "
7882 : "Update of that database are strongly discouraged to avoid "
7883 : "corruption.",
7884 : pszExtName, pszDefinition);
7885 : }
7886 3 : else if (GetUpdate() && EQUAL(pszScope, "read-write"))
7887 : {
7888 1 : CPLError(
7889 : CE_Warning, CPLE_AppDefined,
7890 : "Database relies on the '%s' (%s) extension that should "
7891 : "be implemented in order to read/write it safely, but is "
7892 : "not currently. "
7893 : "Some data may be missing while reading that database, and "
7894 : "updates are strongly discouraged.",
7895 : pszExtName, pszDefinition);
7896 : }
7897 2 : else if (EQUAL(pszScope, "read-write") &&
7898 : // None of the NGA extensions at
7899 : // http://ngageoint.github.io/GeoPackage/docs/extensions/
7900 : // affect read-only scenarios
7901 1 : !STARTS_WITH(pszExtName, "nga_"))
7902 : {
7903 1 : CPLError(
7904 : CE_Warning, CPLE_AppDefined,
7905 : "Database relies on the '%s' (%s) extension that should "
7906 : "be implemented in order to read it safely, but is not "
7907 : "currently. "
7908 : "Some data may be missing while reading that database.",
7909 : pszExtName, pszDefinition);
7910 : }
7911 : }
7912 : }
7913 : }
7914 :
7915 : /************************************************************************/
7916 : /* HasGDALAspatialExtension() */
7917 : /************************************************************************/
7918 :
7919 782 : bool GDALGeoPackageDataset::HasGDALAspatialExtension()
7920 : {
7921 782 : if (!HasExtensionsTable())
7922 84 : return false;
7923 :
7924 : auto oResultTable = SQLQuery(hDB, "SELECT * FROM gpkg_extensions "
7925 : "WHERE (extension_name = 'gdal_aspatial' "
7926 : "AND table_name IS NULL "
7927 : "AND column_name IS NULL)"
7928 : #ifdef WORKAROUND_SQLITE3_BUGS
7929 : " OR 0"
7930 : #endif
7931 698 : );
7932 698 : bool bHasExtension = (oResultTable && oResultTable->RowCount() == 1);
7933 698 : return bHasExtension;
7934 : }
7935 :
7936 : /************************************************************************/
7937 : /* CreateExtensionsTableIfNecessary() */
7938 : /************************************************************************/
7939 :
7940 975 : OGRErr GDALGeoPackageDataset::CreateExtensionsTableIfNecessary()
7941 : {
7942 : /* Check if the table gpkg_extensions exists */
7943 975 : if (HasExtensionsTable())
7944 366 : return OGRERR_NONE;
7945 :
7946 : /* Requirement 79 : Every extension of a GeoPackage SHALL be registered */
7947 : /* in a corresponding row in the gpkg_extensions table. The absence of a */
7948 : /* gpkg_extensions table or the absence of rows in gpkg_extensions table */
7949 : /* SHALL both indicate the absence of extensions to a GeoPackage. */
7950 609 : const char *pszCreateGpkgExtensions =
7951 : "CREATE TABLE gpkg_extensions ("
7952 : "table_name TEXT,"
7953 : "column_name TEXT,"
7954 : "extension_name TEXT NOT NULL,"
7955 : "definition TEXT NOT NULL,"
7956 : "scope TEXT NOT NULL,"
7957 : "CONSTRAINT ge_tce UNIQUE (table_name, column_name, extension_name)"
7958 : ")";
7959 :
7960 609 : return SQLCommand(hDB, pszCreateGpkgExtensions);
7961 : }
7962 :
7963 : /************************************************************************/
7964 : /* OGR_GPKG_Intersects_Spatial_Filter() */
7965 : /************************************************************************/
7966 :
7967 23044 : void OGR_GPKG_Intersects_Spatial_Filter(sqlite3_context *pContext, int argc,
7968 : sqlite3_value **argv)
7969 : {
7970 23044 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
7971 : {
7972 0 : sqlite3_result_int(pContext, 0);
7973 23043 : return;
7974 : }
7975 :
7976 : auto poLayer =
7977 23044 : static_cast<OGRGeoPackageTableLayer *>(sqlite3_user_data(pContext));
7978 :
7979 23044 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
7980 : const GByte *pabyBLOB =
7981 23044 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
7982 :
7983 : GPkgHeader sHeader;
7984 46088 : if (poLayer->m_bFilterIsEnvelope &&
7985 23044 : OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false, 0))
7986 : {
7987 23044 : if (sHeader.bExtentHasXY)
7988 : {
7989 4 : OGREnvelope sEnvelope;
7990 4 : sEnvelope.MinX = sHeader.MinX;
7991 4 : sEnvelope.MinY = sHeader.MinY;
7992 4 : sEnvelope.MaxX = sHeader.MaxX;
7993 4 : sEnvelope.MaxY = sHeader.MaxY;
7994 4 : if (poLayer->m_sFilterEnvelope.Contains(sEnvelope))
7995 : {
7996 2 : sqlite3_result_int(pContext, 1);
7997 2 : return;
7998 : }
7999 : }
8000 :
8001 : // Check if at least one point falls into the layer filter envelope
8002 : // nHeaderLen is > 0 for GeoPackage geometries
8003 46084 : if (sHeader.nHeaderLen > 0 &&
8004 23042 : OGRWKBIntersectsPessimistic(pabyBLOB + sHeader.nHeaderLen,
8005 23042 : nBLOBLen - sHeader.nHeaderLen,
8006 23042 : poLayer->m_sFilterEnvelope))
8007 : {
8008 23041 : sqlite3_result_int(pContext, 1);
8009 23041 : return;
8010 : }
8011 : }
8012 :
8013 : auto poGeom = std::unique_ptr<OGRGeometry>(
8014 1 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8015 1 : if (poGeom == nullptr)
8016 : {
8017 : // Try also spatialite geometry blobs
8018 0 : OGRGeometry *poGeomSpatialite = nullptr;
8019 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8020 0 : &poGeomSpatialite) != OGRERR_NONE)
8021 : {
8022 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8023 0 : sqlite3_result_int(pContext, 0);
8024 0 : return;
8025 : }
8026 0 : poGeom.reset(poGeomSpatialite);
8027 : }
8028 :
8029 1 : sqlite3_result_int(pContext, poLayer->FilterGeometry(poGeom.get()));
8030 : }
8031 :
8032 : /************************************************************************/
8033 : /* OGRGeoPackageSTMinX() */
8034 : /************************************************************************/
8035 :
8036 242083 : static void OGRGeoPackageSTMinX(sqlite3_context *pContext, int argc,
8037 : sqlite3_value **argv)
8038 : {
8039 : GPkgHeader sHeader;
8040 242083 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8041 : {
8042 3 : sqlite3_result_null(pContext);
8043 3 : return;
8044 : }
8045 242080 : sqlite3_result_double(pContext, sHeader.MinX);
8046 : }
8047 :
8048 : /************************************************************************/
8049 : /* OGRGeoPackageSTMinY() */
8050 : /************************************************************************/
8051 :
8052 242081 : static void OGRGeoPackageSTMinY(sqlite3_context *pContext, int argc,
8053 : sqlite3_value **argv)
8054 : {
8055 : GPkgHeader sHeader;
8056 242081 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8057 : {
8058 1 : sqlite3_result_null(pContext);
8059 1 : return;
8060 : }
8061 242080 : sqlite3_result_double(pContext, sHeader.MinY);
8062 : }
8063 :
8064 : /************************************************************************/
8065 : /* OGRGeoPackageSTMaxX() */
8066 : /************************************************************************/
8067 :
8068 242081 : static void OGRGeoPackageSTMaxX(sqlite3_context *pContext, int argc,
8069 : sqlite3_value **argv)
8070 : {
8071 : GPkgHeader sHeader;
8072 242081 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8073 : {
8074 1 : sqlite3_result_null(pContext);
8075 1 : return;
8076 : }
8077 242080 : sqlite3_result_double(pContext, sHeader.MaxX);
8078 : }
8079 :
8080 : /************************************************************************/
8081 : /* OGRGeoPackageSTMaxY() */
8082 : /************************************************************************/
8083 :
8084 242081 : static void OGRGeoPackageSTMaxY(sqlite3_context *pContext, int argc,
8085 : sqlite3_value **argv)
8086 : {
8087 : GPkgHeader sHeader;
8088 242081 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8089 : {
8090 1 : sqlite3_result_null(pContext);
8091 1 : return;
8092 : }
8093 242080 : sqlite3_result_double(pContext, sHeader.MaxY);
8094 : }
8095 :
8096 : /************************************************************************/
8097 : /* OGRGeoPackageSTIsEmpty() */
8098 : /************************************************************************/
8099 :
8100 243303 : static void OGRGeoPackageSTIsEmpty(sqlite3_context *pContext, int argc,
8101 : sqlite3_value **argv)
8102 : {
8103 : GPkgHeader sHeader;
8104 243303 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8105 : {
8106 2 : sqlite3_result_null(pContext);
8107 2 : return;
8108 : }
8109 243301 : sqlite3_result_int(pContext, sHeader.bEmpty);
8110 : }
8111 :
8112 : /************************************************************************/
8113 : /* OGRGeoPackageSTGeometryType() */
8114 : /************************************************************************/
8115 :
8116 7 : static void OGRGeoPackageSTGeometryType(sqlite3_context *pContext, int /*argc*/,
8117 : sqlite3_value **argv)
8118 : {
8119 : GPkgHeader sHeader;
8120 :
8121 7 : int nBLOBLen = sqlite3_value_bytes(argv[0]);
8122 : const GByte *pabyBLOB =
8123 7 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8124 : OGRwkbGeometryType eGeometryType;
8125 :
8126 13 : if (nBLOBLen < 8 ||
8127 6 : GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
8128 : {
8129 2 : if (OGRSQLiteGetSpatialiteGeometryHeader(
8130 : pabyBLOB, nBLOBLen, nullptr, &eGeometryType, nullptr, nullptr,
8131 2 : nullptr, nullptr, nullptr) == OGRERR_NONE)
8132 : {
8133 1 : sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
8134 : SQLITE_TRANSIENT);
8135 4 : return;
8136 : }
8137 : else
8138 : {
8139 1 : sqlite3_result_null(pContext);
8140 1 : return;
8141 : }
8142 : }
8143 :
8144 5 : if (static_cast<size_t>(nBLOBLen) < sHeader.nHeaderLen + 5)
8145 : {
8146 2 : sqlite3_result_null(pContext);
8147 2 : return;
8148 : }
8149 :
8150 3 : OGRErr err = OGRReadWKBGeometryType(pabyBLOB + sHeader.nHeaderLen,
8151 : wkbVariantIso, &eGeometryType);
8152 3 : if (err != OGRERR_NONE)
8153 1 : sqlite3_result_null(pContext);
8154 : else
8155 2 : sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
8156 : SQLITE_TRANSIENT);
8157 : }
8158 :
8159 : /************************************************************************/
8160 : /* OGRGeoPackageSTEnvelopesIntersects() */
8161 : /************************************************************************/
8162 :
8163 358 : static void OGRGeoPackageSTEnvelopesIntersects(sqlite3_context *pContext,
8164 : int argc, sqlite3_value **argv)
8165 : {
8166 : GPkgHeader sHeader;
8167 358 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8168 : {
8169 2 : sqlite3_result_int(pContext, FALSE);
8170 197 : return;
8171 : }
8172 356 : const double dfMinX = sqlite3_value_double(argv[1]);
8173 356 : if (sHeader.MaxX < dfMinX)
8174 : {
8175 105 : sqlite3_result_int(pContext, FALSE);
8176 105 : return;
8177 : }
8178 251 : const double dfMinY = sqlite3_value_double(argv[2]);
8179 251 : if (sHeader.MaxY < dfMinY)
8180 : {
8181 23 : sqlite3_result_int(pContext, FALSE);
8182 23 : return;
8183 : }
8184 228 : const double dfMaxX = sqlite3_value_double(argv[3]);
8185 228 : if (sHeader.MinX > dfMaxX)
8186 : {
8187 67 : sqlite3_result_int(pContext, FALSE);
8188 67 : return;
8189 : }
8190 161 : const double dfMaxY = sqlite3_value_double(argv[4]);
8191 161 : sqlite3_result_int(pContext, sHeader.MinY <= dfMaxY);
8192 : }
8193 :
8194 : /************************************************************************/
8195 : /* OGRGeoPackageSTEnvelopesIntersectsTwoParams() */
8196 : /************************************************************************/
8197 :
8198 : static void
8199 3 : OGRGeoPackageSTEnvelopesIntersectsTwoParams(sqlite3_context *pContext, int argc,
8200 : sqlite3_value **argv)
8201 : {
8202 : GPkgHeader sHeader;
8203 3 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false, 0))
8204 : {
8205 0 : sqlite3_result_int(pContext, FALSE);
8206 2 : return;
8207 : }
8208 : GPkgHeader sHeader2;
8209 3 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader2, true, false,
8210 : 1))
8211 : {
8212 0 : sqlite3_result_int(pContext, FALSE);
8213 0 : return;
8214 : }
8215 3 : if (sHeader.MaxX < sHeader2.MinX)
8216 : {
8217 1 : sqlite3_result_int(pContext, FALSE);
8218 1 : return;
8219 : }
8220 2 : if (sHeader.MaxY < sHeader2.MinY)
8221 : {
8222 0 : sqlite3_result_int(pContext, FALSE);
8223 0 : return;
8224 : }
8225 2 : if (sHeader.MinX > sHeader2.MaxX)
8226 : {
8227 1 : sqlite3_result_int(pContext, FALSE);
8228 1 : return;
8229 : }
8230 1 : sqlite3_result_int(pContext, sHeader.MinY <= sHeader2.MaxY);
8231 : }
8232 :
8233 : /************************************************************************/
8234 : /* OGRGeoPackageGPKGIsAssignable() */
8235 : /************************************************************************/
8236 :
8237 8 : static void OGRGeoPackageGPKGIsAssignable(sqlite3_context *pContext,
8238 : int /*argc*/, sqlite3_value **argv)
8239 : {
8240 15 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8241 7 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
8242 : {
8243 2 : sqlite3_result_int(pContext, 0);
8244 2 : return;
8245 : }
8246 :
8247 : const char *pszExpected =
8248 6 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8249 : const char *pszActual =
8250 6 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8251 6 : int bIsAssignable = OGR_GT_IsSubClassOf(OGRFromOGCGeomType(pszActual),
8252 : OGRFromOGCGeomType(pszExpected));
8253 6 : sqlite3_result_int(pContext, bIsAssignable);
8254 : }
8255 :
8256 : /************************************************************************/
8257 : /* OGRGeoPackageSTSRID() */
8258 : /************************************************************************/
8259 :
8260 12 : static void OGRGeoPackageSTSRID(sqlite3_context *pContext, int argc,
8261 : sqlite3_value **argv)
8262 : {
8263 : GPkgHeader sHeader;
8264 12 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8265 : {
8266 2 : sqlite3_result_null(pContext);
8267 2 : return;
8268 : }
8269 10 : sqlite3_result_int(pContext, sHeader.iSrsId);
8270 : }
8271 :
8272 : /************************************************************************/
8273 : /* OGRGeoPackageSetSRID() */
8274 : /************************************************************************/
8275 :
8276 27 : static void OGRGeoPackageSetSRID(sqlite3_context *pContext, int /* argc */,
8277 : sqlite3_value **argv)
8278 : {
8279 27 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8280 : {
8281 1 : sqlite3_result_null(pContext);
8282 1 : return;
8283 : }
8284 26 : const int nDestSRID = sqlite3_value_int(argv[1]);
8285 : GPkgHeader sHeader;
8286 26 : int nBLOBLen = sqlite3_value_bytes(argv[0]);
8287 : const GByte *pabyBLOB =
8288 26 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8289 :
8290 52 : if (nBLOBLen < 8 ||
8291 26 : GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
8292 : {
8293 : // Try also spatialite geometry blobs
8294 0 : OGRGeometry *poGeom = nullptr;
8295 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeom) !=
8296 : OGRERR_NONE)
8297 : {
8298 0 : sqlite3_result_null(pContext);
8299 0 : return;
8300 : }
8301 0 : size_t nBLOBDestLen = 0;
8302 : GByte *pabyDestBLOB =
8303 0 : GPkgGeometryFromOGR(poGeom, nDestSRID, nullptr, &nBLOBDestLen);
8304 0 : if (!pabyDestBLOB)
8305 : {
8306 0 : sqlite3_result_null(pContext);
8307 0 : return;
8308 : }
8309 0 : sqlite3_result_blob(pContext, pabyDestBLOB,
8310 : static_cast<int>(nBLOBDestLen), VSIFree);
8311 0 : return;
8312 : }
8313 :
8314 26 : GByte *pabyDestBLOB = static_cast<GByte *>(CPLMalloc(nBLOBLen));
8315 26 : memcpy(pabyDestBLOB, pabyBLOB, nBLOBLen);
8316 26 : int32_t nSRIDToSerialize = nDestSRID;
8317 26 : if (OGR_SWAP(sHeader.eByteOrder))
8318 0 : nSRIDToSerialize = CPL_SWAP32(nSRIDToSerialize);
8319 26 : memcpy(pabyDestBLOB + 4, &nSRIDToSerialize, 4);
8320 26 : sqlite3_result_blob(pContext, pabyDestBLOB, nBLOBLen, VSIFree);
8321 : }
8322 :
8323 : /************************************************************************/
8324 : /* OGRGeoPackageSTMakeValid() */
8325 : /************************************************************************/
8326 :
8327 3 : static void OGRGeoPackageSTMakeValid(sqlite3_context *pContext, int argc,
8328 : sqlite3_value **argv)
8329 : {
8330 3 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8331 : {
8332 2 : sqlite3_result_null(pContext);
8333 2 : return;
8334 : }
8335 1 : int nBLOBLen = sqlite3_value_bytes(argv[0]);
8336 : const GByte *pabyBLOB =
8337 1 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8338 :
8339 : GPkgHeader sHeader;
8340 1 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8341 : {
8342 0 : sqlite3_result_null(pContext);
8343 0 : return;
8344 : }
8345 :
8346 : auto poGeom = std::unique_ptr<OGRGeometry>(
8347 1 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8348 1 : if (poGeom == nullptr)
8349 : {
8350 : // Try also spatialite geometry blobs
8351 0 : OGRGeometry *poGeomPtr = nullptr;
8352 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
8353 : OGRERR_NONE)
8354 : {
8355 0 : sqlite3_result_null(pContext);
8356 0 : return;
8357 : }
8358 0 : poGeom.reset(poGeomPtr);
8359 : }
8360 1 : auto poValid = std::unique_ptr<OGRGeometry>(poGeom->MakeValid());
8361 1 : if (poValid == nullptr)
8362 : {
8363 0 : sqlite3_result_null(pContext);
8364 0 : return;
8365 : }
8366 :
8367 1 : size_t nBLOBDestLen = 0;
8368 1 : GByte *pabyDestBLOB = GPkgGeometryFromOGR(poValid.get(), sHeader.iSrsId,
8369 : nullptr, &nBLOBDestLen);
8370 1 : if (!pabyDestBLOB)
8371 : {
8372 0 : sqlite3_result_null(pContext);
8373 0 : return;
8374 : }
8375 1 : sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
8376 : VSIFree);
8377 : }
8378 :
8379 : /************************************************************************/
8380 : /* OGRGeoPackageSTArea() */
8381 : /************************************************************************/
8382 :
8383 19 : static void OGRGeoPackageSTArea(sqlite3_context *pContext, int /*argc*/,
8384 : sqlite3_value **argv)
8385 : {
8386 19 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8387 : {
8388 1 : sqlite3_result_null(pContext);
8389 15 : return;
8390 : }
8391 18 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8392 : const GByte *pabyBLOB =
8393 18 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8394 :
8395 : GPkgHeader sHeader;
8396 0 : std::unique_ptr<OGRGeometry> poGeom;
8397 18 : if (GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) == OGRERR_NONE)
8398 : {
8399 16 : if (sHeader.bEmpty)
8400 : {
8401 3 : sqlite3_result_double(pContext, 0);
8402 13 : return;
8403 : }
8404 13 : const GByte *pabyWkb = pabyBLOB + sHeader.nHeaderLen;
8405 13 : size_t nWKBSize = nBLOBLen - sHeader.nHeaderLen;
8406 : bool bNeedSwap;
8407 : uint32_t nType;
8408 13 : if (OGRWKBGetGeomType(pabyWkb, nWKBSize, bNeedSwap, nType))
8409 : {
8410 13 : if (nType == wkbPolygon || nType == wkbPolygon25D ||
8411 11 : nType == wkbPolygon + 1000 || // wkbPolygonZ
8412 10 : nType == wkbPolygonM || nType == wkbPolygonZM)
8413 : {
8414 : double dfArea;
8415 5 : if (OGRWKBPolygonGetArea(pabyWkb, nWKBSize, dfArea))
8416 : {
8417 5 : sqlite3_result_double(pContext, dfArea);
8418 5 : return;
8419 0 : }
8420 : }
8421 8 : else if (nType == wkbMultiPolygon || nType == wkbMultiPolygon25D ||
8422 6 : nType == wkbMultiPolygon + 1000 || // wkbMultiPolygonZ
8423 5 : nType == wkbMultiPolygonM || nType == wkbMultiPolygonZM)
8424 : {
8425 : double dfArea;
8426 5 : if (OGRWKBMultiPolygonGetArea(pabyWkb, nWKBSize, dfArea))
8427 : {
8428 5 : sqlite3_result_double(pContext, dfArea);
8429 5 : return;
8430 : }
8431 : }
8432 : }
8433 :
8434 : // For curve geometries, fallback to OGRGeometry methods
8435 3 : poGeom.reset(GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8436 : }
8437 : else
8438 : {
8439 : // Try also spatialite geometry blobs
8440 2 : OGRGeometry *poGeomPtr = nullptr;
8441 2 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
8442 : OGRERR_NONE)
8443 : {
8444 1 : sqlite3_result_null(pContext);
8445 1 : return;
8446 : }
8447 1 : poGeom.reset(poGeomPtr);
8448 : }
8449 4 : auto poSurface = dynamic_cast<OGRSurface *>(poGeom.get());
8450 4 : if (poSurface == nullptr)
8451 : {
8452 2 : auto poMultiSurface = dynamic_cast<OGRMultiSurface *>(poGeom.get());
8453 2 : if (poMultiSurface == nullptr)
8454 : {
8455 1 : sqlite3_result_double(pContext, 0);
8456 : }
8457 : else
8458 : {
8459 1 : sqlite3_result_double(pContext, poMultiSurface->get_Area());
8460 : }
8461 : }
8462 : else
8463 : {
8464 2 : sqlite3_result_double(pContext, poSurface->get_Area());
8465 : }
8466 : }
8467 :
8468 : /************************************************************************/
8469 : /* OGRGeoPackageGeodesicArea() */
8470 : /************************************************************************/
8471 :
8472 5 : static void OGRGeoPackageGeodesicArea(sqlite3_context *pContext, int argc,
8473 : sqlite3_value **argv)
8474 : {
8475 5 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8476 : {
8477 1 : sqlite3_result_null(pContext);
8478 3 : return;
8479 : }
8480 4 : if (sqlite3_value_int(argv[1]) != 1)
8481 : {
8482 2 : CPLError(CE_Warning, CPLE_NotSupported,
8483 : "ST_Area(geom, use_ellipsoid) is only supported for "
8484 : "use_ellipsoid = 1");
8485 : }
8486 :
8487 4 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8488 : const GByte *pabyBLOB =
8489 4 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8490 : GPkgHeader sHeader;
8491 4 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8492 : {
8493 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8494 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8495 1 : return;
8496 : }
8497 :
8498 : GDALGeoPackageDataset *poDS =
8499 3 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8500 :
8501 3 : OGRSpatialReference *poSrcSRS = poDS->GetSpatialRef(sHeader.iSrsId, true);
8502 3 : if (poSrcSRS == nullptr)
8503 : {
8504 1 : CPLError(CE_Failure, CPLE_AppDefined,
8505 : "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8506 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8507 1 : return;
8508 : }
8509 :
8510 : auto poGeom = std::unique_ptr<OGRGeometry>(
8511 2 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8512 2 : if (poGeom == nullptr)
8513 : {
8514 : // Try also spatialite geometry blobs
8515 0 : OGRGeometry *poGeomSpatialite = nullptr;
8516 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8517 0 : &poGeomSpatialite) != OGRERR_NONE)
8518 : {
8519 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8520 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8521 0 : return;
8522 : }
8523 0 : poGeom.reset(poGeomSpatialite);
8524 : }
8525 :
8526 2 : poGeom->assignSpatialReference(poSrcSRS);
8527 2 : sqlite3_result_double(
8528 : pContext, OGR_G_GeodesicArea(OGRGeometry::ToHandle(poGeom.get())));
8529 : }
8530 :
8531 : /************************************************************************/
8532 : /* OGRGeoPackageTransform() */
8533 : /************************************************************************/
8534 :
8535 : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
8536 : sqlite3_value **argv);
8537 :
8538 32 : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
8539 : sqlite3_value **argv)
8540 : {
8541 63 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB ||
8542 31 : sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
8543 : {
8544 2 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8545 6 : return;
8546 : }
8547 :
8548 30 : const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8549 : const GByte *pabyBLOB =
8550 30 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8551 : GPkgHeader sHeader;
8552 30 : if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8553 : {
8554 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8555 1 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8556 1 : return;
8557 : }
8558 :
8559 29 : const int nDestSRID = sqlite3_value_int(argv[1]);
8560 29 : if (sHeader.iSrsId == nDestSRID)
8561 : {
8562 : // Return blob unmodified
8563 3 : sqlite3_result_blob(pContext, pabyBLOB, nBLOBLen, SQLITE_TRANSIENT);
8564 3 : return;
8565 : }
8566 :
8567 : GDALGeoPackageDataset *poDS =
8568 26 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8569 :
8570 : // Try to get the cached coordinate transformation
8571 : OGRCoordinateTransformation *poCT;
8572 26 : if (poDS->m_nLastCachedCTSrcSRId == sHeader.iSrsId &&
8573 20 : poDS->m_nLastCachedCTDstSRId == nDestSRID)
8574 : {
8575 20 : poCT = poDS->m_poLastCachedCT.get();
8576 : }
8577 : else
8578 : {
8579 : OGRSpatialReference *poSrcSRS =
8580 6 : poDS->GetSpatialRef(sHeader.iSrsId, true);
8581 6 : if (poSrcSRS == nullptr)
8582 : {
8583 0 : CPLError(CE_Failure, CPLE_AppDefined,
8584 : "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8585 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8586 0 : return;
8587 : }
8588 :
8589 6 : OGRSpatialReference *poDstSRS = poDS->GetSpatialRef(nDestSRID, true);
8590 6 : if (poDstSRS == nullptr)
8591 : {
8592 0 : CPLError(CE_Failure, CPLE_AppDefined, "Target SRID (%d) is invalid",
8593 : nDestSRID);
8594 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8595 0 : poSrcSRS->Release();
8596 0 : return;
8597 : }
8598 6 : poCT = OGRCreateCoordinateTransformation(poSrcSRS, poDstSRS);
8599 6 : poSrcSRS->Release();
8600 6 : poDstSRS->Release();
8601 :
8602 6 : if (poCT == nullptr)
8603 : {
8604 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8605 0 : return;
8606 : }
8607 :
8608 : // Cache coordinate transformation for potential later reuse
8609 6 : poDS->m_nLastCachedCTSrcSRId = sHeader.iSrsId;
8610 6 : poDS->m_nLastCachedCTDstSRId = nDestSRID;
8611 6 : poDS->m_poLastCachedCT.reset(poCT);
8612 6 : poCT = poDS->m_poLastCachedCT.get();
8613 : }
8614 :
8615 : auto poGeom = std::unique_ptr<OGRGeometry>(
8616 26 : GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8617 26 : if (poGeom == nullptr)
8618 : {
8619 : // Try also spatialite geometry blobs
8620 0 : OGRGeometry *poGeomSpatialite = nullptr;
8621 0 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8622 0 : &poGeomSpatialite) != OGRERR_NONE)
8623 : {
8624 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8625 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8626 0 : return;
8627 : }
8628 0 : poGeom.reset(poGeomSpatialite);
8629 : }
8630 :
8631 26 : if (poGeom->transform(poCT) != OGRERR_NONE)
8632 : {
8633 0 : sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8634 0 : return;
8635 : }
8636 :
8637 26 : size_t nBLOBDestLen = 0;
8638 : GByte *pabyDestBLOB =
8639 26 : GPkgGeometryFromOGR(poGeom.get(), nDestSRID, nullptr, &nBLOBDestLen);
8640 26 : if (!pabyDestBLOB)
8641 : {
8642 0 : sqlite3_result_null(pContext);
8643 0 : return;
8644 : }
8645 26 : sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
8646 : VSIFree);
8647 : }
8648 :
8649 : /************************************************************************/
8650 : /* OGRGeoPackageSridFromAuthCRS() */
8651 : /************************************************************************/
8652 :
8653 4 : static void OGRGeoPackageSridFromAuthCRS(sqlite3_context *pContext,
8654 : int /*argc*/, sqlite3_value **argv)
8655 : {
8656 7 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8657 3 : sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
8658 : {
8659 2 : sqlite3_result_int(pContext, -1);
8660 2 : return;
8661 : }
8662 :
8663 : GDALGeoPackageDataset *poDS =
8664 2 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8665 :
8666 2 : char *pszSQL = sqlite3_mprintf(
8667 : "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
8668 : "lower(organization) = lower('%q') AND organization_coordsys_id = %d",
8669 2 : sqlite3_value_text(argv[0]), sqlite3_value_int(argv[1]));
8670 2 : OGRErr err = OGRERR_NONE;
8671 2 : int nSRSId = SQLGetInteger(poDS->GetDB(), pszSQL, &err);
8672 2 : sqlite3_free(pszSQL);
8673 2 : if (err != OGRERR_NONE)
8674 1 : nSRSId = -1;
8675 2 : sqlite3_result_int(pContext, nSRSId);
8676 : }
8677 :
8678 : /************************************************************************/
8679 : /* OGRGeoPackageImportFromEPSG() */
8680 : /************************************************************************/
8681 :
8682 4 : static void OGRGeoPackageImportFromEPSG(sqlite3_context *pContext, int /*argc*/,
8683 : sqlite3_value **argv)
8684 : {
8685 4 : if (sqlite3_value_type(argv[0]) != SQLITE_INTEGER)
8686 : {
8687 1 : sqlite3_result_int(pContext, -1);
8688 2 : return;
8689 : }
8690 :
8691 : GDALGeoPackageDataset *poDS =
8692 3 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8693 3 : OGRSpatialReference oSRS;
8694 3 : if (oSRS.importFromEPSG(sqlite3_value_int(argv[0])) != OGRERR_NONE)
8695 : {
8696 1 : sqlite3_result_int(pContext, -1);
8697 1 : return;
8698 : }
8699 :
8700 2 : sqlite3_result_int(pContext, poDS->GetSrsId(&oSRS));
8701 : }
8702 :
8703 : /************************************************************************/
8704 : /* OGRGeoPackageRegisterGeometryExtension() */
8705 : /************************************************************************/
8706 :
8707 1 : static void OGRGeoPackageRegisterGeometryExtension(sqlite3_context *pContext,
8708 : int /*argc*/,
8709 : sqlite3_value **argv)
8710 : {
8711 1 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8712 2 : sqlite3_value_type(argv[1]) != SQLITE_TEXT ||
8713 1 : sqlite3_value_type(argv[2]) != SQLITE_TEXT)
8714 : {
8715 0 : sqlite3_result_int(pContext, 0);
8716 0 : return;
8717 : }
8718 :
8719 : const char *pszTableName =
8720 1 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8721 : const char *pszGeomName =
8722 1 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8723 : const char *pszGeomType =
8724 1 : reinterpret_cast<const char *>(sqlite3_value_text(argv[2]));
8725 :
8726 : GDALGeoPackageDataset *poDS =
8727 1 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8728 :
8729 1 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
8730 1 : poDS->GetLayerByName(pszTableName));
8731 1 : if (poLyr == nullptr)
8732 : {
8733 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
8734 0 : sqlite3_result_int(pContext, 0);
8735 0 : return;
8736 : }
8737 1 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
8738 : {
8739 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
8740 0 : sqlite3_result_int(pContext, 0);
8741 0 : return;
8742 : }
8743 1 : const OGRwkbGeometryType eGeomType = OGRFromOGCGeomType(pszGeomType);
8744 1 : if (eGeomType == wkbUnknown)
8745 : {
8746 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry type name");
8747 0 : sqlite3_result_int(pContext, 0);
8748 0 : return;
8749 : }
8750 :
8751 1 : sqlite3_result_int(
8752 : pContext,
8753 1 : static_cast<int>(poLyr->CreateGeometryExtensionIfNecessary(eGeomType)));
8754 : }
8755 :
8756 : /************************************************************************/
8757 : /* OGRGeoPackageCreateSpatialIndex() */
8758 : /************************************************************************/
8759 :
8760 14 : static void OGRGeoPackageCreateSpatialIndex(sqlite3_context *pContext,
8761 : int /*argc*/, sqlite3_value **argv)
8762 : {
8763 27 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8764 13 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
8765 : {
8766 2 : sqlite3_result_int(pContext, 0);
8767 2 : return;
8768 : }
8769 :
8770 : const char *pszTableName =
8771 12 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8772 : const char *pszGeomName =
8773 12 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8774 : GDALGeoPackageDataset *poDS =
8775 12 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8776 :
8777 12 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
8778 12 : poDS->GetLayerByName(pszTableName));
8779 12 : if (poLyr == nullptr)
8780 : {
8781 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
8782 1 : sqlite3_result_int(pContext, 0);
8783 1 : return;
8784 : }
8785 11 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
8786 : {
8787 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
8788 1 : sqlite3_result_int(pContext, 0);
8789 1 : return;
8790 : }
8791 :
8792 10 : sqlite3_result_int(pContext, poLyr->CreateSpatialIndex());
8793 : }
8794 :
8795 : /************************************************************************/
8796 : /* OGRGeoPackageDisableSpatialIndex() */
8797 : /************************************************************************/
8798 :
8799 12 : static void OGRGeoPackageDisableSpatialIndex(sqlite3_context *pContext,
8800 : int /*argc*/, sqlite3_value **argv)
8801 : {
8802 23 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8803 11 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
8804 : {
8805 2 : sqlite3_result_int(pContext, 0);
8806 2 : return;
8807 : }
8808 :
8809 : const char *pszTableName =
8810 10 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8811 : const char *pszGeomName =
8812 10 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8813 : GDALGeoPackageDataset *poDS =
8814 10 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8815 :
8816 10 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
8817 10 : poDS->GetLayerByName(pszTableName));
8818 10 : if (poLyr == nullptr)
8819 : {
8820 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
8821 1 : sqlite3_result_int(pContext, 0);
8822 1 : return;
8823 : }
8824 9 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
8825 : {
8826 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
8827 1 : sqlite3_result_int(pContext, 0);
8828 1 : return;
8829 : }
8830 :
8831 8 : sqlite3_result_int(pContext, poLyr->DropSpatialIndex(true));
8832 : }
8833 :
8834 : /************************************************************************/
8835 : /* OGRGeoPackageHasSpatialIndex() */
8836 : /************************************************************************/
8837 :
8838 29 : static void OGRGeoPackageHasSpatialIndex(sqlite3_context *pContext,
8839 : int /*argc*/, sqlite3_value **argv)
8840 : {
8841 57 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8842 28 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
8843 : {
8844 2 : sqlite3_result_int(pContext, 0);
8845 2 : return;
8846 : }
8847 :
8848 : const char *pszTableName =
8849 27 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8850 : const char *pszGeomName =
8851 27 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8852 : GDALGeoPackageDataset *poDS =
8853 27 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8854 :
8855 27 : OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
8856 27 : poDS->GetLayerByName(pszTableName));
8857 27 : if (poLyr == nullptr)
8858 : {
8859 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
8860 1 : sqlite3_result_int(pContext, 0);
8861 1 : return;
8862 : }
8863 26 : if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
8864 : {
8865 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
8866 1 : sqlite3_result_int(pContext, 0);
8867 1 : return;
8868 : }
8869 :
8870 25 : poLyr->RunDeferredCreationIfNecessary();
8871 25 : poLyr->CreateSpatialIndexIfNecessary();
8872 :
8873 25 : sqlite3_result_int(pContext, poLyr->HasSpatialIndex());
8874 : }
8875 :
8876 : /************************************************************************/
8877 : /* GPKG_hstore_get_value() */
8878 : /************************************************************************/
8879 :
8880 4 : static void GPKG_hstore_get_value(sqlite3_context *pContext, int /*argc*/,
8881 : sqlite3_value **argv)
8882 : {
8883 7 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8884 3 : sqlite3_value_type(argv[1]) != SQLITE_TEXT)
8885 : {
8886 2 : sqlite3_result_null(pContext);
8887 2 : return;
8888 : }
8889 :
8890 : const char *pszHStore =
8891 2 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8892 : const char *pszSearchedKey =
8893 2 : reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8894 2 : char *pszValue = OGRHStoreGetValue(pszHStore, pszSearchedKey);
8895 2 : if (pszValue != nullptr)
8896 1 : sqlite3_result_text(pContext, pszValue, -1, CPLFree);
8897 : else
8898 1 : sqlite3_result_null(pContext);
8899 : }
8900 :
8901 : /************************************************************************/
8902 : /* GPKG_GDAL_GetMemFileFromBlob() */
8903 : /************************************************************************/
8904 :
8905 105 : static CPLString GPKG_GDAL_GetMemFileFromBlob(sqlite3_value **argv)
8906 : {
8907 105 : int nBytes = sqlite3_value_bytes(argv[0]);
8908 : const GByte *pabyBLOB =
8909 105 : reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8910 105 : CPLString osMemFileName;
8911 105 : osMemFileName.Printf("/vsimem/GPKG_GDAL_GetMemFileFromBlob_%p", argv);
8912 105 : VSILFILE *fp = VSIFileFromMemBuffer(
8913 : osMemFileName.c_str(), const_cast<GByte *>(pabyBLOB), nBytes, FALSE);
8914 105 : VSIFCloseL(fp);
8915 105 : return osMemFileName;
8916 : }
8917 :
8918 : /************************************************************************/
8919 : /* GPKG_GDAL_GetMimeType() */
8920 : /************************************************************************/
8921 :
8922 35 : static void GPKG_GDAL_GetMimeType(sqlite3_context *pContext, int /*argc*/,
8923 : sqlite3_value **argv)
8924 : {
8925 35 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8926 : {
8927 0 : sqlite3_result_null(pContext);
8928 0 : return;
8929 : }
8930 :
8931 70 : CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
8932 : GDALDriver *poDriver =
8933 35 : GDALDriver::FromHandle(GDALIdentifyDriver(osMemFileName, nullptr));
8934 35 : if (poDriver != nullptr)
8935 : {
8936 35 : const char *pszRes = nullptr;
8937 35 : if (EQUAL(poDriver->GetDescription(), "PNG"))
8938 23 : pszRes = "image/png";
8939 12 : else if (EQUAL(poDriver->GetDescription(), "JPEG"))
8940 6 : pszRes = "image/jpeg";
8941 6 : else if (EQUAL(poDriver->GetDescription(), "WEBP"))
8942 6 : pszRes = "image/x-webp";
8943 0 : else if (EQUAL(poDriver->GetDescription(), "GTIFF"))
8944 0 : pszRes = "image/tiff";
8945 : else
8946 0 : pszRes = CPLSPrintf("gdal/%s", poDriver->GetDescription());
8947 35 : sqlite3_result_text(pContext, pszRes, -1, SQLITE_TRANSIENT);
8948 : }
8949 : else
8950 0 : sqlite3_result_null(pContext);
8951 35 : VSIUnlink(osMemFileName);
8952 : }
8953 :
8954 : /************************************************************************/
8955 : /* GPKG_GDAL_GetBandCount() */
8956 : /************************************************************************/
8957 :
8958 35 : static void GPKG_GDAL_GetBandCount(sqlite3_context *pContext, int /*argc*/,
8959 : sqlite3_value **argv)
8960 : {
8961 35 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8962 : {
8963 0 : sqlite3_result_null(pContext);
8964 0 : return;
8965 : }
8966 :
8967 70 : CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
8968 : auto poDS = std::unique_ptr<GDALDataset>(
8969 : GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
8970 70 : nullptr, nullptr, nullptr));
8971 35 : if (poDS != nullptr)
8972 : {
8973 35 : sqlite3_result_int(pContext, poDS->GetRasterCount());
8974 : }
8975 : else
8976 0 : sqlite3_result_null(pContext);
8977 35 : VSIUnlink(osMemFileName);
8978 : }
8979 :
8980 : /************************************************************************/
8981 : /* GPKG_GDAL_HasColorTable() */
8982 : /************************************************************************/
8983 :
8984 35 : static void GPKG_GDAL_HasColorTable(sqlite3_context *pContext, int /*argc*/,
8985 : sqlite3_value **argv)
8986 : {
8987 35 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8988 : {
8989 0 : sqlite3_result_null(pContext);
8990 0 : return;
8991 : }
8992 :
8993 70 : CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
8994 : auto poDS = std::unique_ptr<GDALDataset>(
8995 : GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
8996 70 : nullptr, nullptr, nullptr));
8997 35 : if (poDS != nullptr)
8998 : {
8999 35 : sqlite3_result_int(
9000 46 : pContext, poDS->GetRasterCount() == 1 &&
9001 11 : poDS->GetRasterBand(1)->GetColorTable() != nullptr);
9002 : }
9003 : else
9004 0 : sqlite3_result_null(pContext);
9005 35 : VSIUnlink(osMemFileName);
9006 : }
9007 :
9008 : /************************************************************************/
9009 : /* GetRasterLayerDataset() */
9010 : /************************************************************************/
9011 :
9012 : GDALDataset *
9013 6 : GDALGeoPackageDataset::GetRasterLayerDataset(const char *pszLayerName)
9014 : {
9015 6 : auto oIter = m_oCachedRasterDS.find(pszLayerName);
9016 6 : if (oIter != m_oCachedRasterDS.end())
9017 4 : return oIter->second.get();
9018 :
9019 : auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
9020 4 : (std::string("GPKG:\"") + m_pszFilename + "\":" + pszLayerName).c_str(),
9021 4 : GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
9022 2 : if (!poDS)
9023 : {
9024 0 : return nullptr;
9025 : }
9026 2 : m_oCachedRasterDS[pszLayerName] = std::move(poDS);
9027 2 : return m_oCachedRasterDS[pszLayerName].get();
9028 : }
9029 :
9030 : /************************************************************************/
9031 : /* GPKG_gdal_get_layer_pixel_value() */
9032 : /************************************************************************/
9033 :
9034 11 : static void GPKG_gdal_get_layer_pixel_value(sqlite3_context *pContext,
9035 : CPL_UNUSED int argc,
9036 : sqlite3_value **argv)
9037 : {
9038 11 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9039 10 : sqlite3_value_type(argv[1]) != SQLITE_INTEGER ||
9040 9 : sqlite3_value_type(argv[2]) != SQLITE_TEXT ||
9041 8 : (sqlite3_value_type(argv[3]) != SQLITE_INTEGER &&
9042 21 : sqlite3_value_type(argv[3]) != SQLITE_FLOAT) ||
9043 7 : (sqlite3_value_type(argv[4]) != SQLITE_INTEGER &&
9044 1 : sqlite3_value_type(argv[4]) != SQLITE_FLOAT))
9045 : {
9046 5 : CPLError(CE_Failure, CPLE_AppDefined,
9047 : "Invalid arguments to gdal_get_layer_pixel_value()");
9048 5 : sqlite3_result_null(pContext);
9049 5 : return;
9050 : }
9051 :
9052 : const char *pszLayerName =
9053 6 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9054 :
9055 : GDALGeoPackageDataset *poGlobalDS =
9056 6 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9057 6 : auto poDS = poGlobalDS->GetRasterLayerDataset(pszLayerName);
9058 6 : if (!poDS)
9059 : {
9060 0 : sqlite3_result_null(pContext);
9061 0 : return;
9062 : }
9063 :
9064 6 : const int nBand = sqlite3_value_int(argv[1]);
9065 6 : auto poBand = poDS->GetRasterBand(nBand);
9066 6 : if (!poBand)
9067 : {
9068 1 : sqlite3_result_null(pContext);
9069 1 : return;
9070 : }
9071 :
9072 : const char *pszCoordType =
9073 5 : reinterpret_cast<const char *>(sqlite3_value_text(argv[2]));
9074 : int x, y;
9075 5 : if (EQUAL(pszCoordType, "georef"))
9076 : {
9077 1 : const double X = sqlite3_value_double(argv[3]);
9078 1 : const double Y = sqlite3_value_double(argv[4]);
9079 : double adfGeoTransform[6];
9080 1 : if (poDS->GetGeoTransform(adfGeoTransform) != CE_None)
9081 : {
9082 0 : sqlite3_result_null(pContext);
9083 0 : return;
9084 : }
9085 : double adfInvGT[6];
9086 1 : if (!GDALInvGeoTransform(adfGeoTransform, adfInvGT))
9087 : {
9088 0 : sqlite3_result_null(pContext);
9089 0 : return;
9090 : }
9091 1 : x = static_cast<int>(adfInvGT[0] + X * adfInvGT[1] + Y * adfInvGT[2]);
9092 1 : y = static_cast<int>(adfInvGT[3] + X * adfInvGT[4] + Y * adfInvGT[5]);
9093 : }
9094 4 : else if (EQUAL(pszCoordType, "pixel"))
9095 : {
9096 3 : x = sqlite3_value_int(argv[3]);
9097 3 : y = sqlite3_value_int(argv[4]);
9098 : }
9099 : else
9100 : {
9101 1 : CPLError(CE_Failure, CPLE_AppDefined,
9102 : "Invalid value for 3rd argument of gdal_get_pixel_value(): "
9103 : "only 'georef' or 'pixel' are supported");
9104 1 : sqlite3_result_null(pContext);
9105 1 : return;
9106 : }
9107 7 : if (x < 0 || x >= poDS->GetRasterXSize() || y < 0 ||
9108 3 : y >= poDS->GetRasterYSize())
9109 : {
9110 1 : sqlite3_result_null(pContext);
9111 1 : return;
9112 : }
9113 3 : const auto eDT = poBand->GetRasterDataType();
9114 3 : if (eDT != GDT_UInt64 && GDALDataTypeIsInteger(eDT))
9115 : {
9116 2 : int64_t nValue = 0;
9117 2 : if (poBand->RasterIO(GF_Read, x, y, 1, 1, &nValue, 1, 1, GDT_Int64, 0,
9118 2 : 0, nullptr) != CE_None)
9119 : {
9120 0 : sqlite3_result_null(pContext);
9121 0 : return;
9122 : }
9123 2 : return sqlite3_result_int64(pContext, nValue);
9124 : }
9125 : else
9126 : {
9127 1 : double dfValue = 0;
9128 1 : if (poBand->RasterIO(GF_Read, x, y, 1, 1, &dfValue, 1, 1, GDT_Float64,
9129 1 : 0, 0, nullptr) != CE_None)
9130 : {
9131 0 : sqlite3_result_null(pContext);
9132 0 : return;
9133 : }
9134 1 : return sqlite3_result_double(pContext, dfValue);
9135 : }
9136 : }
9137 :
9138 : /************************************************************************/
9139 : /* GPKG_ogr_layer_Extent() */
9140 : /************************************************************************/
9141 :
9142 3 : static void GPKG_ogr_layer_Extent(sqlite3_context *pContext, int /*argc*/,
9143 : sqlite3_value **argv)
9144 : {
9145 3 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
9146 : {
9147 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s: Invalid argument type",
9148 : "ogr_layer_Extent");
9149 1 : sqlite3_result_null(pContext);
9150 2 : return;
9151 : }
9152 :
9153 : const char *pszLayerName =
9154 2 : reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9155 : GDALGeoPackageDataset *poDS =
9156 2 : static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9157 2 : OGRLayer *poLayer = poDS->GetLayerByName(pszLayerName);
9158 2 : if (!poLayer)
9159 : {
9160 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s: unknown layer",
9161 : "ogr_layer_Extent");
9162 1 : sqlite3_result_null(pContext);
9163 1 : return;
9164 : }
9165 :
9166 1 : if (poLayer->GetGeomType() == wkbNone)
9167 : {
9168 0 : sqlite3_result_null(pContext);
9169 0 : return;
9170 : }
9171 :
9172 1 : OGREnvelope sExtent;
9173 1 : if (poLayer->GetExtent(&sExtent) != OGRERR_NONE)
9174 : {
9175 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s: Cannot fetch layer extent",
9176 : "ogr_layer_Extent");
9177 0 : sqlite3_result_null(pContext);
9178 0 : return;
9179 : }
9180 :
9181 1 : OGRPolygon oPoly;
9182 1 : OGRLinearRing *poRing = new OGRLinearRing();
9183 1 : oPoly.addRingDirectly(poRing);
9184 1 : poRing->addPoint(sExtent.MinX, sExtent.MinY);
9185 1 : poRing->addPoint(sExtent.MaxX, sExtent.MinY);
9186 1 : poRing->addPoint(sExtent.MaxX, sExtent.MaxY);
9187 1 : poRing->addPoint(sExtent.MinX, sExtent.MaxY);
9188 1 : poRing->addPoint(sExtent.MinX, sExtent.MinY);
9189 :
9190 1 : const auto poSRS = poLayer->GetSpatialRef();
9191 1 : const int nSRID = poDS->GetSrsId(poSRS);
9192 1 : size_t nBLOBDestLen = 0;
9193 : GByte *pabyDestBLOB =
9194 1 : GPkgGeometryFromOGR(&oPoly, nSRID, nullptr, &nBLOBDestLen);
9195 1 : if (!pabyDestBLOB)
9196 : {
9197 0 : sqlite3_result_null(pContext);
9198 0 : return;
9199 : }
9200 1 : sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
9201 : VSIFree);
9202 : }
9203 :
9204 : /************************************************************************/
9205 : /* InstallSQLFunctions() */
9206 : /************************************************************************/
9207 :
9208 : #ifndef SQLITE_DETERMINISTIC
9209 : #define SQLITE_DETERMINISTIC 0
9210 : #endif
9211 :
9212 : #ifndef SQLITE_INNOCUOUS
9213 : #define SQLITE_INNOCUOUS 0
9214 : #endif
9215 :
9216 : #ifndef UTF8_INNOCUOUS
9217 : #define UTF8_INNOCUOUS (SQLITE_UTF8 | SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS)
9218 : #endif
9219 :
9220 1671 : void GDALGeoPackageDataset::InstallSQLFunctions()
9221 : {
9222 1671 : InitSpatialite();
9223 :
9224 : // Enable SpatiaLite 4.3 "amphibious" mode, i.e. that SpatiaLite functions
9225 : // that take geometries will accept GPKG encoded geometries without
9226 : // explicit conversion.
9227 : // Use sqlite3_exec() instead of SQLCommand() since we don't want verbose
9228 : // error.
9229 1671 : sqlite3_exec(hDB, "SELECT EnableGpkgAmphibiousMode()", nullptr, nullptr,
9230 : nullptr);
9231 :
9232 : /* Used by RTree Spatial Index Extension */
9233 1671 : sqlite3_create_function(hDB, "ST_MinX", 1, UTF8_INNOCUOUS, nullptr,
9234 : OGRGeoPackageSTMinX, nullptr, nullptr);
9235 1671 : sqlite3_create_function(hDB, "ST_MinY", 1, UTF8_INNOCUOUS, nullptr,
9236 : OGRGeoPackageSTMinY, nullptr, nullptr);
9237 1671 : sqlite3_create_function(hDB, "ST_MaxX", 1, UTF8_INNOCUOUS, nullptr,
9238 : OGRGeoPackageSTMaxX, nullptr, nullptr);
9239 1671 : sqlite3_create_function(hDB, "ST_MaxY", 1, UTF8_INNOCUOUS, nullptr,
9240 : OGRGeoPackageSTMaxY, nullptr, nullptr);
9241 1671 : sqlite3_create_function(hDB, "ST_IsEmpty", 1, UTF8_INNOCUOUS, nullptr,
9242 : OGRGeoPackageSTIsEmpty, nullptr, nullptr);
9243 :
9244 : /* Used by Geometry Type Triggers Extension */
9245 1671 : sqlite3_create_function(hDB, "ST_GeometryType", 1, UTF8_INNOCUOUS, nullptr,
9246 : OGRGeoPackageSTGeometryType, nullptr, nullptr);
9247 1671 : sqlite3_create_function(hDB, "GPKG_IsAssignable", 2, UTF8_INNOCUOUS,
9248 : nullptr, OGRGeoPackageGPKGIsAssignable, nullptr,
9249 : nullptr);
9250 :
9251 : /* Used by Geometry SRS ID Triggers Extension */
9252 1671 : sqlite3_create_function(hDB, "ST_SRID", 1, UTF8_INNOCUOUS, nullptr,
9253 : OGRGeoPackageSTSRID, nullptr, nullptr);
9254 :
9255 : /* Spatialite-like functions */
9256 1671 : sqlite3_create_function(hDB, "CreateSpatialIndex", 2, SQLITE_UTF8, this,
9257 : OGRGeoPackageCreateSpatialIndex, nullptr, nullptr);
9258 1671 : sqlite3_create_function(hDB, "DisableSpatialIndex", 2, SQLITE_UTF8, this,
9259 : OGRGeoPackageDisableSpatialIndex, nullptr, nullptr);
9260 1671 : sqlite3_create_function(hDB, "HasSpatialIndex", 2, SQLITE_UTF8, this,
9261 : OGRGeoPackageHasSpatialIndex, nullptr, nullptr);
9262 :
9263 : // HSTORE functions
9264 1671 : sqlite3_create_function(hDB, "hstore_get_value", 2, UTF8_INNOCUOUS, nullptr,
9265 : GPKG_hstore_get_value, nullptr, nullptr);
9266 :
9267 : // Override a few Spatialite functions to work with gpkg_spatial_ref_sys
9268 1671 : sqlite3_create_function(hDB, "ST_Transform", 2, UTF8_INNOCUOUS, this,
9269 : OGRGeoPackageTransform, nullptr, nullptr);
9270 1671 : sqlite3_create_function(hDB, "Transform", 2, UTF8_INNOCUOUS, this,
9271 : OGRGeoPackageTransform, nullptr, nullptr);
9272 1671 : sqlite3_create_function(hDB, "SridFromAuthCRS", 2, SQLITE_UTF8, this,
9273 : OGRGeoPackageSridFromAuthCRS, nullptr, nullptr);
9274 :
9275 1671 : sqlite3_create_function(hDB, "ST_EnvIntersects", 2, UTF8_INNOCUOUS, nullptr,
9276 : OGRGeoPackageSTEnvelopesIntersectsTwoParams,
9277 : nullptr, nullptr);
9278 1671 : sqlite3_create_function(
9279 : hDB, "ST_EnvelopesIntersects", 2, UTF8_INNOCUOUS, nullptr,
9280 : OGRGeoPackageSTEnvelopesIntersectsTwoParams, nullptr, nullptr);
9281 :
9282 1671 : sqlite3_create_function(hDB, "ST_EnvIntersects", 5, UTF8_INNOCUOUS, nullptr,
9283 : OGRGeoPackageSTEnvelopesIntersects, nullptr,
9284 : nullptr);
9285 1671 : sqlite3_create_function(hDB, "ST_EnvelopesIntersects", 5, UTF8_INNOCUOUS,
9286 : nullptr, OGRGeoPackageSTEnvelopesIntersects,
9287 : nullptr, nullptr);
9288 :
9289 : // Implementation that directly hacks the GeoPackage geometry blob header
9290 1671 : sqlite3_create_function(hDB, "SetSRID", 2, UTF8_INNOCUOUS, nullptr,
9291 : OGRGeoPackageSetSRID, nullptr, nullptr);
9292 :
9293 : // GDAL specific function
9294 1671 : sqlite3_create_function(hDB, "ImportFromEPSG", 1, SQLITE_UTF8, this,
9295 : OGRGeoPackageImportFromEPSG, nullptr, nullptr);
9296 :
9297 : // May be used by ogrmerge.py
9298 1671 : sqlite3_create_function(hDB, "RegisterGeometryExtension", 3, SQLITE_UTF8,
9299 : this, OGRGeoPackageRegisterGeometryExtension,
9300 : nullptr, nullptr);
9301 :
9302 1671 : if (OGRGeometryFactory::haveGEOS())
9303 : {
9304 1671 : sqlite3_create_function(hDB, "ST_MakeValid", 1, UTF8_INNOCUOUS, nullptr,
9305 : OGRGeoPackageSTMakeValid, nullptr, nullptr);
9306 : }
9307 :
9308 1671 : sqlite3_create_function(hDB, "ST_Area", 1, UTF8_INNOCUOUS, nullptr,
9309 : OGRGeoPackageSTArea, nullptr, nullptr);
9310 1671 : sqlite3_create_function(hDB, "ST_Area", 2, UTF8_INNOCUOUS, this,
9311 : OGRGeoPackageGeodesicArea, nullptr, nullptr);
9312 :
9313 : // Debug functions
9314 1671 : if (CPLTestBool(CPLGetConfigOption("GPKG_DEBUG", "FALSE")))
9315 : {
9316 409 : sqlite3_create_function(hDB, "GDAL_GetMimeType", 1,
9317 : SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9318 : GPKG_GDAL_GetMimeType, nullptr, nullptr);
9319 409 : sqlite3_create_function(hDB, "GDAL_GetBandCount", 1,
9320 : SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9321 : GPKG_GDAL_GetBandCount, nullptr, nullptr);
9322 409 : sqlite3_create_function(hDB, "GDAL_HasColorTable", 1,
9323 : SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9324 : GPKG_GDAL_HasColorTable, nullptr, nullptr);
9325 : }
9326 :
9327 1671 : sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 5, SQLITE_UTF8,
9328 : this, GPKG_gdal_get_layer_pixel_value, nullptr,
9329 : nullptr);
9330 :
9331 : // Function from VirtualOGR
9332 1671 : sqlite3_create_function(hDB, "ogr_layer_Extent", 1, SQLITE_UTF8, this,
9333 : GPKG_ogr_layer_Extent, nullptr, nullptr);
9334 :
9335 1671 : m_pSQLFunctionData = OGRSQLiteRegisterSQLFunctionsCommon(hDB);
9336 1671 : }
9337 :
9338 : /************************************************************************/
9339 : /* OpenOrCreateDB() */
9340 : /************************************************************************/
9341 :
9342 1672 : bool GDALGeoPackageDataset::OpenOrCreateDB(int flags)
9343 : {
9344 1672 : const bool bSuccess = OGRSQLiteBaseDataSource::OpenOrCreateDB(
9345 : flags, /*bRegisterOGR2SQLiteExtensions=*/false,
9346 : /*bLoadExtensions=*/true);
9347 1672 : if (!bSuccess)
9348 6 : return false;
9349 :
9350 : // Turning on recursive_triggers is needed so that DELETE triggers fire
9351 : // in a INSERT OR REPLACE statement. In particular this is needed to
9352 : // make sure gpkg_ogr_contents.feature_count is properly updated.
9353 1666 : SQLCommand(hDB, "PRAGMA recursive_triggers = 1");
9354 :
9355 1666 : InstallSQLFunctions();
9356 :
9357 : const char *pszSqlitePragma =
9358 1666 : CPLGetConfigOption("OGR_SQLITE_PRAGMA", nullptr);
9359 1666 : OGRErr eErr = OGRERR_NONE;
9360 5 : if ((!pszSqlitePragma || !strstr(pszSqlitePragma, "trusted_schema")) &&
9361 : // Older sqlite versions don't have this pragma
9362 3337 : SQLGetInteger(hDB, "PRAGMA trusted_schema", &eErr) == 0 &&
9363 1666 : eErr == OGRERR_NONE)
9364 : {
9365 1666 : bool bNeedsTrustedSchema = false;
9366 :
9367 : // Current SQLite versions require PRAGMA trusted_schema = 1 to be
9368 : // able to use the RTree from triggers, which is only needed when
9369 : // modifying the RTree.
9370 4093 : if (((flags & SQLITE_OPEN_READWRITE) != 0 ||
9371 2571 : (flags & SQLITE_OPEN_CREATE) != 0) &&
9372 905 : OGRSQLiteRTreeRequiresTrustedSchemaOn())
9373 : {
9374 905 : bNeedsTrustedSchema = true;
9375 : }
9376 :
9377 : #ifdef HAVE_SPATIALITE
9378 : // Spatialite <= 5.1.0 doesn't declare its functions as SQLITE_INNOCUOUS
9379 761 : if (!bNeedsTrustedSchema && HasExtensionsTable() &&
9380 683 : SQLGetInteger(
9381 : hDB,
9382 : "SELECT 1 FROM gpkg_extensions WHERE "
9383 : "extension_name ='gdal_spatialite_computed_geom_column'",
9384 1 : nullptr) == 1 &&
9385 2427 : SpatialiteRequiresTrustedSchemaOn() && AreSpatialiteTriggersSafe())
9386 : {
9387 1 : bNeedsTrustedSchema = true;
9388 : }
9389 : #endif
9390 :
9391 1666 : if (bNeedsTrustedSchema)
9392 : {
9393 906 : CPLDebug("GPKG", "Setting PRAGMA trusted_schema = 1");
9394 906 : SQLCommand(hDB, "PRAGMA trusted_schema = 1");
9395 : }
9396 : }
9397 :
9398 1666 : return true;
9399 : }
9400 :
9401 : /************************************************************************/
9402 : /* GetLayerWithGetSpatialWhereByName() */
9403 : /************************************************************************/
9404 :
9405 : std::pair<OGRLayer *, IOGRSQLiteGetSpatialWhere *>
9406 90 : GDALGeoPackageDataset::GetLayerWithGetSpatialWhereByName(const char *pszName)
9407 : {
9408 : OGRGeoPackageLayer *poRet =
9409 90 : cpl::down_cast<OGRGeoPackageLayer *>(GetLayerByName(pszName));
9410 90 : return std::pair(poRet, poRet);
9411 : }
9412 :
9413 : /************************************************************************/
9414 : /* CommitTransaction() */
9415 : /************************************************************************/
9416 :
9417 143 : OGRErr GDALGeoPackageDataset::CommitTransaction()
9418 :
9419 : {
9420 143 : if (nSoftTransactionLevel == 1)
9421 : {
9422 142 : FlushMetadata();
9423 322 : for (int i = 0; i < m_nLayers; i++)
9424 : {
9425 180 : m_papoLayers[i]->DoJobAtTransactionCommit();
9426 : }
9427 : }
9428 :
9429 143 : return OGRSQLiteBaseDataSource::CommitTransaction();
9430 : }
9431 :
9432 : /************************************************************************/
9433 : /* RollbackTransaction() */
9434 : /************************************************************************/
9435 :
9436 29 : OGRErr GDALGeoPackageDataset::RollbackTransaction()
9437 :
9438 : {
9439 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9440 58 : std::vector<bool> abAddTriggers;
9441 29 : std::vector<bool> abTriggersDeletedInTransaction;
9442 : #endif
9443 29 : if (nSoftTransactionLevel == 1)
9444 : {
9445 28 : FlushMetadata();
9446 58 : for (int i = 0; i < m_nLayers; i++)
9447 : {
9448 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9449 30 : abAddTriggers.push_back(
9450 30 : m_papoLayers[i]->GetAddOGRFeatureCountTriggers());
9451 30 : abTriggersDeletedInTransaction.push_back(
9452 30 : m_papoLayers[i]
9453 30 : ->GetOGRFeatureCountTriggersDeletedInTransaction());
9454 30 : m_papoLayers[i]->SetAddOGRFeatureCountTriggers(false);
9455 : #endif
9456 30 : m_papoLayers[i]->DoJobAtTransactionRollback();
9457 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9458 30 : m_papoLayers[i]->DisableFeatureCount();
9459 : #endif
9460 : }
9461 : }
9462 :
9463 29 : OGRErr eErr = OGRSQLiteBaseDataSource::RollbackTransaction();
9464 : #ifdef ENABLE_GPKG_OGR_CONTENTS
9465 29 : if (!abAddTriggers.empty())
9466 : {
9467 56 : for (int i = 0; i < m_nLayers; i++)
9468 : {
9469 30 : if (abTriggersDeletedInTransaction[i])
9470 : {
9471 7 : m_papoLayers[i]->SetOGRFeatureCountTriggersEnabled(true);
9472 : }
9473 : else
9474 : {
9475 23 : m_papoLayers[i]->SetAddOGRFeatureCountTriggers(
9476 46 : abAddTriggers[i]);
9477 : }
9478 : }
9479 : }
9480 : #endif
9481 58 : return eErr;
9482 : }
9483 :
9484 : /************************************************************************/
9485 : /* GetGeometryTypeString() */
9486 : /************************************************************************/
9487 :
9488 : const char *
9489 1177 : GDALGeoPackageDataset::GetGeometryTypeString(OGRwkbGeometryType eType)
9490 : {
9491 1177 : const char *pszGPKGGeomType = OGRToOGCGeomType(eType);
9492 1189 : if (EQUAL(pszGPKGGeomType, "GEOMETRYCOLLECTION") &&
9493 12 : CPLTestBool(CPLGetConfigOption("OGR_GPKG_GEOMCOLLECTION", "NO")))
9494 : {
9495 0 : pszGPKGGeomType = "GEOMCOLLECTION";
9496 : }
9497 1177 : return pszGPKGGeomType;
9498 : }
9499 :
9500 : /************************************************************************/
9501 : /* GetFieldDomainNames() */
9502 : /************************************************************************/
9503 :
9504 : std::vector<std::string>
9505 10 : GDALGeoPackageDataset::GetFieldDomainNames(CSLConstList) const
9506 : {
9507 10 : if (!HasDataColumnConstraintsTable())
9508 3 : return std::vector<std::string>();
9509 :
9510 14 : std::vector<std::string> oDomainNamesList;
9511 :
9512 7 : std::unique_ptr<SQLResult> oResultTable;
9513 : {
9514 : std::string osSQL =
9515 : "SELECT DISTINCT constraint_name "
9516 : "FROM gpkg_data_column_constraints "
9517 : "WHERE constraint_name NOT LIKE '_%_domain_description' "
9518 : "ORDER BY constraint_name "
9519 7 : "LIMIT 10000" // to avoid denial of service
9520 : ;
9521 7 : oResultTable = SQLQuery(hDB, osSQL.c_str());
9522 7 : if (!oResultTable)
9523 0 : return oDomainNamesList;
9524 : }
9525 :
9526 7 : if (oResultTable->RowCount() == 10000)
9527 : {
9528 0 : CPLError(CE_Warning, CPLE_AppDefined,
9529 : "Number of rows returned for field domain names has been "
9530 : "truncated.");
9531 : }
9532 7 : else if (oResultTable->RowCount() > 0)
9533 : {
9534 7 : oDomainNamesList.reserve(oResultTable->RowCount());
9535 89 : for (int i = 0; i < oResultTable->RowCount(); i++)
9536 : {
9537 82 : const char *pszConstraintName = oResultTable->GetValue(0, i);
9538 82 : if (!pszConstraintName)
9539 0 : continue;
9540 :
9541 82 : oDomainNamesList.emplace_back(pszConstraintName);
9542 : }
9543 : }
9544 :
9545 7 : return oDomainNamesList;
9546 : }
9547 :
9548 : /************************************************************************/
9549 : /* GetFieldDomain() */
9550 : /************************************************************************/
9551 :
9552 : const OGRFieldDomain *
9553 102 : GDALGeoPackageDataset::GetFieldDomain(const std::string &name) const
9554 : {
9555 102 : const auto baseRet = GDALDataset::GetFieldDomain(name);
9556 102 : if (baseRet)
9557 42 : return baseRet;
9558 :
9559 60 : if (!HasDataColumnConstraintsTable())
9560 4 : return nullptr;
9561 :
9562 56 : const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
9563 56 : const char *min_is_inclusive =
9564 56 : bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
9565 56 : const char *max_is_inclusive =
9566 56 : bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
9567 :
9568 56 : std::unique_ptr<SQLResult> oResultTable;
9569 : // Note: for coded domains, we use a little trick by using a dummy
9570 : // _{domainname}_domain_description enum that has a single entry whose
9571 : // description is the description of the main domain.
9572 : {
9573 56 : char *pszSQL = sqlite3_mprintf(
9574 : "SELECT constraint_type, value, min, %s, "
9575 : "max, %s, description, constraint_name "
9576 : "FROM gpkg_data_column_constraints "
9577 : "WHERE constraint_name IN ('%q', "
9578 : "'_%q_domain_description') "
9579 : "AND length(constraint_type) < 100 " // to
9580 : // avoid
9581 : // denial
9582 : // of
9583 : // service
9584 : "AND (value IS NULL OR length(value) < "
9585 : "10000) " // to avoid denial
9586 : // of service
9587 : "AND (description IS NULL OR "
9588 : "length(description) < 10000) " // to
9589 : // avoid
9590 : // denial
9591 : // of
9592 : // service
9593 : "ORDER BY value "
9594 : "LIMIT 10000", // to avoid denial of
9595 : // service
9596 : min_is_inclusive, max_is_inclusive, name.c_str(), name.c_str());
9597 56 : oResultTable = SQLQuery(hDB, pszSQL);
9598 56 : sqlite3_free(pszSQL);
9599 56 : if (!oResultTable)
9600 0 : return nullptr;
9601 : }
9602 56 : if (oResultTable->RowCount() == 0)
9603 : {
9604 15 : return nullptr;
9605 : }
9606 41 : if (oResultTable->RowCount() == 10000)
9607 : {
9608 0 : CPLError(CE_Warning, CPLE_AppDefined,
9609 : "Number of rows returned for field domain %s has been "
9610 : "truncated.",
9611 : name.c_str());
9612 : }
9613 :
9614 : // Try to find the field domain data type from fields that implement it
9615 41 : int nFieldType = -1;
9616 41 : OGRFieldSubType eSubType = OFSTNone;
9617 41 : if (HasDataColumnsTable())
9618 : {
9619 36 : char *pszSQL = sqlite3_mprintf(
9620 : "SELECT table_name, column_name FROM gpkg_data_columns WHERE "
9621 : "constraint_name = '%q' LIMIT 10",
9622 : name.c_str());
9623 72 : auto oResultTable2 = SQLQuery(hDB, pszSQL);
9624 36 : sqlite3_free(pszSQL);
9625 36 : if (oResultTable2 && oResultTable2->RowCount() >= 1)
9626 : {
9627 46 : for (int iRecord = 0; iRecord < oResultTable2->RowCount();
9628 : iRecord++)
9629 : {
9630 23 : const char *pszTableName = oResultTable2->GetValue(0, iRecord);
9631 23 : const char *pszColumnName = oResultTable2->GetValue(1, iRecord);
9632 23 : if (pszTableName == nullptr || pszColumnName == nullptr)
9633 0 : continue;
9634 : OGRLayer *poLayer =
9635 46 : const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
9636 23 : pszTableName);
9637 23 : if (poLayer)
9638 : {
9639 23 : const auto poFDefn = poLayer->GetLayerDefn();
9640 23 : int nIdx = poFDefn->GetFieldIndex(pszColumnName);
9641 23 : if (nIdx >= 0)
9642 : {
9643 23 : const auto poFieldDefn = poFDefn->GetFieldDefn(nIdx);
9644 23 : const auto eType = poFieldDefn->GetType();
9645 23 : if (nFieldType < 0)
9646 : {
9647 23 : nFieldType = eType;
9648 23 : eSubType = poFieldDefn->GetSubType();
9649 : }
9650 0 : else if ((eType == OFTInteger64 || eType == OFTReal) &&
9651 : nFieldType == OFTInteger)
9652 : {
9653 : // ok
9654 : }
9655 0 : else if (eType == OFTInteger &&
9656 0 : (nFieldType == OFTInteger64 ||
9657 : nFieldType == OFTReal))
9658 : {
9659 0 : nFieldType = OFTInteger;
9660 0 : eSubType = OFSTNone;
9661 : }
9662 0 : else if (nFieldType != eType)
9663 : {
9664 0 : nFieldType = -1;
9665 0 : eSubType = OFSTNone;
9666 0 : break;
9667 : }
9668 : }
9669 : }
9670 : }
9671 : }
9672 : }
9673 :
9674 41 : std::unique_ptr<OGRFieldDomain> poDomain;
9675 82 : std::vector<OGRCodedValue> asValues;
9676 41 : bool error = false;
9677 82 : CPLString osLastConstraintType;
9678 41 : int nFieldTypeFromEnumCode = -1;
9679 82 : std::string osConstraintDescription;
9680 82 : std::string osDescrConstraintName("_");
9681 41 : osDescrConstraintName += name;
9682 41 : osDescrConstraintName += "_domain_description";
9683 100 : for (int iRecord = 0; iRecord < oResultTable->RowCount(); iRecord++)
9684 : {
9685 63 : const char *pszConstraintType = oResultTable->GetValue(0, iRecord);
9686 63 : if (pszConstraintType == nullptr)
9687 0 : continue;
9688 63 : const char *pszValue = oResultTable->GetValue(1, iRecord);
9689 63 : const char *pszMin = oResultTable->GetValue(2, iRecord);
9690 : const bool bIsMinIncluded =
9691 63 : oResultTable->GetValueAsInteger(3, iRecord) == 1;
9692 63 : const char *pszMax = oResultTable->GetValue(4, iRecord);
9693 : const bool bIsMaxIncluded =
9694 63 : oResultTable->GetValueAsInteger(5, iRecord) == 1;
9695 63 : const char *pszDescription = oResultTable->GetValue(6, iRecord);
9696 63 : const char *pszConstraintName = oResultTable->GetValue(7, iRecord);
9697 :
9698 63 : if (!osLastConstraintType.empty() && osLastConstraintType != "enum")
9699 : {
9700 1 : CPLError(CE_Failure, CPLE_AppDefined,
9701 : "Only constraint of type 'enum' can have multiple rows");
9702 1 : error = true;
9703 1 : break;
9704 : }
9705 :
9706 62 : if (strcmp(pszConstraintType, "enum") == 0)
9707 : {
9708 42 : if (pszValue == nullptr)
9709 : {
9710 1 : CPLError(CE_Failure, CPLE_AppDefined,
9711 : "NULL in 'value' column of enumeration");
9712 1 : error = true;
9713 1 : break;
9714 : }
9715 41 : if (osDescrConstraintName == pszConstraintName)
9716 : {
9717 1 : if (pszDescription)
9718 : {
9719 1 : osConstraintDescription = pszDescription;
9720 : }
9721 1 : continue;
9722 : }
9723 40 : if (asValues.empty())
9724 : {
9725 20 : asValues.reserve(oResultTable->RowCount() + 1);
9726 : }
9727 : OGRCodedValue cv;
9728 : // intended: the 'value' column in GPKG is actually the code
9729 40 : cv.pszCode = VSI_STRDUP_VERBOSE(pszValue);
9730 40 : if (cv.pszCode == nullptr)
9731 : {
9732 0 : error = true;
9733 0 : break;
9734 : }
9735 40 : if (pszDescription)
9736 : {
9737 29 : cv.pszValue = VSI_STRDUP_VERBOSE(pszDescription);
9738 29 : if (cv.pszValue == nullptr)
9739 : {
9740 0 : VSIFree(cv.pszCode);
9741 0 : error = true;
9742 0 : break;
9743 : }
9744 : }
9745 : else
9746 : {
9747 11 : cv.pszValue = nullptr;
9748 : }
9749 :
9750 : // If we can't get the data type from field definition, guess it
9751 : // from code.
9752 40 : if (nFieldType < 0 && nFieldTypeFromEnumCode != OFTString)
9753 : {
9754 18 : switch (CPLGetValueType(cv.pszCode))
9755 : {
9756 13 : case CPL_VALUE_INTEGER:
9757 : {
9758 13 : if (nFieldTypeFromEnumCode != OFTReal &&
9759 : nFieldTypeFromEnumCode != OFTInteger64)
9760 : {
9761 9 : const auto nVal = CPLAtoGIntBig(cv.pszCode);
9762 17 : if (nVal < std::numeric_limits<int>::min() ||
9763 8 : nVal > std::numeric_limits<int>::max())
9764 : {
9765 3 : nFieldTypeFromEnumCode = OFTInteger64;
9766 : }
9767 : else
9768 : {
9769 6 : nFieldTypeFromEnumCode = OFTInteger;
9770 : }
9771 : }
9772 13 : break;
9773 : }
9774 :
9775 3 : case CPL_VALUE_REAL:
9776 3 : nFieldTypeFromEnumCode = OFTReal;
9777 3 : break;
9778 :
9779 2 : case CPL_VALUE_STRING:
9780 2 : nFieldTypeFromEnumCode = OFTString;
9781 2 : break;
9782 : }
9783 : }
9784 :
9785 40 : asValues.emplace_back(cv);
9786 : }
9787 20 : else if (strcmp(pszConstraintType, "range") == 0)
9788 : {
9789 : OGRField sMin;
9790 : OGRField sMax;
9791 14 : OGR_RawField_SetUnset(&sMin);
9792 14 : OGR_RawField_SetUnset(&sMax);
9793 14 : if (nFieldType != OFTInteger && nFieldType != OFTInteger64)
9794 8 : nFieldType = OFTReal;
9795 27 : if (pszMin != nullptr &&
9796 13 : CPLAtof(pszMin) != -std::numeric_limits<double>::infinity())
9797 : {
9798 10 : if (nFieldType == OFTInteger)
9799 3 : sMin.Integer = atoi(pszMin);
9800 7 : else if (nFieldType == OFTInteger64)
9801 3 : sMin.Integer64 = CPLAtoGIntBig(pszMin);
9802 : else /* if( nFieldType == OFTReal ) */
9803 4 : sMin.Real = CPLAtof(pszMin);
9804 : }
9805 27 : if (pszMax != nullptr &&
9806 13 : CPLAtof(pszMax) != std::numeric_limits<double>::infinity())
9807 : {
9808 10 : if (nFieldType == OFTInteger)
9809 3 : sMax.Integer = atoi(pszMax);
9810 7 : else if (nFieldType == OFTInteger64)
9811 3 : sMax.Integer64 = CPLAtoGIntBig(pszMax);
9812 : else /* if( nFieldType == OFTReal ) */
9813 4 : sMax.Real = CPLAtof(pszMax);
9814 : }
9815 28 : poDomain.reset(new OGRRangeFieldDomain(
9816 : name, pszDescription ? pszDescription : "",
9817 : static_cast<OGRFieldType>(nFieldType), eSubType, sMin,
9818 14 : bIsMinIncluded, sMax, bIsMaxIncluded));
9819 : }
9820 6 : else if (strcmp(pszConstraintType, "glob") == 0)
9821 : {
9822 5 : if (pszValue == nullptr)
9823 : {
9824 1 : CPLError(CE_Failure, CPLE_AppDefined,
9825 : "NULL in 'value' column of glob");
9826 1 : error = true;
9827 1 : break;
9828 : }
9829 4 : if (nFieldType < 0)
9830 1 : nFieldType = OFTString;
9831 8 : poDomain.reset(new OGRGlobFieldDomain(
9832 : name, pszDescription ? pszDescription : "",
9833 4 : static_cast<OGRFieldType>(nFieldType), eSubType, pszValue));
9834 : }
9835 : else
9836 : {
9837 1 : CPLError(CE_Failure, CPLE_AppDefined,
9838 : "Unhandled constraint_type: %s", pszConstraintType);
9839 1 : error = true;
9840 1 : break;
9841 : }
9842 :
9843 58 : osLastConstraintType = pszConstraintType;
9844 : }
9845 :
9846 41 : if (!asValues.empty())
9847 : {
9848 20 : if (nFieldType < 0)
9849 9 : nFieldType = nFieldTypeFromEnumCode;
9850 20 : poDomain.reset(
9851 : new OGRCodedFieldDomain(name, osConstraintDescription,
9852 : static_cast<OGRFieldType>(nFieldType),
9853 20 : eSubType, std::move(asValues)));
9854 : }
9855 :
9856 41 : if (error)
9857 : {
9858 4 : return nullptr;
9859 : }
9860 :
9861 37 : m_oMapFieldDomains[name] = std::move(poDomain);
9862 37 : return GDALDataset::GetFieldDomain(name);
9863 : }
9864 :
9865 : /************************************************************************/
9866 : /* AddFieldDomain() */
9867 : /************************************************************************/
9868 :
9869 18 : bool GDALGeoPackageDataset::AddFieldDomain(
9870 : std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
9871 : {
9872 36 : const std::string domainName(domain->GetName());
9873 18 : if (!GetUpdate())
9874 : {
9875 0 : CPLError(CE_Failure, CPLE_NotSupported,
9876 : "AddFieldDomain() not supported on read-only dataset");
9877 0 : return false;
9878 : }
9879 18 : if (GetFieldDomain(domainName) != nullptr)
9880 : {
9881 1 : failureReason = "A domain of identical name already exists";
9882 1 : return false;
9883 : }
9884 17 : if (!CreateColumnsTableAndColumnConstraintsTablesIfNecessary())
9885 0 : return false;
9886 :
9887 17 : const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
9888 17 : const char *min_is_inclusive =
9889 17 : bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
9890 17 : const char *max_is_inclusive =
9891 17 : bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
9892 :
9893 17 : const auto &osDescription = domain->GetDescription();
9894 17 : switch (domain->GetDomainType())
9895 : {
9896 11 : case OFDT_CODED:
9897 : {
9898 : const auto poCodedDomain =
9899 11 : cpl::down_cast<const OGRCodedFieldDomain *>(domain.get());
9900 11 : if (!osDescription.empty())
9901 : {
9902 : // We use a little trick by using a dummy
9903 : // _{domainname}_domain_description enum that has a single
9904 : // entry whose description is the description of the main
9905 : // domain.
9906 1 : char *pszSQL = sqlite3_mprintf(
9907 : "INSERT INTO gpkg_data_column_constraints ("
9908 : "constraint_name, constraint_type, value, "
9909 : "min, %s, max, %s, "
9910 : "description) VALUES ("
9911 : "'_%q_domain_description', 'enum', '', NULL, NULL, NULL, "
9912 : "NULL, %Q)",
9913 : min_is_inclusive, max_is_inclusive, domainName.c_str(),
9914 : osDescription.c_str());
9915 1 : CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
9916 1 : sqlite3_free(pszSQL);
9917 : }
9918 11 : const auto &enumeration = poCodedDomain->GetEnumeration();
9919 33 : for (int i = 0; enumeration[i].pszCode != nullptr; ++i)
9920 : {
9921 22 : char *pszSQL = sqlite3_mprintf(
9922 : "INSERT INTO gpkg_data_column_constraints ("
9923 : "constraint_name, constraint_type, value, "
9924 : "min, %s, max, %s, "
9925 : "description) VALUES ("
9926 : "'%q', 'enum', '%q', NULL, NULL, NULL, NULL, %Q)",
9927 : min_is_inclusive, max_is_inclusive, domainName.c_str(),
9928 22 : enumeration[i].pszCode, enumeration[i].pszValue);
9929 22 : bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
9930 22 : sqlite3_free(pszSQL);
9931 22 : if (!ok)
9932 0 : return false;
9933 : }
9934 11 : break;
9935 : }
9936 :
9937 5 : case OFDT_RANGE:
9938 : {
9939 : const auto poRangeDomain =
9940 5 : cpl::down_cast<const OGRRangeFieldDomain *>(domain.get());
9941 5 : const auto eFieldType = poRangeDomain->GetFieldType();
9942 5 : if (eFieldType != OFTInteger && eFieldType != OFTInteger64 &&
9943 : eFieldType != OFTReal)
9944 : {
9945 : failureReason = "Only range domains of numeric type are "
9946 0 : "supported in GeoPackage";
9947 0 : return false;
9948 : }
9949 :
9950 5 : double dfMin = -std::numeric_limits<double>::infinity();
9951 5 : double dfMax = std::numeric_limits<double>::infinity();
9952 5 : bool bMinIsInclusive = true;
9953 5 : const auto &sMin = poRangeDomain->GetMin(bMinIsInclusive);
9954 5 : bool bMaxIsInclusive = true;
9955 5 : const auto &sMax = poRangeDomain->GetMax(bMaxIsInclusive);
9956 5 : if (eFieldType == OFTInteger)
9957 : {
9958 1 : if (!OGR_RawField_IsUnset(&sMin))
9959 1 : dfMin = sMin.Integer;
9960 1 : if (!OGR_RawField_IsUnset(&sMax))
9961 1 : dfMax = sMax.Integer;
9962 : }
9963 4 : else if (eFieldType == OFTInteger64)
9964 : {
9965 1 : if (!OGR_RawField_IsUnset(&sMin))
9966 1 : dfMin = static_cast<double>(sMin.Integer64);
9967 1 : if (!OGR_RawField_IsUnset(&sMax))
9968 1 : dfMax = static_cast<double>(sMax.Integer64);
9969 : }
9970 : else /* if( eFieldType == OFTReal ) */
9971 : {
9972 3 : if (!OGR_RawField_IsUnset(&sMin))
9973 3 : dfMin = sMin.Real;
9974 3 : if (!OGR_RawField_IsUnset(&sMax))
9975 3 : dfMax = sMax.Real;
9976 : }
9977 :
9978 5 : sqlite3_stmt *hInsertStmt = nullptr;
9979 : const char *pszSQL =
9980 5 : CPLSPrintf("INSERT INTO gpkg_data_column_constraints ("
9981 : "constraint_name, constraint_type, value, "
9982 : "min, %s, max, %s, "
9983 : "description) VALUES ("
9984 : "?, 'range', NULL, ?, ?, ?, ?, ?)",
9985 : min_is_inclusive, max_is_inclusive);
9986 5 : if (sqlite3_prepare_v2(hDB, pszSQL, -1, &hInsertStmt, nullptr) !=
9987 : SQLITE_OK)
9988 : {
9989 0 : CPLError(CE_Failure, CPLE_AppDefined,
9990 : "failed to prepare SQL: %s", pszSQL);
9991 0 : return false;
9992 : }
9993 5 : sqlite3_bind_text(hInsertStmt, 1, domainName.c_str(),
9994 5 : static_cast<int>(domainName.size()),
9995 : SQLITE_TRANSIENT);
9996 5 : sqlite3_bind_double(hInsertStmt, 2, dfMin);
9997 5 : sqlite3_bind_int(hInsertStmt, 3, bMinIsInclusive ? 1 : 0);
9998 5 : sqlite3_bind_double(hInsertStmt, 4, dfMax);
9999 5 : sqlite3_bind_int(hInsertStmt, 5, bMaxIsInclusive ? 1 : 0);
10000 5 : if (osDescription.empty())
10001 : {
10002 3 : sqlite3_bind_null(hInsertStmt, 6);
10003 : }
10004 : else
10005 : {
10006 2 : sqlite3_bind_text(hInsertStmt, 6, osDescription.c_str(),
10007 2 : static_cast<int>(osDescription.size()),
10008 : SQLITE_TRANSIENT);
10009 : }
10010 5 : const int sqlite_err = sqlite3_step(hInsertStmt);
10011 5 : sqlite3_finalize(hInsertStmt);
10012 5 : if (sqlite_err != SQLITE_OK && sqlite_err != SQLITE_DONE)
10013 : {
10014 0 : CPLError(CE_Failure, CPLE_AppDefined,
10015 : "failed to execute insertion: %s",
10016 : sqlite3_errmsg(hDB));
10017 0 : return false;
10018 : }
10019 :
10020 5 : break;
10021 : }
10022 :
10023 1 : case OFDT_GLOB:
10024 : {
10025 : const auto poGlobDomain =
10026 1 : cpl::down_cast<const OGRGlobFieldDomain *>(domain.get());
10027 2 : char *pszSQL = sqlite3_mprintf(
10028 : "INSERT INTO gpkg_data_column_constraints ("
10029 : "constraint_name, constraint_type, value, "
10030 : "min, %s, max, %s, "
10031 : "description) VALUES ("
10032 : "'%q', 'glob', '%q', NULL, NULL, NULL, NULL, %Q)",
10033 : min_is_inclusive, max_is_inclusive, domainName.c_str(),
10034 1 : poGlobDomain->GetGlob().c_str(),
10035 2 : osDescription.empty() ? nullptr : osDescription.c_str());
10036 1 : bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
10037 1 : sqlite3_free(pszSQL);
10038 1 : if (!ok)
10039 0 : return false;
10040 :
10041 1 : break;
10042 : }
10043 : }
10044 :
10045 17 : m_oMapFieldDomains[domainName] = std::move(domain);
10046 17 : return true;
10047 : }
10048 :
10049 : /************************************************************************/
10050 : /* AddRelationship() */
10051 : /************************************************************************/
10052 :
10053 20 : bool GDALGeoPackageDataset::AddRelationship(
10054 : std::unique_ptr<GDALRelationship> &&relationship,
10055 : std::string &failureReason)
10056 : {
10057 20 : if (!GetUpdate())
10058 : {
10059 0 : CPLError(CE_Failure, CPLE_NotSupported,
10060 : "AddRelationship() not supported on read-only dataset");
10061 0 : return false;
10062 : }
10063 :
10064 : const std::string osRelationshipName = GenerateNameForRelationship(
10065 20 : relationship->GetLeftTableName().c_str(),
10066 20 : relationship->GetRightTableName().c_str(),
10067 80 : relationship->GetRelatedTableType().c_str());
10068 : // sanity checks
10069 20 : if (GetRelationship(osRelationshipName) != nullptr)
10070 : {
10071 1 : failureReason = "A relationship of identical name already exists";
10072 1 : return false;
10073 : }
10074 :
10075 19 : if (!ValidateRelationship(relationship.get(), failureReason))
10076 : {
10077 12 : return false;
10078 : }
10079 :
10080 7 : if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
10081 : {
10082 0 : return false;
10083 : }
10084 7 : if (!CreateRelationsTableIfNecessary())
10085 : {
10086 0 : failureReason = "Could not create gpkgext_relations table";
10087 0 : return false;
10088 : }
10089 7 : if (SQLGetInteger(GetDB(),
10090 : "SELECT 1 FROM gpkg_extensions WHERE "
10091 : "table_name = 'gpkgext_relations'",
10092 7 : nullptr) != 1)
10093 : {
10094 2 : if (OGRERR_NONE !=
10095 2 : SQLCommand(
10096 : GetDB(),
10097 : "INSERT INTO gpkg_extensions "
10098 : "(table_name,column_name,extension_name,definition,scope) "
10099 : "VALUES ('gpkgext_relations', NULL, 'gpkg_related_tables', "
10100 : "'http://www.geopackage.org/18-000.html', "
10101 : "'read-write')"))
10102 : {
10103 : failureReason =
10104 0 : "Could not create gpkg_extensions entry for gpkgext_relations";
10105 0 : return false;
10106 : }
10107 : }
10108 :
10109 7 : const std::string &osLeftTableName = relationship->GetLeftTableName();
10110 7 : const std::string &osRightTableName = relationship->GetRightTableName();
10111 7 : const auto &aosLeftTableFields = relationship->GetLeftTableFields();
10112 7 : const auto &aosRightTableFields = relationship->GetRightTableFields();
10113 :
10114 14 : std::string osRelatedTableType = relationship->GetRelatedTableType();
10115 7 : if (osRelatedTableType.empty())
10116 : {
10117 5 : osRelatedTableType = "features";
10118 : }
10119 :
10120 : // generate mapping table if not set
10121 14 : CPLString osMappingTableName = relationship->GetMappingTableName();
10122 7 : if (osMappingTableName.empty())
10123 : {
10124 3 : int nIndex = 1;
10125 3 : osMappingTableName = osLeftTableName + "_" + osRightTableName;
10126 3 : while (FindLayerIndex(osMappingTableName.c_str()) >= 0)
10127 : {
10128 0 : nIndex += 1;
10129 : osMappingTableName.Printf("%s_%s_%d", osLeftTableName.c_str(),
10130 0 : osRightTableName.c_str(), nIndex);
10131 : }
10132 :
10133 : // determine whether base/related keys are unique
10134 3 : bool bBaseKeyIsUnique = false;
10135 : {
10136 : const std::set<std::string> uniqueBaseFieldsUC =
10137 : SQLGetUniqueFieldUCConstraints(GetDB(),
10138 6 : osLeftTableName.c_str());
10139 6 : if (uniqueBaseFieldsUC.find(
10140 3 : CPLString(aosLeftTableFields[0]).toupper()) !=
10141 6 : uniqueBaseFieldsUC.end())
10142 : {
10143 2 : bBaseKeyIsUnique = true;
10144 : }
10145 : }
10146 3 : bool bRelatedKeyIsUnique = false;
10147 : {
10148 : const std::set<std::string> uniqueRelatedFieldsUC =
10149 : SQLGetUniqueFieldUCConstraints(GetDB(),
10150 6 : osRightTableName.c_str());
10151 6 : if (uniqueRelatedFieldsUC.find(
10152 3 : CPLString(aosRightTableFields[0]).toupper()) !=
10153 6 : uniqueRelatedFieldsUC.end())
10154 : {
10155 2 : bRelatedKeyIsUnique = true;
10156 : }
10157 : }
10158 :
10159 : // create mapping table
10160 :
10161 3 : std::string osBaseIdDefinition = "base_id INTEGER";
10162 3 : if (bBaseKeyIsUnique)
10163 : {
10164 2 : char *pszSQL = sqlite3_mprintf(
10165 : " CONSTRAINT 'fk_base_id_%q' REFERENCES \"%w\"(\"%w\") ON "
10166 : "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
10167 : "DEFERRED",
10168 : osMappingTableName.c_str(), osLeftTableName.c_str(),
10169 2 : aosLeftTableFields[0].c_str());
10170 2 : osBaseIdDefinition += pszSQL;
10171 2 : sqlite3_free(pszSQL);
10172 : }
10173 :
10174 3 : std::string osRelatedIdDefinition = "related_id INTEGER";
10175 3 : if (bRelatedKeyIsUnique)
10176 : {
10177 2 : char *pszSQL = sqlite3_mprintf(
10178 : " CONSTRAINT 'fk_related_id_%q' REFERENCES \"%w\"(\"%w\") ON "
10179 : "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
10180 : "DEFERRED",
10181 : osMappingTableName.c_str(), osRightTableName.c_str(),
10182 2 : aosRightTableFields[0].c_str());
10183 2 : osRelatedIdDefinition += pszSQL;
10184 2 : sqlite3_free(pszSQL);
10185 : }
10186 :
10187 3 : char *pszSQL = sqlite3_mprintf("CREATE TABLE \"%w\" ("
10188 : "id INTEGER PRIMARY KEY AUTOINCREMENT, "
10189 : "%s, %s);",
10190 : osMappingTableName.c_str(),
10191 : osBaseIdDefinition.c_str(),
10192 : osRelatedIdDefinition.c_str());
10193 3 : OGRErr eErr = SQLCommand(hDB, pszSQL);
10194 3 : sqlite3_free(pszSQL);
10195 3 : if (eErr != OGRERR_NONE)
10196 : {
10197 : failureReason =
10198 0 : ("Could not create mapping table " + osMappingTableName)
10199 0 : .c_str();
10200 0 : return false;
10201 : }
10202 :
10203 : /*
10204 : * Strictly speaking we should NOT be inserting the mapping table into gpkg_contents.
10205 : * The related tables extension explicitly states that the mapping table should only be
10206 : * in the gpkgext_relations table and not in gpkg_contents. (See also discussion at
10207 : * https://github.com/opengeospatial/geopackage/issues/679).
10208 : *
10209 : * However, if we don't insert the mapping table into gpkg_contents then it is no longer
10210 : * visible to some clients (eg ESRI software only allows opening tables that are present
10211 : * in gpkg_contents). So we'll do this anyway, for maximum compatibility and flexibility.
10212 : *
10213 : * More related discussion is at https://github.com/OSGeo/gdal/pull/9258
10214 : */
10215 3 : pszSQL = sqlite3_mprintf(
10216 : "INSERT INTO gpkg_contents "
10217 : "(table_name,data_type,identifier,description,last_change,srs_id) "
10218 : "VALUES "
10219 : "('%q','attributes','%q','Mapping table for relationship between "
10220 : "%q and %q',%s,0)",
10221 : osMappingTableName.c_str(), /*table_name*/
10222 : osMappingTableName.c_str(), /*identifier*/
10223 : osLeftTableName.c_str(), /*description left table name*/
10224 : osRightTableName.c_str(), /*description right table name*/
10225 6 : GDALGeoPackageDataset::GetCurrentDateEscapedSQL().c_str());
10226 :
10227 : // Note -- we explicitly ignore failures here, because hey, we aren't really
10228 : // supposed to be adding this table to gpkg_contents anyway!
10229 3 : (void)SQLCommand(hDB, pszSQL);
10230 3 : sqlite3_free(pszSQL);
10231 :
10232 3 : pszSQL = sqlite3_mprintf(
10233 : "CREATE INDEX \"idx_%w_base_id\" ON \"%w\" (base_id);",
10234 : osMappingTableName.c_str(), osMappingTableName.c_str());
10235 3 : eErr = SQLCommand(hDB, pszSQL);
10236 3 : sqlite3_free(pszSQL);
10237 3 : if (eErr != OGRERR_NONE)
10238 : {
10239 0 : failureReason = ("Could not create index for " +
10240 0 : osMappingTableName + " (base_id)")
10241 0 : .c_str();
10242 0 : return false;
10243 : }
10244 :
10245 3 : pszSQL = sqlite3_mprintf(
10246 : "CREATE INDEX \"idx_%qw_related_id\" ON \"%w\" (related_id);",
10247 : osMappingTableName.c_str(), osMappingTableName.c_str());
10248 3 : eErr = SQLCommand(hDB, pszSQL);
10249 3 : sqlite3_free(pszSQL);
10250 3 : if (eErr != OGRERR_NONE)
10251 : {
10252 0 : failureReason = ("Could not create index for " +
10253 0 : osMappingTableName + " (related_id)")
10254 0 : .c_str();
10255 0 : return false;
10256 : }
10257 : }
10258 : else
10259 : {
10260 : // validate mapping table structure
10261 4 : if (OGRGeoPackageTableLayer *poLayer =
10262 4 : cpl::down_cast<OGRGeoPackageTableLayer *>(
10263 4 : GetLayerByName(osMappingTableName)))
10264 : {
10265 3 : if (poLayer->GetLayerDefn()->GetFieldIndex("base_id") < 0)
10266 : {
10267 : failureReason =
10268 2 : ("Field base_id must exist in " + osMappingTableName)
10269 1 : .c_str();
10270 1 : return false;
10271 : }
10272 2 : if (poLayer->GetLayerDefn()->GetFieldIndex("related_id") < 0)
10273 : {
10274 : failureReason =
10275 2 : ("Field related_id must exist in " + osMappingTableName)
10276 1 : .c_str();
10277 1 : return false;
10278 : }
10279 : }
10280 : else
10281 : {
10282 : failureReason =
10283 1 : ("Could not retrieve table " + osMappingTableName).c_str();
10284 1 : return false;
10285 : }
10286 : }
10287 :
10288 4 : char *pszSQL = sqlite3_mprintf(
10289 : "INSERT INTO gpkg_extensions "
10290 : "(table_name,column_name,extension_name,definition,scope) "
10291 : "VALUES ('%q', NULL, 'gpkg_related_tables', "
10292 : "'http://www.geopackage.org/18-000.html', "
10293 : "'read-write')",
10294 : osMappingTableName.c_str());
10295 4 : OGRErr eErr = SQLCommand(hDB, pszSQL);
10296 4 : sqlite3_free(pszSQL);
10297 4 : if (eErr != OGRERR_NONE)
10298 : {
10299 0 : failureReason = ("Could not insert mapping table " +
10300 0 : osMappingTableName + " into gpkg_extensions")
10301 0 : .c_str();
10302 0 : return false;
10303 : }
10304 :
10305 12 : pszSQL = sqlite3_mprintf(
10306 : "INSERT INTO gpkgext_relations "
10307 : "(base_table_name,base_primary_column,related_table_name,related_"
10308 : "primary_column,relation_name,mapping_table_name) "
10309 : "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
10310 4 : osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
10311 4 : osRightTableName.c_str(), aosRightTableFields[0].c_str(),
10312 : osRelatedTableType.c_str(), osMappingTableName.c_str());
10313 4 : eErr = SQLCommand(hDB, pszSQL);
10314 4 : sqlite3_free(pszSQL);
10315 4 : if (eErr != OGRERR_NONE)
10316 : {
10317 0 : failureReason = "Could not insert relationship into gpkgext_relations";
10318 0 : return false;
10319 : }
10320 :
10321 4 : ClearCachedRelationships();
10322 4 : LoadRelationships();
10323 4 : return true;
10324 : }
10325 :
10326 : /************************************************************************/
10327 : /* DeleteRelationship() */
10328 : /************************************************************************/
10329 :
10330 4 : bool GDALGeoPackageDataset::DeleteRelationship(const std::string &name,
10331 : std::string &failureReason)
10332 : {
10333 4 : if (eAccess != GA_Update)
10334 : {
10335 0 : CPLError(CE_Failure, CPLE_NotSupported,
10336 : "DeleteRelationship() not supported on read-only dataset");
10337 0 : return false;
10338 : }
10339 :
10340 : // ensure relationships are up to date before we try to remove one
10341 4 : ClearCachedRelationships();
10342 4 : LoadRelationships();
10343 :
10344 8 : std::string osMappingTableName;
10345 : {
10346 4 : const GDALRelationship *poRelationship = GetRelationship(name);
10347 4 : if (poRelationship == nullptr)
10348 : {
10349 1 : failureReason = "Could not find relationship with name " + name;
10350 1 : return false;
10351 : }
10352 :
10353 3 : osMappingTableName = poRelationship->GetMappingTableName();
10354 : }
10355 :
10356 : // DeleteLayerCommon will delete existing relationship objects, so we can't
10357 : // refer to poRelationship or any of its members previously obtained here
10358 3 : if (DeleteLayerCommon(osMappingTableName.c_str()) != OGRERR_NONE)
10359 : {
10360 : failureReason =
10361 0 : "Could not remove mapping layer name " + osMappingTableName;
10362 :
10363 : // relationships may have been left in an inconsistent state -- reload
10364 : // them now
10365 0 : ClearCachedRelationships();
10366 0 : LoadRelationships();
10367 0 : return false;
10368 : }
10369 :
10370 3 : ClearCachedRelationships();
10371 3 : LoadRelationships();
10372 3 : return true;
10373 : }
10374 :
10375 : /************************************************************************/
10376 : /* UpdateRelationship() */
10377 : /************************************************************************/
10378 :
10379 6 : bool GDALGeoPackageDataset::UpdateRelationship(
10380 : std::unique_ptr<GDALRelationship> &&relationship,
10381 : std::string &failureReason)
10382 : {
10383 6 : if (eAccess != GA_Update)
10384 : {
10385 0 : CPLError(CE_Failure, CPLE_NotSupported,
10386 : "UpdateRelationship() not supported on read-only dataset");
10387 0 : return false;
10388 : }
10389 :
10390 : // ensure relationships are up to date before we try to update one
10391 6 : ClearCachedRelationships();
10392 6 : LoadRelationships();
10393 :
10394 6 : const std::string &osRelationshipName = relationship->GetName();
10395 6 : const std::string &osLeftTableName = relationship->GetLeftTableName();
10396 6 : const std::string &osRightTableName = relationship->GetRightTableName();
10397 6 : const std::string &osMappingTableName = relationship->GetMappingTableName();
10398 6 : const auto &aosLeftTableFields = relationship->GetLeftTableFields();
10399 6 : const auto &aosRightTableFields = relationship->GetRightTableFields();
10400 :
10401 : // sanity checks
10402 : {
10403 : const GDALRelationship *poExistingRelationship =
10404 6 : GetRelationship(osRelationshipName);
10405 6 : if (poExistingRelationship == nullptr)
10406 : {
10407 : failureReason =
10408 1 : "The relationship should already exist to be updated";
10409 1 : return false;
10410 : }
10411 :
10412 5 : if (!ValidateRelationship(relationship.get(), failureReason))
10413 : {
10414 2 : return false;
10415 : }
10416 :
10417 : // we don't permit changes to the participating tables
10418 3 : if (osLeftTableName != poExistingRelationship->GetLeftTableName())
10419 : {
10420 0 : failureReason = ("Cannot change base table from " +
10421 0 : poExistingRelationship->GetLeftTableName() +
10422 0 : " to " + osLeftTableName)
10423 0 : .c_str();
10424 0 : return false;
10425 : }
10426 3 : if (osRightTableName != poExistingRelationship->GetRightTableName())
10427 : {
10428 0 : failureReason = ("Cannot change related table from " +
10429 0 : poExistingRelationship->GetRightTableName() +
10430 0 : " to " + osRightTableName)
10431 0 : .c_str();
10432 0 : return false;
10433 : }
10434 3 : if (osMappingTableName != poExistingRelationship->GetMappingTableName())
10435 : {
10436 0 : failureReason = ("Cannot change mapping table from " +
10437 0 : poExistingRelationship->GetMappingTableName() +
10438 0 : " to " + osMappingTableName)
10439 0 : .c_str();
10440 0 : return false;
10441 : }
10442 : }
10443 :
10444 6 : std::string osRelatedTableType = relationship->GetRelatedTableType();
10445 3 : if (osRelatedTableType.empty())
10446 : {
10447 0 : osRelatedTableType = "features";
10448 : }
10449 :
10450 3 : char *pszSQL = sqlite3_mprintf(
10451 : "DELETE FROM gpkgext_relations WHERE mapping_table_name='%q'",
10452 : osMappingTableName.c_str());
10453 3 : OGRErr eErr = SQLCommand(hDB, pszSQL);
10454 3 : sqlite3_free(pszSQL);
10455 3 : if (eErr != OGRERR_NONE)
10456 : {
10457 : failureReason =
10458 0 : "Could not delete old relationship from gpkgext_relations";
10459 0 : return false;
10460 : }
10461 :
10462 9 : pszSQL = sqlite3_mprintf(
10463 : "INSERT INTO gpkgext_relations "
10464 : "(base_table_name,base_primary_column,related_table_name,related_"
10465 : "primary_column,relation_name,mapping_table_name) "
10466 : "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
10467 3 : osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
10468 3 : osRightTableName.c_str(), aosRightTableFields[0].c_str(),
10469 : osRelatedTableType.c_str(), osMappingTableName.c_str());
10470 3 : eErr = SQLCommand(hDB, pszSQL);
10471 3 : sqlite3_free(pszSQL);
10472 3 : if (eErr != OGRERR_NONE)
10473 : {
10474 : failureReason =
10475 0 : "Could not insert updated relationship into gpkgext_relations";
10476 0 : return false;
10477 : }
10478 :
10479 3 : ClearCachedRelationships();
10480 3 : LoadRelationships();
10481 3 : return true;
10482 : }
10483 :
10484 : /************************************************************************/
10485 : /* GetSqliteMasterContent() */
10486 : /************************************************************************/
10487 :
10488 : const std::vector<SQLSqliteMasterContent> &
10489 2 : GDALGeoPackageDataset::GetSqliteMasterContent()
10490 : {
10491 2 : if (m_aoSqliteMasterContent.empty())
10492 : {
10493 : auto oResultTable =
10494 2 : SQLQuery(hDB, "SELECT sql, type, tbl_name FROM sqlite_master");
10495 1 : if (oResultTable)
10496 : {
10497 58 : for (int rowCnt = 0; rowCnt < oResultTable->RowCount(); ++rowCnt)
10498 : {
10499 114 : SQLSqliteMasterContent row;
10500 57 : const char *pszSQL = oResultTable->GetValue(0, rowCnt);
10501 57 : row.osSQL = pszSQL ? pszSQL : "";
10502 57 : const char *pszType = oResultTable->GetValue(1, rowCnt);
10503 57 : row.osType = pszType ? pszType : "";
10504 57 : const char *pszTableName = oResultTable->GetValue(2, rowCnt);
10505 57 : row.osTableName = pszTableName ? pszTableName : "";
10506 57 : m_aoSqliteMasterContent.emplace_back(std::move(row));
10507 : }
10508 : }
10509 : }
10510 2 : return m_aoSqliteMasterContent;
10511 : }
|