LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/pmtiles - vsipmtiles.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 212 234 90.6 %
Date: 2026-05-29 23:25:07 Functions: 8 8 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  OpenGIS Simple Features Reference Implementation
       4             :  * Purpose:  Virtual file system /vsipmtiles/ 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 "cpl_vsi_virtual.h"
      14             : 
      15             : #include "vsipmtiles.h"
      16             : #include "ogr_pmtiles.h"
      17             : 
      18             : #include "cpl_json.h"
      19             : 
      20             : #include <set>
      21             : 
      22             : #define ENDS_WITH_CI(a, b)                                                     \
      23             :     (strlen(a) >= strlen(b) && EQUAL(a + strlen(a) - strlen(b), b))
      24             : 
      25             : constexpr const char *PMTILES_HEADER_JSON = "pmtiles_header.json";
      26             : constexpr const char *METADATA_JSON = "metadata.json";
      27             : 
      28             : /************************************************************************/
      29             : /*                     VSIPMTilesFilesystemHandler                      */
      30             : /************************************************************************/
      31             : 
      32             : class VSIPMTilesFilesystemHandler final : public VSIFilesystemHandler
      33             : {
      34             :   public:
      35        1863 :     VSIPMTilesFilesystemHandler() = default;
      36             : 
      37             :     VSIVirtualHandleUniquePtr Open(const char *pszFilename,
      38             :                                    const char *pszAccess, bool bSetError,
      39             :                                    CSLConstList papszOptions) override;
      40             :     int Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
      41             :              int nFlags) override;
      42             :     char **ReadDirEx(const char *pszDirname, int nMaxFiles) override;
      43             : };
      44             : 
      45             : /************************************************************************/
      46             : /*                     VSIPMTilesGetTileExtension()                     */
      47             : /************************************************************************/
      48             : 
      49         117 : const char *VSIPMTilesGetTileExtension(OGRPMTilesDataset *poDS)
      50             : {
      51         117 :     const auto &sHeader = poDS->GetHeader();
      52         117 :     switch (sHeader.tile_type)
      53             :     {
      54          36 :         case pmtiles::TILETYPE_MVT:
      55          36 :             return ".mvt";
      56          33 :         case pmtiles::TILETYPE_PNG:
      57          33 :             return ".png";
      58          18 :         case pmtiles::TILETYPE_JPEG:
      59          18 :             return ".jpg";
      60          30 :         case pmtiles::TILETYPE_WEBP:
      61          30 :             return ".webp";
      62           0 :         case pmtiles::TILETYPE_AVIF:
      63           0 :             return ".avif";
      64             :     }
      65           0 :     if (sHeader.tile_compression == pmtiles::COMPRESSION_GZIP)
      66           0 :         return ".bin.gz";
      67           0 :     if (sHeader.tile_compression == pmtiles::COMPRESSION_ZSTD)
      68           0 :         return ".bin.zstd";
      69           0 :     return ".bin";
      70             : }
      71             : 
      72             : /************************************************************************/
      73             : /*                   VSIPMTilesGetPMTilesHeaderJson()                   */
      74             : /************************************************************************/
      75             : 
      76           4 : static std::string VSIPMTilesGetPMTilesHeaderJson(OGRPMTilesDataset *poDS)
      77             : {
      78           4 :     const auto &sHeader = poDS->GetHeader();
      79           8 :     CPLJSONDocument oDoc;
      80           8 :     CPLJSONObject oRoot;
      81           4 :     oRoot.Set("root_dir_offset", sHeader.root_dir_offset);
      82           4 :     oRoot.Set("json_metadata_offset", sHeader.json_metadata_offset);
      83           4 :     oRoot.Set("json_metadata_bytes", sHeader.json_metadata_bytes);
      84           4 :     oRoot.Set("leaf_dirs_offset", sHeader.leaf_dirs_offset);
      85           4 :     oRoot.Set("leaf_dirs_bytes", sHeader.leaf_dirs_bytes);
      86           4 :     oRoot.Set("tile_data_offset", sHeader.tile_data_offset);
      87           4 :     oRoot.Set("tile_data_bytes", sHeader.tile_data_bytes);
      88           4 :     oRoot.Set("addressed_tiles_count", sHeader.addressed_tiles_count);
      89           4 :     oRoot.Set("tile_entries_count", sHeader.tile_entries_count);
      90           4 :     oRoot.Set("tile_contents_count", sHeader.tile_contents_count);
      91           4 :     oRoot.Set("clustered", sHeader.clustered);
      92           4 :     oRoot.Set("internal_compression", sHeader.internal_compression);
      93           4 :     oRoot.Set("internal_compression_str",
      94           4 :               OGRPMTilesDataset::GetCompression(sHeader.internal_compression));
      95           4 :     oRoot.Set("tile_compression", sHeader.tile_compression);
      96           4 :     oRoot.Set("tile_compression_str",
      97           4 :               OGRPMTilesDataset::GetCompression(sHeader.tile_compression));
      98           4 :     oRoot.Set("tile_type", sHeader.tile_type);
      99           4 :     oRoot.Set("tile_type_str", OGRPMTilesDataset::GetTileType(sHeader));
     100           4 :     oRoot.Set("min_zoom", sHeader.min_zoom);
     101           4 :     oRoot.Set("max_zoom", sHeader.max_zoom);
     102           4 :     oRoot.Set("min_lon_e7", sHeader.min_lon_e7);
     103           4 :     oRoot.Set("min_lon_e7_float", sHeader.min_lon_e7 / 10e6);
     104           4 :     oRoot.Set("min_lat_e7", sHeader.min_lat_e7);
     105           4 :     oRoot.Set("min_lat_e7_float", sHeader.min_lat_e7 / 10e6);
     106           4 :     oRoot.Set("max_lon_e7", sHeader.max_lon_e7);
     107           4 :     oRoot.Set("max_lon_e7_float", sHeader.max_lon_e7 / 10e6);
     108           4 :     oRoot.Set("max_lat_e7", sHeader.max_lat_e7);
     109           4 :     oRoot.Set("max_lat_e7_float", sHeader.max_lat_e7 / 10e6);
     110           4 :     oRoot.Set("center_zoom", sHeader.center_zoom);
     111           4 :     oRoot.Set("center_lon_e7", sHeader.center_lon_e7);
     112           4 :     oRoot.Set("center_lat_e7", sHeader.center_lat_e7);
     113           4 :     oDoc.SetRoot(oRoot);
     114           8 :     return oDoc.SaveAsString();
     115             : }
     116             : 
     117             : /************************************************************************/
     118             : /*                           VSIPMTilesOpen()                           */
     119             : /************************************************************************/
     120             : 
     121             : static std::unique_ptr<OGRPMTilesDataset>
     122         114 : VSIPMTilesOpen(const char *pszFilename, std::string &osSubfilename,
     123             :                int &nComponents, int &nZ, int &nX, int &nY)
     124             : {
     125         114 :     if (!STARTS_WITH(pszFilename, "/vsipmtiles/"))
     126           0 :         return nullptr;
     127         114 :     pszFilename += strlen("/vsipmtiles/");
     128             : 
     129         228 :     std::string osFilename(pszFilename);
     130         114 :     if (!osFilename.empty() && osFilename.back() == '/')
     131           0 :         osFilename.pop_back();
     132         114 :     pszFilename = osFilename.c_str();
     133             : 
     134         114 :     nZ = nX = nY = -1;
     135         114 :     nComponents = 0;
     136         228 :     std::string osPmtilesFilename;
     137             : 
     138         114 :     const auto nPos = CPLString(pszFilename).ifind(".pmtiles");
     139         114 :     if (nPos == std::string::npos)
     140           1 :         return nullptr;
     141         113 :     const char *pszPmtilesExt = pszFilename + nPos;
     142             : 
     143         226 :     CPLStringList aosTokens;
     144             :     do
     145             :     {
     146         113 :         if (pszPmtilesExt[strlen(".pmtiles")] == '/')
     147             :         {
     148         109 :             const char *pszSubFile = pszPmtilesExt + strlen(".pmtiles/");
     149         109 :             osPmtilesFilename.assign(pszFilename, pszSubFile - pszFilename - 1);
     150         109 :             osSubfilename = pszPmtilesExt + strlen(".pmtiles/");
     151         210 :             if (osSubfilename == METADATA_JSON ||
     152         101 :                 osSubfilename == PMTILES_HEADER_JSON)
     153             :             {
     154          12 :                 break;
     155             :             }
     156             :         }
     157             :         else
     158             :         {
     159           4 :             osPmtilesFilename = pszFilename;
     160           4 :             osSubfilename.clear();
     161           4 :             break;
     162             :         }
     163             : 
     164          97 :         aosTokens = CSLTokenizeString2(osSubfilename.c_str(), "/", 0);
     165          97 :         nComponents = aosTokens.size();
     166          97 :         if (nComponents >= 4)
     167           0 :             return nullptr;
     168             : 
     169          97 :         if (CPLGetValueType(aosTokens[0]) != CPL_VALUE_INTEGER)
     170           2 :             return nullptr;
     171          95 :         nZ = atoi(aosTokens[0]);
     172          95 :         if (nComponents == 1)
     173           3 :             break;
     174             : 
     175          92 :         if (CPLGetValueType(aosTokens[1]) != CPL_VALUE_INTEGER)
     176           1 :             return nullptr;
     177          91 :         nX = atoi(aosTokens[1]);
     178          91 :         if (nComponents == 2)
     179          31 :             break;
     180             : 
     181          60 :         break;
     182             :     } while (false);
     183             : 
     184         220 :     GDALOpenInfo oOpenInfo(osPmtilesFilename.c_str(), GA_ReadOnly);
     185         220 :     CPLStringList aosOptions;
     186         110 :     aosOptions.SetNameValue("DECOMPRESS_TILES", "NO");
     187         110 :     aosOptions.SetNameValue("ACCEPT_ANY_TILE_TYPE", "YES");
     188         110 :     oOpenInfo.papszOpenOptions = aosOptions.List();
     189         220 :     auto poDS = std::make_unique<OGRPMTilesDataset>();
     190             :     {
     191         110 :         CPLErrorHandlerPusher oErrorHandler(CPLQuietErrorHandler);
     192         110 :         if (!poDS->Open(&oOpenInfo))
     193           4 :             return nullptr;
     194             :     }
     195             : 
     196         106 :     if (nComponents == 3)
     197             :     {
     198          60 :         const char *pszTileExt = VSIPMTilesGetTileExtension(poDS.get());
     199          60 :         if (!ENDS_WITH_CI(aosTokens[2], pszTileExt))
     200           2 :             return nullptr;
     201          58 :         aosTokens[2][strlen(aosTokens[2]) - strlen(pszTileExt)] = 0;
     202          58 :         if (CPLGetValueType(aosTokens[2]) != CPL_VALUE_INTEGER)
     203           0 :             return nullptr;
     204          58 :         nY = atoi(aosTokens[2]);
     205             :     }
     206         104 :     return poDS;
     207             : }
     208             : 
     209             : /************************************************************************/
     210             : /*                                Open()                                */
     211             : /************************************************************************/
     212             : 
     213             : VSIVirtualHandleUniquePtr
     214          50 : VSIPMTilesFilesystemHandler::Open(const char *pszFilename,
     215             :                                   const char *pszAccess, bool /*bSetError*/,
     216             :                                   CSLConstList /*papszOptions*/)
     217             : {
     218          50 :     if (strchr(pszAccess, '+') || strchr(pszAccess, 'w') ||
     219          49 :         strchr(pszAccess, 'a'))
     220           1 :         return nullptr;
     221          98 :     std::string osSubfilename;
     222             :     int nComponents;
     223             :     int nZ;
     224             :     int nX;
     225             :     int nY;
     226             :     auto poDS =
     227          98 :         VSIPMTilesOpen(pszFilename, osSubfilename, nComponents, nZ, nX, nY);
     228          49 :     if (!poDS)
     229           7 :         return nullptr;
     230             : 
     231          42 :     if (osSubfilename == METADATA_JSON)
     232             :     {
     233             :         return VSIVirtualHandleUniquePtr(
     234             :             VSIFileFromMemBuffer(nullptr,
     235           2 :                                  reinterpret_cast<GByte *>(CPLStrdup(
     236           2 :                                      poDS->GetMetadataContent().c_str())),
     237           4 :                                  poDS->GetMetadataContent().size(), true));
     238             :     }
     239             : 
     240          40 :     if (osSubfilename == PMTILES_HEADER_JSON)
     241             :     {
     242           6 :         const auto osStr = VSIPMTilesGetPMTilesHeaderJson(poDS.get());
     243             :         return VSIVirtualHandleUniquePtr(VSIFileFromMemBuffer(
     244           3 :             nullptr, reinterpret_cast<GByte *>(CPLStrdup(osStr.c_str())),
     245           6 :             osStr.size(), true));
     246             :     }
     247             : 
     248          37 :     if (nComponents != 3)
     249           3 :         return nullptr;
     250             : 
     251          68 :     CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
     252             : 
     253          68 :     OGRPMTilesTileIterator oIter(poDS.get(), nZ, nX, nY, nX, nY);
     254          34 :     auto sTile = oIter.GetNextTile();
     255          34 :     if (sTile.offset == 0)
     256           1 :         return nullptr;
     257             : 
     258          33 :     const auto *posStr = poDS->ReadTileData(sTile.offset, sTile.length);
     259          33 :     if (!posStr)
     260             :     {
     261           0 :         return nullptr;
     262             :     }
     263             : 
     264          33 :     GByte *pabyData = static_cast<GByte *>(CPLMalloc(posStr->size()));
     265          33 :     memcpy(pabyData, posStr->data(), posStr->size());
     266             :     return VSIVirtualHandleUniquePtr(
     267          33 :         VSIFileFromMemBuffer(nullptr, pabyData, posStr->size(), true));
     268             : }
     269             : 
     270             : /************************************************************************/
     271             : /*                                Stat()                                */
     272             : /************************************************************************/
     273             : 
     274          28 : int VSIPMTilesFilesystemHandler::Stat(const char *pszFilename,
     275             :                                       VSIStatBufL *pStatBuf, int /*nFlags*/)
     276             : {
     277          28 :     memset(pStatBuf, 0, sizeof(VSIStatBufL));
     278             : 
     279          56 :     std::string osSubfilename;
     280             :     int nComponents;
     281             :     int nZ;
     282             :     int nX;
     283             :     int nY;
     284             :     auto poDS =
     285          56 :         VSIPMTilesOpen(pszFilename, osSubfilename, nComponents, nZ, nX, nY);
     286          28 :     if (!poDS)
     287           0 :         return -1;
     288             : 
     289             :     VSIStatBufL sStatPmtiles;
     290          28 :     if (VSIStatL(poDS->GetDescription(), &sStatPmtiles) == 0)
     291             :     {
     292          28 :         pStatBuf->st_mtime = sStatPmtiles.st_mtime;
     293             :     }
     294             : 
     295          28 :     if (osSubfilename.empty())
     296             :     {
     297           1 :         pStatBuf->st_mode = S_IFDIR;
     298           1 :         return 0;
     299             :     }
     300          27 :     else if (osSubfilename == METADATA_JSON)
     301             :     {
     302           3 :         pStatBuf->st_mode = S_IFREG;
     303           3 :         pStatBuf->st_size = poDS->GetMetadataContent().size();
     304           3 :         return 0;
     305             :     }
     306          24 :     else if (osSubfilename == PMTILES_HEADER_JSON)
     307             :     {
     308           1 :         pStatBuf->st_mode = S_IFREG;
     309           1 :         pStatBuf->st_size = VSIPMTilesGetPMTilesHeaderJson(poDS.get()).size();
     310           1 :         return 0;
     311             :     }
     312             : 
     313          46 :     CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
     314             : 
     315          46 :     OGRPMTilesTileIterator oIter(poDS.get(), nZ, nX, nY, nX, nY);
     316          23 :     auto sTile = oIter.GetNextTile();
     317          23 :     if (sTile.offset == 0)
     318           1 :         return -1;
     319             : 
     320          22 :     if (nComponents <= 2)
     321             :     {
     322           0 :         pStatBuf->st_mode = S_IFDIR;
     323           0 :         return 0;
     324             :     }
     325             : 
     326          22 :     pStatBuf->st_mode = S_IFREG;
     327          22 :     pStatBuf->st_size = sTile.length;
     328          22 :     return 0;
     329             : }
     330             : 
     331             : /************************************************************************/
     332             : /*                             ReadDirEx()                              */
     333             : /************************************************************************/
     334             : 
     335          37 : char **VSIPMTilesFilesystemHandler::ReadDirEx(const char *pszFilename,
     336             :                                               int nMaxFiles)
     337             : {
     338          74 :     std::string osSubfilename;
     339             :     int nComponents;
     340             :     int nZ;
     341             :     int nX;
     342             :     int nY;
     343             :     auto poDS =
     344          74 :         VSIPMTilesOpen(pszFilename, osSubfilename, nComponents, nZ, nX, nY);
     345          37 :     if (!poDS)
     346           3 :         return nullptr;
     347             : 
     348          34 :     if (osSubfilename.empty())
     349             :     {
     350           2 :         CPLStringList aosFiles;
     351           1 :         aosFiles.AddString(PMTILES_HEADER_JSON);
     352           1 :         aosFiles.AddString(METADATA_JSON);
     353           4 :         for (int i = poDS->GetMinZoomLevel(); i <= poDS->GetMaxZoomLevel(); ++i)
     354             :         {
     355           3 :             OGRPMTilesTileIterator oIter(poDS.get(), i);
     356           3 :             auto sTile = oIter.GetNextTile();
     357           3 :             if (sTile.offset != 0)
     358             :             {
     359           3 :                 if (nMaxFiles > 0 && aosFiles.size() >= nMaxFiles)
     360           0 :                     break;
     361           3 :                 aosFiles.AddString(CPLSPrintf("%d", i));
     362             :             }
     363             :         }
     364           1 :         return aosFiles.StealList();
     365             :     }
     366             : 
     367          33 :     if (nComponents == 1)
     368             :     {
     369           4 :         std::set<int> oSetX;
     370           4 :         OGRPMTilesTileIterator oIter(poDS.get(), nZ);
     371             :         while (true)
     372             :         {
     373           4 :             auto sTile = oIter.GetNextTile();
     374           4 :             if (sTile.offset == 0)
     375           2 :                 break;
     376           2 :             oSetX.insert(sTile.x);
     377           2 :             if (nMaxFiles > 0 && static_cast<int>(oSetX.size()) >= nMaxFiles)
     378           0 :                 break;
     379           2 :             if (oSetX.size() == 1024 * 1024)
     380             :             {
     381           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Too many tiles");
     382           0 :                 return nullptr;
     383             :             }
     384           2 :         }
     385           4 :         CPLStringList aosFiles;
     386           4 :         for (int x : oSetX)
     387             :         {
     388           2 :             aosFiles.AddString(CPLSPrintf("%d", x));
     389             :         }
     390           2 :         return aosFiles.StealList();
     391             :     }
     392             : 
     393          31 :     if (nComponents == 2)
     394             :     {
     395          60 :         std::set<int> oSetY;
     396          60 :         OGRPMTilesTileIterator oIter(poDS.get(), nZ, nX, -1, nX, -1);
     397             :         while (true)
     398             :         {
     399          58 :             auto sTile = oIter.GetNextTile();
     400          58 :             if (sTile.offset == 0)
     401          30 :                 break;
     402          28 :             oSetY.insert(sTile.y);
     403          28 :             if (nMaxFiles > 0 && static_cast<int>(oSetY.size()) >= nMaxFiles)
     404           0 :                 break;
     405          28 :             if (oSetY.size() == 1024 * 1024)
     406             :             {
     407           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Too many tiles");
     408           0 :                 return nullptr;
     409             :             }
     410          28 :         }
     411          60 :         CPLStringList aosFiles;
     412          30 :         const char *pszTileExt = VSIPMTilesGetTileExtension(poDS.get());
     413          58 :         for (int y : oSetY)
     414             :         {
     415          28 :             aosFiles.AddString(CPLSPrintf("%d%s", y, pszTileExt));
     416             :         }
     417          30 :         return aosFiles.StealList();
     418             :     }
     419             : 
     420           1 :     return nullptr;
     421             : }
     422             : 
     423             : /************************************************************************/
     424             : /*                         VSIPMTilesRegister()                         */
     425             : /************************************************************************/
     426             : 
     427        1863 : void VSIPMTilesRegister()
     428             : {
     429        1863 :     if (VSIFileManager::GetHandler("/vsipmtiles/") ==
     430        1863 :         VSIFileManager::GetHandler("/"))
     431             :     {
     432        1863 :         VSIFileManager::InstallHandler(
     433        3726 :             "/vsipmtiles/", std::make_shared<VSIPMTilesFilesystemHandler>());
     434             :     }
     435        1863 : }

Generated by: LCOV version 1.14