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

Generated by: LCOV version 1.14