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

Generated by: LCOV version 1.14