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

Generated by: LCOV version 1.14