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

Generated by: LCOV version 1.14