LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/pmtiles - vsipmtiles.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 205 231 88.7 %
Date: 2025-11-07 02:20:00 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        1757 :     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          60 : VSIPMTilesOpen(const char *pszFilename, std::string &osSubfilename,
     121             :                int &nComponents, int &nZ, int &nX, int &nY)
     122             : {
     123          60 :     if (!STARTS_WITH(pszFilename, "/vsipmtiles/"))
     124           0 :         return nullptr;
     125          60 :     pszFilename += strlen("/vsipmtiles/");
     126             : 
     127         120 :     std::string osFilename(pszFilename);
     128          60 :     if (!osFilename.empty() && osFilename.back() == '/')
     129           0 :         osFilename.pop_back();
     130          60 :     pszFilename = osFilename.c_str();
     131             : 
     132          60 :     nZ = nX = nY = -1;
     133          60 :     nComponents = 0;
     134         120 :     std::string osPmtilesFilename;
     135             : 
     136          60 :     const char *pszPmtilesExt = strstr(pszFilename, ".pmtiles");
     137          60 :     if (!pszPmtilesExt)
     138           1 :         return nullptr;
     139             : 
     140         118 :     CPLStringList aosTokens;
     141             :     do
     142             :     {
     143          59 :         if (pszPmtilesExt[strlen(".pmtiles")] == '/')
     144             :         {
     145          55 :             const char *pszSubFile = pszPmtilesExt + strlen(".pmtiles/");
     146          55 :             osPmtilesFilename.assign(pszFilename, pszSubFile - pszFilename - 1);
     147          55 :             osSubfilename = pszPmtilesExt + strlen(".pmtiles/");
     148         102 :             if (osSubfilename == METADATA_JSON ||
     149          47 :                 osSubfilename == PMTILES_HEADER_JSON)
     150             :             {
     151          12 :                 break;
     152             :             }
     153             :         }
     154             :         else
     155             :         {
     156           4 :             osPmtilesFilename = pszFilename;
     157           4 :             osSubfilename.clear();
     158           4 :             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         112 :     GDALOpenInfo oOpenInfo(osPmtilesFilename.c_str(), GA_ReadOnly);
     182         112 :     CPLStringList aosOptions;
     183          56 :     aosOptions.SetNameValue("DECOMPRESS_TILES", "NO");
     184          56 :     aosOptions.SetNameValue("ACCEPT_ANY_TILE_TYPE", "YES");
     185          56 :     oOpenInfo.papszOpenOptions = aosOptions.List();
     186         112 :     auto poDS = std::make_unique<OGRPMTilesDataset>();
     187             :     {
     188          56 :         CPLErrorHandlerPusher oErrorHandler(CPLQuietErrorHandler);
     189          56 :         if (!poDS->Open(&oOpenInfo))
     190           4 :             return nullptr;
     191             :     }
     192             : 
     193          52 :     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          50 :     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          28 : int VSIPMTilesFilesystemHandler::Stat(const char *pszFilename,
     272             :                                       VSIStatBufL *pStatBuf, int /*nFlags*/)
     273             : {
     274          28 :     memset(pStatBuf, 0, sizeof(VSIStatBufL));
     275             : 
     276          56 :     std::string osSubfilename;
     277             :     int nComponents;
     278             :     int nZ;
     279             :     int nX;
     280             :     int nY;
     281             :     auto poDS =
     282          56 :         VSIPMTilesOpen(pszFilename, osSubfilename, nComponents, nZ, nX, nY);
     283          28 :     if (!poDS)
     284           0 :         return -1;
     285             : 
     286             :     VSIStatBufL sStatPmtiles;
     287          28 :     if (VSIStatL(poDS->GetDescription(), &sStatPmtiles) == 0)
     288             :     {
     289          28 :         pStatBuf->st_mtime = sStatPmtiles.st_mtime;
     290             :     }
     291             : 
     292          28 :     if (osSubfilename.empty())
     293             :     {
     294           1 :         pStatBuf->st_mode = S_IFDIR;
     295           1 :         return 0;
     296             :     }
     297          27 :     else if (osSubfilename == METADATA_JSON)
     298             :     {
     299           3 :         pStatBuf->st_mode = S_IFREG;
     300           3 :         pStatBuf->st_size = poDS->GetMetadataContent().size();
     301           3 :         return 0;
     302             :     }
     303          24 :     else if (osSubfilename == PMTILES_HEADER_JSON)
     304             :     {
     305           1 :         pStatBuf->st_mode = S_IFREG;
     306           1 :         pStatBuf->st_size = VSIPMTilesGetPMTilesHeaderJson(poDS.get()).size();
     307           1 :         return 0;
     308             :     }
     309             : 
     310          46 :     CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
     311             : 
     312          46 :     OGRPMTilesTileIterator oIter(poDS.get(), nZ, nX, nY, nX, nY);
     313          23 :     auto sTile = oIter.GetNextTile();
     314          23 :     if (sTile.offset == 0)
     315           1 :         return -1;
     316             : 
     317          22 :     if (nComponents <= 2)
     318             :     {
     319           0 :         pStatBuf->st_mode = S_IFDIR;
     320           0 :         return 0;
     321             :     }
     322             : 
     323          22 :     pStatBuf->st_mode = S_IFREG;
     324          22 :     pStatBuf->st_size = sTile.length;
     325          22 :     return 0;
     326             : }
     327             : 
     328             : /************************************************************************/
     329             : /*                            ReadDirEx()                               */
     330             : /************************************************************************/
     331             : 
     332          10 : char **VSIPMTilesFilesystemHandler::ReadDirEx(const char *pszFilename,
     333             :                                               int nMaxFiles)
     334             : {
     335          20 :     std::string osSubfilename;
     336             :     int nComponents;
     337             :     int nZ;
     338             :     int nX;
     339             :     int nY;
     340             :     auto poDS =
     341          20 :         VSIPMTilesOpen(pszFilename, osSubfilename, nComponents, nZ, nX, nY);
     342          10 :     if (!poDS)
     343           3 :         return nullptr;
     344             : 
     345           7 :     if (osSubfilename.empty())
     346             :     {
     347           2 :         CPLStringList aosFiles;
     348           1 :         aosFiles.AddString(PMTILES_HEADER_JSON);
     349           1 :         aosFiles.AddString(METADATA_JSON);
     350           4 :         for (int i = poDS->GetMinZoomLevel(); i <= poDS->GetMaxZoomLevel(); ++i)
     351             :         {
     352           3 :             OGRPMTilesTileIterator oIter(poDS.get(), i);
     353           3 :             auto sTile = oIter.GetNextTile();
     354           3 :             if (sTile.offset != 0)
     355             :             {
     356           3 :                 if (nMaxFiles > 0 && aosFiles.size() >= nMaxFiles)
     357           0 :                     break;
     358           3 :                 aosFiles.AddString(CPLSPrintf("%d", i));
     359             :             }
     360             :         }
     361           1 :         return aosFiles.StealList();
     362             :     }
     363             : 
     364           6 :     if (nComponents == 1)
     365             :     {
     366           4 :         std::set<int> oSetX;
     367           4 :         OGRPMTilesTileIterator oIter(poDS.get(), nZ);
     368             :         while (true)
     369             :         {
     370           4 :             auto sTile = oIter.GetNextTile();
     371           4 :             if (sTile.offset == 0)
     372           2 :                 break;
     373           2 :             oSetX.insert(sTile.x);
     374           2 :             if (nMaxFiles > 0 && static_cast<int>(oSetX.size()) >= nMaxFiles)
     375           0 :                 break;
     376           2 :             if (oSetX.size() == 1024 * 1024)
     377             :             {
     378           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Too many tiles");
     379           0 :                 return nullptr;
     380             :             }
     381           2 :         }
     382           4 :         CPLStringList aosFiles;
     383           4 :         for (int x : oSetX)
     384             :         {
     385           2 :             aosFiles.AddString(CPLSPrintf("%d", x));
     386             :         }
     387           2 :         return aosFiles.StealList();
     388             :     }
     389             : 
     390           4 :     if (nComponents == 2)
     391             :     {
     392           6 :         std::set<int> oSetY;
     393           6 :         OGRPMTilesTileIterator oIter(poDS.get(), nZ, nX, -1, nX, -1);
     394             :         while (true)
     395             :         {
     396           4 :             auto sTile = oIter.GetNextTile();
     397           4 :             if (sTile.offset == 0)
     398           3 :                 break;
     399           1 :             oSetY.insert(sTile.y);
     400           1 :             if (nMaxFiles > 0 && static_cast<int>(oSetY.size()) >= nMaxFiles)
     401           0 :                 break;
     402           1 :             if (oSetY.size() == 1024 * 1024)
     403             :             {
     404           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Too many tiles");
     405           0 :                 return nullptr;
     406             :             }
     407           1 :         }
     408           6 :         CPLStringList aosFiles;
     409           3 :         const char *pszTileExt = VSIPMTilesGetTileExtension(poDS.get());
     410           4 :         for (int y : oSetY)
     411             :         {
     412           1 :             aosFiles.AddString(CPLSPrintf("%d%s", y, pszTileExt));
     413             :         }
     414           3 :         return aosFiles.StealList();
     415             :     }
     416             : 
     417           1 :     return nullptr;
     418             : }
     419             : 
     420             : /************************************************************************/
     421             : /*                         VSIPMTilesRegister()                         */
     422             : /************************************************************************/
     423             : 
     424        1757 : void VSIPMTilesRegister()
     425             : {
     426        1757 :     if (VSIFileManager::GetHandler("/vsipmtiles/") ==
     427        1757 :         VSIFileManager::GetHandler("/"))
     428             :     {
     429        1757 :         VSIFileManager::InstallHandler("/vsipmtiles/",
     430        1757 :                                        new VSIPMTilesFilesystemHandler());
     431             :     }
     432        1757 : }

Generated by: LCOV version 1.14