LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/pmtiles - ogrpmtilesdriver.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 155 196 79.1 %
Date: 2026-05-29 23:25:07 Functions: 7 7 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  OpenGIS Simple Features Reference Implementation
       4             :  * Purpose:  Implementation of PMTiles
       5             :  * Author:   Even Rouault <even.rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2023, Planet Labs
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "ogr_pmtiles.h"
      14             : 
      15             : #include "vsipmtiles.h"
      16             : 
      17             : #include "ogrpmtilesfrommbtiles.h"
      18             : 
      19             : #include "mbtiles.h"
      20             : 
      21             : #ifdef GDAL_ENABLE_ALGORITHMS
      22             : #include "gdalalg_raster_tile.h"
      23             : #include "ogrpmtilesfromtileset.h"
      24             : #endif
      25             : 
      26             : #ifdef HAVE_MVT_WRITE_SUPPORT
      27             : #include "mvtutils.h"
      28             : #endif
      29             : 
      30             : #include <algorithm>
      31             : #include <cmath>
      32             : 
      33             : /************************************************************************/
      34             : /*                      OGRPMTilesDriverIdentify()                      */
      35             : /************************************************************************/
      36             : 
      37       60198 : static int OGRPMTilesDriverIdentify(GDALOpenInfo *poOpenInfo)
      38             : {
      39       62642 :     if (poOpenInfo->nHeaderBytes < PMTILES_HEADER_LENGTH || !poOpenInfo->fpL ||
      40        2444 :         !poOpenInfo->IsExtensionEqualToCI("pmtiles"))
      41             :     {
      42       60059 :         return FALSE;
      43             :     }
      44         139 :     return memcmp(poOpenInfo->pabyHeader, "PMTiles\x03", 8) == 0;
      45             : }
      46             : 
      47             : /************************************************************************/
      48             : /*                        OGRPMTilesDriverOpen()                        */
      49             : /************************************************************************/
      50             : 
      51          84 : static GDALDataset *OGRPMTilesDriverOpen(GDALOpenInfo *poOpenInfo)
      52             : {
      53          84 :     if (!OGRPMTilesDriverIdentify(poOpenInfo))
      54           5 :         return nullptr;
      55         158 :     auto poDS = std::make_unique<OGRPMTilesDataset>();
      56          79 :     if (!poDS->Open(poOpenInfo))
      57           8 :         return nullptr;
      58          71 :     return poDS.release();
      59             : }
      60             : 
      61             : /************************************************************************/
      62             : /*               OGRPMTilesDriverCanVectorTranslateFrom()               */
      63             : /************************************************************************/
      64             : 
      65           5 : static bool OGRPMTilesDriverCanVectorTranslateFrom(
      66             :     const char * /*pszDestName*/, GDALDataset *poSourceDS,
      67             :     CSLConstList papszVectorTranslateArguments, char ***ppapszFailureReasons)
      68             : {
      69           5 :     auto poSrcDriver = poSourceDS->GetDriver();
      70           5 :     if (!(poSrcDriver && EQUAL(poSrcDriver->GetDescription(), "MBTiles")))
      71             :     {
      72           1 :         if (ppapszFailureReasons)
      73           1 :             *ppapszFailureReasons = CSLAddString(
      74             :                 *ppapszFailureReasons, "Source driver is not MBTiles");
      75           1 :         return false;
      76             :     }
      77             : 
      78           4 :     if (papszVectorTranslateArguments)
      79             :     {
      80           2 :         const int nArgs = CSLCount(papszVectorTranslateArguments);
      81           4 :         for (int i = 0; i < nArgs; ++i)
      82             :         {
      83           2 :             if (i + 1 < nArgs &&
      84           2 :                 (strcmp(papszVectorTranslateArguments[i], "-f") == 0 ||
      85           0 :                  strcmp(papszVectorTranslateArguments[i], "-of") == 0))
      86             :             {
      87           2 :                 ++i;
      88             :             }
      89             :             else
      90             :             {
      91           0 :                 if (ppapszFailureReasons)
      92           0 :                     *ppapszFailureReasons =
      93           0 :                         CSLAddString(*ppapszFailureReasons,
      94             :                                      "Direct copy from MBTiles does not "
      95             :                                      "support GDALVectorTranslate() options");
      96           0 :                 return false;
      97             :             }
      98             :         }
      99             :     }
     100             : 
     101           4 :     return true;
     102             : }
     103             : 
     104             : /************************************************************************/
     105             : /*                OGRPMTilesDriverVectorTranslateFrom()                 */
     106             : /************************************************************************/
     107             : 
     108           2 : static GDALDataset *OGRPMTilesDriverVectorTranslateFrom(
     109             :     const char *pszDestName, GDALDataset *poSourceDS,
     110             :     CSLConstList papszVectorTranslateArguments,
     111             :     GDALProgressFunc /* pfnProgress */, void * /* pProgressData */)
     112             : {
     113           2 :     if (!OGRPMTilesDriverCanVectorTranslateFrom(
     114             :             pszDestName, poSourceDS, papszVectorTranslateArguments, nullptr))
     115             :     {
     116           0 :         return nullptr;
     117             :     }
     118             : 
     119           2 :     if (!OGRPMTilesConvertFromMBTiles(pszDestName,
     120           2 :                                       poSourceDS->GetDescription()))
     121             :     {
     122           0 :         return nullptr;
     123             :     }
     124             : 
     125           4 :     GDALOpenInfo oOpenInfo(pszDestName, GA_ReadOnly);
     126           2 :     oOpenInfo.nOpenFlags = GDAL_OF_VECTOR;
     127           2 :     return OGRPMTilesDriverOpen(&oOpenInfo);
     128             : }
     129             : 
     130             : #ifdef HAVE_MVT_WRITE_SUPPORT
     131             : /************************************************************************/
     132             : /*                       OGRPMTilesDriverCreate()                       */
     133             : /************************************************************************/
     134             : 
     135          66 : static GDALDataset *OGRPMTilesDriverCreate(const char *pszFilename, int nXSize,
     136             :                                            int nYSize, int nBandsIn,
     137             :                                            GDALDataType eDT,
     138             :                                            CSLConstList papszOptions)
     139             : {
     140          66 :     if (nXSize == 0 && nYSize == 0 && nBandsIn == 0 && eDT == GDT_Unknown)
     141             :     {
     142          68 :         auto poDS = std::make_unique<OGRPMTilesWriterDataset>();
     143          34 :         if (!poDS->Create(pszFilename, papszOptions))
     144           1 :             return nullptr;
     145          33 :         return poDS.release();
     146             :     }
     147          32 :     CPLError(CE_Failure, CPLE_AppDefined, "Create() not supported for raster");
     148          32 :     return nullptr;
     149             : }
     150             : #endif
     151             : 
     152             : /************************************************************************/
     153             : /*                     OGRPMTilesDriverCreateCopy()                     */
     154             : /************************************************************************/
     155             : 
     156          35 : static GDALDataset *OGRPMTilesDriverCreateCopy(const char *pszFilename,
     157             :                                                GDALDataset *poSrcDS, int,
     158             :                                                CSLConstList papszOptions,
     159             :                                                GDALProgressFunc pfnProgress,
     160             :                                                void *pProgressData)
     161             : {
     162          35 :     auto poSrcDriver = poSrcDS->GetDriver();
     163          35 :     if (poSrcDriver && EQUAL(poSrcDriver->GetDescription(), "MBTiles"))
     164             :     {
     165           3 :         if (papszOptions && papszOptions[0])
     166             :         {
     167           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     168             :                      "No creation option is supported for conversion of "
     169             :                      "MBTiles to PMTiles");
     170           0 :             return nullptr;
     171             :         }
     172           3 :         if (!OGRPMTilesConvertFromMBTiles(pszFilename,
     173           3 :                                           poSrcDS->GetDescription()))
     174           0 :             return nullptr;
     175             :     }
     176             :     else
     177             :     {
     178             :         const int nBlockSize = std::clamp(
     179           0 :             atoi(CSLFetchNameValueDef(papszOptions, "BLOCKSIZE", "256")), 64,
     180          32 :             8192);
     181             :         const char *pszResampling =
     182          32 :             CSLFetchNameValueDef(papszOptions, "RESAMPLING", "BILINEAR");
     183          32 :         CPLStringList aosOptions(papszOptions);
     184          32 :         if (!aosOptions.FetchNameValue("NAME"))
     185             :             aosOptions.SetNameValue("NAME",
     186          32 :                                     CPLGetBasenameSafe(pszFilename).c_str());
     187          32 :         if (!aosOptions.FetchNameValue("DESCRIPTION"))
     188             :             aosOptions.SetNameValue("DESCRIPTION",
     189          32 :                                     CPLGetBasenameSafe(pszFilename).c_str());
     190             : 
     191             :         // There are two code paths possible:
     192             :         // - the preferred one, using "gdal raster tile" when possible, since
     193             :         //   this is multi-threaded
     194             :         // - the fallback one, using a temporary MBTiles dataset
     195             : 
     196             : #if GDAL_ENABLE_ALGORITHMS
     197           0 :         std::unique_ptr<GDALDataset> poTmpSrcDS;
     198          32 :         std::string osTmpSrcFilename;
     199             : 
     200             :         // Check that the dataset can be used in a thread-safe way to be able
     201             :         // to use gdal raster tile
     202             :         bool bCanCreateThreadSafeDataset;
     203             :         {
     204          64 :             CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
     205             :             bCanCreateThreadSafeDataset =
     206          64 :                 std::unique_ptr<GDALDataset>(GDALGetThreadSafeDataset(
     207          32 :                     poSrcDS, GDAL_OF_RASTER)) != nullptr;
     208             : 
     209             :             // Case of anonymous VRT, e.g. when doing
     210             :             // gdal_translate in.tif out.pmtiles -outsize ...
     211           1 :             if (!bCanCreateThreadSafeDataset && poSrcDriver &&
     212          33 :                 EQUAL(poSrcDriver->GetDescription(), "VRT") &&
     213           0 :                 EQUAL(poSrcDS->GetDescription(), ""))
     214             :             {
     215             :                 osTmpSrcFilename =
     216           0 :                     CPLGenerateTempFilenameSafe(CPLGetFilename(pszFilename));
     217           0 :                 osTmpSrcFilename += ".vrt";
     218           0 :                 poTmpSrcDS.reset(
     219             :                     poSrcDriver->CreateCopy(osTmpSrcFilename.c_str(), poSrcDS,
     220             :                                             false, nullptr, nullptr, nullptr));
     221           0 :                 if (poTmpSrcDS)
     222             :                 {
     223           0 :                     poTmpSrcDS->MarkSuppressOnClose();
     224           0 :                     bCanCreateThreadSafeDataset = true;
     225           0 :                     poSrcDS = poTmpSrcDS.get();
     226             :                 }
     227             :                 else
     228             :                 {
     229           0 :                     VSIUnlink(osTmpSrcFilename.c_str());
     230             :                 }
     231             :             }
     232             :         }
     233             : 
     234             :         const char *pszTileFormat =
     235          32 :             CSLFetchNameValueDef(papszOptions, "TILE_FORMAT", "PNG");
     236             :         // gdal raster tile doesn't support the PNG8 tile format
     237             :         // or the ZOOM_LEVEL_STRATEGY option
     238          59 :         if (bCanCreateThreadSafeDataset && !EQUAL(pszTileFormat, "PNG8") &&
     239          27 :             !CSLFetchNameValue(papszOptions, "ZOOM_LEVEL_STRATEGY"))
     240             :         {
     241             :             std::string osTmpTilesDirectory =
     242          48 :                 std::string(pszFilename).append(".tmp");
     243          24 :             if (!VSIIsLocal(pszFilename))
     244             :             {
     245             :                 osTmpTilesDirectory =
     246           0 :                     CPLGenerateTempFilenameSafe(CPLGetFilename(pszFilename));
     247             :             }
     248             : 
     249          24 :             GDALRasterTileAlgorithm alg;
     250             :             try
     251             :             {
     252          24 :                 alg["input"] = poSrcDS;
     253          24 :                 alg["output"] = osTmpTilesDirectory;
     254          24 :                 alg["format"] = pszTileFormat;
     255          24 :                 alg["tile-size"] = nBlockSize;
     256          24 :                 alg["min-zoom-single-tile"] = true;
     257          24 :                 alg["resampling"] = pszResampling;
     258          24 :                 alg["overview-resampling"] = pszResampling;
     259          24 :                 CPLStringList aosCO;
     260          24 :                 if (const char *pszQuality =
     261          24 :                         CSLFetchNameValue(papszOptions, "QUALITY"))
     262             :                 {
     263           0 :                     if (EQUAL(pszTileFormat, "JPEG") ||
     264           0 :                         EQUAL(pszTileFormat, "WEBP"))
     265           0 :                         aosCO.SetNameValue("QUALITY", pszQuality);
     266             :                 }
     267          24 :                 if (const char *pszZLevel =
     268          24 :                         CSLFetchNameValue(papszOptions, "ZLEVEL"))
     269             :                 {
     270           0 :                     if (EQUAL(pszTileFormat, "PNG"))
     271           0 :                         aosCO.SetNameValue("ZLEVEL", pszZLevel);
     272             :                 }
     273          48 :                 alg["creation-option"] =
     274          72 :                     static_cast<std::vector<std::string>>(aosCO);
     275             :             }
     276           0 :             catch (const std::exception &e)
     277             :             {
     278           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
     279           0 :                 return nullptr;
     280             :             }
     281             : 
     282             :             std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)>
     283             :                 pScaledProgress(GDALCreateScaledProgress(0.0, 0.9, pfnProgress,
     284             :                                                          pProgressData),
     285          24 :                                 GDALDestroyScaledProgress);
     286             :             const bool bRet =
     287          24 :                 alg.Run(pScaledProgress ? GDALScaledProgress : nullptr,
     288          12 :                         pScaledProgress.get()) &&
     289          36 :                 alg.Finalize() &&
     290          12 :                 OGRPMTilesConvertFromTileset(pszFilename,
     291             :                                              osTmpTilesDirectory.c_str(),
     292          12 :                                              poSrcDS, aosOptions.List());
     293          24 :             VSIRmdirRecursive(osTmpTilesDirectory.c_str());
     294          24 :             if (!bRet)
     295          12 :                 return nullptr;
     296          12 :             if (pfnProgress)
     297          12 :                 pfnProgress(1.0, "", pProgressData);
     298             :         }
     299             :         else
     300             : #endif
     301             :         {
     302             :             auto poMBTilesDrv =
     303           8 :                 GetGDALDriverManager()->GetDriverByName("MBTiles");
     304           8 :             if (!poMBTilesDrv)
     305             :             {
     306           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     307             :                          "Conversion to PMTiles requires prior conversion to "
     308             :                          "MBTiles but driver is not available");
     309           1 :                 return nullptr;
     310             :             }
     311             :             std::string osTmpMBTilesFilename =
     312           8 :                 CPLResetExtensionSafe(pszFilename, "mbtiles");
     313           8 :             if (!VSIIsLocal(pszFilename))
     314             :             {
     315             :                 osTmpMBTilesFilename =
     316           0 :                     CPLGenerateTempFilenameSafe(CPLGetFilename(pszFilename));
     317             :             }
     318             : 
     319             :             std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)>
     320             :                 pScaledProgress(GDALCreateScaledProgress(0.0, 2.0 / 3 * 0.9,
     321             :                                                          pfnProgress,
     322             :                                                          pProgressData),
     323           8 :                                 GDALDestroyScaledProgress);
     324             :             std::unique_ptr<GDALDataset> poMBTilesDS(poMBTilesDrv->CreateCopy(
     325           8 :                 osTmpMBTilesFilename.c_str(), poSrcDS, false, aosOptions.List(),
     326           8 :                 pScaledProgress ? GDALScaledProgress : nullptr,
     327          24 :                 pScaledProgress.get()));
     328           8 :             if (!poMBTilesDS)
     329             :             {
     330             :                 // Error message emitted by CreateCopy()
     331             :                 VSIStatBufL sStat;
     332           1 :                 if (VSIStatL(osTmpMBTilesFilename.c_str(), &sStat) == 0 &&
     333           0 :                     VSIUnlink(osTmpMBTilesFilename.c_str()) != 0)
     334             :                 {
     335           0 :                     CPLError(CE_Warning, CPLE_FileIO, "Cannot delete %s",
     336             :                              osTmpMBTilesFilename.c_str());
     337             :                 }
     338           1 :                 return nullptr;
     339             :             }
     340           7 :             const int nMaxDim = std::max(poMBTilesDS->GetRasterXSize(),
     341          14 :                                          poMBTilesDS->GetRasterYSize());
     342           7 :             const int nOvrCount = static_cast<int>(std::ceil(
     343           7 :                 std::log2(static_cast<double>(nMaxDim) / nBlockSize)));
     344           7 :             if (nOvrCount > 0)
     345             :             {
     346           3 :                 std::vector<int> anLevels;
     347           7 :                 for (int i = 0; i < nOvrCount; ++i)
     348           4 :                     anLevels.push_back(1 << (i + 1));
     349           3 :                 pScaledProgress.reset(GDALCreateScaledProgress(
     350             :                     2.0 / 3 * 0.9, 0.9, pfnProgress, pProgressData));
     351             :                 const bool bOK =
     352           9 :                     (poMBTilesDS->BuildOverviews(
     353           3 :                          pszResampling, nOvrCount, anLevels.data(), 0, nullptr,
     354           3 :                          pScaledProgress ? GDALScaledProgress : nullptr,
     355           3 :                          pScaledProgress.get(), nullptr) == CE_None);
     356           3 :                 if (!bOK)
     357             :                 {
     358           0 :                     poMBTilesDS.reset();
     359             :                     // Error message emitted by BuildOverviewss()
     360           0 :                     if (VSIUnlink(osTmpMBTilesFilename.c_str()) != 0)
     361             :                     {
     362           0 :                         CPLError(CE_Warning, CPLE_FileIO, "Cannot delete %s",
     363             :                                  osTmpMBTilesFilename.c_str());
     364             :                     }
     365           0 :                     return nullptr;
     366             :                 }
     367             :             }
     368           7 :             poMBTilesDS.reset();
     369           7 :             if (!OGRPMTilesConvertFromMBTiles(pszFilename,
     370             :                                               osTmpMBTilesFilename.c_str()))
     371             :             {
     372           0 :                 if (VSIUnlink(osTmpMBTilesFilename.c_str()) != 0)
     373             :                 {
     374           0 :                     CPLError(CE_Warning, CPLE_FileIO, "Cannot delete %s",
     375             :                              osTmpMBTilesFilename.c_str());
     376             :                 }
     377           0 :                 return nullptr;
     378             :             }
     379           7 :             if (pfnProgress)
     380           7 :                 pfnProgress(1.0, "", pProgressData);
     381           7 :             VSIUnlink(osTmpMBTilesFilename.c_str());
     382             :         }
     383             :     }
     384          44 :     GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
     385          22 :     oOpenInfo.nOpenFlags = GDAL_OF_RASTER | GDAL_OF_VECTOR;
     386          22 :     return OGRPMTilesDriverOpen(&oOpenInfo);
     387             : }
     388             : 
     389             : /************************************************************************/
     390             : /*                         RegisterOGRPMTiles()                         */
     391             : /************************************************************************/
     392             : 
     393        2126 : void RegisterOGRPMTiles()
     394             : {
     395        2126 :     if (GDALGetDriverByName("PMTiles") != nullptr)
     396         263 :         return;
     397             : 
     398        1863 :     VSIPMTilesRegister();
     399             : 
     400        1863 :     GDALDriver *poDriver = new GDALDriver();
     401             : 
     402        1863 :     poDriver->SetDescription("PMTiles");
     403        1863 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
     404        1863 :     poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
     405        1863 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "ProtoMap Tiles");
     406        1863 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "pmtiles");
     407        1863 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
     408        1863 :                               "drivers/vector/pmtiles.html");
     409             : 
     410        1863 :     poDriver->SetMetadataItem(
     411             :         GDAL_DMD_OPENOPTIONLIST,
     412             :         "<OpenOptionList>"
     413             :         "  <Option name='ZOOM_LEVEL' type='integer' "
     414             :         "description='Zoom level of full resolution. If not specified, maximum "
     415             :         "non-empty zoom level'/>"
     416             :         "  <Option name='CLIP' type='boolean' "
     417             :         "description='Whether to clip geometries to tile extent' "
     418             :         "default='YES'/>"
     419             :         "  <Option name='ZOOM_LEVEL_AUTO' type='boolean' "
     420             :         "description='Whether to auto-select the zoom level for vector layers "
     421             :         "according to spatial filter extent. Only for display purpose' "
     422             :         "default='NO'/>"
     423             :         "  <Option name='JSON_FIELD' type='boolean' "
     424             :         "description='For vector layers, "
     425             :         "whether to put all attributes as a serialized JSon dictionary'/>"
     426        1863 :         "</OpenOptionList>");
     427             : 
     428        1863 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
     429             : 
     430        1863 :     poDriver->pfnOpen = OGRPMTilesDriverOpen;
     431        1863 :     poDriver->pfnIdentify = OGRPMTilesDriverIdentify;
     432        1863 :     poDriver->pfnCanVectorTranslateFrom =
     433             :         OGRPMTilesDriverCanVectorTranslateFrom;
     434        1863 :     poDriver->pfnVectorTranslateFrom = OGRPMTilesDriverVectorTranslateFrom;
     435             : 
     436        1863 :     poDriver->SetMetadataItem(
     437             :         GDAL_DMD_CREATIONOPTIONLIST,
     438             :         "<CreationOptionList>" MBTILES_RASTER_CREATION_OPTIONS
     439             : #ifdef HAVE_MVT_WRITE_SUPPORT
     440             :         MVT_MBTILES_COMMON_DSCO
     441             : #endif
     442        1863 :         "</CreationOptionList>");
     443             : 
     444             : #ifdef HAVE_MVT_WRITE_SUPPORT
     445        1863 :     poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
     446        1863 :     poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
     447        1863 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES,
     448        1863 :                               "Integer Integer64 Real String");
     449        1863 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES,
     450        1863 :                               "Boolean Float32");
     451             : 
     452        1863 :     poDriver->SetMetadataItem(GDAL_DS_LAYER_CREATIONOPTIONLIST, MVT_LCO);
     453             : 
     454        1863 :     poDriver->pfnCreate = OGRPMTilesDriverCreate;
     455             : #endif
     456        1863 :     poDriver->pfnCreateCopy = OGRPMTilesDriverCreateCopy;
     457             : 
     458        1863 :     GetGDALDriverManager()->RegisterDriver(poDriver);
     459             : }

Generated by: LCOV version 1.14