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

Generated by: LCOV version 1.14