LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/pmtiles - ogrpmtilesfrommbtiles.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 207 277 74.7 %
Date: 2024-11-21 22:18:42 Functions: 7 8 87.5 %

          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 "cpl_json.h"
      14             : 
      15             : #include "ogrsf_frmts.h"
      16             : #include "ogrpmtilesfrommbtiles.h"
      17             : 
      18             : #include "include_pmtiles.h"
      19             : 
      20             : #include "cpl_compressor.h"
      21             : #include "cpl_md5.h"
      22             : #include "cpl_string.h"
      23             : #include "cpl_vsi_virtual.h"
      24             : 
      25             : #include <algorithm>
      26             : #include <array>
      27             : #include <cassert>
      28             : #include <unordered_map>
      29             : #include <utility>
      30             : 
      31             : /************************************************************************/
      32             : /*                         ProcessMetadata()                            */
      33             : /************************************************************************/
      34             : 
      35          35 : static bool ProcessMetadata(GDALDataset *poSQLiteDS, pmtiles::headerv3 &sHeader,
      36             :                             std::string &osMetadata)
      37             : {
      38             : 
      39          35 :     auto poMetadata = poSQLiteDS->GetLayerByName("metadata");
      40          35 :     if (!poMetadata)
      41             :     {
      42           0 :         CPLError(CE_Failure, CPLE_AppDefined, "metadata table not found");
      43           0 :         return false;
      44             :     }
      45             : 
      46          35 :     const int iName = poMetadata->GetLayerDefn()->GetFieldIndex("name");
      47          35 :     const int iValue = poMetadata->GetLayerDefn()->GetFieldIndex("value");
      48          35 :     if (iName < 0 || iValue < 0)
      49             :     {
      50           0 :         CPLError(CE_Failure, CPLE_AppDefined,
      51             :                  "Bad structure for metadata table");
      52           0 :         return false;
      53             :     }
      54             : 
      55          70 :     CPLJSONObject oObj;
      56          70 :     CPLJSONDocument oJsonDoc;
      57         420 :     for (auto &&poFeature : poMetadata)
      58             :     {
      59         385 :         const char *pszName = poFeature->GetFieldAsString(iName);
      60         385 :         const char *pszValue = poFeature->GetFieldAsString(iValue);
      61         385 :         if (EQUAL(pszName, "json"))
      62             :         {
      63          35 :             if (!oJsonDoc.LoadMemory(pszValue))
      64             :             {
      65           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
      66             :                          "Cannot parse 'json' metadata item");
      67           0 :                 return false;
      68             :             }
      69         105 :             for (const auto &oChild : oJsonDoc.GetRoot().GetChildren())
      70             :             {
      71          70 :                 oObj.Add(oChild.GetName(), oChild);
      72             :             }
      73             :         }
      74             :         else
      75             :         {
      76         350 :             oObj.Add(pszName, pszValue);
      77             :         }
      78             :     }
      79             : 
      80             :     // MBTiles advertises scheme=tms. Override this
      81          35 :     oObj.Set("scheme", "xyz");
      82             : 
      83         105 :     const auto osFormat = oObj.GetString("format", "{missing}");
      84          35 :     if (osFormat != "pbf")
      85             :     {
      86           0 :         CPLError(CE_Failure, CPLE_AppDefined, "format=%s unhandled",
      87             :                  osFormat.c_str());
      88           0 :         return false;
      89             :     }
      90             : 
      91          35 :     int nMinZoom = atoi(oObj.GetString("minzoom", "-1").c_str());
      92          35 :     if (nMinZoom < 0 || nMinZoom > 255)
      93             :     {
      94           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Missing or invalid minzoom");
      95           0 :         return false;
      96             :     }
      97             : 
      98          35 :     int nMaxZoom = atoi(oObj.GetString("maxzoom", "-1").c_str());
      99          35 :     if (nMaxZoom < 0 || nMaxZoom > 255)
     100             :     {
     101           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Missing or invalid maxzoom");
     102           0 :         return false;
     103             :     }
     104             : 
     105             :     const CPLStringList aosCenter(
     106         105 :         CSLTokenizeString2(oObj.GetString("center").c_str(), ",", 0));
     107          35 :     if (aosCenter.size() != 3)
     108             :     {
     109           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Expected 3 values for center");
     110           0 :         return false;
     111             :     }
     112          35 :     const double dfCenterLong = CPLAtof(aosCenter[0]);
     113          35 :     const double dfCenterLat = CPLAtof(aosCenter[1]);
     114          35 :     if (std::fabs(dfCenterLong) > 180 || std::fabs(dfCenterLat) > 90)
     115             :     {
     116           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid center");
     117           0 :         return false;
     118             :     }
     119          35 :     const int nCenterZoom = atoi(aosCenter[2]);
     120          35 :     if (nCenterZoom < 0 || nCenterZoom > 255)
     121             :     {
     122           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Missing or invalid center zoom");
     123           0 :         return false;
     124             :     }
     125             : 
     126             :     const CPLStringList aosBounds(
     127         105 :         CSLTokenizeString2(oObj.GetString("bounds").c_str(), ",", 0));
     128          35 :     if (aosBounds.size() != 4)
     129             :     {
     130           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Expected 4 values for bounds");
     131           0 :         return false;
     132             :     }
     133          35 :     const double dfMinX = CPLAtof(aosBounds[0]);
     134          35 :     const double dfMinY = CPLAtof(aosBounds[1]);
     135          35 :     const double dfMaxX = CPLAtof(aosBounds[2]);
     136          35 :     const double dfMaxY = CPLAtof(aosBounds[3]);
     137          35 :     if (std::fabs(dfMinX) > 180 || std::fabs(dfMinY) > 90 ||
     138          19 :         std::fabs(dfMaxX) > 180 || std::fabs(dfMaxY) > 90)
     139             :     {
     140          16 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid bounds");
     141          16 :         return false;
     142             :     }
     143             : 
     144          19 :     CPLJSONDocument oMetadataDoc;
     145          19 :     oMetadataDoc.SetRoot(oObj);
     146          19 :     osMetadata = oMetadataDoc.SaveAsString();
     147             :     // CPLDebugOnly("PMTiles", "Metadata = %s", osMetadata.c_str());
     148             : 
     149          19 :     sHeader.root_dir_offset = 127;
     150          19 :     sHeader.root_dir_bytes = 0;
     151          19 :     sHeader.json_metadata_offset = 0;
     152          19 :     sHeader.json_metadata_bytes = 0;
     153          19 :     sHeader.leaf_dirs_offset = 0;
     154          19 :     sHeader.leaf_dirs_bytes = 0;
     155          19 :     sHeader.tile_data_offset = 0;
     156          19 :     sHeader.tile_data_bytes = 0;
     157          19 :     sHeader.addressed_tiles_count = 0;
     158          19 :     sHeader.tile_entries_count = 0;
     159          19 :     sHeader.tile_contents_count = 0;
     160          19 :     sHeader.clustered = true;
     161          19 :     sHeader.internal_compression = pmtiles::COMPRESSION_GZIP;
     162          19 :     sHeader.tile_compression = pmtiles::COMPRESSION_GZIP;
     163          19 :     sHeader.tile_type = pmtiles::TILETYPE_MVT;
     164          19 :     sHeader.min_zoom = static_cast<uint8_t>(nMinZoom);
     165          19 :     sHeader.max_zoom = static_cast<uint8_t>(nMaxZoom);
     166          19 :     sHeader.min_lon_e7 = static_cast<int32_t>(dfMinX * 10e6);
     167          19 :     sHeader.min_lat_e7 = static_cast<int32_t>(dfMinY * 10e6);
     168          19 :     sHeader.max_lon_e7 = static_cast<int32_t>(dfMaxX * 10e6);
     169          19 :     sHeader.max_lat_e7 = static_cast<int32_t>(dfMaxY * 10e6);
     170          19 :     sHeader.center_zoom = static_cast<uint8_t>(nCenterZoom);
     171          19 :     sHeader.center_lon_e7 = static_cast<int32_t>(dfCenterLong * 10e6);
     172          19 :     sHeader.center_lat_e7 = static_cast<int32_t>(dfCenterLat * 10e6);
     173             : 
     174          19 :     return true;
     175             : }
     176             : 
     177             : /************************************************************************/
     178             : /*                               HashArray()                            */
     179             : /************************************************************************/
     180             : 
     181             : // From https://codereview.stackexchange.com/questions/171999/specializing-stdhash-for-stdarray
     182             : // We do not use std::hash<std::array<T, N>> as the name of the struct
     183             : // because with gcc 5.4 we get the following error:
     184             : // https://stackoverflow.com/questions/25594644/warning-specialization-of-template-in-different-namespace
     185             : template <class T, size_t N> struct HashArray
     186             : {
     187             :     CPL_NOSANITIZE_UNSIGNED_INT_OVERFLOW
     188         328 :     size_t operator()(const std::array<T, N> &key) const
     189             :     {
     190             :         std::hash<T> hasher;
     191         328 :         size_t result = 0;
     192        5576 :         for (size_t i = 0; i < N; ++i)
     193             :         {
     194        5248 :             result = result * 31 + hasher(key[i]);
     195             :         }
     196         328 :         return result;
     197             :     }
     198             : };
     199             : 
     200             : /************************************************************************/
     201             : /*                    OGRPMTilesConvertFromMBTiles()                    */
     202             : /************************************************************************/
     203             : 
     204          35 : bool OGRPMTilesConvertFromMBTiles(const char *pszDestName,
     205             :                                   const char *pszSrcName)
     206             : {
     207          35 :     const char *const apszAllowedDrivers[] = {"SQLite", nullptr};
     208             :     auto poSQLiteDS = std::unique_ptr<GDALDataset>(
     209          70 :         GDALDataset::Open(pszSrcName, GDAL_OF_VECTOR, apszAllowedDrivers));
     210          35 :     if (!poSQLiteDS)
     211             :     {
     212           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     213             :                  "Cannot open %s with SQLite driver", pszSrcName);
     214           0 :         return false;
     215             :     }
     216             : 
     217             :     pmtiles::headerv3 sHeader;
     218          70 :     std::string osMetadata;
     219          35 :     if (!ProcessMetadata(poSQLiteDS.get(), sHeader, osMetadata))
     220          16 :         return false;
     221             : 
     222          19 :     auto poTilesLayer = poSQLiteDS->GetLayerByName("tiles");
     223          19 :     if (!poTilesLayer)
     224             :     {
     225           0 :         CPLError(CE_Failure, CPLE_AppDefined, "tiles table not found");
     226           0 :         return false;
     227             :     }
     228             : 
     229             :     const int iZoomLevel =
     230          19 :         poTilesLayer->GetLayerDefn()->GetFieldIndex("zoom_level");
     231             :     const int iTileColumn =
     232          19 :         poTilesLayer->GetLayerDefn()->GetFieldIndex("tile_column");
     233             :     const int iTileRow =
     234          19 :         poTilesLayer->GetLayerDefn()->GetFieldIndex("tile_row");
     235             :     const int iTileData =
     236          19 :         poTilesLayer->GetLayerDefn()->GetFieldIndex("tile_data");
     237          19 :     if (iZoomLevel < 0 || iTileColumn < 0 || iTileRow < 0 || iTileData < 0)
     238             :     {
     239           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Bad structure for tiles table");
     240           0 :         return false;
     241             :     }
     242             : 
     243             :     struct TileEntry
     244             :     {
     245             :         uint64_t nTileId;
     246             :         std::array<unsigned char, 16> abyMD5;
     247             :     };
     248             : 
     249             :     // In a first step browse through the tiles table to compute the PMTiles
     250             :     // tile_id of each tile, and compute a hash of the tile data for
     251             :     // deduplication
     252          38 :     std::vector<TileEntry> asTileEntries;
     253         276 :     for (auto &&poFeature : poTilesLayer)
     254             :     {
     255         257 :         const int nZoomLevel = poFeature->GetFieldAsInteger(iZoomLevel);
     256         257 :         if (nZoomLevel < 0 || nZoomLevel > 30)
     257             :         {
     258           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     259             :                      "Skipping tile with missing or invalid zoom_level");
     260           0 :             continue;
     261             :         }
     262         257 :         const int nColumn = poFeature->GetFieldAsInteger(iTileColumn);
     263         257 :         if (nColumn < 0 || nColumn >= (1 << nZoomLevel))
     264             :         {
     265           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     266             :                      "Skipping tile with missing or invalid tile_column");
     267           0 :             continue;
     268             :         }
     269         257 :         const int nRow = poFeature->GetFieldAsInteger(iTileRow);
     270         257 :         if (nRow < 0 || nRow >= (1 << nZoomLevel))
     271             :         {
     272           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     273             :                      "Skipping tile with missing or invalid tile_row");
     274           0 :             continue;
     275             :         }
     276             :         // MBTiles uses a 0=bottom-most row, whereas PMTiles uses
     277             :         // 0=top-most row
     278         257 :         const int nY = (1 << nZoomLevel) - 1 - nRow;
     279             :         uint64_t nTileId;
     280             :         try
     281             :         {
     282         257 :             nTileId = pmtiles::zxy_to_tileid(static_cast<uint8_t>(nZoomLevel),
     283             :                                              nColumn, nY);
     284             :         }
     285           0 :         catch (const std::exception &e)
     286             :         {
     287             :             // shouldn't happen given previous checks
     288           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot compute tile id: %s",
     289           0 :                      e.what());
     290           0 :             return false;
     291             :         }
     292         257 :         int nTileDataLength = 0;
     293             :         const GByte *pabyData =
     294         257 :             poFeature->GetFieldAsBinary(iTileData, &nTileDataLength);
     295         257 :         if (!pabyData)
     296             :         {
     297           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Missing tile_data");
     298           0 :             return false;
     299             :         }
     300             : 
     301             :         TileEntry sEntry;
     302         257 :         sEntry.nTileId = nTileId;
     303             : 
     304             :         CPLMD5Context md5context;
     305         257 :         CPLMD5Init(&md5context);
     306         257 :         CPLMD5Update(&md5context, pabyData, nTileDataLength);
     307         257 :         CPLMD5Final(&sEntry.abyMD5[0], &md5context);
     308             :         try
     309             :         {
     310         257 :             asTileEntries.push_back(sEntry);
     311             :         }
     312           0 :         catch (const std::exception &e)
     313             :         {
     314           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     315           0 :                      "Out of memory browsing through tiles: %s", e.what());
     316           0 :             return false;
     317             :         }
     318             :     }
     319             : 
     320             :     // Sort the tiles by ascending tile_id. This is a requirement to build
     321             :     // the PMTiles directories.
     322          19 :     std::sort(asTileEntries.begin(), asTileEntries.end(),
     323         840 :               [](const TileEntry &a, const TileEntry &b)
     324         840 :               { return a.nTileId < b.nTileId; });
     325             : 
     326             :     // Let's build a temporary file that contains the tile data in
     327             :     // a way that corresponds to the "clustered" mode, that is
     328             :     // "offsets are either contiguous with the previous offset+length, or
     329             :     // refer to a lesser offset, when writing with deduplication."
     330          57 :     std::string osTmpFile(std::string(pszDestName) + ".tmp");
     331          19 :     if (!VSIIsLocal(pszDestName))
     332             :     {
     333           0 :         osTmpFile = CPLGenerateTempFilename(CPLGetFilename(pszDestName));
     334             :     }
     335             : 
     336             :     auto poTmpFile =
     337          38 :         VSIVirtualHandleUniquePtr(VSIFOpenL(osTmpFile.c_str(), "wb+"));
     338          19 :     VSIUnlink(osTmpFile.c_str());
     339          19 :     if (!poTmpFile)
     340             :     {
     341           0 :         CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s for write",
     342             :                  osTmpFile.c_str());
     343           0 :         return false;
     344             :     }
     345             : 
     346             :     struct ResetAndUnlinkTmpFile
     347             :     {
     348             :         VSIVirtualHandleUniquePtr &m_poFile;
     349             :         std::string m_osFilename;
     350             : 
     351          19 :         ResetAndUnlinkTmpFile(VSIVirtualHandleUniquePtr &poFile,
     352             :                               const std::string &osFilename)
     353          19 :             : m_poFile(poFile), m_osFilename(osFilename)
     354             :         {
     355          19 :         }
     356             : 
     357          19 :         ~ResetAndUnlinkTmpFile()
     358          19 :         {
     359          19 :             m_poFile.reset();
     360          19 :             VSIUnlink(m_osFilename.c_str());
     361          19 :         }
     362             :     };
     363             : 
     364          38 :     ResetAndUnlinkTmpFile oReseer(poTmpFile, osTmpFile);
     365             : 
     366          38 :     std::vector<pmtiles::entryv3> asPMTilesEntries;
     367          19 :     uint64_t nLastTileId = 0;
     368          19 :     uint64_t nFileOffset = 0;
     369             :     std::array<unsigned char, 16> abyLastMD5;
     370             :     std::unordered_map<std::array<unsigned char, 16>,
     371             :                        std::pair<uint64_t, uint32_t>,
     372             :                        HashArray<unsigned char, 16>>
     373          38 :         oMapMD5ToOffsetLen;
     374         276 :     for (const auto &sEntry : asTileEntries)
     375             :     {
     376         257 :         if (sEntry.nTileId == nLastTileId + 1 && sEntry.abyMD5 == abyLastMD5)
     377             :         {
     378             :             // If the tile id immediately follows the previous one and
     379             :             // has the same tile data, increase the run_length
     380           3 :             asPMTilesEntries.back().run_length++;
     381             :         }
     382             :         else
     383             :         {
     384         254 :             pmtiles::entryv3 sPMTilesEntry;
     385         254 :             sPMTilesEntry.tile_id = sEntry.nTileId;
     386         254 :             sPMTilesEntry.run_length = 1;
     387             : 
     388         254 :             auto oIter = oMapMD5ToOffsetLen.find(sEntry.abyMD5);
     389         254 :             if (oIter != oMapMD5ToOffsetLen.end())
     390             :             {
     391             :                 // Point to previously written tile data if this content
     392             :                 // has already been written
     393         180 :                 sPMTilesEntry.offset = oIter->second.first;
     394         180 :                 sPMTilesEntry.length = oIter->second.second;
     395             :             }
     396             :             else
     397             :             {
     398             :                 try
     399             :                 {
     400          74 :                     const auto sXYZ = pmtiles::tileid_to_zxy(sEntry.nTileId);
     401          74 :                     poTilesLayer->SetAttributeFilter(CPLSPrintf(
     402             :                         "zoom_level = %d AND tile_column = %u AND tile_row = "
     403             :                         "%u",
     404          74 :                         sXYZ.z, sXYZ.x, (1U << sXYZ.z) - 1U - sXYZ.y));
     405             :                 }
     406           0 :                 catch (const std::exception &e)
     407             :                 {
     408             :                     // shouldn't happen given previous checks
     409           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
     410           0 :                              "Cannot compute xyz: %s", e.what());
     411           0 :                     return false;
     412             :                 }
     413          74 :                 poTilesLayer->ResetReading();
     414             :                 auto poFeature =
     415          74 :                     std::unique_ptr<OGRFeature>(poTilesLayer->GetNextFeature());
     416          74 :                 if (!poFeature)
     417             :                 {
     418           0 :                     CPLError(CE_Failure, CPLE_AppDefined, "Cannot find tile");
     419           0 :                     return false;
     420             :                 }
     421          74 :                 int nTileDataLength = 0;
     422             :                 const GByte *pabyData =
     423          74 :                     poFeature->GetFieldAsBinary(iTileData, &nTileDataLength);
     424          74 :                 if (!pabyData)
     425             :                 {
     426           0 :                     CPLError(CE_Failure, CPLE_AppDefined, "Missing tile_data");
     427           0 :                     return false;
     428             :                 }
     429             : 
     430          74 :                 sPMTilesEntry.offset = nFileOffset;
     431          74 :                 sPMTilesEntry.length = nTileDataLength;
     432             : 
     433          74 :                 oMapMD5ToOffsetLen[sEntry.abyMD5] =
     434         148 :                     std::pair<uint64_t, uint32_t>(nFileOffset, nTileDataLength);
     435             : 
     436          74 :                 nFileOffset += nTileDataLength;
     437             : 
     438          74 :                 if (poTmpFile->Write(pabyData, nTileDataLength, 1) != 1)
     439             :                 {
     440           0 :                     CPLError(CE_Failure, CPLE_FileIO, "Failed writing");
     441           0 :                     return false;
     442             :                 }
     443             :             }
     444             : 
     445         254 :             asPMTilesEntries.push_back(sPMTilesEntry);
     446             : 
     447         254 :             nLastTileId = sEntry.nTileId;
     448         254 :             abyLastMD5 = sEntry.abyMD5;
     449             :         }
     450             :     }
     451             : 
     452          19 :     const CPLCompressor *psCompressor = CPLGetCompressor("gzip");
     453          19 :     assert(psCompressor);
     454          38 :     std::string osCompressed;
     455             : 
     456             :     struct compression_exception : std::exception
     457             :     {
     458           0 :         const char *what() const noexcept override
     459             :         {
     460           0 :             return "Compression failed";
     461             :         }
     462             :     };
     463             : 
     464          38 :     const auto oCompressFunc = [psCompressor,
     465             :                                 &osCompressed](const std::string &osBytes,
     466         228 :                                                uint8_t) -> std::string
     467             :     {
     468          38 :         osCompressed.resize(32 + osBytes.size() * 2);
     469          38 :         size_t nOutputSize = osCompressed.size();
     470          38 :         void *pOutputData = &osCompressed[0];
     471          38 :         if (!psCompressor->pfnFunc(osBytes.data(), osBytes.size(), &pOutputData,
     472             :                                    &nOutputSize, nullptr,
     473          38 :                                    psCompressor->user_data))
     474             :         {
     475           0 :             throw compression_exception();
     476             :         }
     477          38 :         osCompressed.resize(nOutputSize);
     478          76 :         return osCompressed;
     479          19 :     };
     480             : 
     481          38 :     std::string osCompressedMetadata;
     482             : 
     483          38 :     std::string osRootBytes;
     484          38 :     std::string osLeaveBytes;
     485             :     int nNumLeaves;
     486             :     try
     487             :     {
     488             :         osCompressedMetadata =
     489          19 :             oCompressFunc(osMetadata, pmtiles::COMPRESSION_GZIP);
     490             : 
     491             :         // Build the root and leave directories (one depth max)
     492          19 :         std::tie(osRootBytes, osLeaveBytes, nNumLeaves) =
     493          38 :             pmtiles::make_root_leaves(oCompressFunc, pmtiles::COMPRESSION_GZIP,
     494          19 :                                       asPMTilesEntries);
     495             :     }
     496           0 :     catch (const std::exception &e)
     497             :     {
     498           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot build directories: %s",
     499           0 :                  e.what());
     500           0 :         return false;
     501             :     }
     502             : 
     503             :     // Finalize the header fields related to offsets and size of the
     504             :     // different parts of the file
     505          19 :     sHeader.root_dir_bytes = osRootBytes.size();
     506          19 :     sHeader.json_metadata_offset =
     507          19 :         sHeader.root_dir_offset + sHeader.root_dir_bytes;
     508          19 :     sHeader.json_metadata_bytes = osCompressedMetadata.size();
     509          19 :     sHeader.leaf_dirs_offset =
     510          19 :         sHeader.json_metadata_offset + sHeader.json_metadata_bytes;
     511          19 :     sHeader.leaf_dirs_bytes = osLeaveBytes.size();
     512          19 :     sHeader.tile_data_offset =
     513          19 :         sHeader.leaf_dirs_offset + sHeader.leaf_dirs_bytes;
     514          19 :     sHeader.tile_data_bytes = nFileOffset;
     515             : 
     516             :     // Nomber of tiles that are addressable in the PMTiles archive, that is
     517             :     // the number of tiles we would have if not deduplicating them
     518          19 :     sHeader.addressed_tiles_count = asTileEntries.size();
     519             : 
     520             :     // Number of tile entries in root and leave directories
     521             :     // ie entries whose run_length >= 1
     522          19 :     sHeader.tile_entries_count = asPMTilesEntries.size();
     523             : 
     524             :     // Number of distinct tile blobs
     525          19 :     sHeader.tile_contents_count = oMapMD5ToOffsetLen.size();
     526             : 
     527             :     // Now build the final file!
     528          38 :     auto poFile = VSIVirtualHandleUniquePtr(VSIFOpenL(pszDestName, "wb"));
     529          19 :     if (!poFile)
     530             :     {
     531           0 :         CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s for write",
     532             :                  pszDestName);
     533           0 :         return false;
     534             :     }
     535          38 :     const auto osHeader = sHeader.serialize();
     536             : 
     537          19 :     if (poTmpFile->Seek(0, SEEK_SET) != 0 ||
     538          19 :         poFile->Write(osHeader.data(), osHeader.size(), 1) != 1 ||
     539          19 :         poFile->Write(osRootBytes.data(), osRootBytes.size(), 1) != 1 ||
     540          38 :         poFile->Write(osCompressedMetadata.data(), osCompressedMetadata.size(),
     541          57 :                       1) != 1 ||
     542          19 :         (!osLeaveBytes.empty() &&
     543           0 :          poFile->Write(osLeaveBytes.data(), osLeaveBytes.size(), 1) != 1))
     544             :     {
     545           0 :         CPLError(CE_Failure, CPLE_FileIO, "Failed writing");
     546           0 :         return false;
     547             :     }
     548             : 
     549             :     // Copy content of the temporary file at end of the output file.
     550          38 :     std::string oCopyBuffer;
     551          19 :     oCopyBuffer.resize(1024 * 1024);
     552          19 :     const uint64_t nTotalSize = nFileOffset;
     553          19 :     nFileOffset = 0;
     554          33 :     while (nFileOffset < nTotalSize)
     555             :     {
     556             :         const size_t nToRead = static_cast<size_t>(
     557          14 :             std::min<uint64_t>(nTotalSize - nFileOffset, oCopyBuffer.size()));
     558          28 :         if (poTmpFile->Read(&oCopyBuffer[0], nToRead, 1) != 1 ||
     559          14 :             poFile->Write(&oCopyBuffer[0], nToRead, 1) != 1)
     560             :         {
     561           0 :             CPLError(CE_Failure, CPLE_FileIO, "Failed writing");
     562           0 :             return false;
     563             :         }
     564          14 :         nFileOffset += nToRead;
     565             :     }
     566             : 
     567          19 :     if (poFile->Close() != 0)
     568           0 :         return false;
     569             : 
     570          19 :     return true;
     571             : }

Generated by: LCOV version 1.14