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

Generated by: LCOV version 1.14