LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/gpkg - ogrgeopackagedriver.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 285 290 98.3 %
Date: 2025-10-14 18:17:50 Functions: 17 17 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GeoPackage Translator
       4             :  * Purpose:  Implements GeoPackageDriver.
       5             :  * Author:   Paul Ramsey <pramsey@boundlessgeo.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2013, Paul Ramsey <pramsey@boundlessgeo.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "gdalsubdatasetinfo.h"
      14             : #include "ogr_geopackage.h"
      15             : 
      16             : #include "tilematrixset.hpp"
      17             : #include "gdalalgorithm.h"
      18             : 
      19             : #include <cctype>
      20             : #include <mutex>
      21             : 
      22             : // g++ -g -Wall -fPIC -shared -o ogr_geopackage.so -Iport -Igcore -Iogr
      23             : // -Iogr/ogrsf_frmts -Iogr/ogrsf_frmts/gpkg ogr/ogrsf_frmts/gpkg/*.c* -L. -lgdal
      24             : 
      25         757 : static inline bool ENDS_WITH_CI(const char *a, const char *b)
      26             : {
      27         757 :     return strlen(a) >= strlen(b) && EQUAL(a + strlen(a) - strlen(b), b);
      28             : }
      29             : 
      30             : /************************************************************************/
      31             : /*                       OGRGeoPackageDriverIdentify()                  */
      32             : /************************************************************************/
      33             : 
      34       63704 : static int OGRGeoPackageDriverIdentify(GDALOpenInfo *poOpenInfo,
      35             :                                        std::string &osFilenameInGpkgZip,
      36             :                                        bool bEmitWarning)
      37             : {
      38       63704 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GPKG:"))
      39         492 :         return TRUE;
      40             : 
      41             : #ifdef ENABLE_SQL_GPKG_FORMAT
      42       63212 :     if (poOpenInfo->pabyHeader &&
      43        6703 :         STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
      44             :                     "-- SQL GPKG"))
      45             :     {
      46          10 :         return TRUE;
      47             :     }
      48             : #endif
      49             : 
      50             :     // Try to recognize "foo.gpkg.zip"
      51       63202 :     const size_t nFilenameLen = strlen(poOpenInfo->pszFilename);
      52       63202 :     if ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) == 0 &&
      53       59969 :         nFilenameLen > strlen(".gpkg.zip") &&
      54       59969 :         !STARTS_WITH(poOpenInfo->pszFilename, "/vsizip/") &&
      55       59936 :         EQUAL(poOpenInfo->pszFilename + nFilenameLen - strlen(".gpkg.zip"),
      56             :               ".gpkg.zip"))
      57             :     {
      58           8 :         int nCountGpkg = 0;
      59             :         const CPLStringList aosFiles(VSIReadDirEx(
      60          24 :             (std::string("/vsizip/") + poOpenInfo->pszFilename).c_str(), 1000));
      61          12 :         for (int i = 0; i < aosFiles.size(); ++i)
      62             :         {
      63           4 :             const size_t nLen = strlen(aosFiles[i]);
      64           8 :             if (nLen > strlen(".gpkg") &&
      65           4 :                 EQUAL(aosFiles[i] + nLen - strlen(".gpkg"), ".gpkg"))
      66             :             {
      67           4 :                 osFilenameInGpkgZip = aosFiles[i];
      68           4 :                 nCountGpkg++;
      69           4 :                 if (nCountGpkg == 2)
      70           0 :                     return FALSE;
      71             :             }
      72             :         }
      73           8 :         return nCountGpkg == 1;
      74             :     }
      75             : 
      76       63194 :     if (poOpenInfo->nHeaderBytes < 100 || poOpenInfo->pabyHeader == nullptr ||
      77        6277 :         !STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
      78             :                      "SQLite format 3"))
      79             :     {
      80       60889 :         return FALSE;
      81             :     }
      82             : 
      83             :     /* Requirement 3: File name has to end in "gpkg" */
      84             :     /* http://opengis.github.io/geopackage/#_file_extension_name */
      85             :     /* But be tolerant, if the GPKG application id is found, because some */
      86             :     /* producers don't necessarily honour that requirement (#6396) */
      87        2305 :     const char *pszExt = poOpenInfo->osExtension.c_str();
      88        2305 :     const bool bIsRecognizedExtension =
      89        2305 :         EQUAL(pszExt, "GPKG") || EQUAL(pszExt, "GPKX");
      90             : 
      91             :     /* Requirement 2: application id */
      92             :     /* http://opengis.github.io/geopackage/#_file_format */
      93             :     /* Be tolerant since some datasets don't actually follow that requirement */
      94             :     GUInt32 nApplicationId;
      95        2305 :     memcpy(&nApplicationId, poOpenInfo->pabyHeader + knApplicationIdPos, 4);
      96        2305 :     nApplicationId = CPL_MSBWORD32(nApplicationId);
      97             :     GUInt32 nUserVersion;
      98        2305 :     memcpy(&nUserVersion, poOpenInfo->pabyHeader + knUserVersionPos, 4);
      99        2305 :     nUserVersion = CPL_MSBWORD32(nUserVersion);
     100        2305 :     if (nApplicationId != GP10_APPLICATION_ID &&
     101        2290 :         nApplicationId != GP11_APPLICATION_ID &&
     102        2285 :         nApplicationId != GPKG_APPLICATION_ID)
     103             :     {
     104             : #ifdef DEBUG
     105         240 :         if (EQUAL(CPLGetFilename(poOpenInfo->pszFilename), ".cur_input"))
     106             :         {
     107           1 :             return FALSE;
     108             :         }
     109             : #endif
     110         239 :         if (!bIsRecognizedExtension)
     111         235 :             return FALSE;
     112             : 
     113           4 :         if (bEmitWarning)
     114             :         {
     115             :             GByte abySignature[4 + 1];
     116           2 :             memcpy(abySignature, poOpenInfo->pabyHeader + knApplicationIdPos,
     117             :                    4);
     118           2 :             abySignature[4] = '\0';
     119             : 
     120             :             /* Is this a GPxx version ? */
     121           2 :             const bool bWarn = CPLTestBool(CPLGetConfigOption(
     122             :                 "GPKG_WARN_UNRECOGNIZED_APPLICATION_ID", "YES"));
     123           2 :             if (bWarn)
     124             :             {
     125           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
     126             :                          "GPKG: bad application_id=0x%02X%02X%02X%02X on '%s'",
     127           1 :                          abySignature[0], abySignature[1], abySignature[2],
     128           1 :                          abySignature[3], poOpenInfo->pszFilename);
     129             :             }
     130             :             else
     131             :             {
     132           1 :                 CPLDebug("GPKG",
     133             :                          "bad application_id=0x%02X%02X%02X%02X on '%s'",
     134           1 :                          abySignature[0], abySignature[1], abySignature[2],
     135           1 :                          abySignature[3], poOpenInfo->pszFilename);
     136             :             }
     137           4 :         }
     138             :     }
     139        2065 :     else if (nApplicationId == GPKG_APPLICATION_ID &&
     140             :              // Accept any 102XX version
     141        2045 :              !((nUserVersion >= GPKG_1_2_VERSION &&
     142        2040 :                 nUserVersion < GPKG_1_2_VERSION + 99) ||
     143             :                // Accept any 103XX version
     144        1948 :                (nUserVersion >= GPKG_1_3_VERSION &&
     145        1943 :                 nUserVersion < GPKG_1_3_VERSION + 99) ||
     146             :                // Accept any 104XX version
     147        1944 :                (nUserVersion >= GPKG_1_4_VERSION &&
     148        1939 :                 nUserVersion < GPKG_1_4_VERSION + 99)))
     149             :     {
     150             : #ifdef DEBUG
     151           9 :         if (EQUAL(CPLGetFilename(poOpenInfo->pszFilename), ".cur_input"))
     152             :         {
     153           1 :             return FALSE;
     154             :         }
     155             : #endif
     156           8 :         if (!bIsRecognizedExtension)
     157           0 :             return FALSE;
     158             : 
     159           8 :         if (bEmitWarning)
     160             :         {
     161             :             GByte abySignature[4 + 1];
     162           4 :             memcpy(abySignature, poOpenInfo->pabyHeader + knUserVersionPos, 4);
     163           4 :             abySignature[4] = '\0';
     164             : 
     165           4 :             const bool bWarn = CPLTestBool(CPLGetConfigOption(
     166             :                 "GPKG_WARN_UNRECOGNIZED_APPLICATION_ID", "YES"));
     167           4 :             if (bWarn)
     168             :             {
     169           2 :                 if (nUserVersion > GPKG_1_4_VERSION)
     170             :                 {
     171           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
     172             :                              "This version of GeoPackage "
     173             :                              "user_version=0x%02X%02X%02X%02X "
     174             :                              "(%u, v%d.%d.%d) on '%s' may only be "
     175             :                              "partially supported",
     176           1 :                              abySignature[0], abySignature[1], abySignature[2],
     177           1 :                              abySignature[3], nUserVersion,
     178           1 :                              nUserVersion / 10000, (nUserVersion % 10000) / 100,
     179             :                              nUserVersion % 100, poOpenInfo->pszFilename);
     180             :                 }
     181             :                 else
     182             :                 {
     183           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
     184             :                              "GPKG: unrecognized user_version="
     185             :                              "0x%02X%02X%02X%02X (%u) on '%s'",
     186           1 :                              abySignature[0], abySignature[1], abySignature[2],
     187           1 :                              abySignature[3], nUserVersion,
     188             :                              poOpenInfo->pszFilename);
     189             :                 }
     190             :             }
     191             :             else
     192             :             {
     193           2 :                 if (nUserVersion > GPKG_1_4_VERSION)
     194             :                 {
     195           1 :                     CPLDebug("GPKG",
     196             :                              "This version of GeoPackage "
     197             :                              "user_version=0x%02X%02X%02X%02X "
     198             :                              "(%u, v%d.%d.%d) on '%s' may only be "
     199             :                              "partially supported",
     200           1 :                              abySignature[0], abySignature[1], abySignature[2],
     201           1 :                              abySignature[3], nUserVersion,
     202           1 :                              nUserVersion / 10000, (nUserVersion % 10000) / 100,
     203             :                              nUserVersion % 100, poOpenInfo->pszFilename);
     204             :                 }
     205             :                 else
     206             :                 {
     207           1 :                     CPLDebug("GPKG",
     208             :                              "unrecognized user_version=0x%02X%02X%02X%02X"
     209             :                              "(%u) on '%s'",
     210           1 :                              abySignature[0], abySignature[1], abySignature[2],
     211           1 :                              abySignature[3], nUserVersion,
     212             :                              poOpenInfo->pszFilename);
     213             :                 }
     214             :             }
     215           8 :         }
     216             :     }
     217        4112 :     else if (!bIsRecognizedExtension
     218             : #ifdef DEBUG
     219          34 :              && !EQUAL(CPLGetFilename(poOpenInfo->pszFilename), ".cur_input")
     220             : #endif
     221          34 :              && !(STARTS_WITH(poOpenInfo->pszFilename, "/vsizip/") &&
     222        2090 :                   poOpenInfo->IsExtensionEqualToCI("zip")) &&
     223          32 :              !STARTS_WITH(poOpenInfo->pszFilename, "/vsigzip/"))
     224             :     {
     225          32 :         if (bEmitWarning)
     226             :         {
     227          16 :             CPLError(CE_Warning, CPLE_AppDefined,
     228             :                      "File %s has GPKG application_id, but non conformant file "
     229             :                      "extension",
     230             :                      poOpenInfo->pszFilename);
     231             :         }
     232             :     }
     233             : 
     234        2825 :     if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
     235         757 :         ENDS_WITH_CI(poOpenInfo->pszFilename, ".gti.gpkg"))
     236             :     {
     237             :         // Most likely handled by GTI driver, but we can't be sure
     238          33 :         return GDAL_IDENTIFY_UNKNOWN;
     239             :     }
     240             : 
     241        2035 :     return TRUE;
     242             : }
     243             : 
     244       62478 : static int OGRGeoPackageDriverIdentify(GDALOpenInfo *poOpenInfo)
     245             : {
     246      124956 :     std::string osIgnored;
     247      124956 :     return OGRGeoPackageDriverIdentify(poOpenInfo, osIgnored, false);
     248             : }
     249             : 
     250             : /************************************************************************/
     251             : /*                    OGRGeoPackageDriverGetSubdatasetInfo()            */
     252             : /************************************************************************/
     253             : 
     254             : struct OGRGeoPackageDriverSubdatasetInfo final : public GDALSubdatasetInfo
     255             : {
     256             :   public:
     257          11 :     explicit OGRGeoPackageDriverSubdatasetInfo(const std::string &fileName)
     258          11 :         : GDALSubdatasetInfo(fileName)
     259             :     {
     260          11 :     }
     261             : 
     262             :     // GDALSubdatasetInfo interface
     263             :   private:
     264             :     void parseFileName() override;
     265             : };
     266             : 
     267          11 : void OGRGeoPackageDriverSubdatasetInfo::parseFileName()
     268             : {
     269          11 :     if (!STARTS_WITH_CI(m_fileName.c_str(), "GPKG:"))
     270             :     {
     271           1 :         return;
     272             :     }
     273             : 
     274          11 :     CPLStringList aosParts{CSLTokenizeString2(m_fileName.c_str(), ":", 0)};
     275          11 :     const int iPartsCount{CSLCount(aosParts)};
     276             : 
     277          11 :     if (iPartsCount == 3 || iPartsCount == 4)
     278             :     {
     279             : 
     280           9 :         m_driverPrefixComponent = aosParts[0];
     281             : 
     282           9 :         int subdatasetIndex{2};
     283             :         const bool hasDriveLetter{
     284          14 :             strlen(aosParts[1]) == 1 &&
     285           5 :             std::isalpha(static_cast<unsigned char>(aosParts[1][0]))};
     286             : 
     287             :         // Check for drive letter
     288           9 :         if (iPartsCount == 4)
     289             :         {
     290             :             // Invalid
     291           4 :             if (!hasDriveLetter)
     292             :             {
     293           0 :                 return;
     294             :             }
     295           4 :             m_pathComponent = aosParts[1];
     296           4 :             m_pathComponent.append(":");
     297           4 :             m_pathComponent.append(aosParts[2]);
     298           4 :             subdatasetIndex++;
     299             :         }
     300             :         else  // count is 3
     301             :         {
     302           5 :             if (hasDriveLetter)
     303             :             {
     304           1 :                 return;
     305             :             }
     306           4 :             m_pathComponent = aosParts[1];
     307             :         }
     308             : 
     309           8 :         m_subdatasetComponent = aosParts[subdatasetIndex];
     310             :     }
     311             : }
     312             : 
     313             : static GDALSubdatasetInfo *
     314        2687 : OGRGeoPackageDriverGetSubdatasetInfo(const char *pszFileName)
     315             : {
     316        2687 :     if (STARTS_WITH_CI(pszFileName, "GPKG:"))
     317             :     {
     318             :         std::unique_ptr<GDALSubdatasetInfo> info =
     319          11 :             std::make_unique<OGRGeoPackageDriverSubdatasetInfo>(pszFileName);
     320          30 :         if (!info->GetSubdatasetComponent().empty() &&
     321          19 :             !info->GetPathComponent().empty())
     322             :         {
     323           8 :             return info.release();
     324             :         }
     325             :     }
     326        2679 :     return nullptr;
     327             : }
     328             : 
     329             : /************************************************************************/
     330             : /*                                Open()                                */
     331             : /************************************************************************/
     332             : 
     333        1226 : static GDALDataset *OGRGeoPackageDriverOpen(GDALOpenInfo *poOpenInfo)
     334             : {
     335        2452 :     std::string osFilenameInGpkgZip;
     336        1226 :     if (OGRGeoPackageDriverIdentify(poOpenInfo, osFilenameInGpkgZip, true) ==
     337             :         GDAL_IDENTIFY_FALSE)
     338           0 :         return nullptr;
     339             : 
     340        1226 :     GDALGeoPackageDataset *poDS = new GDALGeoPackageDataset();
     341             : 
     342        1226 :     if (!poDS->Open(poOpenInfo, osFilenameInGpkgZip))
     343             :     {
     344          17 :         delete poDS;
     345          17 :         poDS = nullptr;
     346             :     }
     347             : 
     348        1226 :     return poDS;
     349             : }
     350             : 
     351             : /************************************************************************/
     352             : /*                               Create()                               */
     353             : /************************************************************************/
     354             : 
     355         955 : static GDALDataset *OGRGeoPackageDriverCreate(const char *pszFilename,
     356             :                                               int nXSize, int nYSize,
     357             :                                               int nBands, GDALDataType eDT,
     358             :                                               char **papszOptions)
     359             : {
     360         955 :     if (strcmp(pszFilename, ":memory:") != 0)
     361             :     {
     362         951 :         const size_t nFilenameLen = strlen(pszFilename);
     363         951 :         if (nFilenameLen > strlen(".gpkg.zip") &&
     364         950 :             !STARTS_WITH(pszFilename, "/vsizip/") &&
     365         950 :             EQUAL(pszFilename + nFilenameLen - strlen(".gpkg.zip"),
     366             :                   ".gpkg.zip"))
     367             :         {
     368             :             // do nothing
     369             :         }
     370             :         else
     371             :         {
     372        1898 :             const std::string osExt = CPLGetExtensionSafe(pszFilename);
     373             :             const bool bIsRecognizedExtension =
     374         949 :                 EQUAL(osExt.c_str(), "GPKG") || EQUAL(osExt.c_str(), "GPKX");
     375         949 :             if (!bIsRecognizedExtension)
     376             :             {
     377          36 :                 CPLError(
     378             :                     CE_Warning, CPLE_AppDefined,
     379             :                     "The filename extension should be 'gpkg' instead of '%s' "
     380             :                     "to conform to the GPKG specification.",
     381             :                     osExt.c_str());
     382             :             }
     383             :         }
     384             :     }
     385             : 
     386         955 :     GDALGeoPackageDataset *poDS = new GDALGeoPackageDataset();
     387             : 
     388         955 :     if (!poDS->Create(pszFilename, nXSize, nYSize, nBands, eDT, papszOptions))
     389             :     {
     390          39 :         delete poDS;
     391          39 :         poDS = nullptr;
     392             :     }
     393             : 
     394         955 :     return poDS;
     395             : }
     396             : 
     397             : /************************************************************************/
     398             : /*                               Delete()                               */
     399             : /************************************************************************/
     400             : 
     401         127 : static CPLErr OGRGeoPackageDriverDelete(const char *pszFilename)
     402             : 
     403             : {
     404         254 :     std::string osAuxXml(pszFilename);
     405         127 :     osAuxXml += ".aux.xml";
     406             :     VSIStatBufL sStat;
     407         127 :     if (VSIStatL(osAuxXml.c_str(), &sStat) == 0)
     408           3 :         CPL_IGNORE_RET_VAL(VSIUnlink(osAuxXml.c_str()));
     409             : 
     410         127 :     if (VSIUnlink(pszFilename) == 0)
     411         117 :         return CE_None;
     412             :     else
     413          10 :         return CE_Failure;
     414             : }
     415             : 
     416             : /************************************************************************/
     417             : /*                          GDALGPKGDriver                              */
     418             : /************************************************************************/
     419             : 
     420             : class GDALGPKGDriver final : public GDALDriver
     421             : {
     422             :     std::recursive_mutex m_oMutex{};
     423             :     bool m_bInitialized = false;
     424             : 
     425             :     void InitializeCreationOptionList();
     426             : 
     427             :   public:
     428        1750 :     GDALGPKGDriver() = default;
     429             : 
     430             :     const char *GetMetadataItem(const char *pszName,
     431             :                                 const char *pszDomain) override;
     432             : 
     433         587 :     char **GetMetadata(const char *pszDomain) override
     434             :     {
     435        1174 :         std::lock_guard oLock(m_oMutex);
     436         587 :         InitializeCreationOptionList();
     437        1174 :         return GDALDriver::GetMetadata(pszDomain);
     438             :     }
     439             : };
     440             : 
     441       52189 : const char *GDALGPKGDriver::GetMetadataItem(const char *pszName,
     442             :                                             const char *pszDomain)
     443             : {
     444      104379 :     std::lock_guard oLock(m_oMutex);
     445       52190 :     if (EQUAL(pszName, GDAL_DMD_CREATIONOPTIONLIST))
     446             :     {
     447        1464 :         InitializeCreationOptionList();
     448             :     }
     449      104380 :     return GDALDriver::GetMetadataItem(pszName, pszDomain);
     450             : }
     451             : 
     452             : #define COMPRESSION_OPTIONS                                                    \
     453             :     "  <Option name='TILE_FORMAT' type='string-select' scope='raster' "        \
     454             :     "description='Format to use to create tiles' default='AUTO'>"              \
     455             :     "    <Value>AUTO</Value>"                                                  \
     456             :     "    <Value>PNG_JPEG</Value>"                                              \
     457             :     "    <Value>PNG</Value>"                                                   \
     458             :     "    <Value>PNG8</Value>"                                                  \
     459             :     "    <Value>JPEG</Value>"                                                  \
     460             :     "    <Value>WEBP</Value>"                                                  \
     461             :     "    <Value>TIFF</Value>"                                                  \
     462             :     "  </Option>"                                                              \
     463             :     "  <Option name='QUALITY' type='int' min='1' max='100' scope='raster' "    \
     464             :     "description='Quality for JPEG and WEBP tiles' default='75'/>"             \
     465             :     "  <Option name='ZLEVEL' type='int' min='1' max='9' scope='raster' "       \
     466             :     "description='DEFLATE compression level for PNG tiles' default='6'/>"      \
     467             :     "  <Option name='DITHER' type='boolean' scope='raster' "                   \
     468             :     "description='Whether to apply Floyd-Steinberg dithering (for "            \
     469             :     "TILE_FORMAT=PNG8)' default='NO'/>"
     470             : 
     471        2051 : void GDALGPKGDriver::InitializeCreationOptionList()
     472             : {
     473        2051 :     if (m_bInitialized)
     474        1802 :         return;
     475         249 :     m_bInitialized = true;
     476             : 
     477         249 :     const char *pszCOBegin =
     478             :         "<CreationOptionList>"
     479             :         "  <Option name='RASTER_TABLE' type='string' scope='raster' "
     480             :         "description='Name of tile user table'/>"
     481             :         "  <Option name='APPEND_SUBDATASET' type='boolean' scope='raster' "
     482             :         "description='Set to YES to add a new tile user table to an existing "
     483             :         "GeoPackage instead of replacing it' default='NO'/>"
     484             :         "  <Option name='RASTER_IDENTIFIER' type='string' scope='raster' "
     485             :         "description='Human-readable identifier (e.g. short name)'/>"
     486             :         "  <Option name='RASTER_DESCRIPTION' type='string' scope='raster' "
     487             :         "description='Human-readable description'/>"
     488             :         "  <Option name='BLOCKSIZE' type='int' scope='raster' "
     489             :         "description='Block size in pixels' default='256' max='4096'/>"
     490             :         "  <Option name='BLOCKXSIZE' type='int' scope='raster' "
     491             :         "description='Block width in pixels' default='256' max='4096'/>"
     492             :         "  <Option name='BLOCKYSIZE' type='int' scope='raster' "
     493             :         "description='Block height in pixels' default='256' "
     494             :         "max='4096'/>" COMPRESSION_OPTIONS
     495             :         "  <Option name='TILING_SCHEME' type='string' scope='raster' "
     496             :         "description='Which tiling scheme to use: pre-defined value or custom "
     497             :         "inline/outline JSON definition' default='CUSTOM'>"
     498             :         "    <Value>CUSTOM</Value>"
     499             :         "    <Value>GoogleCRS84Quad</Value>"
     500             :         "    <Value>PseudoTMS_GlobalGeodetic</Value>"
     501             :         "    <Value>PseudoTMS_GlobalMercator</Value>";
     502             : 
     503         249 :     const char *pszCOEnd =
     504             :         "  </Option>"
     505             :         "  <Option name='ZOOM_LEVEL' type='integer' scope='raster' "
     506             :         "description='Zoom level of full resolution. Only "
     507             :         "used for TILING_SCHEME != CUSTOM' min='0' max='30'/>"
     508             :         "  <Option name='ZOOM_LEVEL_STRATEGY' type='string-select' "
     509             :         "scope='raster' description='Strategy to determine zoom level. Only "
     510             :         "used for TILING_SCHEME != CUSTOM' default='AUTO'>"
     511             :         "    <Value>AUTO</Value>"
     512             :         "    <Value>LOWER</Value>"
     513             :         "    <Value>UPPER</Value>"
     514             :         "  </Option>"
     515             :         "  <Option name='RESAMPLING' type='string-select' scope='raster' "
     516             :         "description='Resampling algorithm. Only used for TILING_SCHEME != "
     517             :         "CUSTOM' default='BILINEAR'>"
     518             :         "    <Value>NEAREST</Value>"
     519             :         "    <Value>BILINEAR</Value>"
     520             :         "    <Value>CUBIC</Value>"
     521             :         "    <Value>CUBICSPLINE</Value>"
     522             :         "    <Value>LANCZOS</Value>"
     523             :         "    <Value>MODE</Value>"
     524             :         "    <Value>AVERAGE</Value>"
     525             :         "  </Option>"
     526             :         "  <Option name='PRECISION' type='float' scope='raster' "
     527             :         "description='Smallest significant value. Only used for tiled gridded "
     528             :         "coverage datasets' default='1'/>"
     529             :         "  <Option name='UOM' type='string' scope='raster' description='Unit "
     530             :         "of Measurement. Only used for tiled gridded coverage datasets' />"
     531             :         "  <Option name='FIELD_NAME' type='string' scope='raster' "
     532             :         "description='Field name. Only used for tiled gridded coverage "
     533             :         "datasets' default='Height'/>"
     534             :         "  <Option name='QUANTITY_DEFINITION' type='string' scope='raster' "
     535             :         "description='Description of the field. Only used for tiled gridded "
     536             :         "coverage datasets' default='Height'/>"
     537             :         "  <Option name='GRID_CELL_ENCODING' type='string-select' "
     538             :         "scope='raster' description='Grid cell encoding. Only used for tiled "
     539             :         "gridded coverage datasets' default='grid-value-is-center'>"
     540             :         "     <Value>grid-value-is-center</Value>"
     541             :         "     <Value>grid-value-is-area</Value>"
     542             :         "     <Value>grid-value-is-corner</Value>"
     543             :         "  </Option>"
     544             :         "  <Option name='VERSION' type='string-select' description='Set "
     545             :         "GeoPackage version (for application_id and user_version fields)' "
     546             :         "default='AUTO'>"
     547             :         "     <Value>AUTO</Value>"
     548             :         "     <Value>1.0</Value>"
     549             :         "     <Value>1.1</Value>"
     550             :         "     <Value>1.2</Value>"
     551             :         "     <Value>1.3</Value>"
     552             :         "     <Value>1.4</Value>"
     553             :         "  </Option>"
     554             :         "  <Option name='DATETIME_FORMAT' type='string-select' "
     555             :         "description='How to encode DateTime not in UTC' default='WITH_TZ'>"
     556             :         "     <Value>WITH_TZ</Value>"
     557             :         "     <Value>UTC</Value>"
     558             :         "  </Option>"
     559             : #ifdef ENABLE_GPKG_OGR_CONTENTS
     560             :         "  <Option name='ADD_GPKG_OGR_CONTENTS' type='boolean' "
     561             :         "description='Whether to add a gpkg_ogr_contents table to keep feature "
     562             :         "count' default='YES'/>"
     563             : #endif
     564             :         "  <Option name='CRS_WKT_EXTENSION' type='boolean' "
     565             :         "description='Whether to create the database with the crs_wkt "
     566             :         "extension'/>"
     567             :         "  <Option name='METADATA_TABLES' type='boolean' "
     568             :         "description='Whether to create the metadata related system tables'/>"
     569             :         "</CreationOptionList>";
     570             : 
     571         498 :     std::string osOptions(pszCOBegin);
     572         498 :     const auto tmsList = gdal::TileMatrixSet::listPredefinedTileMatrixSets();
     573        2490 :     for (const auto &tmsName : tmsList)
     574             :     {
     575        4482 :         const auto poTM = gdal::TileMatrixSet::parse(tmsName.c_str());
     576        4482 :         if (poTM && poTM->haveAllLevelsSameTopLeft() &&
     577        2241 :             poTM->haveAllLevelsSameTileSize() &&
     578        6225 :             poTM->hasOnlyPowerOfTwoVaryingScales() &&
     579        1743 :             !poTM->hasVariableMatrixWidth())
     580             :         {
     581        1743 :             osOptions += "    <Value>";
     582        1743 :             osOptions += tmsName;
     583        1743 :             osOptions += "</Value>";
     584             :         }
     585             :     }
     586         249 :     osOptions += pszCOEnd;
     587             : 
     588         249 :     SetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST, osOptions.c_str());
     589             : }
     590             : 
     591             : /************************************************************************/
     592             : /*                    OGRGeoPackageRepackAlgorithm                      */
     593             : /************************************************************************/
     594             : 
     595             : #ifndef _
     596             : #define _(x) x
     597             : #endif
     598             : 
     599             : class OGRGeoPackageRepackAlgorithm final : public GDALAlgorithm
     600             : {
     601             :   public:
     602          11 :     OGRGeoPackageRepackAlgorithm()
     603          11 :         : GDALAlgorithm("repack", "Repack/vacuum in-place a GeoPackage dataset",
     604          11 :                         "/drivers/vector/gpkg.html")
     605             :     {
     606          11 :         constexpr int type = GDAL_OF_RASTER | GDAL_OF_VECTOR | GDAL_OF_UPDATE;
     607             :         auto &arg =
     608          22 :             AddArg("dataset", 0, _("GeoPackage dataset"), &m_dataset, type)
     609          11 :                 .SetPositional()
     610          11 :                 .SetRequired();
     611          11 :         SetAutoCompleteFunctionForFilename(arg, type);
     612          11 :     }
     613             : 
     614             :   protected:
     615             :     bool RunImpl(GDALProgressFunc, void *) override;
     616             : 
     617             :   private:
     618             :     GDALArgDatasetValue m_dataset{};
     619             : };
     620             : 
     621           2 : bool OGRGeoPackageRepackAlgorithm::RunImpl(GDALProgressFunc, void *)
     622             : {
     623             :     auto poDS =
     624           2 :         dynamic_cast<GDALGeoPackageDataset *>(m_dataset.GetDatasetRef());
     625           2 :     if (!poDS)
     626             :     {
     627           1 :         ReportError(CE_Failure, CPLE_AppDefined, "%s is not a GeoPackage",
     628           1 :                     m_dataset.GetName().c_str());
     629           1 :         return false;
     630             :     }
     631           1 :     CPLErrorReset();
     632           1 :     delete poDS->ExecuteSQL("VACUUM", nullptr, nullptr);
     633           1 :     return CPLGetLastErrorType() == CE_None;
     634             : }
     635             : 
     636             : /************************************************************************/
     637             : /*               OGRGeoPackageDriverInstantiateAlgorithm()              */
     638             : /************************************************************************/
     639             : 
     640             : static GDALAlgorithm *
     641          11 : OGRGeoPackageDriverInstantiateAlgorithm(const std::vector<std::string> &aosPath)
     642             : {
     643          11 :     if (aosPath.size() == 1 && aosPath[0] == "repack")
     644             :     {
     645          11 :         return std::make_unique<OGRGeoPackageRepackAlgorithm>().release();
     646             :     }
     647             :     else
     648             :     {
     649           0 :         return nullptr;
     650             :     }
     651             : }
     652             : 
     653             : /************************************************************************/
     654             : /*                         RegisterOGRGeoPackage()                       */
     655             : /************************************************************************/
     656             : 
     657        2033 : void RegisterOGRGeoPackage()
     658             : {
     659        2033 :     if (GDALGetDriverByName("GPKG") != nullptr)
     660         283 :         return;
     661             : 
     662        1750 :     GDALDriver *poDriver = new GDALGPKGDriver();
     663             : 
     664        1750 :     poDriver->SetDescription("GPKG");
     665        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
     666        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
     667        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
     668        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_DELETE_LAYER, "YES");
     669        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
     670        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_DELETE_FIELD, "YES");
     671        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_REORDER_FIELDS, "YES");
     672        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_CURVE_GEOMETRIES, "YES");
     673        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_MEASURED_GEOMETRIES, "YES");
     674        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_Z_GEOMETRIES, "YES");
     675        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_CAN_READ_AFTER_DELETE, "YES");
     676        1750 :     poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
     677        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_CREATE_SUBDATASETS, "YES");
     678        1750 :     poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS,
     679        1750 :                               "NATIVE OGRSQL SQLITE");
     680             : 
     681        1750 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "GeoPackage");
     682        1750 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "gpkg gpkg.zip");
     683        1750 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/vector/gpkg.html");
     684        1750 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES,
     685        1750 :                               "Byte Int16 UInt16 Float32");
     686             : 
     687        1750 :     poDriver->SetMetadataItem(
     688             :         GDAL_DMD_OPENOPTIONLIST,
     689             :         "<OpenOptionList>"
     690             :         "  <Option name='LIST_ALL_TABLES' type='string-select' scope='vector' "
     691             :         "description='Whether all tables, including those non listed in "
     692             :         "gpkg_contents, should be listed' default='AUTO'>"
     693             :         "    <Value>AUTO</Value>"
     694             :         "    <Value>YES</Value>"
     695             :         "    <Value>NO</Value>"
     696             :         "  </Option>"
     697             :         "  <Option name='TABLE' type='string' scope='raster' description='Name "
     698             :         "of tile user-table'/>"
     699             :         "  <Option name='ZOOM_LEVEL' type='integer' scope='raster' "
     700             :         "description='Zoom level of full resolution. If not specified, maximum "
     701             :         "non-empty zoom level'/>"
     702             :         "  <Option name='BAND_COUNT' type='string-select' scope='raster' "
     703             :         "description='Number of raster bands (only for Byte data type)' "
     704             :         "default='AUTO'>"
     705             :         "    <Value>AUTO</Value>"
     706             :         "    <Value>1</Value>"
     707             :         "    <Value>2</Value>"
     708             :         "    <Value>3</Value>"
     709             :         "    <Value>4</Value>"
     710             :         "  </Option>"
     711             :         "  <Option name='MINX' type='float' scope='raster' "
     712             :         "description='Minimum X of area of interest'/>"
     713             :         "  <Option name='MINY' type='float' scope='raster' "
     714             :         "description='Minimum Y of area of interest'/>"
     715             :         "  <Option name='MAXX' type='float' scope='raster' "
     716             :         "description='Maximum X of area of interest'/>"
     717             :         "  <Option name='MAXY' type='float' scope='raster' "
     718             :         "description='Maximum Y of area of interest'/>"
     719             :         "  <Option name='USE_TILE_EXTENT' type='boolean' scope='raster' "
     720             :         "description='Use tile extent of content to determine area of "
     721             :         "interest' default='NO'/>"
     722             :         "  <Option name='WHERE' type='string' scope='raster' description='SQL "
     723             :         "WHERE clause to be appended to tile requests'/>" COMPRESSION_OPTIONS
     724             :         "  <Option name='PRELUDE_STATEMENTS' type='string' "
     725             :         "scope='raster,vector' description='SQL statement(s) to send on the "
     726             :         "SQLite connection before any other ones'/>"
     727             :         "  <Option name='NOLOCK' type='boolean' description='Whether the "
     728             :         "database should be opened in nolock mode'/>"
     729             :         "  <Option name='IMMUTABLE' type='boolean' description='Whether the "
     730             :         "database should be opened in immutable mode'/>"
     731        1750 :         "</OpenOptionList>");
     732        1750 :     poDriver->SetMetadataItem(GDAL_DMD_OVERVIEW_CREATIONOPTIONLIST,
     733             :                               "<OverviewCreationOptionList>"
     734        1750 :                               "</OverviewCreationOptionList>");
     735             : 
     736        1750 :     poDriver->SetMetadataItem(
     737             :         GDAL_DS_LAYER_CREATIONOPTIONLIST,
     738             :         "<LayerCreationOptionList>"
     739             :         "  <Option name='LAUNDER' type='boolean' description='Whether layer "
     740             :         "and field names will be laundered.' default='NO'/>"
     741             :         "  <Option name='GEOMETRY_NAME' type='string' description='Name of "
     742             :         "geometry column.' default='geom' deprecated_alias='GEOMETRY_COLUMN'/>"
     743             :         "  <Option name='GEOMETRY_NULLABLE' type='boolean' "
     744             :         "description='Whether the values of the geometry column can be NULL' "
     745             :         "default='YES'/>"
     746             :         "  <Option name='SRID' type='integer' description='Forced srs_id of "
     747             :         "the "
     748             :         "entry in the gpkg_spatial_ref_sys table to point to'/>"
     749             :         "  <Option name='DISCARD_COORD_LSB' type='boolean' "
     750             :         "description='Whether the geometry coordinate precision should be used "
     751             :         "to set to zero non-significant least-significant bits of geometries. "
     752             :         "Helps when further compression is used' default='NO'/>"
     753             :         "  <Option name='UNDO_DISCARD_COORD_LSB_ON_READING' type='boolean' "
     754             :         "description='Whether to ask GDAL to take into coordinate precision to "
     755             :         "undo the effects of DISCARD_COORD_LSB' default='NO'/>"
     756             :         "  <Option name='FID' type='string' description='Name of the FID "
     757             :         "column to create' default='fid'/>"
     758             :         "  <Option name='OVERWRITE' type='boolean' description='Whether to "
     759             :         "overwrite an existing table with the layer name to be created' "
     760             :         "default='NO'/>"
     761             :         "  <Option name='PRECISION' type='boolean' description='Whether text "
     762             :         "fields created should keep the width' default='YES'/>"
     763             :         "  <Option name='TRUNCATE_FIELDS' type='boolean' description='Whether "
     764             :         "to truncate text content that exceeds maximum width' default='NO'/>"
     765             :         "  <Option name='SPATIAL_INDEX' type='boolean' description='Whether to "
     766             :         "create a spatial index' default='YES'/>"
     767             :         "  <Option name='IDENTIFIER' type='string' description='Identifier of "
     768             :         "the layer, as put in the contents table'/>"
     769             :         "  <Option name='DESCRIPTION' type='string' description='Description "
     770             :         "of the layer, as put in the contents table'/>"
     771             :         "  <Option name='ASPATIAL_VARIANT' type='string-select' "
     772             :         "description='How to register non spatial tables' "
     773             :         "default='GPKG_ATTRIBUTES'>"
     774             :         "     <Value>GPKG_ATTRIBUTES</Value>"
     775             :         "     <Value>NOT_REGISTERED</Value>"
     776             :         "  </Option>"
     777             :         "  <Option name='DATETIME_PRECISION' type='string-select' "
     778             :         "description='Number of components of datetime fields' "
     779             :         "default='AUTO'>"
     780             :         "     <Value>AUTO</Value>"
     781             :         "     <Value>MILLISECOND</Value>"
     782             :         "     <Value>SECOND</Value>"
     783             :         "     <Value>MINUTE</Value>"
     784             :         "  </Option>"
     785        1750 :         "</LayerCreationOptionList>");
     786             : 
     787        1750 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES,
     788             :                               "Integer Integer64 Real String Date DateTime "
     789        1750 :                               "Binary");
     790        1750 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES,
     791        1750 :                               "Boolean Int16 Float32 JSON");
     792        1750 :     poDriver->SetMetadataItem(GDAL_DMD_CREATION_FIELD_DEFN_FLAGS,
     793             :                               "WidthPrecision Nullable Default Unique "
     794        1750 :                               "Comment AlternativeName Domain");
     795             : 
     796        1750 :     poDriver->SetMetadataItem(GDAL_DMD_ALTER_FIELD_DEFN_FLAGS,
     797             :                               "Name Type WidthPrecision Nullable Default "
     798        1750 :                               "Unique Domain AlternativeName Comment");
     799             : 
     800        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_NOTNULL_FIELDS, "YES");
     801        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_DEFAULT_FIELDS, "YES");
     802        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_UNIQUE_FIELDS, "YES");
     803        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_NOTNULL_GEOMFIELDS, "YES");
     804        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_MULTIPLE_VECTOR_LAYERS, "YES");
     805        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_FIELD_DOMAINS, "YES");
     806        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_RELATIONSHIPS, "YES");
     807        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_CREATE_RELATIONSHIP, "YES");
     808        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_DELETE_RELATIONSHIP, "YES");
     809        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_UPDATE_RELATIONSHIP, "YES");
     810        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_FLUSHCACHE_CONSISTENT_STATE, "YES");
     811        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_UPSERT, "YES");
     812             : 
     813        1750 :     poDriver->SetMetadataItem(GDAL_DMD_RELATIONSHIP_FLAGS,
     814        1750 :                               "ManyToMany Association");
     815             : 
     816        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_RENAME_LAYERS, "YES");
     817        1750 :     poDriver->SetMetadataItem(GDAL_DMD_CREATION_FIELD_DOMAIN_TYPES,
     818        1750 :                               "Coded Range Glob");
     819             : 
     820        1750 :     poDriver->SetMetadataItem(GDAL_DMD_ALTER_GEOM_FIELD_DEFN_FLAGS,
     821        1750 :                               "Name SRS CoordinateEpoch");
     822             : 
     823        1750 :     poDriver->SetMetadataItem(
     824             :         GDAL_DMD_RELATIONSHIP_RELATED_TABLE_TYPES,
     825        1750 :         "features media simple_attributes attributes tiles");
     826             : 
     827        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION, "YES");
     828             : 
     829             : #ifdef ENABLE_SQL_GPKG_FORMAT
     830        1750 :     poDriver->SetMetadataItem("ENABLE_SQL_GPKG_FORMAT", "YES");
     831             : #endif
     832             : #ifdef SQLITE_HAS_COLUMN_METADATA
     833        1750 :     poDriver->SetMetadataItem("SQLITE_HAS_COLUMN_METADATA", "YES");
     834             : #endif
     835             : 
     836        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_UPDATE, "YES");
     837        1750 :     poDriver->SetMetadataItem(GDAL_DMD_UPDATE_ITEMS,
     838             :                               "DatasetMetadata BandMetadata RasterValues "
     839        1750 :                               "LayerMetadata Features");
     840             : 
     841        1750 :     poDriver->pfnOpen = OGRGeoPackageDriverOpen;
     842        1750 :     poDriver->pfnIdentify = OGRGeoPackageDriverIdentify;
     843        1750 :     poDriver->pfnCreate = OGRGeoPackageDriverCreate;
     844        1750 :     poDriver->pfnCreateCopy = GDALGeoPackageDataset::CreateCopy;
     845        1750 :     poDriver->pfnDelete = OGRGeoPackageDriverDelete;
     846        1750 :     poDriver->pfnGetSubdatasetInfoFunc = OGRGeoPackageDriverGetSubdatasetInfo;
     847             : 
     848        1750 :     poDriver->pfnInstantiateAlgorithm = OGRGeoPackageDriverInstantiateAlgorithm;
     849        3500 :     poDriver->DeclareAlgorithm({"repack"});
     850             : 
     851        1750 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
     852             : 
     853        1750 :     GetGDALDriverManager()->RegisterDriver(poDriver);
     854             : }

Generated by: LCOV version 1.14