LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/pmtiles - ogrpmtilesdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 140 198 70.7 %
Date: 2024-11-21 22:18:42 Functions: 10 10 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  OpenGIS Simple Features Reference Implementation
       4             :  * Purpose:  Implementation of 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 "ogr_pmtiles.h"
      14             : 
      15             : #include "cpl_json.h"
      16             : 
      17             : #include "mvtutils.h"
      18             : 
      19             : #include <math.h>
      20             : 
      21             : /************************************************************************/
      22             : /*                       ~OGRPMTilesDataset()                           */
      23             : /************************************************************************/
      24             : 
      25         192 : OGRPMTilesDataset::~OGRPMTilesDataset()
      26             : {
      27          96 :     if (!m_osMetadataFilename.empty())
      28          92 :         VSIUnlink(m_osMetadataFilename.c_str());
      29         192 : }
      30             : 
      31             : /************************************************************************/
      32             : /*                              GetLayer()                              */
      33             : /************************************************************************/
      34             : 
      35         113 : OGRLayer *OGRPMTilesDataset::GetLayer(int iLayer)
      36             : 
      37             : {
      38         113 :     if (iLayer < 0 || iLayer >= GetLayerCount())
      39          10 :         return nullptr;
      40         103 :     return m_apoLayers[iLayer].get();
      41             : }
      42             : 
      43             : /************************************************************************/
      44             : /*                     LongLatToSphericalMercator()                     */
      45             : /************************************************************************/
      46             : 
      47         184 : static void LongLatToSphericalMercator(double *x, double *y)
      48             : {
      49         184 :     double X = SPHERICAL_RADIUS * (*x) / 180 * M_PI;
      50         184 :     double Y = SPHERICAL_RADIUS * log(tan(M_PI / 4 + 0.5 * (*y) / 180 * M_PI));
      51         184 :     *x = X;
      52         184 :     *y = Y;
      53         184 : }
      54             : 
      55             : /************************************************************************/
      56             : /*                            GetCompression()                          */
      57             : /************************************************************************/
      58             : 
      59         143 : /*static*/ const char *OGRPMTilesDataset::GetCompression(uint8_t nVal)
      60             : {
      61         143 :     switch (nVal)
      62             :     {
      63           6 :         case pmtiles::COMPRESSION_UNKNOWN:
      64           6 :             return "unknown";
      65           0 :         case pmtiles::COMPRESSION_NONE:
      66           0 :             return "none";
      67         137 :         case pmtiles::COMPRESSION_GZIP:
      68         137 :             return "gzip";
      69           0 :         case pmtiles::COMPRESSION_BROTLI:
      70           0 :             return "brotli";
      71           0 :         case pmtiles::COMPRESSION_ZSTD:
      72           0 :             return "zstd";
      73           0 :         default:
      74           0 :             break;
      75             :     }
      76           0 :     return CPLSPrintf("invalid (%d)", nVal);
      77             : }
      78             : 
      79             : /************************************************************************/
      80             : /*                           GetTileType()                              */
      81             : /************************************************************************/
      82             : 
      83             : /* static */
      84           4 : const char *OGRPMTilesDataset::GetTileType(const pmtiles::headerv3 &sHeader)
      85             : {
      86           4 :     switch (sHeader.tile_type)
      87             :     {
      88           0 :         case pmtiles::TILETYPE_UNKNOWN:
      89           0 :             return "unknown";
      90           0 :         case pmtiles::TILETYPE_PNG:
      91           0 :             return "PNG";
      92           0 :         case pmtiles::TILETYPE_JPEG:
      93           0 :             return "JPEG";
      94           0 :         case pmtiles::TILETYPE_WEBP:
      95           0 :             return "WEBP";
      96           4 :         case pmtiles::TILETYPE_MVT:
      97           4 :             return "MVT";
      98           0 :         default:
      99           0 :             break;
     100             :     }
     101           0 :     return CPLSPrintf("invalid (%d)", sHeader.tile_type);
     102             : }
     103             : 
     104             : /************************************************************************/
     105             : /*                                Open()                                */
     106             : /************************************************************************/
     107             : 
     108          96 : bool OGRPMTilesDataset::Open(GDALOpenInfo *poOpenInfo)
     109             : {
     110          96 :     if (!poOpenInfo->fpL || poOpenInfo->nHeaderBytes < 127)
     111           2 :         return false;
     112             : 
     113          94 :     SetDescription(poOpenInfo->pszFilename);
     114             : 
     115             :     // Borrow file handle
     116          94 :     m_poFile.reset(poOpenInfo->fpL);
     117          94 :     poOpenInfo->fpL = nullptr;
     118             : 
     119             :     // Deserizalize header
     120         188 :     std::string osHeader;
     121          94 :     osHeader.assign(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     122          94 :                     127);
     123             :     try
     124             :     {
     125          94 :         m_sHeader = pmtiles::deserialize_header(osHeader);
     126             :     }
     127           1 :     catch (const std::exception &)
     128             :     {
     129           1 :         return false;
     130             :     }
     131             : 
     132             :     // Check tile type
     133          93 :     const bool bAcceptAnyTileType = CPLTestBool(CSLFetchNameValueDef(
     134          93 :         poOpenInfo->papszOpenOptions, "ACCEPT_ANY_TILE_TYPE", "NO"));
     135          93 :     if (bAcceptAnyTileType)
     136             :     {
     137             :         // do nothing. Internal use only by /vsipmtiles/
     138             :     }
     139          42 :     else if (m_sHeader.tile_type != pmtiles::TILETYPE_MVT)
     140             :     {
     141           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     142             :                  "Tile type %s not handled by the driver",
     143           0 :                  GetTileType(m_sHeader));
     144           0 :         return false;
     145             :     }
     146             : 
     147             :     // Check compression method for metadata and directories
     148          93 :     CPLDebugOnly("PMTiles", "internal_compression = %s",
     149             :                  GetCompression(m_sHeader.internal_compression));
     150             : 
     151          93 :     if (m_sHeader.internal_compression == pmtiles::COMPRESSION_GZIP)
     152             :     {
     153          93 :         m_psInternalDecompressor = CPLGetDecompressor("gzip");
     154             :     }
     155           0 :     else if (m_sHeader.internal_compression == pmtiles::COMPRESSION_ZSTD)
     156             :     {
     157           0 :         m_psInternalDecompressor = CPLGetDecompressor("zstd");
     158           0 :         if (m_psInternalDecompressor == nullptr)
     159             :         {
     160           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     161             :                      "File %s requires ZSTD decompression, but not available "
     162             :                      "in this GDAL build",
     163             :                      poOpenInfo->pszFilename);
     164           0 :             return false;
     165             :         }
     166             :     }
     167           0 :     else if (m_sHeader.internal_compression != pmtiles::COMPRESSION_NONE)
     168             :     {
     169           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     170             :                  "Unhandled internal_compression = %s",
     171           0 :                  GetCompression(m_sHeader.internal_compression));
     172           0 :         return false;
     173             :     }
     174             : 
     175             :     // Check compression for tile data
     176          93 :     if (!CPLTestBool(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
     177             :                                           "DECOMPRESS_TILES", "YES")))
     178             :     {
     179             :         // do nothing. Internal use only by /vsipmtiles/
     180             :     }
     181             :     else
     182             :     {
     183          42 :         CPLDebugOnly("PMTiles", "tile_compression = %s",
     184             :                      GetCompression(m_sHeader.tile_compression));
     185             : 
     186          42 :         if (m_sHeader.tile_compression == pmtiles::COMPRESSION_UNKNOWN)
     187             :         {
     188             :             // Python pmtiles-convert generates this. The MVT driver can autodetect
     189             :             // uncompressed and GZip-compressed tiles automatically.
     190             :         }
     191          38 :         else if (m_sHeader.tile_compression == pmtiles::COMPRESSION_GZIP)
     192             :         {
     193          38 :             m_psTileDataDecompressor = CPLGetDecompressor("gzip");
     194             :         }
     195           0 :         else if (m_sHeader.tile_compression == pmtiles::COMPRESSION_ZSTD)
     196             :         {
     197           0 :             m_psTileDataDecompressor = CPLGetDecompressor("zstd");
     198           0 :             if (m_psTileDataDecompressor == nullptr)
     199             :             {
     200           0 :                 CPLError(
     201             :                     CE_Failure, CPLE_AppDefined,
     202             :                     "File %s requires ZSTD decompression, but not available "
     203             :                     "in this GDAL build",
     204             :                     poOpenInfo->pszFilename);
     205           0 :                 return false;
     206             :             }
     207             :         }
     208           0 :         else if (m_sHeader.tile_compression != pmtiles::COMPRESSION_NONE)
     209             :         {
     210           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     211             :                      "Unhandled tile_compression = %s",
     212           0 :                      GetCompression(m_sHeader.tile_compression));
     213           0 :             return false;
     214             :         }
     215             :     }
     216             : 
     217             :     // Read metadata
     218             :     const auto *posMetadata =
     219          93 :         ReadInternal(m_sHeader.json_metadata_offset,
     220             :                      m_sHeader.json_metadata_bytes, "metadata");
     221          93 :     if (!posMetadata)
     222           1 :         return false;
     223          92 :     CPLDebugOnly("PMTiles", "Metadata = %s", posMetadata->c_str());
     224          92 :     m_osMetadata = *posMetadata;
     225             : 
     226             :     m_osMetadataFilename =
     227          92 :         VSIMemGenerateHiddenFilename("pmtiles_metadata.json");
     228          92 :     VSIFCloseL(VSIFileFromMemBuffer(m_osMetadataFilename.c_str(),
     229          92 :                                     reinterpret_cast<GByte *>(&m_osMetadata[0]),
     230          92 :                                     m_osMetadata.size(), false));
     231             : 
     232         184 :     CPLJSONDocument oJsonDoc;
     233          92 :     if (!oJsonDoc.LoadMemory(m_osMetadata))
     234             :     {
     235           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot parse metadata");
     236           0 :         return false;
     237             :     }
     238             : 
     239         184 :     auto oJsonRoot = oJsonDoc.GetRoot();
     240        1117 :     for (const auto &oChild : oJsonRoot.GetChildren())
     241             :     {
     242        1025 :         if (oChild.GetType() == CPLJSONObject::Type::String)
     243             :         {
     244         905 :             if (oChild.GetName() == "json")
     245             :             {
     246             :                 // Tippecanoe metadata includes a "json" item, which is a
     247             :                 // serialized JSON object with vector_layers[] and layers[]
     248             :                 // arrays we are interested in later.
     249             :                 // so use "json" content as the new root
     250          31 :                 if (!oJsonDoc.LoadMemory(oChild.ToString()))
     251             :                 {
     252           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
     253             :                              "Cannot parse 'json' metadata item");
     254           0 :                     return false;
     255             :                 }
     256          31 :                 oJsonRoot = oJsonDoc.GetRoot();
     257             :             }
     258             :             // Tippecanoe generates a "strategies" member with serialized JSON
     259         874 :             else if (oChild.GetName() != "strategies")
     260             :             {
     261         874 :                 SetMetadataItem(oChild.GetName().c_str(),
     262        1748 :                                 oChild.ToString().c_str());
     263             :             }
     264             :         }
     265             :     }
     266             : 
     267          92 :     double dfMinX = m_sHeader.min_lon_e7 / 10e6;
     268          92 :     double dfMinY = m_sHeader.min_lat_e7 / 10e6;
     269          92 :     double dfMaxX = m_sHeader.max_lon_e7 / 10e6;
     270          92 :     double dfMaxY = m_sHeader.max_lat_e7 / 10e6;
     271          92 :     LongLatToSphericalMercator(&dfMinX, &dfMinY);
     272          92 :     LongLatToSphericalMercator(&dfMaxX, &dfMaxY);
     273             : 
     274          92 :     m_nMinZoomLevel = m_sHeader.min_zoom;
     275          92 :     m_nMaxZoomLevel = m_sHeader.max_zoom;
     276          92 :     if (m_nMinZoomLevel > m_nMaxZoomLevel)
     277             :     {
     278           1 :         CPLError(CE_Failure, CPLE_AppDefined, "min_zoom(=%d) > max_zoom(=%d)",
     279             :                  m_nMinZoomLevel, m_nMaxZoomLevel);
     280           1 :         return false;
     281             :     }
     282          91 :     if (m_nMinZoomLevel > 30)
     283             :     {
     284           1 :         CPLError(CE_Warning, CPLE_AppDefined, "Clamping min_zoom from %d to %d",
     285             :                  m_nMinZoomLevel, 30);
     286           1 :         m_nMinZoomLevel = 30;
     287             :     }
     288          91 :     if (m_nMaxZoomLevel > 30)
     289             :     {
     290           1 :         CPLError(CE_Warning, CPLE_AppDefined, "Clamping max_zoom from %d to %d",
     291             :                  m_nMaxZoomLevel, 30);
     292           1 :         m_nMaxZoomLevel = 30;
     293             :     }
     294             : 
     295          91 :     if (bAcceptAnyTileType)
     296          50 :         return true;
     297             : 
     298             :     // If using the pmtiles go utility, vector_layers and tilestats are
     299             :     // moved from Tippecanoe's json metadata item to the root element.
     300         123 :     CPLJSONArray oVectorLayers = oJsonRoot.GetArray("vector_layers");
     301          41 :     if (oVectorLayers.Size() == 0)
     302             :     {
     303           5 :         CPLError(CE_Failure, CPLE_AppDefined,
     304             :                  "Missing vector_layers[] metadata");
     305           5 :         return false;
     306             :     }
     307             : 
     308         108 :     CPLJSONArray oTileStatLayers = oJsonRoot.GetArray("tilestats/layers");
     309             : 
     310             :     const int nZoomLevel =
     311          36 :         atoi(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "ZOOM_LEVEL",
     312          36 :                                   CPLSPrintf("%d", m_nMaxZoomLevel)));
     313          36 :     if (nZoomLevel < m_nMinZoomLevel || nZoomLevel > m_nMaxZoomLevel)
     314             :     {
     315           2 :         CPLError(CE_Failure, CPLE_AppDefined,
     316             :                  "Invalid zoom level. Should be in [%d,%d] range",
     317             :                  m_nMinZoomLevel, m_nMaxZoomLevel);
     318           2 :         return false;
     319             :     }
     320          34 :     SetMetadataItem("ZOOM_LEVEL", CPLSPrintf("%d", nZoomLevel));
     321             : 
     322             :     m_osClipOpenOption =
     323          34 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "CLIP", "");
     324             : 
     325          68 :     const bool bZoomLevelFromSpatialFilter = CPLFetchBool(
     326          34 :         poOpenInfo->papszOpenOptions, "ZOOM_LEVEL_AUTO",
     327          34 :         CPLTestBool(CPLGetConfigOption("MVT_ZOOM_LEVEL_AUTO", "NO")));
     328             :     const bool bJsonField =
     329          34 :         CPLFetchBool(poOpenInfo->papszOpenOptions, "JSON_FIELD", false);
     330             : 
     331          75 :     for (int i = 0; i < oVectorLayers.Size(); i++)
     332             :     {
     333         123 :         CPLJSONObject oId = oVectorLayers[i].GetObj("id");
     334          41 :         if (oId.IsValid() && oId.GetType() == CPLJSONObject::Type::String)
     335             :         {
     336          41 :             OGRwkbGeometryType eGeomType = wkbUnknown;
     337          41 :             if (oTileStatLayers.IsValid())
     338             :             {
     339          39 :                 eGeomType = OGRMVTFindGeomTypeFromTileStat(
     340          78 :                     oTileStatLayers, oId.ToString().c_str());
     341             :             }
     342          41 :             if (eGeomType == wkbUnknown)
     343             :             {
     344           2 :                 eGeomType = OGRPMTilesVectorLayer::GuessGeometryType(
     345           4 :                     this, oId.ToString().c_str(), nZoomLevel);
     346             :             }
     347             : 
     348         123 :             CPLJSONObject oFields = oVectorLayers[i].GetObj("fields");
     349             :             CPLJSONArray oAttributesFromTileStats =
     350             :                 OGRMVTFindAttributesFromTileStat(oTileStatLayers,
     351          82 :                                                  oId.ToString().c_str());
     352             : 
     353          41 :             m_apoLayers.push_back(std::make_unique<OGRPMTilesVectorLayer>(
     354          82 :                 this, oId.ToString().c_str(), oFields, oAttributesFromTileStats,
     355             :                 bJsonField, dfMinX, dfMinY, dfMaxX, dfMaxY, eGeomType,
     356             :                 nZoomLevel, bZoomLevelFromSpatialFilter));
     357             :         }
     358             :     }
     359             : 
     360          34 :     return true;
     361             : }
     362             : 
     363             : /************************************************************************/
     364             : /*                              Read()                                  */
     365             : /************************************************************************/
     366             : 
     367         798 : const std::string *OGRPMTilesDataset::Read(const CPLCompressor *psDecompressor,
     368             :                                            uint64_t nOffset, uint64_t nSize,
     369             :                                            const char *pszDataType)
     370             : {
     371         798 :     if (nSize > 10 * 1024 * 1024)
     372             :     {
     373           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     374             :                  "Too large amount of %s to read: " CPL_FRMT_GUIB
     375             :                  " bytes at offset " CPL_FRMT_GUIB,
     376             :                  pszDataType, static_cast<GUIntBig>(nSize),
     377             :                  static_cast<GUIntBig>(nOffset));
     378           0 :         return nullptr;
     379             :     }
     380         798 :     m_osBuffer.resize(static_cast<size_t>(nSize));
     381         798 :     m_poFile->Seek(nOffset, SEEK_SET);
     382         798 :     if (m_poFile->Read(&m_osBuffer[0], m_osBuffer.size(), 1) != 1)
     383             :     {
     384           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     385             :                  "Cannot read %s of length %u at offset " CPL_FRMT_GUIB,
     386             :                  pszDataType, unsigned(nSize), static_cast<GUIntBig>(nOffset));
     387           1 :         return nullptr;
     388             :     }
     389             : 
     390         797 :     if (psDecompressor)
     391             :     {
     392         684 :         m_osDecompressedBuffer.resize(32 + 16 * m_osBuffer.size());
     393         684 :         for (int iTry = 0; iTry < 2; ++iTry)
     394             :         {
     395         684 :             void *pOutputData = &m_osDecompressedBuffer[0];
     396         684 :             size_t nOutputSize = m_osDecompressedBuffer.size();
     397         684 :             if (!psDecompressor->pfnFunc(m_osBuffer.data(), m_osBuffer.size(),
     398             :                                          &pOutputData, &nOutputSize, nullptr,
     399         684 :                                          psDecompressor->user_data))
     400             :             {
     401           0 :                 if (iTry == 0)
     402             :                 {
     403           0 :                     pOutputData = nullptr;
     404           0 :                     nOutputSize = 0;
     405           0 :                     if (psDecompressor->pfnFunc(
     406           0 :                             m_osBuffer.data(), m_osBuffer.size(), &pOutputData,
     407           0 :                             &nOutputSize, nullptr, psDecompressor->user_data))
     408             :                     {
     409           0 :                         CPLDebug("PMTiles",
     410             :                                  "Buffer of size %u uncompresses to %u bytes",
     411             :                                  unsigned(nSize), unsigned(nOutputSize));
     412           0 :                         m_osDecompressedBuffer.resize(nOutputSize);
     413           0 :                         continue;
     414             :                     }
     415             :                 }
     416             : 
     417           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     418             :                          "Cannot decompress %s of length %u at "
     419             :                          "offset " CPL_FRMT_GUIB,
     420             :                          pszDataType, unsigned(nSize),
     421             :                          static_cast<GUIntBig>(nOffset));
     422           0 :                 return nullptr;
     423             :             }
     424         684 :             m_osDecompressedBuffer.resize(nOutputSize);
     425         684 :             break;
     426             :         }
     427         684 :         return &m_osDecompressedBuffer;
     428             :     }
     429             :     else
     430             :     {
     431         113 :         return &m_osBuffer;
     432             :     }
     433             : }
     434             : 
     435             : /************************************************************************/
     436             : /*                              ReadInternal()                          */
     437             : /************************************************************************/
     438             : 
     439         549 : const std::string *OGRPMTilesDataset::ReadInternal(uint64_t nOffset,
     440             :                                                    uint64_t nSize,
     441             :                                                    const char *pszDataType)
     442             : {
     443         549 :     return Read(m_psInternalDecompressor, nOffset, nSize, pszDataType);
     444             : }
     445             : 
     446             : /************************************************************************/
     447             : /*                              ReadTileData()                          */
     448             : /************************************************************************/
     449             : 
     450         249 : const std::string *OGRPMTilesDataset::ReadTileData(uint64_t nOffset,
     451             :                                                    uint64_t nSize)
     452             : {
     453         249 :     return Read(m_psTileDataDecompressor, nOffset, nSize, "tile data");
     454             : }

Generated by: LCOV version 1.14