LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/pmtiles - ogrpmtilesdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 332 410 81.0 %
Date: 2026-05-16 03:35:24 Functions: 13 13 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             :  * Copyright (c) 2026, Even Rouault <even.rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : #include "ogr_pmtiles.h"
      15             : 
      16             : #include "cpl_json.h"
      17             : 
      18             : #include "mvtutils.h"
      19             : 
      20             : #include <algorithm>
      21             : #include <math.h>
      22             : 
      23             : /************************************************************************/
      24             : /*                         ~OGRPMTilesDataset()                         */
      25             : /************************************************************************/
      26             : 
      27         414 : OGRPMTilesDataset::~OGRPMTilesDataset()
      28             : {
      29         207 :     if (!m_osMetadataFilename.empty())
      30         185 :         VSIUnlink(m_osMetadataFilename.c_str());
      31         207 :     if (!m_osTmpGPKGFilename.empty())
      32          56 :         VSIUnlink(m_osTmpGPKGFilename.c_str());
      33         414 : }
      34             : 
      35             : /************************************************************************/
      36             : /*                              GetLayer()                              */
      37             : /************************************************************************/
      38             : 
      39         113 : const OGRLayer *OGRPMTilesDataset::GetLayer(int iLayer) const
      40             : 
      41             : {
      42         113 :     if (iLayer < 0 || iLayer >= GetLayerCount())
      43          10 :         return nullptr;
      44         103 :     return m_apoLayers[iLayer].get();
      45             : }
      46             : 
      47             : /************************************************************************/
      48             : /*                     LongLatToSphericalMercator()                     */
      49             : /************************************************************************/
      50             : 
      51         142 : static void LongLatToSphericalMercator(double *x, double *y)
      52             : {
      53         142 :     double X = SPHERICAL_RADIUS * (*x) / 180 * M_PI;
      54         142 :     double Y = SPHERICAL_RADIUS * log(tan(M_PI / 4 + 0.5 * (*y) / 180 * M_PI));
      55         142 :     *x = X;
      56         142 :     *y = Y;
      57         142 : }
      58             : 
      59             : /************************************************************************/
      60             : /*                           GetCompression()                           */
      61             : /************************************************************************/
      62             : 
      63         273 : /*static*/ const char *OGRPMTilesDataset::GetCompression(uint8_t nVal)
      64             : {
      65         273 :     switch (nVal)
      66             :     {
      67           6 :         case pmtiles::COMPRESSION_UNKNOWN:
      68           6 :             return "unknown";
      69          38 :         case pmtiles::COMPRESSION_NONE:
      70          38 :             return "none";
      71         229 :         case pmtiles::COMPRESSION_GZIP:
      72         229 :             return "gzip";
      73           0 :         case pmtiles::COMPRESSION_BROTLI:
      74           0 :             return "brotli";
      75           0 :         case pmtiles::COMPRESSION_ZSTD:
      76           0 :             return "zstd";
      77           0 :         default:
      78           0 :             break;
      79             :     }
      80           0 :     return CPLSPrintf("invalid (%d)", nVal);
      81             : }
      82             : 
      83             : /************************************************************************/
      84             : /*                            GetTileType()                             */
      85             : /************************************************************************/
      86             : 
      87             : /* static */
      88         107 : const char *OGRPMTilesDataset::GetTileType(const pmtiles::headerv3 &sHeader)
      89             : {
      90         107 :     switch (sHeader.tile_type)
      91             :     {
      92           0 :         case pmtiles::TILETYPE_UNKNOWN:
      93           0 :             return "unknown";
      94           4 :         case pmtiles::TILETYPE_MVT:
      95           4 :             return "MVT";
      96          57 :         case pmtiles::TILETYPE_PNG:
      97          57 :             return "PNG";
      98          22 :         case pmtiles::TILETYPE_JPEG:
      99          22 :             return "JPEG";
     100          24 :         case pmtiles::TILETYPE_WEBP:
     101          24 :             return "WEBP";
     102           0 :         case pmtiles::TILETYPE_AVIF:
     103           0 :             return "AVIF";
     104           0 :         case pmtiles::TILETYPE_MAPLIBRE_VECTOR_TILE:
     105           0 :             return "MAPLIBRE_VECTOR_TILE";
     106           0 :         default:
     107           0 :             break;
     108             :     }
     109           0 :     return CPLSPrintf("invalid (%d)", sHeader.tile_type);
     110             : }
     111             : 
     112             : /************************************************************************/
     113             : /*                                Open()                                */
     114             : /************************************************************************/
     115             : 
     116         189 : bool OGRPMTilesDataset::Open(GDALOpenInfo *poOpenInfo)
     117             : {
     118         189 :     if (!poOpenInfo->fpL || poOpenInfo->nHeaderBytes < PMTILES_HEADER_LENGTH)
     119           2 :         return false;
     120             : 
     121         187 :     SetDescription(poOpenInfo->pszFilename);
     122             : 
     123             :     // Borrow file handle
     124         187 :     m_poFileUniquePtr.reset(poOpenInfo->fpL);
     125         187 :     poOpenInfo->fpL = nullptr;
     126             : 
     127         187 :     m_poFile = m_poFileUniquePtr.get();
     128             : 
     129             :     // Deserizalize header
     130         374 :     std::string osHeader;
     131         187 :     osHeader.assign(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     132         187 :                     PMTILES_HEADER_LENGTH);
     133             :     try
     134             :     {
     135         187 :         m_sHeader = pmtiles::deserialize_header(osHeader);
     136             :     }
     137           1 :     catch (const std::exception &)
     138             :     {
     139           1 :         return false;
     140             :     }
     141             : 
     142             :     // Check tile type
     143         186 :     const bool bAcceptAnyTileType = CPLTestBool(CSLFetchNameValueDef(
     144         186 :         poOpenInfo->papszOpenOptions, "ACCEPT_ANY_TILE_TYPE", "NO"));
     145         186 :     if (bAcceptAnyTileType)
     146             :     {
     147             :         // do nothing. Internal use only by /vsipmtiles/
     148             :     }
     149          79 :     else if (m_sHeader.tile_type == pmtiles::TILETYPE_MVT)
     150             :     {
     151          41 :         if ((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
     152             :         {
     153           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     154             :                      "Tile type %s not handled in raster mode",
     155           0 :                      GetTileType(m_sHeader));
     156           0 :             return false;
     157             :         }
     158             :     }
     159          38 :     else if (m_sHeader.tile_type == pmtiles::TILETYPE_PNG ||
     160          15 :              m_sHeader.tile_type == pmtiles::TILETYPE_JPEG ||
     161           7 :              m_sHeader.tile_type == pmtiles::TILETYPE_WEBP ||
     162           0 :              m_sHeader.tile_type == pmtiles::TILETYPE_AVIF)
     163             :     {
     164          38 :         if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) == 0)
     165             :         {
     166           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     167             :                      "Tile type %s not handled in vector mode",
     168           0 :                      GetTileType(m_sHeader));
     169           0 :             return false;
     170             :         }
     171             :     }
     172             :     else
     173             :     {
     174           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     175             :                  "Tile type %s not handled by the driver",
     176           0 :                  GetTileType(m_sHeader));
     177           0 :         return false;
     178             :     }
     179             : 
     180             :     // Check compression method for metadata and directories
     181         186 :     CPLDebugOnly("PMTiles", "internal_compression = %s",
     182             :                  GetCompression(m_sHeader.internal_compression));
     183             : 
     184         186 :     if (m_sHeader.internal_compression == pmtiles::COMPRESSION_GZIP)
     185             :     {
     186         186 :         m_psInternalDecompressor = CPLGetDecompressor("gzip");
     187             :     }
     188           0 :     else if (m_sHeader.internal_compression == pmtiles::COMPRESSION_ZSTD)
     189             :     {
     190           0 :         m_psInternalDecompressor = CPLGetDecompressor("zstd");
     191           0 :         if (m_psInternalDecompressor == nullptr)
     192             :         {
     193           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     194             :                      "File %s requires ZSTD decompression, but not available "
     195             :                      "in this GDAL build",
     196             :                      poOpenInfo->pszFilename);
     197           0 :             return false;
     198             :         }
     199             :     }
     200           0 :     else if (m_sHeader.internal_compression != pmtiles::COMPRESSION_NONE)
     201             :     {
     202           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     203             :                  "Unhandled internal_compression = %s",
     204           0 :                  GetCompression(m_sHeader.internal_compression));
     205           0 :         return false;
     206             :     }
     207             : 
     208             :     // Check compression for tile data
     209         186 :     if (!CPLTestBool(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
     210             :                                           "DECOMPRESS_TILES", "YES")))
     211             :     {
     212             :         // do nothing. Internal use only by /vsipmtiles/
     213             :     }
     214             :     else
     215             :     {
     216          79 :         CPLDebugOnly("PMTiles", "tile_compression = %s",
     217             :                      GetCompression(m_sHeader.tile_compression));
     218             : 
     219          79 :         if (m_sHeader.tile_compression == pmtiles::COMPRESSION_UNKNOWN)
     220             :         {
     221             :             // Python pmtiles-convert generates this. The MVT driver can autodetect
     222             :             // uncompressed and GZip-compressed tiles automatically.
     223             :         }
     224          75 :         else if (m_sHeader.tile_compression == pmtiles::COMPRESSION_GZIP)
     225             :         {
     226          37 :             m_psTileDataDecompressor = CPLGetDecompressor("gzip");
     227             :         }
     228          38 :         else if (m_sHeader.tile_compression == pmtiles::COMPRESSION_ZSTD)
     229             :         {
     230           0 :             m_psTileDataDecompressor = CPLGetDecompressor("zstd");
     231           0 :             if (m_psTileDataDecompressor == nullptr)
     232             :             {
     233           0 :                 CPLError(
     234             :                     CE_Failure, CPLE_AppDefined,
     235             :                     "File %s requires ZSTD decompression, but not available "
     236             :                     "in this GDAL build",
     237             :                     poOpenInfo->pszFilename);
     238           0 :                 return false;
     239             :             }
     240             :         }
     241          38 :         else if (m_sHeader.tile_compression != pmtiles::COMPRESSION_NONE)
     242             :         {
     243           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     244             :                      "Unhandled tile_compression = %s",
     245           0 :                      GetCompression(m_sHeader.tile_compression));
     246           0 :             return false;
     247             :         }
     248             :     }
     249             : 
     250             :     // Read metadata
     251             :     const auto *posMetadata =
     252         186 :         ReadInternal(m_sHeader.json_metadata_offset,
     253             :                      m_sHeader.json_metadata_bytes, "metadata");
     254         186 :     if (!posMetadata)
     255           1 :         return false;
     256         185 :     CPLDebugOnly("PMTiles", "Metadata = %s", posMetadata->c_str());
     257         185 :     m_osMetadata = *posMetadata;
     258             : 
     259             :     m_osMetadataFilename =
     260         185 :         VSIMemGenerateHiddenFilename("pmtiles_metadata.json");
     261         185 :     VSIFCloseL(VSIFileFromMemBuffer(m_osMetadataFilename.c_str(),
     262         185 :                                     reinterpret_cast<GByte *>(&m_osMetadata[0]),
     263         185 :                                     m_osMetadata.size(), false));
     264             : 
     265         370 :     CPLJSONDocument oJsonDoc;
     266         185 :     if (!oJsonDoc.LoadMemory(m_osMetadata))
     267             :     {
     268           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot parse metadata");
     269           0 :         return false;
     270             :     }
     271             : 
     272         370 :     auto oJsonRoot = oJsonDoc.GetRoot();
     273        2015 :     for (const auto &oChild : oJsonRoot.GetChildren())
     274             :     {
     275        1830 :         if (oChild.GetType() == CPLJSONObject::Type::String)
     276             :         {
     277        1712 :             if (oChild.GetName() == "json")
     278             :             {
     279             :                 // Tippecanoe metadata includes a "json" item, which is a
     280             :                 // serialized JSON object with vector_layers[] and layers[]
     281             :                 // arrays we are interested in later.
     282             :                 // so use "json" content as the new root
     283          33 :                 if (!oJsonDoc.LoadMemory(oChild.ToString()))
     284             :                 {
     285           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
     286             :                              "Cannot parse 'json' metadata item");
     287           0 :                     return false;
     288             :                 }
     289          33 :                 oJsonRoot = oJsonDoc.GetRoot();
     290             :             }
     291             :             // Tippecanoe generates a "strategies" member with serialized JSON
     292        1679 :             else if (oChild.GetName() != "strategies")
     293             :             {
     294        1679 :                 SetMetadataItem(oChild.GetName().c_str(),
     295        3358 :                                 oChild.ToString().c_str());
     296             :             }
     297             :         }
     298             :     }
     299             : 
     300         185 :     m_nMinZoomLevel = m_sHeader.min_zoom;
     301         185 :     m_nMaxZoomLevel = m_sHeader.max_zoom;
     302         185 :     if (m_nMinZoomLevel > m_nMaxZoomLevel)
     303             :     {
     304           1 :         CPLError(CE_Failure, CPLE_AppDefined, "min_zoom(=%d) > max_zoom(=%d)",
     305             :                  m_nMinZoomLevel, m_nMaxZoomLevel);
     306           1 :         return false;
     307             :     }
     308         184 :     if (m_nMinZoomLevel > 30)
     309             :     {
     310           0 :         CPLError(CE_Warning, CPLE_AppDefined, "Clamping min_zoom from %d to %d",
     311             :                  m_nMinZoomLevel, 30);
     312           0 :         m_nMinZoomLevel = 30;
     313             :     }
     314         184 :     if (m_nMaxZoomLevel > 30)
     315             :     {
     316           0 :         CPLError(CE_Warning, CPLE_AppDefined, "Clamping max_zoom from %d to %d",
     317             :                  m_nMaxZoomLevel, 30);
     318           0 :         m_nMaxZoomLevel = 30;
     319             :     }
     320             : 
     321         184 :     if (bAcceptAnyTileType)
     322         106 :         return true;
     323             : 
     324             :     const int nZoomLevel =
     325          78 :         atoi(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "ZOOM_LEVEL",
     326             :                                   CPLSPrintf("%d", m_nMaxZoomLevel)));
     327          78 :     if (nZoomLevel < m_nMinZoomLevel || nZoomLevel > m_nMaxZoomLevel)
     328             :     {
     329           2 :         CPLError(CE_Failure, CPLE_AppDefined,
     330             :                  "Invalid zoom level. Should be in [%d,%d] range",
     331             :                  m_nMinZoomLevel, m_nMaxZoomLevel);
     332           2 :         return false;
     333             :     }
     334          76 :     SetMetadataItem("ZOOM_LEVEL", CPLSPrintf("%d", nZoomLevel));
     335             : 
     336             :     m_osClipOpenOption =
     337          76 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "CLIP", "");
     338             : 
     339          76 :     if (m_sHeader.tile_type == pmtiles::TILETYPE_MVT)
     340          38 :         return OpenVector(poOpenInfo, oJsonRoot, nZoomLevel);
     341             :     else
     342          38 :         return OpenRaster(nZoomLevel);
     343             : }
     344             : 
     345             : /************************************************************************/
     346             : /*                             OpenVector()                             */
     347             : /************************************************************************/
     348             : 
     349          38 : bool OGRPMTilesDataset::OpenVector(const GDALOpenInfo *poOpenInfo,
     350             :                                    const CPLJSONObject &oJsonRoot,
     351             :                                    int nZoomLevel)
     352             : {
     353             :     // If using the pmtiles go utility, vector_layers and tilestats are
     354             :     // moved from Tippecanoe's json metadata item to the root element.
     355         114 :     CPLJSONArray oVectorLayers = oJsonRoot.GetArray("vector_layers");
     356          38 :     if (oVectorLayers.Size() == 0)
     357             :     {
     358           5 :         CPLError(CE_Failure, CPLE_AppDefined,
     359             :                  "Missing vector_layers[] metadata");
     360           5 :         return false;
     361             :     }
     362             : 
     363          66 :     CPLJSONArray oTileStatLayers = oJsonRoot.GetArray("tilestats/layers");
     364             : 
     365          66 :     const bool bZoomLevelFromSpatialFilter = CPLFetchBool(
     366          33 :         poOpenInfo->papszOpenOptions, "ZOOM_LEVEL_AUTO",
     367          33 :         CPLTestBool(CPLGetConfigOption("MVT_ZOOM_LEVEL_AUTO", "NO")));
     368             :     const bool bJsonField =
     369          33 :         CPLFetchBool(poOpenInfo->papszOpenOptions, "JSON_FIELD", false);
     370             : 
     371          33 :     double dfMinX = m_sHeader.min_lon_e7 / 10e6;
     372          33 :     double dfMinY = m_sHeader.min_lat_e7 / 10e6;
     373          33 :     double dfMaxX = m_sHeader.max_lon_e7 / 10e6;
     374          33 :     double dfMaxY = m_sHeader.max_lat_e7 / 10e6;
     375          33 :     LongLatToSphericalMercator(&dfMinX, &dfMinY);
     376          33 :     LongLatToSphericalMercator(&dfMaxX, &dfMaxY);
     377             : 
     378          73 :     for (int i = 0; i < oVectorLayers.Size(); i++)
     379             :     {
     380         120 :         CPLJSONObject oId = oVectorLayers[i].GetObj("id");
     381          40 :         if (oId.IsValid() && oId.GetType() == CPLJSONObject::Type::String)
     382             :         {
     383          40 :             OGRwkbGeometryType eGeomType = wkbUnknown;
     384          40 :             if (oTileStatLayers.IsValid())
     385             :             {
     386          38 :                 eGeomType = OGRMVTFindGeomTypeFromTileStat(
     387          76 :                     oTileStatLayers, oId.ToString().c_str());
     388             :             }
     389          40 :             if (eGeomType == wkbUnknown)
     390             :             {
     391           2 :                 eGeomType = OGRPMTilesVectorLayer::GuessGeometryType(
     392           4 :                     this, oId.ToString().c_str(), nZoomLevel);
     393             :             }
     394             : 
     395         120 :             CPLJSONObject oFields = oVectorLayers[i].GetObj("fields");
     396             :             CPLJSONArray oAttributesFromTileStats =
     397             :                 OGRMVTFindAttributesFromTileStat(oTileStatLayers,
     398          80 :                                                  oId.ToString().c_str());
     399             : 
     400          40 :             m_apoLayers.push_back(std::make_unique<OGRPMTilesVectorLayer>(
     401          80 :                 this, oId.ToString().c_str(), oFields, oAttributesFromTileStats,
     402             :                 bJsonField, dfMinX, dfMinY, dfMaxX, dfMaxY, eGeomType,
     403             :                 nZoomLevel, bZoomLevelFromSpatialFilter));
     404             :         }
     405             :     }
     406             : 
     407          33 :     return true;
     408             : }
     409             : 
     410             : /************************************************************************/
     411             : /*                             OpenRaster()                             */
     412             : /************************************************************************/
     413             : 
     414          38 : bool OGRPMTilesDataset::OpenRaster(int nZoomLevel)
     415             : {
     416          38 :     m_nZoomLevel = nZoomLevel;
     417             : 
     418          38 :     m_oSRS.importFromEPSG(3857);
     419             : 
     420             :     // Open a tile to get its dimension in pixel (to compute the resolution)
     421          76 :     OGRPMTilesTileIterator oIter(this, nZoomLevel);
     422          38 :     auto sTile = oIter.GetNextTile();
     423          38 :     if (sTile.offset == 0)
     424             :     {
     425           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     426             :                  "File does not contain any tile at zoom level %d", nZoomLevel);
     427           0 :         return false;
     428             :     }
     429             : 
     430          38 :     const auto *posStr = ReadTileData(sTile.offset, sTile.length);
     431          38 :     if (!posStr)
     432             :     {
     433             :         // Error message emitted by ReadTileData()
     434           0 :         return false;
     435             :     }
     436          38 :     const auto &osTileData = *posStr;
     437             : 
     438          38 :     auto poDM = GetGDALDriverManager();
     439          38 :     if (!poDM->GetDriverByName(GetTileType(m_sHeader)))
     440             :     {
     441           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     442             :                  "%s driver needed for inner working of PMTiles driver is not "
     443             :                  "available",
     444           0 :                  GetTileType(m_sHeader));
     445           0 :         return false;
     446             :     }
     447          38 :     m_poGPKGDriver = poDM->GetDriverByName("GPKG");
     448          38 :     if (!m_poGPKGDriver)
     449             :     {
     450           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     451             :                  "GeoPackage driver needed for inner working of PMTiles driver "
     452             :                  "is not available");
     453           0 :         return false;
     454             :     }
     455          38 :     if (!poDM->GetDriverByName("GTI"))
     456             :     {
     457           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     458             :                  "GTI driver needed for inner working of PMTiles driver is not "
     459             :                  "available");
     460           0 :         return false;
     461             :     }
     462             :     const std::string osTmpFilename = VSIMemGenerateHiddenFilename(
     463          76 :         CPLSPrintf("pmtiles_%u_%u_tile", sTile.x, sTile.y));
     464          38 :     VSIFCloseL(VSIFileFromMemBuffer(
     465             :         osTmpFilename.c_str(),
     466          38 :         reinterpret_cast<GByte *>(const_cast<char *>(osTileData.data())),
     467          38 :         osTileData.size(), false));
     468             : 
     469          38 :     const char *const apszAllowedDrivers[] = {GetTileType(m_sHeader), nullptr};
     470             :     auto poTileDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
     471             :         osTmpFilename.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
     472          76 :         apszAllowedDrivers));
     473          38 :     const int nTileSize = poTileDS ? poTileDS->GetRasterXSize() : 0;
     474          38 :     if (nTileSize == 0)
     475           0 :         return false;
     476             : 
     477          38 :     const double dfRes = 2 * MAX_GM / (1 << nZoomLevel) / nTileSize;
     478          38 :     constexpr double EPSILON = 1e-2;
     479             : 
     480             :     // Compute the raster georeferenced extent from the bounding box in the
     481             :     // PMTiles metadata
     482          38 :     double dfMinX = m_sHeader.min_lon_e7 / 10e6;
     483          38 :     double dfMinY = m_sHeader.min_lat_e7 / 10e6;
     484          38 :     double dfMaxX = m_sHeader.max_lon_e7 / 10e6;
     485          38 :     double dfMaxY = m_sHeader.max_lat_e7 / 10e6;
     486          38 :     LongLatToSphericalMercator(&dfMinX, &dfMinY);
     487          38 :     LongLatToSphericalMercator(&dfMaxX, &dfMaxY);
     488             : 
     489             :     // Align with the resolution at the zoom level of interest, to avoid
     490             :     // resampling due to sub-pixel shifts
     491          38 :     dfMinX = std::floor(dfMinX / dfRes + EPSILON) * dfRes;
     492          38 :     dfMinY = std::floor(dfMinY / dfRes + EPSILON) * dfRes;
     493          38 :     dfMaxX = std::ceil(dfMaxX / dfRes - EPSILON) * dfRes;
     494          38 :     dfMaxY = std::ceil(dfMaxY / dfRes - EPSILON) * dfRes;
     495             : 
     496             :     // Compute the geotransform
     497          38 :     m_gt.xorig = dfMinX;
     498          38 :     m_gt.xscale = dfRes;
     499          38 :     m_gt.yorig = dfMaxY;
     500          38 :     m_gt.yscale = -dfRes;
     501             : 
     502             :     // Compute the raster dimension
     503          38 :     const double dfXSize = (dfMaxX - dfMinX) / dfRes;
     504          38 :     const double dfYSize = (dfMaxY - dfMinY) / dfRes;
     505          38 :     if (dfXSize > INT_MAX || dfYSize > INT_MAX)
     506             :     {
     507           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     508             :                  "Too large zoom level %d compared to dataset extent",
     509             :                  m_nZoomLevel);
     510           0 :         return false;
     511             :     }
     512          38 :     nRasterXSize = std::max(1, static_cast<int>(dfXSize + EPSILON));
     513          38 :     nRasterYSize = std::max(1, static_cast<int>(dfYSize + EPSILON));
     514             : 
     515             :     // Create bands
     516          38 :     const int nTargetBands = poTileDS->GetRasterBand(1)->GetColorTable()
     517          38 :                                  ? 3
     518          38 :                                  : poTileDS->GetRasterCount();
     519         180 :     for (int i = 0; i < nTargetBands; ++i)
     520         142 :         SetBand(i + 1, std::make_unique<GDALPMTilesRasterBand>(this, i + 1,
     521             :                                                                nTileSize));
     522             : 
     523          38 :     m_osTmpGPKGFilename = VSIMemGenerateHiddenFilename("pmtiles.gti.gpkg");
     524             : 
     525          38 :     if (nTargetBands > 1)
     526          38 :         SetMetadataItem("INTERLEAVING", "PIXEL", "IMAGE_STRUCTURE");
     527             : 
     528             :     // Create overview datasets
     529          56 :     for (int iOvr = 0; iOvr < m_nZoomLevel - m_nMinZoomLevel; ++iOvr)
     530             :     {
     531          36 :         auto poOvrDS = std::make_unique<OGRPMTilesDataset>();
     532          18 :         poOvrDS->SetDescription(GetDescription());
     533          18 :         poOvrDS->m_psInternalDecompressor = m_psInternalDecompressor;
     534          18 :         poOvrDS->m_psTileDataDecompressor = m_psTileDataDecompressor;
     535          18 :         poOvrDS->m_poFile = m_poFile;
     536          18 :         poOvrDS->m_nZoomLevel = m_nZoomLevel - 1 - iOvr;
     537          18 :         poOvrDS->m_osTmpGPKGFilename = m_osTmpGPKGFilename;
     538          18 :         poOvrDS->m_poGPKGDriver = m_poGPKGDriver;
     539          18 :         poOvrDS->m_sHeader = m_sHeader;
     540          18 :         poOvrDS->m_oSRS = m_oSRS;
     541          18 :         poOvrDS->m_gt = m_gt;
     542          18 :         poOvrDS->m_gt.xscale *= (1 << (iOvr + 1));
     543          18 :         poOvrDS->m_gt.yscale *= (1 << (iOvr + 1));
     544          18 :         poOvrDS->nRasterXSize = std::max(
     545          36 :             1,
     546          18 :             static_cast<int>((dfMaxX - dfMinX) / poOvrDS->m_gt.xscale + 0.5));
     547          18 :         poOvrDS->nRasterYSize = std::max(
     548          36 :             1,
     549          18 :             static_cast<int>((dfMaxY - dfMinY) / -poOvrDS->m_gt.yscale + 0.5));
     550          18 :         poOvrDS->m_gt.xscale = (dfMaxX - dfMinX) / poOvrDS->nRasterXSize;
     551          18 :         poOvrDS->m_gt.yscale = -(dfMaxY - dfMinY) / poOvrDS->nRasterYSize;
     552             : 
     553          86 :         for (int i = 0; i < nTargetBands; ++i)
     554         136 :             poOvrDS->SetBand(i + 1, std::make_unique<GDALPMTilesRasterBand>(
     555         136 :                                         poOvrDS.get(), i + 1, nTileSize));
     556             : 
     557          18 :         if (nTargetBands > 1)
     558          18 :             poOvrDS->SetMetadataItem("INTERLEAVING", "PIXEL",
     559          18 :                                      "IMAGE_STRUCTURE");
     560             : 
     561          18 :         m_apoOverviews.push_back(std::move(poOvrDS));
     562             :     }
     563             : 
     564          38 :     return true;
     565             : }
     566             : 
     567             : /************************************************************************/
     568             : /*                             IRasterIO()                              */
     569             : /************************************************************************/
     570             : 
     571          28 : CPLErr OGRPMTilesDataset::IRasterIO(
     572             :     GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
     573             :     void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
     574             :     int nBandCount, BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
     575             :     GSpacing nLineSpace, GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
     576             : {
     577             : 
     578          28 :     if (nBufXSize < nXSize && nBufYSize < nYSize && AreOverviewsEnabled())
     579             :     {
     580           1 :         int bTried = FALSE;
     581           1 :         const CPLErr eErr = TryOverviewRasterIO(
     582             :             eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
     583             :             eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace,
     584             :             nBandSpace, psExtraArg, &bTried);
     585           1 :         if (bTried)
     586           1 :             return eErr;
     587             :     }
     588             : 
     589             :     // Compute the georeferenced coordinates of the window of interest
     590             :     // defined by (nXOff, nYOff, nXSize, nYSize) (or the floating point
     591             :     // coordinates)
     592          27 :     const double dfXOff =
     593          27 :         psExtraArg->bFloatingPointWindowValidity ? psExtraArg->dfXOff : nXOff;
     594          27 :     const double dfYOff =
     595          27 :         psExtraArg->bFloatingPointWindowValidity ? psExtraArg->dfYOff : nYOff;
     596          27 :     const double dfXSize =
     597          27 :         psExtraArg->bFloatingPointWindowValidity ? psExtraArg->dfXSize : nXSize;
     598          27 :     const double dfYSize =
     599          27 :         psExtraArg->bFloatingPointWindowValidity ? psExtraArg->dfYSize : nYSize;
     600          27 :     const double dfMinX = m_gt.xorig + dfXOff * m_gt.xscale;
     601          27 :     const double dfMaxY = m_gt.yorig + dfYOff * m_gt.yscale;
     602          27 :     const double dfMaxX = m_gt.xorig + (dfXOff + dfXSize) * m_gt.xscale;
     603          27 :     const double dfMinY = m_gt.yorig + (dfYOff + dfYSize) * m_gt.yscale;
     604          27 :     const double dfTileDim = 2 * MAX_GM / (1 << m_nZoomLevel);
     605             : 
     606             :     // Compute the minimum and maximum tile indices covering the window
     607             :     // of interest
     608          27 :     constexpr double EPSILON = 1e-5;
     609             :     const int nTileMinX = std::max(
     610          27 :         0, static_cast<int>(floor((dfMinX + MAX_GM) / dfTileDim + EPSILON)));
     611             :     // PMTiles uses a Y=MAX_GM as the y=0 tile
     612             :     const int nTileMinY = std::max(
     613          27 :         0, static_cast<int>(floor((MAX_GM - dfMaxY) / dfTileDim + EPSILON)));
     614             :     const int nTileMaxX = std::min(
     615          54 :         static_cast<int>(floor((dfMaxX + MAX_GM) / dfTileDim + EPSILON)),
     616          27 :         (1 << m_nZoomLevel) - 1);
     617             :     const int nTileMaxY = std::min(
     618          54 :         static_cast<int>(floor((MAX_GM - dfMinY) / dfTileDim + EPSILON)),
     619          27 :         (1 << m_nZoomLevel) - 1);
     620             : 
     621             :     // Create a GTI GeoPackage file with the tiles that intersect the window
     622             :     // of interest.
     623          27 :     VSIUnlink(m_osTmpGPKGFilename.c_str());
     624          27 :     auto poGPKGDS = std::unique_ptr<GDALDataset>(m_poGPKGDriver->Create(
     625          54 :         m_osTmpGPKGFilename.c_str(), 0, 0, 0, GDT_Unknown, nullptr));
     626          27 :     auto poLayer = poGPKGDS ? poGPKGDS->CreateLayer("index") : nullptr;
     627          27 :     if (!poLayer)
     628             :     {
     629           0 :         return CE_Failure;
     630             :     }
     631          54 :     OGRFieldDefn oFieldDefn("location", OFTString);
     632          27 :     CPL_IGNORE_RET_VAL(poLayer->CreateField(&oFieldDefn));
     633             : 
     634             :     // Add layer-level metadata items recognized by the GTI driver.
     635          27 :     const double dfMosaicMinX = -MAX_GM + nTileMinX * dfTileDim;
     636          27 :     const double dfMosaicMinY = MAX_GM - (nTileMaxY + 1) * dfTileDim;
     637          27 :     const double dfMosaicMaxX = -MAX_GM + (nTileMaxX + 1) * dfTileDim;
     638          27 :     const double dfMosaicMaxY = MAX_GM - nTileMinY * dfTileDim;
     639          27 :     poLayer->SetMetadataItem("RESX", CPLSPrintf("%.17g", m_gt.xscale));
     640          27 :     poLayer->SetMetadataItem("RESY", CPLSPrintf("%.17g", -m_gt.yscale));
     641          27 :     poLayer->SetMetadataItem("MINX", CPLSPrintf("%.17g", dfMosaicMinX));
     642          27 :     poLayer->SetMetadataItem("MINY", CPLSPrintf("%.17g", dfMosaicMinY));
     643          27 :     poLayer->SetMetadataItem("MAXX", CPLSPrintf("%.17g", dfMosaicMaxX));
     644          27 :     poLayer->SetMetadataItem("MAXY", CPLSPrintf("%.17g", dfMosaicMaxY));
     645          27 :     poLayer->SetMetadataItem("DATA_TYPE", "UInt8");
     646          27 :     poLayer->SetMetadataItem("BAND_COUNT", CPLSPrintf("%d", nBands));
     647          27 :     if (nBands <= 4)
     648             :     {
     649          27 :         poLayer->SetMetadataItem("COLOR_INTERPRETATION",
     650          27 :                                  nBands == 1   ? "gray"
     651          49 :                                  : nBands == 2 ? "gray,alpha"
     652          22 :                                  : nBands == 3 ? "red,green,blue"
     653          27 :                                                : "red,green,blue,alpha");
     654             :     }
     655             : 
     656          27 :     poGPKGDS->StartTransaction();
     657             :     OGRPMTilesTileIterator oTileIter(this, m_nZoomLevel, nTileMinX, nTileMinY,
     658          54 :                                      nTileMaxX, nTileMaxY);
     659             :     while (true)
     660             :     {
     661          54 :         const auto sTile = oTileIter.GetNextTile();
     662          54 :         if (sTile.offset == 0)
     663             :         {
     664          27 :             break;
     665             :         }
     666             : 
     667          54 :         OGRFeature oFeature(poLayer->GetLayerDefn());
     668          54 :         oFeature.SetField(0, CPLSPrintf("/vsipmtiles/%s/%d/%d/%d%s",
     669          27 :                                         GetDescription(), m_nZoomLevel, sTile.x,
     670          27 :                                         sTile.y,
     671             :                                         VSIPMTilesGetTileExtension(this)));
     672          54 :         auto poLR = std::make_unique<OGRLinearRing>();
     673          27 :         const double dfTileMinX = -MAX_GM + sTile.x * dfTileDim;
     674          27 :         const double dfTileMaxX = -MAX_GM + (sTile.x + 1) * dfTileDim;
     675          27 :         const double dfTileMaxY = MAX_GM - sTile.y * dfTileDim;
     676          27 :         const double dfTileMinY = MAX_GM - (sTile.y + 1) * dfTileDim;
     677          27 :         poLR->addPoint(dfTileMinX, dfTileMinY);
     678          27 :         poLR->addPoint(dfTileMinX, dfTileMaxY);
     679          27 :         poLR->addPoint(dfTileMaxX, dfTileMaxY);
     680          27 :         poLR->addPoint(dfTileMaxX, dfTileMinY);
     681          27 :         poLR->addPoint(dfTileMinX, dfTileMinY);
     682          27 :         auto poPoly = std::make_unique<OGRPolygon>();
     683          27 :         poPoly->addRing(std::move(poLR));
     684          27 :         oFeature.SetGeometry(std::move(poPoly));
     685          27 :         CPL_IGNORE_RET_VAL(poLayer->CreateFeature(&oFeature));
     686          27 :     }
     687          27 :     poGPKGDS->CommitTransaction();
     688          27 :     poGPKGDS.reset();
     689             : 
     690             :     // Open the GTI GeoPackage file has a raster
     691          27 :     const char *const apszAllowedDriversGTI[] = {"GTI", nullptr};
     692          54 :     CPLStringList aosOpenOptionsGTI;
     693             :     aosOpenOptionsGTI.SetNameValue("ALLOWED_RASTER_DRIVERS",
     694          27 :                                    GetTileType(m_sHeader));
     695             :     auto poGTIDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
     696             :         m_osTmpGPKGFilename.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
     697          54 :         apszAllowedDriversGTI, aosOpenOptionsGTI.List()));
     698          27 :     if (!poGTIDS)
     699             :     {
     700           0 :         return CE_Failure;
     701             :     }
     702             : 
     703             :     // Compute the window of interest relative to the origin of the GTI dataset
     704             :     GDALRasterIOExtraArg sExtraArg;
     705          27 :     INIT_RASTERIO_EXTRA_ARG(sExtraArg);
     706          27 :     sExtraArg.eResampleAlg = psExtraArg->eResampleAlg;
     707          27 :     sExtraArg.bFloatingPointWindowValidity =
     708          27 :         psExtraArg->bFloatingPointWindowValidity;
     709          27 :     sExtraArg.dfXOff = (dfMinX - dfMosaicMinX) / m_gt.xscale;
     710          27 :     sExtraArg.dfYOff = (dfMosaicMaxY - dfMaxY) / -m_gt.yscale;
     711          27 :     sExtraArg.dfXSize = psExtraArg->dfXSize;
     712          27 :     sExtraArg.dfYSize = psExtraArg->dfYSize;
     713             : 
     714          27 :     const int nGTIXOff = static_cast<int>(sExtraArg.dfXOff + EPSILON);
     715          27 :     const int nGTIYOff = static_cast<int>(sExtraArg.dfYOff + EPSILON);
     716             : 
     717             :     // Redirect the pixel request to the GTI dataset
     718          27 :     return poGTIDS->RasterIO(GF_Read, nGTIXOff, nGTIYOff, nXSize, nYSize, pData,
     719             :                              nBufXSize, nBufYSize, eBufType, nBandCount,
     720             :                              panBandMap, nPixelSpace, nLineSpace, nBandSpace,
     721          27 :                              &sExtraArg);
     722             : }
     723             : 
     724             : /************************************************************************/
     725             : /*                                Read()                                */
     726             : /************************************************************************/
     727             : 
     728        1100 : const std::string *OGRPMTilesDataset::Read(const CPLCompressor *psDecompressor,
     729             :                                            uint64_t nOffset, uint64_t nSize,
     730             :                                            const char *pszDataType)
     731             : {
     732        1100 :     if (nSize > 10 * 1024 * 1024)
     733             :     {
     734           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     735             :                  "Too large amount of %s to read: " CPL_FRMT_GUIB
     736             :                  " bytes at offset " CPL_FRMT_GUIB,
     737             :                  pszDataType, static_cast<GUIntBig>(nSize),
     738             :                  static_cast<GUIntBig>(nOffset));
     739           0 :         return nullptr;
     740             :     }
     741        1100 :     m_osBuffer.resize(static_cast<size_t>(nSize));
     742        2200 :     if (m_poFile->Seek(nOffset, SEEK_SET) != 0 ||
     743        1100 :         m_poFile->Read(&m_osBuffer[0], m_osBuffer.size(), 1) != 1)
     744             :     {
     745           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     746             :                  "Cannot read %s of length %u at offset " CPL_FRMT_GUIB,
     747             :                  pszDataType, unsigned(nSize), static_cast<GUIntBig>(nOffset));
     748           1 :         return nullptr;
     749             :     }
     750             : 
     751        1099 :     if (psDecompressor)
     752             :     {
     753         917 :         m_osDecompressedBuffer.resize(32 + 16 * m_osBuffer.size());
     754         917 :         for (int iTry = 0; iTry < 2; ++iTry)
     755             :         {
     756         917 :             void *pOutputData = &m_osDecompressedBuffer[0];
     757         917 :             size_t nOutputSize = m_osDecompressedBuffer.size();
     758         917 :             if (!psDecompressor->pfnFunc(m_osBuffer.data(), m_osBuffer.size(),
     759             :                                          &pOutputData, &nOutputSize, nullptr,
     760         917 :                                          psDecompressor->user_data))
     761             :             {
     762           0 :                 if (iTry == 0)
     763             :                 {
     764           0 :                     pOutputData = nullptr;
     765           0 :                     nOutputSize = 0;
     766           0 :                     if (psDecompressor->pfnFunc(
     767           0 :                             m_osBuffer.data(), m_osBuffer.size(), &pOutputData,
     768           0 :                             &nOutputSize, nullptr, psDecompressor->user_data))
     769             :                     {
     770           0 :                         CPLDebug("PMTiles",
     771             :                                  "Buffer of size %u uncompresses to %u bytes",
     772             :                                  unsigned(nSize), unsigned(nOutputSize));
     773           0 :                         m_osDecompressedBuffer.resize(nOutputSize);
     774           0 :                         continue;
     775             :                     }
     776             :                 }
     777             : 
     778           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     779             :                          "Cannot decompress %s of length %u at "
     780             :                          "offset " CPL_FRMT_GUIB,
     781             :                          pszDataType, unsigned(nSize),
     782             :                          static_cast<GUIntBig>(nOffset));
     783           0 :                 return nullptr;
     784             :             }
     785         917 :             m_osDecompressedBuffer.resize(nOutputSize);
     786         917 :             break;
     787             :         }
     788         917 :         return &m_osDecompressedBuffer;
     789             :     }
     790             :     else
     791             :     {
     792         182 :         return &m_osBuffer;
     793             :     }
     794             : }
     795             : 
     796             : /************************************************************************/
     797             : /*                            ReadInternal()                            */
     798             : /************************************************************************/
     799             : 
     800         778 : const std::string *OGRPMTilesDataset::ReadInternal(uint64_t nOffset,
     801             :                                                    uint64_t nSize,
     802             :                                                    const char *pszDataType)
     803             : {
     804         778 :     return Read(m_psInternalDecompressor, nOffset, nSize, pszDataType);
     805             : }
     806             : 
     807             : /************************************************************************/
     808             : /*                            ReadTileData()                            */
     809             : /************************************************************************/
     810             : 
     811         322 : const std::string *OGRPMTilesDataset::ReadTileData(uint64_t nOffset,
     812             :                                                    uint64_t nSize)
     813             : {
     814         322 :     return Read(m_psTileDataDecompressor, nOffset, nSize, "tile data");
     815             : }

Generated by: LCOV version 1.14