LCOV - code coverage report
Current view: top level - frmts/esric - esric_dataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 465 536 86.8 %
Date: 2026-06-19 21:24:00 Functions: 29 30 96.7 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Purpose : gdal driver for reading Esri compact cache as raster
       4             :  *           based on public documentation available at
       5             :  *           https://github.com/Esri/raster-tiles-compactcache
       6             :  *
       7             :  * Author : Lucian Plesea
       8             :  *
       9             :  * Udate : 06 / 10 / 2020
      10             :  *
      11             :  *  Copyright 2020 Esri
      12             :  *
      13             :  * SPDX-License-Identifier: MIT
      14             :  *****************************************************************************/
      15             : 
      16             : #include "gdal_priv.h"
      17             : #include <cassert>
      18             : #include <vector>
      19             : #include <algorithm>
      20             : #include "cpl_json.h"
      21             : #include "gdal_proxy.h"
      22             : #include "gdal_utils.h"
      23             : #include "cpl_vsi_virtual.h"
      24             : 
      25             : using namespace std;
      26             : 
      27             : CPL_C_START
      28             : void CPL_DLL GDALRegister_ESRIC();
      29             : CPL_C_END
      30             : 
      31             : namespace ESRIC
      32             : {
      33             : 
      34             : #define ENDS_WITH_CI(a, b)                                                     \
      35             :     (strlen(a) >= strlen(b) && EQUAL(a + strlen(a) - strlen(b), b))
      36             : 
      37             : // ESRI tpkx files use root.json
      38       68016 : static int IdentifyJSON(GDALOpenInfo *poOpenInfo)
      39             : {
      40       68016 :     if (poOpenInfo->eAccess != GA_ReadOnly || poOpenInfo->nHeaderBytes < 512)
      41       60687 :         return false;
      42             : 
      43             :     // Recognize .tpkx file directly passed
      44        7329 :     if (!STARTS_WITH(poOpenInfo->pszFilename, "/vsizip/") &&
      45             : #if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
      46        7309 :         ENDS_WITH_CI(poOpenInfo->pszFilename, ".tpkx") &&
      47             : #endif
      48          14 :         memcmp(poOpenInfo->pabyHeader, "PK\x03\x04", 4) == 0)
      49             :     {
      50          14 :         return true;
      51             :     }
      52             : 
      53             : #if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
      54        7315 :     if (!ENDS_WITH_CI(poOpenInfo->pszFilename, "root.json"))
      55        7300 :         return false;
      56             : #endif
      57          16 :     for (int i = 0; i < 2; ++i)
      58             :     {
      59             :         const std::string osHeader(
      60          16 :             reinterpret_cast<char *>(poOpenInfo->pabyHeader),
      61          16 :             poOpenInfo->nHeaderBytes);
      62          16 :         if (std::string::npos != osHeader.find("tileBundlesPath"))
      63             :         {
      64          15 :             return true;
      65             :         }
      66             :         // If we didn't find tileBundlesPath i, the first bytes, but find
      67             :         // other elements typically of .tpkx, then ingest more bytes and
      68             :         // retry
      69           1 :         constexpr int MORE_BYTES = 8192;
      70           3 :         if (poOpenInfo->nHeaderBytes < MORE_BYTES &&
      71           2 :             (std::string::npos != osHeader.find("tileInfo") ||
      72           1 :              std::string::npos != osHeader.find("tileImageInfo")))
      73             :         {
      74           1 :             poOpenInfo->TryToIngest(MORE_BYTES);
      75             :         }
      76             :         else
      77           0 :             break;
      78             :     }
      79           0 :     return false;
      80             : }
      81             : 
      82             : // Without full XML parsing, weak, might still fail
      83       68028 : static int IdentifyXML(GDALOpenInfo *poOpenInfo)
      84             : {
      85       68028 :     if (poOpenInfo->eAccess != GA_ReadOnly
      86             : #if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
      87       67711 :         || !ENDS_WITH_CI(poOpenInfo->pszFilename, "conf.xml")
      88             : #endif
      89          12 :         || poOpenInfo->nHeaderBytes < 512)
      90       68016 :         return false;
      91          12 :     CPLString header(reinterpret_cast<char *>(poOpenInfo->pabyHeader),
      92          12 :                      poOpenInfo->nHeaderBytes);
      93          12 :     return (CPLString::npos != header.find("<CacheInfo"));
      94             : }
      95             : 
      96       68004 : static int Identify(GDALOpenInfo *poOpenInfo)
      97             : {
      98       68004 :     return (IdentifyXML(poOpenInfo) || IdentifyJSON(poOpenInfo));
      99             : }
     100             : 
     101             : // Stub default delete, don't delete a tile cache from GDAL
     102           0 : static CPLErr Delete(const char *)
     103             : {
     104           0 :     return CE_None;
     105             : }
     106             : 
     107             : // Read a 32bit unsigned integer stored in little endian
     108             : // Same as CPL_LSBUINT32PTR
     109          35 : static inline GUInt32 u32lat(void *data)
     110             : {
     111             :     GUInt32 val;
     112          35 :     memcpy(&val, data, 4);
     113          35 :     return CPL_LSBWORD32(val);
     114             : }
     115             : 
     116             : struct Bundle
     117             : {
     118           8 :     void Init(const char *filename)
     119             :     {
     120           8 :         name = filename;
     121           8 :         fh.reset(VSIFOpenL(name.c_str(), "rb"));
     122           8 :         if (nullptr == fh)
     123           1 :             return;
     124           7 :         GByte header[64] = {0};
     125             :         // Check a few header locations, then read the index
     126           7 :         fh->Read(header, 1, 64);
     127           7 :         index.resize(BSZ * BSZ);
     128          21 :         if (3 != u32lat(header) || 5 != u32lat(header + 12) ||
     129          14 :             40 != u32lat(header + 32) || 0 != u32lat(header + 36) ||
     130          21 :             BSZ * BSZ * 8 != u32lat(header + 60) ||
     131           7 :             index.size() != fh->Read(index.data(), 8, index.size()))
     132             :         {
     133           0 :             fh.reset();
     134             :         }
     135             : 
     136             :         if constexpr (!CPL_IS_LSB)
     137             :         {
     138             :             for (auto &v : index)
     139             :                 CPL_LSBPTR64(&v);
     140             :         }
     141             :     }
     142             : 
     143             :     std::vector<GUInt64> index{};
     144             :     VSIVirtualHandleUniquePtr fh{};
     145             :     bool isV2 = false;
     146             :     CPLString name{};
     147             :     const size_t BSZ = 128;
     148             : };
     149             : 
     150             : class ECDataset final : public GDALDataset
     151             : {
     152             :     friend class ECBand;
     153             : 
     154             :   public:
     155             :     ECDataset();
     156             : 
     157          12 :     CPLErr GetGeoTransform(GDALGeoTransform &gt) const override
     158             :     {
     159          12 :         gt = m_gt;
     160          12 :         return CE_None;
     161             :     }
     162             : 
     163             :     const OGRSpatialReference *GetSpatialRef() const override;
     164             : 
     165             :     static GDALDataset *Open(GDALOpenInfo *poOpenInfo);
     166             :     static GDALDataset *Open(GDALOpenInfo *poOpenInfo,
     167             :                              const char *pszDescription);
     168             : 
     169             :   protected:
     170             :     GDALGeoTransform m_gt{};
     171             :     CPLString dname{};
     172             :     int isV2{};  // V2 bundle format
     173             :     int BSZ{};   // Bundle size in tiles
     174             :     int TSZ{};   // Tile size in pixels
     175             :     std::vector<Bundle> bundles{};
     176             : 
     177             :     Bundle &GetBundle(const char *fname);
     178             : 
     179             :   private:
     180             :     CPLErr Initialize(CPLXMLNode *CacheInfo, bool ignoreOversizedLods);
     181             :     CPLErr InitializeFromJSON(const CPLJSONObject &oRoot,
     182             :                               bool ignoreOversizedLods);
     183             :     CPLString compression{};
     184             :     std::vector<double> resolutions{};
     185             :     int m_nMinLOD = 0;
     186             :     OGRSpatialReference oSRS{};
     187             :     std::vector<GByte> tilebuffer{};  // Last read tile, decompressed
     188             :     std::vector<GByte> filebuffer{};  // raw tile buffer
     189             : 
     190             :     OGREnvelope m_sInitialExtent{};
     191             :     OGREnvelope m_sFullExtent{};
     192             : };
     193             : 
     194           7 : const OGRSpatialReference *ECDataset::GetSpatialRef() const
     195             : {
     196           7 :     return &oSRS;
     197             : }
     198             : 
     199             : class ECBand final : public GDALRasterBand
     200             : {
     201             :     friend class ECDataset;
     202             : 
     203             :   public:
     204             :     ECBand(ECDataset *parent, int b, int level = 0);
     205             :     ~ECBand() override;
     206             : 
     207             :     CPLErr IReadBlock(int xblk, int yblk, void *buffer) override;
     208             : 
     209          25 :     GDALColorInterp GetColorInterpretation() override
     210             :     {
     211          25 :         return ci;
     212             :     }
     213             : 
     214         150 :     int GetOverviewCount() override
     215             :     {
     216         150 :         return static_cast<int>(overviews.size());
     217             :     }
     218             : 
     219         126 :     GDALRasterBand *GetOverview(int n) override
     220             :     {
     221         126 :         return (n >= 0 && n < GetOverviewCount()) ? overviews[n] : nullptr;
     222             :     }
     223             : 
     224             :   protected:
     225             :   private:
     226             :     int lvl{};
     227             :     GDALColorInterp ci{};
     228             : 
     229             :     // Image image;
     230             :     void AddOverviews();
     231             :     std::vector<ECBand *> overviews{};
     232             : };
     233             : 
     234          17 : ECDataset::ECDataset() : isV2(true), BSZ(128), TSZ(256)
     235             : {
     236          17 : }
     237             : 
     238           6 : CPLErr ECDataset::Initialize(CPLXMLNode *CacheInfo, bool ignoreOversizedLods)
     239             : {
     240           6 :     CPLErr error = CE_None;
     241             :     try
     242             :     {
     243           6 :         CPLXMLNode *CSI = CPLGetXMLNode(CacheInfo, "CacheStorageInfo");
     244           6 :         CPLXMLNode *TCI = CPLGetXMLNode(CacheInfo, "TileCacheInfo");
     245           6 :         if (!CSI || !TCI)
     246           0 :             throw CPLString("Error parsing cache configuration");
     247           6 :         auto format = CPLGetXMLValue(CSI, "StorageFormat", "");
     248           6 :         isV2 = EQUAL(format, "esriMapCacheStorageModeCompactV2");
     249           6 :         if (!isV2)
     250           0 :             throw CPLString("Not recognized as esri V2 bundled cache");
     251           6 :         if (BSZ != CPLAtof(CPLGetXMLValue(CSI, "PacketSize", "128")))
     252           0 :             throw CPLString("Only PacketSize of 128 is supported");
     253           6 :         TSZ = static_cast<int>(CPLAtof(CPLGetXMLValue(TCI, "TileCols", "256")));
     254           6 :         if (TSZ != CPLAtof(CPLGetXMLValue(TCI, "TileRows", "256")))
     255           0 :             throw CPLString("Non-square tiles are not supported");
     256           6 :         if (TSZ < 0 || TSZ > 8192)
     257           0 :             throw CPLString("Unsupported TileCols value");
     258             : 
     259           6 :         double minx = CPLAtof(CPLGetXMLValue(TCI, "TileOrigin.X", "-180"));
     260           6 :         double maxy = CPLAtof(CPLGetXMLValue(TCI, "TileOrigin.Y", "90"));
     261             :         // Assume symmetric coverage, check custom end
     262           6 :         double maxx = -minx;
     263           6 :         double miny = -maxy;
     264           6 :         const char *pszmaxx = CPLGetXMLValue(TCI, "TileEnd.X", nullptr);
     265           6 :         const char *pszminy = CPLGetXMLValue(TCI, "TileEnd.Y", nullptr);
     266           6 :         if (pszmaxx && pszminy)
     267             :         {
     268           6 :             maxx = CPLAtof(pszmaxx);
     269           6 :             miny = CPLAtof(pszminy);
     270             :         }
     271             : 
     272           6 :         CPLXMLNode *LODInfo = CPLGetXMLNode(TCI, "LODInfos.LODInfo");
     273           6 :         double res = 0;
     274          91 :         while (LODInfo)
     275             :         {
     276          87 :             res = CPLAtof(CPLGetXMLValue(LODInfo, "Resolution", "0"));
     277          87 :             if (!(res > 0))
     278           0 :                 throw CPLString("Can't parse resolution for LOD");
     279             : 
     280          87 :             double dxsz = (maxx - minx) / res;
     281          87 :             double dysz = (maxy - miny) / res;
     282             :             // Allow size just above INT32_MAX to handle FP rounding. Actual size is later clamped to INT32_MAX
     283          87 :             double maxRasterSize = static_cast<double>(INT32_MAX) + 2;
     284          87 :             if (dxsz < 1 || dxsz > maxRasterSize || dysz < 1 ||
     285             :                 dysz > maxRasterSize)
     286             :             {
     287           3 :                 if (ignoreOversizedLods)
     288             :                 {
     289           1 :                     CPLDebug(
     290             :                         "ESRIC",
     291             :                         "Skipping resolution %.10f: raster size exceeds the "
     292             :                         "GDAL limit",
     293             :                         res);
     294             :                 }
     295             :                 else
     296             :                 {
     297             :                     throw CPLString(
     298             :                         "Too many levels, resulting raster size exceeds "
     299             :                         "the GDAL limit. Open with IGNORE_OVERSIZED_LODS=YES "
     300           2 :                         "to ignore this");
     301             :                 }
     302             :             }
     303             :             else
     304             :             {
     305          84 :                 resolutions.push_back(res);
     306             :             }
     307             : 
     308          85 :             LODInfo = LODInfo->psNext;
     309             :         }
     310             : 
     311           4 :         sort(resolutions.begin(), resolutions.end());
     312           4 :         if (resolutions.empty())
     313           0 :             throw CPLString("Can't parse LODInfos");
     314             : 
     315             :         CPLString RawProj(
     316           8 :             CPLGetXMLValue(TCI, "SpatialReference.WKT", "EPSG:4326"));
     317           4 :         if (OGRERR_NONE != oSRS.SetFromUserInput(RawProj.c_str()))
     318           0 :             throw CPLString("Invalid Spatial Reference");
     319           4 :         oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     320             : 
     321             :         // resolution is the smallest figure
     322           4 :         res = resolutions[0];
     323           4 :         m_gt = GDALGeoTransform();
     324           4 :         m_gt.xorig = minx;
     325           4 :         m_gt.yorig = maxy;
     326           4 :         m_gt.xscale = res;
     327           4 :         m_gt.yscale = -res;
     328             : 
     329           4 :         double dxsz = (maxx - minx) / res;
     330           4 :         double dysz = (maxy - miny) / res;
     331             : 
     332           4 :         nRasterXSize = int(std::min(dxsz, double(INT32_MAX)));
     333           4 :         nRasterYSize = int(std::min(dysz, double(INT32_MAX)));
     334             : 
     335           4 :         SetMetadataItem(GDALMD_INTERLEAVE, "PIXEL", GDAL_MDD_IMAGE_STRUCTURE);
     336             :         compression =
     337           4 :             CPLGetXMLValue(CacheInfo, "TileImageInfo.CacheTileFormat", "JPEG");
     338           4 :         SetMetadataItem("COMPRESS", compression.c_str(),
     339           4 :                         GDAL_MDD_IMAGE_STRUCTURE);
     340             : 
     341           4 :         nBands = EQUAL(compression, "JPEG") ? 3 : 4;
     342          20 :         for (int i = 1; i <= nBands; i++)
     343             :         {
     344          16 :             ECBand *band = new ECBand(this, i);
     345          16 :             SetBand(i, band);
     346             :         }
     347             :         // Keep 4 bundle files open
     348           4 :         bundles.resize(4);
     349             :     }
     350           4 :     catch (CPLString &err)
     351             :     {
     352           2 :         error = CE_Failure;
     353           2 :         CPLError(error, CPLE_OpenFailed, "%s", err.c_str());
     354             :     }
     355           6 :     return error;
     356             : }
     357             : 
     358             : static std::unique_ptr<OGRSpatialReference>
     359          27 : CreateSRS(const CPLJSONObject &oSRSRoot)
     360             : {
     361          54 :     auto poSRS = std::make_unique<OGRSpatialReference>();
     362             : 
     363          27 :     bool bSuccess = false;
     364          27 :     const int nCode = oSRSRoot.GetInteger("wkid");
     365             :     // The concept of LatestWKID is explained in
     366             :     // https://support.esri.com/en/technical-article/000013950
     367          27 :     const int nLatestCode = oSRSRoot.GetInteger("latestWkid");
     368             : 
     369             :     // Try first with nLatestWKID as there is a higher chance it is a
     370             :     // EPSG code and not an ESRI one.
     371          27 :     if (nLatestCode > 0)
     372             :     {
     373          27 :         if (nLatestCode > 32767)
     374             :         {
     375           0 :             if (poSRS->SetFromUserInput(CPLSPrintf("ESRI:%d", nLatestCode)) ==
     376             :                 OGRERR_NONE)
     377             :             {
     378           0 :                 bSuccess = true;
     379             :             }
     380             :         }
     381          27 :         else if (poSRS->importFromEPSG(nLatestCode) == OGRERR_NONE)
     382             :         {
     383          27 :             bSuccess = true;
     384             :         }
     385             :     }
     386          27 :     if (!bSuccess && nCode > 0)
     387             :     {
     388           0 :         if (nCode > 32767)
     389             :         {
     390           0 :             if (poSRS->SetFromUserInput(CPLSPrintf("ESRI:%d", nCode)) ==
     391             :                 OGRERR_NONE)
     392             :             {
     393           0 :                 bSuccess = true;
     394             :             }
     395             :         }
     396           0 :         else if (poSRS->importFromEPSG(nCode) == OGRERR_NONE)
     397             :         {
     398           0 :             bSuccess = true;
     399             :         }
     400             :     }
     401          27 :     if (!bSuccess)
     402             :     {
     403           0 :         return nullptr;
     404             :     }
     405             : 
     406          27 :     poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     407          27 :     return poSRS;
     408             : }
     409             : 
     410          11 : CPLErr ECDataset::InitializeFromJSON(const CPLJSONObject &oRoot,
     411             :                                      bool ignoreOversizedLods)
     412             : {
     413          11 :     CPLErr error = CE_None;
     414             :     try
     415             :     {
     416          33 :         auto format = oRoot.GetString("storageInfo/storageFormat");
     417          11 :         isV2 = EQUAL(format.c_str(), "esriMapCacheStorageModeCompactV2");
     418          11 :         if (!isV2)
     419           0 :             throw CPLString("Not recognized as esri V2 bundled cache");
     420          11 :         if (BSZ != oRoot.GetInteger("storageInfo/packetSize"))
     421           0 :             throw CPLString("Only PacketSize of 128 is supported");
     422             : 
     423          11 :         TSZ = oRoot.GetInteger("tileInfo/rows");
     424          11 :         if (TSZ != oRoot.GetInteger("tileInfo/cols"))
     425           0 :             throw CPLString("Non-square tiles are not supported");
     426          11 :         if (TSZ < 0 || TSZ > 8192)
     427           0 :             throw CPLString("Unsupported tileInfo/rows value");
     428             : 
     429          11 :         double minx = oRoot.GetDouble("tileInfo/origin/x");
     430          11 :         double maxy = oRoot.GetDouble("tileInfo/origin/y");
     431             :         // Assume symmetric coverage
     432          11 :         double maxx = -minx;
     433          11 :         double miny = -maxy;
     434             : 
     435          33 :         const auto oLODs = oRoot.GetArray("tileInfo/lods");
     436          11 :         double res = 0;
     437             :         // we need to skip levels that don't have bundle files
     438          11 :         m_nMinLOD = oRoot.GetInteger("minLOD");
     439          11 :         if (m_nMinLOD < 0 || m_nMinLOD >= 31)
     440           0 :             throw CPLString("Invalid minLOD");
     441          11 :         const int maxLOD = std::min(oRoot.GetInteger("maxLOD"), 31);
     442         280 :         for (const auto &oLOD : oLODs)
     443             :         {
     444         267 :             const int level = oLOD.GetInteger("level");
     445         267 :             if (level < m_nMinLOD || level > maxLOD)
     446         149 :                 continue;
     447             : 
     448         118 :             res = oLOD.GetDouble("resolution");
     449         118 :             if (!(res > 0))
     450           0 :                 throw CPLString("Can't parse resolution for LOD");
     451             : 
     452         118 :             double dxsz = (maxx - minx) / res;
     453         118 :             double dysz = (maxy - miny) / res;
     454             :             // Allow size just above INT32_MAX to handle FP rounding. Actual size is later clamped to INT32_MAX
     455         118 :             double maxRasterSize = static_cast<double>(INT32_MAX) + 2;
     456         118 :             if (dxsz < 1 || dxsz > maxRasterSize || dysz < 1 ||
     457             :                 dysz > maxRasterSize)
     458             :             {
     459           3 :                 if (ignoreOversizedLods)
     460             :                 {
     461           1 :                     CPLDebug("ESRIC",
     462             :                              "Skipping LOD with resolution %.10f: raster size "
     463             :                              "exceeds the GDAL limit",
     464             :                              res);
     465           1 :                     continue;
     466             :                 }
     467             :                 else
     468             :                 {
     469             :                     throw CPLString(
     470             :                         "Too many levels, resulting raster size exceeds "
     471             :                         "the GDAL limit. Open with IGNORE_OVERSIZED_LODS=YES "
     472           2 :                         "to ignore this");
     473             :                 }
     474             :             }
     475             : 
     476         115 :             resolutions.push_back(res);
     477             :         }
     478           9 :         sort(resolutions.begin(), resolutions.end());
     479           9 :         if (resolutions.empty())
     480           0 :             throw CPLString("Can't parse lods");
     481             : 
     482             :         {
     483          27 :             auto poSRS = CreateSRS(oRoot.GetObj("spatialReference"));
     484           9 :             if (!poSRS)
     485             :             {
     486           0 :                 throw CPLString("Invalid Spatial Reference");
     487             :             }
     488           9 :             oSRS = std::move(*poSRS);
     489             :         }
     490             : 
     491             :         // resolution is the smallest figure
     492           9 :         res = resolutions[0];
     493           9 :         m_gt = GDALGeoTransform();
     494           9 :         m_gt.xorig = minx;
     495           9 :         m_gt.yorig = maxy;
     496           9 :         m_gt.xscale = res;
     497           9 :         m_gt.yscale = -res;
     498             : 
     499           9 :         double dxsz = (maxx - minx) / res;
     500           9 :         double dysz = (maxy - miny) / res;
     501             : 
     502           9 :         nRasterXSize = int(std::min(dxsz, double(INT32_MAX)));
     503           9 :         nRasterYSize = int(std::min(dysz, double(INT32_MAX)));
     504             : 
     505           9 :         SetMetadataItem(GDALMD_INTERLEAVE, "PIXEL", GDAL_MDD_IMAGE_STRUCTURE);
     506           9 :         compression = oRoot.GetString("tileImageInfo/format");
     507           9 :         SetMetadataItem("COMPRESS", compression.c_str(),
     508           9 :                         GDAL_MDD_IMAGE_STRUCTURE);
     509             : 
     510          27 :         auto oInitialExtent = oRoot.GetObj("initialExtent");
     511          18 :         if (oInitialExtent.IsValid() &&
     512           9 :             oInitialExtent.GetType() == CPLJSONObject::Type::Object)
     513             :         {
     514           9 :             m_sInitialExtent.MinX = oInitialExtent.GetDouble("xmin");
     515           9 :             m_sInitialExtent.MinY = oInitialExtent.GetDouble("ymin");
     516           9 :             m_sInitialExtent.MaxX = oInitialExtent.GetDouble("xmax");
     517           9 :             m_sInitialExtent.MaxY = oInitialExtent.GetDouble("ymax");
     518          27 :             auto oSRSRoot = oInitialExtent.GetObj("spatialReference");
     519           9 :             if (oSRSRoot.IsValid())
     520             :             {
     521          18 :                 auto poSRS = CreateSRS(oSRSRoot);
     522           9 :                 if (!poSRS)
     523             :                 {
     524             :                     throw CPLString(
     525           0 :                         "Invalid Spatial Reference in initialExtent");
     526             :                 }
     527           9 :                 if (!poSRS->IsSame(&oSRS))
     528             :                 {
     529           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
     530             :                              "Ignoring initialExtent, because its SRS is "
     531             :                              "different from the main one");
     532           0 :                     m_sInitialExtent = OGREnvelope();
     533             :                 }
     534             :             }
     535             :         }
     536             : 
     537          27 :         auto oFullExtent = oRoot.GetObj("fullExtent");
     538          18 :         if (oFullExtent.IsValid() &&
     539           9 :             oFullExtent.GetType() == CPLJSONObject::Type::Object)
     540             :         {
     541           9 :             m_sFullExtent.MinX = oFullExtent.GetDouble("xmin");
     542           9 :             m_sFullExtent.MinY = oFullExtent.GetDouble("ymin");
     543           9 :             m_sFullExtent.MaxX = oFullExtent.GetDouble("xmax");
     544           9 :             m_sFullExtent.MaxY = oFullExtent.GetDouble("ymax");
     545          27 :             auto oSRSRoot = oFullExtent.GetObj("spatialReference");
     546           9 :             if (oSRSRoot.IsValid())
     547             :             {
     548          18 :                 auto poSRS = CreateSRS(oSRSRoot);
     549           9 :                 if (!poSRS)
     550             :                 {
     551           0 :                     throw CPLString("Invalid Spatial Reference in fullExtent");
     552             :                 }
     553           9 :                 if (!poSRS->IsSame(&oSRS))
     554             :                 {
     555           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
     556             :                              "Ignoring fullExtent, because its SRS is "
     557             :                              "different from the main one");
     558           0 :                     m_sFullExtent = OGREnvelope();
     559             :                 }
     560             :             }
     561             :         }
     562             : 
     563           9 :         nBands = EQUAL(compression, "JPEG") ? 3 : 4;
     564          45 :         for (int i = 1; i <= nBands; i++)
     565             :         {
     566          36 :             ECBand *band = new ECBand(this, i);
     567          36 :             SetBand(i, band);
     568             :         }
     569             :         // Keep 4 bundle files open
     570           9 :         bundles.resize(4);
     571             :     }
     572           4 :     catch (CPLString &err)
     573             :     {
     574           2 :         error = CE_Failure;
     575           2 :         CPLError(error, CPLE_OpenFailed, "%s", err.c_str());
     576             :     }
     577          11 :     return error;
     578             : }
     579             : 
     580             : class ESRICProxyRasterBand final : public GDALProxyRasterBand
     581             : {
     582             :   private:
     583             :     GDALRasterBand *m_poUnderlyingBand = nullptr;
     584             : 
     585             :     CPL_DISALLOW_COPY_ASSIGN(ESRICProxyRasterBand)
     586             : 
     587             :   protected:
     588             :     GDALRasterBand *RefUnderlyingRasterBand(bool /*bForceOpen*/) const override;
     589             : 
     590             :   public:
     591          20 :     explicit ESRICProxyRasterBand(GDALRasterBand *poUnderlyingBand)
     592          20 :         : m_poUnderlyingBand(poUnderlyingBand)
     593             :     {
     594          20 :         nBand = poUnderlyingBand->GetBand();
     595          20 :         eDataType = poUnderlyingBand->GetRasterDataType();
     596          20 :         nRasterXSize = poUnderlyingBand->GetXSize();
     597          20 :         nRasterYSize = poUnderlyingBand->GetYSize();
     598          20 :         poUnderlyingBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
     599          20 :     }
     600             : };
     601             : 
     602             : GDALRasterBand *
     603          28 : ESRICProxyRasterBand::RefUnderlyingRasterBand(bool /*bForceOpen*/) const
     604             : {
     605          28 :     return m_poUnderlyingBand;
     606             : }
     607             : 
     608             : class ESRICProxyDataset final : public GDALProxyDataset
     609             : {
     610             :   private:
     611             :     // m_poSrcDS must be placed before m_poUnderlyingDS for proper destruction
     612             :     // as m_poUnderlyingDS references m_poSrcDS
     613             :     std::unique_ptr<GDALDataset> m_poSrcDS{};
     614             :     std::unique_ptr<GDALDataset> m_poUnderlyingDS{};
     615             :     CPLStringList m_aosFileList{};
     616             : 
     617             :   protected:
     618             :     GDALDataset *RefUnderlyingDataset() const override;
     619             : 
     620             :   public:
     621           5 :     ESRICProxyDataset(GDALDataset *poSrcDS, GDALDataset *poUnderlyingDS,
     622             :                       const char *pszDescription)
     623           5 :         : m_poSrcDS(poSrcDS), m_poUnderlyingDS(poUnderlyingDS)
     624             :     {
     625           5 :         nRasterXSize = poUnderlyingDS->GetRasterXSize();
     626           5 :         nRasterYSize = poUnderlyingDS->GetRasterYSize();
     627          25 :         for (int i = 0; i < poUnderlyingDS->GetRasterCount(); ++i)
     628          20 :             SetBand(i + 1, new ESRICProxyRasterBand(
     629          20 :                                poUnderlyingDS->GetRasterBand(i + 1)));
     630           5 :         m_aosFileList.AddString(pszDescription);
     631           5 :     }
     632             : 
     633           3 :     GDALDriver *GetDriver() const override
     634             :     {
     635           3 :         return GDALDriver::FromHandle(GDALGetDriverByName("ESRIC"));
     636             :     }
     637             : 
     638           3 :     char **GetFileList() override
     639             :     {
     640           3 :         return CSLDuplicate(m_aosFileList.List());
     641             :     }
     642             : };
     643             : 
     644          12 : GDALDataset *ESRICProxyDataset::RefUnderlyingDataset() const
     645             : {
     646          12 :     return m_poUnderlyingDS.get();
     647             : }
     648             : 
     649          17 : GDALDataset *ECDataset::Open(GDALOpenInfo *poOpenInfo)
     650             : {
     651          17 :     return Open(poOpenInfo, poOpenInfo->pszFilename);
     652             : }
     653             : 
     654          24 : GDALDataset *ECDataset::Open(GDALOpenInfo *poOpenInfo,
     655             :                              const char *pszDescription)
     656             : {
     657          24 :     bool ignoreOversizedLods = CPL_TO_BOOL(CSLFetchBoolean(
     658          24 :         poOpenInfo->papszOpenOptions, "IGNORE_OVERSIZED_LODS", FALSE));
     659          24 :     if (IdentifyXML(poOpenInfo))
     660             :     {
     661           6 :         CPLXMLNode *config = CPLParseXMLFile(poOpenInfo->pszFilename);
     662           6 :         if (!config)  // Error was reported from parsing XML
     663           0 :             return nullptr;
     664           6 :         CPLXMLNode *CacheInfo = CPLGetXMLNode(config, "=CacheInfo");
     665           6 :         if (!CacheInfo)
     666             :         {
     667           0 :             CPLError(
     668             :                 CE_Warning, CPLE_OpenFailed,
     669             :                 "Error parsing configuration, can't find CacheInfo element");
     670           0 :             CPLDestroyXMLNode(config);
     671           0 :             return nullptr;
     672             :         }
     673           6 :         auto ds = new ECDataset();
     674           6 :         ds->dname = CPLGetDirnameSafe(poOpenInfo->pszFilename) + "/_alllayers";
     675           6 :         CPLErr error = ds->Initialize(CacheInfo, ignoreOversizedLods);
     676           6 :         CPLDestroyXMLNode(config);
     677           6 :         if (CE_None != error)
     678             :         {
     679           2 :             delete ds;
     680           2 :             ds = nullptr;
     681             :         }
     682           6 :         return ds;
     683             :     }
     684          18 :     else if (IdentifyJSON(poOpenInfo))
     685             :     {
     686             :         // Recognize .tpkx file directly passed
     687          18 :         if (!STARTS_WITH(poOpenInfo->pszFilename, "/vsizip/") &&
     688             : #if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
     689          11 :             ENDS_WITH_CI(poOpenInfo->pszFilename, ".tpkx") &&
     690             : #endif
     691           7 :             memcmp(poOpenInfo->pabyHeader, "PK\x03\x04", 4) == 0)
     692             :         {
     693          14 :             GDALOpenInfo oOpenInfo((std::string("/vsizip/{") +
     694          21 :                                     poOpenInfo->pszFilename + "}/root.json")
     695             :                                        .c_str(),
     696          14 :                                    GA_ReadOnly);
     697           7 :             oOpenInfo.papszOpenOptions = poOpenInfo->papszOpenOptions;
     698           7 :             return Open(&oOpenInfo, pszDescription);
     699             :         }
     700             : 
     701          22 :         CPLJSONDocument oJSONDocument;
     702          11 :         if (!oJSONDocument.Load(poOpenInfo->pszFilename))
     703             :         {
     704           0 :             CPLError(CE_Warning, CPLE_OpenFailed,
     705             :                      "Error parsing configuration");
     706           0 :             return nullptr;
     707             :         }
     708             : 
     709          22 :         const CPLJSONObject &oRoot = oJSONDocument.GetRoot();
     710          11 :         if (!oRoot.IsValid())
     711             :         {
     712           0 :             CPLError(CE_Warning, CPLE_OpenFailed, "Invalid json document root");
     713           0 :             return nullptr;
     714             :         }
     715             : 
     716          22 :         auto ds = std::make_unique<ECDataset>();
     717          33 :         auto tileBundlesPath = oRoot.GetString("tileBundlesPath");
     718             :         // Strip leading relative path indicator (if present)
     719          11 :         if (tileBundlesPath.substr(0, 2) == "./")
     720             :         {
     721          11 :             tileBundlesPath.erase(0, 2);
     722             :         }
     723             : 
     724          11 :         ds->dname.Printf("%s/%s",
     725          22 :                          CPLGetDirnameSafe(poOpenInfo->pszFilename).c_str(),
     726          22 :                          tileBundlesPath.c_str());
     727          11 :         CPLErr error = ds->InitializeFromJSON(oRoot, ignoreOversizedLods);
     728          11 :         if (CE_None != error)
     729             :         {
     730           2 :             return nullptr;
     731             :         }
     732             : 
     733             :         const bool bIsFullExtentValid =
     734           9 :             (ds->m_sFullExtent.IsInit() &&
     735          17 :              ds->m_sFullExtent.MinX < ds->m_sFullExtent.MaxX &&
     736           8 :              ds->m_sFullExtent.MinY < ds->m_sFullExtent.MaxY);
     737             :         const char *pszExtentSource =
     738           9 :             CSLFetchNameValue(poOpenInfo->papszOpenOptions, "EXTENT_SOURCE");
     739             : 
     740          18 :         CPLStringList aosOptions;
     741           9 :         if ((!pszExtentSource && bIsFullExtentValid) ||
     742           5 :             (pszExtentSource && EQUAL(pszExtentSource, "FULL_EXTENT")))
     743             :         {
     744           4 :             if (!bIsFullExtentValid)
     745             :             {
     746           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     747             :                          "fullExtent is not valid");
     748           0 :                 return nullptr;
     749             :             }
     750           4 :             aosOptions.AddString("-projwin");
     751           4 :             aosOptions.AddString(CPLSPrintf("%.17g", ds->m_sFullExtent.MinX));
     752           4 :             aosOptions.AddString(CPLSPrintf("%.17g", ds->m_sFullExtent.MaxY));
     753           4 :             aosOptions.AddString(CPLSPrintf("%.17g", ds->m_sFullExtent.MaxX));
     754           4 :             aosOptions.AddString(CPLSPrintf("%.17g", ds->m_sFullExtent.MinY));
     755             :         }
     756           5 :         else if (pszExtentSource && EQUAL(pszExtentSource, "INITIAL_EXTENT"))
     757             :         {
     758             :             const bool bIsInitialExtentValid =
     759           1 :                 (ds->m_sInitialExtent.IsInit() &&
     760           2 :                  ds->m_sInitialExtent.MinX < ds->m_sInitialExtent.MaxX &&
     761           1 :                  ds->m_sInitialExtent.MinY < ds->m_sInitialExtent.MaxY);
     762           1 :             if (!bIsInitialExtentValid)
     763             :             {
     764           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     765             :                          "initialExtent is not valid");
     766           0 :                 return nullptr;
     767             :             }
     768           1 :             aosOptions.AddString("-projwin");
     769             :             aosOptions.AddString(
     770           1 :                 CPLSPrintf("%.17g", ds->m_sInitialExtent.MinX));
     771             :             aosOptions.AddString(
     772           1 :                 CPLSPrintf("%.17g", ds->m_sInitialExtent.MaxY));
     773             :             aosOptions.AddString(
     774           1 :                 CPLSPrintf("%.17g", ds->m_sInitialExtent.MaxX));
     775             :             aosOptions.AddString(
     776           1 :                 CPLSPrintf("%.17g", ds->m_sInitialExtent.MinY));
     777             :         }
     778             : 
     779           9 :         if (!aosOptions.empty())
     780             :         {
     781           5 :             aosOptions.AddString("-of");
     782           5 :             aosOptions.AddString("VRT");
     783           5 :             aosOptions.AddString("-co");
     784           5 :             aosOptions.AddString(CPLSPrintf("BLOCKXSIZE=%d", ds->TSZ));
     785           5 :             aosOptions.AddString("-co");
     786           5 :             aosOptions.AddString(CPLSPrintf("BLOCKYSIZE=%d", ds->TSZ));
     787             :             auto psOptions =
     788           5 :                 GDALTranslateOptionsNew(aosOptions.List(), nullptr);
     789           5 :             auto hDS = GDALTranslate("", GDALDataset::ToHandle(ds.get()),
     790             :                                      psOptions, nullptr);
     791           5 :             GDALTranslateOptionsFree(psOptions);
     792           5 :             if (!hDS)
     793             :             {
     794           0 :                 return nullptr;
     795             :             }
     796             :             return new ESRICProxyDataset(
     797           5 :                 ds.release(), GDALDataset::FromHandle(hDS), pszDescription);
     798             :         }
     799           4 :         return ds.release();
     800             :     }
     801           0 :     return nullptr;
     802             : }
     803             : 
     804             : // Fetch a reference to an initialized bundle, based on file name
     805             : // The returned bundle could still have an invalid file handle, if the
     806             : // target bundle is not valid
     807        4229 : Bundle &ECDataset::GetBundle(const char *fname)
     808             : {
     809        4261 :     for (auto &bundle : bundles)
     810             :     {
     811             :         // If a bundle is missing, it still occupies a slot, with fh == nullptr
     812        4253 :         if (EQUAL(bundle.name.c_str(), fname))
     813        4221 :             return bundle;
     814             :     }
     815             :     // Not found, look for an empty // missing slot
     816           8 :     for (auto &bundle : bundles)
     817             :     {
     818           8 :         if (nullptr == bundle.fh)
     819             :         {
     820           8 :             bundle.Init(fname);
     821           8 :             return bundle;
     822             :         }
     823             :     }
     824             :     // No empties, eject one
     825             :     Bundle &bundle = bundles[
     826             : #ifndef __COVERITY__
     827           0 :         rand() % bundles.size()
     828             : #else
     829             :         0
     830             : #endif
     831           0 :     ];
     832           0 :     bundle.Init(fname);
     833           0 :     return bundle;
     834             : }
     835             : 
     836         824 : ECBand::~ECBand()
     837             : {
     838         772 :     for (auto ovr : overviews)
     839         360 :         if (ovr)
     840         360 :             delete ovr;
     841         412 :     overviews.clear();
     842         824 : }
     843             : 
     844         412 : ECBand::ECBand(ECDataset *parent, int b, int level)
     845         412 :     : lvl(level), ci(GCI_Undefined)
     846             : {
     847             :     static const GDALColorInterp rgba[4] = {GCI_RedBand, GCI_GreenBand,
     848             :                                             GCI_BlueBand, GCI_AlphaBand};
     849             :     static const GDALColorInterp la[2] = {GCI_GrayIndex, GCI_AlphaBand};
     850         412 :     poDS = parent;
     851         412 :     nBand = b;
     852             : 
     853         412 :     double factor = parent->resolutions[0] / parent->resolutions[lvl];
     854         412 :     nRasterXSize = static_cast<int>(parent->nRasterXSize * factor + 0.5);
     855         412 :     nRasterYSize = static_cast<int>(parent->nRasterYSize * factor + 0.5);
     856         412 :     nBlockXSize = nBlockYSize = parent->TSZ;
     857             : 
     858             :     // Default color interpretation
     859         412 :     assert(b - 1 >= 0);
     860         412 :     if (parent->nBands >= 3)
     861             :     {
     862         412 :         assert(b - 1 < static_cast<int>(CPL_ARRAYSIZE(rgba)));
     863         412 :         ci = rgba[b - 1];
     864             :     }
     865             :     else
     866             :     {
     867           0 :         assert(b - 1 < static_cast<int>(CPL_ARRAYSIZE(la)));
     868           0 :         ci = la[b - 1];
     869             :     }
     870         412 :     if (0 == lvl)
     871          52 :         AddOverviews();
     872         412 : }
     873             : 
     874          52 : void ECBand::AddOverviews()
     875             : {
     876          52 :     auto parent = cpl::down_cast<ECDataset *>(poDS);
     877         412 :     for (size_t i = 1; i < parent->resolutions.size(); i++)
     878             :     {
     879         360 :         ECBand *ovl = new ECBand(parent, nBand, int(i));
     880         360 :         if (!ovl)
     881           0 :             break;
     882         360 :         overviews.push_back(ovl);
     883             :     }
     884          52 : }
     885             : 
     886        4229 : CPLErr ECBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pData)
     887             : {
     888        4229 :     auto parent = cpl::down_cast<ECDataset *>(poDS);
     889        4229 :     auto &buffer = parent->tilebuffer;
     890        4229 :     auto TSZ = parent->TSZ;
     891        4229 :     auto BSZ = parent->BSZ;
     892        4229 :     size_t nBytes = size_t(TSZ) * TSZ;
     893             : 
     894        4229 :     buffer.resize(nBytes * parent->nBands);
     895             : 
     896        8458 :     const int lxx = parent->m_nMinLOD +
     897        4229 :                     static_cast<int>(parent->resolutions.size() - lvl - 1);
     898             :     int bx, by;
     899        4229 :     bx = (nBlockXOff / BSZ) * BSZ;
     900        4229 :     by = (nBlockYOff / BSZ) * BSZ;
     901        8458 :     CPLString fname;
     902        8458 :     fname = CPLString().Printf("%s/L%02d/R%04xC%04x.bundle",
     903        4229 :                                parent->dname.c_str(), lxx, by, bx);
     904        4229 :     Bundle &bundle = parent->GetBundle(fname);
     905        4229 :     if (nullptr == bundle.fh)
     906             :     {  // This is not an error in general, bundles can be missing
     907          64 :         CPLDebug("ESRIC", "Can't open bundle %s", fname.c_str());
     908          64 :         memset(pData, 0, nBytes);
     909          64 :         return CE_None;
     910             :     }
     911        4165 :     int block = static_cast<int>((nBlockYOff % BSZ) * BSZ + (nBlockXOff % BSZ));
     912        4165 :     GUInt64 offset = bundle.index[block] & 0xffffffffffull;
     913        4165 :     GUInt64 size = bundle.index[block] >> 40;
     914        4165 :     if (0 == size)
     915             :     {
     916        3755 :         memset(pData, 0, nBytes);
     917        3755 :         return CE_None;
     918             :     }
     919         410 :     auto &fbuffer = parent->filebuffer;
     920         410 :     fbuffer.resize(size_t(size));
     921         410 :     bundle.fh->Seek(offset, SEEK_SET);
     922         410 :     if (size != bundle.fh->Read(fbuffer.data(), size_t(1), size_t(size)))
     923             :     {
     924           0 :         CPLError(CE_Failure, CPLE_FileIO,
     925             :                  "Error reading tile, reading " CPL_FRMT_GUIB
     926             :                  " at " CPL_FRMT_GUIB,
     927             :                  GUInt64(size), GUInt64(offset));
     928           0 :         return CE_Failure;
     929             :     }
     930         820 :     const CPLString magic(VSIMemGenerateHiddenFilename("esric.tmp"));
     931         410 :     auto mfh = VSIFileFromMemBuffer(magic.c_str(), fbuffer.data(), size, false);
     932         410 :     VSIFCloseL(mfh);
     933             :     // Can't open a raster by handle?
     934             :     auto inds = std::unique_ptr<GDALDataset>(GDALDataset::Open(
     935         820 :         magic.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
     936         410 :     if (!inds)
     937             :     {
     938           0 :         VSIUnlink(magic.c_str());
     939           0 :         CPLError(CE_Failure, CPLE_FileIO, "Error opening tile");
     940           0 :         return CE_Failure;
     941             :     }
     942             :     // Duplicate first band if not sufficient bands are provided
     943         410 :     auto inbands = inds->GetRasterCount();
     944         410 :     int ubands[4] = {1, 1, 1, 1};
     945         410 :     int *usebands = nullptr;
     946         410 :     int bandcount = parent->nBands;
     947         410 :     GDALColorTableH hCT = nullptr;
     948         410 :     if (inbands != bandcount)
     949             :     {
     950             :         // Opaque if output expects alpha channel
     951         259 :         if (0 == bandcount % 2)
     952             :         {
     953         259 :             fill(buffer.begin(), buffer.end(), GByte(255));
     954         259 :             bandcount--;
     955             :         }
     956         259 :         if (3 == inbands)
     957             :         {
     958             :             // Lacking opacity, copy the first three bands
     959           8 :             ubands[1] = 2;
     960           8 :             ubands[2] = 3;
     961           8 :             usebands = ubands;
     962             :         }
     963         251 :         else if (1 == inbands)
     964             :         {
     965             :             // Grayscale, expecting color
     966         251 :             usebands = ubands;
     967             :             // Check for the color table of 1 band rasters
     968         251 :             hCT = GDALGetRasterColorTable(
     969             :                 GDALGetRasterBand(GDALDataset::ToHandle(inds.get()), 1));
     970             :         }
     971             :     }
     972             : 
     973         410 :     auto errcode = CE_None;
     974         410 :     if (nullptr != hCT)
     975             :     {
     976             :         // Expand color indexed to RGB(A)
     977         250 :         errcode = GDALDatasetRasterIO(GDALDataset::ToHandle(inds.get()),
     978         250 :                                       GF_Read, 0, 0, TSZ, TSZ, buffer.data(),
     979             :                                       TSZ, TSZ, GDT_UInt8, 1, usebands,
     980         250 :                                       parent->nBands, parent->nBands * TSZ, 1);
     981         250 :         if (CE_None == errcode)
     982             :         {
     983             :             GByte abyCT[4 * 256];
     984         250 :             GByte *pabyTileData = buffer.data();
     985         250 :             const int nEntries = std::min(256, GDALGetColorEntryCount(hCT));
     986        2148 :             for (int i = 0; i < nEntries; i++)
     987             :             {
     988        1898 :                 const GDALColorEntry *psEntry = GDALGetColorEntry(hCT, i);
     989        1898 :                 abyCT[4 * i] = static_cast<GByte>(psEntry->c1);
     990        1898 :                 abyCT[4 * i + 1] = static_cast<GByte>(psEntry->c2);
     991        1898 :                 abyCT[4 * i + 2] = static_cast<GByte>(psEntry->c3);
     992        1898 :                 abyCT[4 * i + 3] = static_cast<GByte>(psEntry->c4);
     993             :             }
     994       62352 :             for (int i = nEntries; i < 256; i++)
     995             :             {
     996       62102 :                 abyCT[4 * i] = 0;
     997       62102 :                 abyCT[4 * i + 1] = 0;
     998       62102 :                 abyCT[4 * i + 2] = 0;
     999       62102 :                 abyCT[4 * i + 3] = 0;
    1000             :             }
    1001             : 
    1002         250 :             if (parent->nBands == 4)
    1003             :             {
    1004    16384200 :                 for (size_t i = 0; i < nBytes; i++)
    1005             :                 {
    1006    16384000 :                     const GByte byVal = pabyTileData[4 * i];
    1007    16384000 :                     pabyTileData[4 * i] = abyCT[4 * byVal];
    1008    16384000 :                     pabyTileData[4 * i + 1] = abyCT[4 * byVal + 1];
    1009    16384000 :                     pabyTileData[4 * i + 2] = abyCT[4 * byVal + 2];
    1010    16384000 :                     pabyTileData[4 * i + 3] = abyCT[4 * byVal + 3];
    1011             :                 }
    1012             :             }
    1013           0 :             else if (parent->nBands == 3)
    1014             :             {
    1015           0 :                 for (size_t i = 0; i < nBytes; i++)
    1016             :                 {
    1017           0 :                     const GByte byVal = pabyTileData[3 * i];
    1018           0 :                     pabyTileData[3 * i] = abyCT[4 * byVal];
    1019           0 :                     pabyTileData[3 * i + 1] = abyCT[4 * byVal + 1];
    1020           0 :                     pabyTileData[3 * i + 2] = abyCT[4 * byVal + 2];
    1021             :                 }
    1022             :             }
    1023             :             else
    1024             :             {
    1025             :                 // Assuming grayscale output
    1026           0 :                 for (size_t i = 0; i < nBytes; i++)
    1027             :                 {
    1028           0 :                     const GByte byVal = pabyTileData[i];
    1029           0 :                     pabyTileData[i] = abyCT[4 * byVal];
    1030             :                 }
    1031             :             }
    1032             :         }
    1033             :     }
    1034             :     else
    1035             :     {
    1036         160 :         errcode = GDALDatasetRasterIO(GDALDataset::ToHandle(inds.get()),
    1037         160 :                                       GF_Read, 0, 0, TSZ, TSZ, buffer.data(),
    1038             :                                       TSZ, TSZ, GDT_UInt8, bandcount, usebands,
    1039         160 :                                       parent->nBands, parent->nBands * TSZ, 1);
    1040             :     }
    1041         410 :     inds.reset();
    1042         410 :     VSIUnlink(magic.c_str());
    1043             :     // Error while unpacking tile
    1044         410 :     if (CE_None != errcode)
    1045           0 :         return errcode;
    1046             : 
    1047        2050 :     for (int iBand = 1; iBand <= parent->nBands; iBand++)
    1048             :     {
    1049        1640 :         auto band = parent->GetRasterBand(iBand);
    1050        1640 :         if (lvl)
    1051          52 :             band = band->GetOverview(lvl - 1);
    1052        1640 :         GDALRasterBlock *poBlock = nullptr;
    1053        1640 :         if (band != this)
    1054             :         {
    1055        1230 :             poBlock = band->GetLockedBlockRef(nBlockXOff, nBlockYOff, 1);
    1056        1230 :             if (poBlock != nullptr)
    1057             :             {
    1058        1230 :                 GDALCopyWords(buffer.data() + iBand - 1, GDT_UInt8,
    1059             :                               parent->nBands, poBlock->GetDataRef(), GDT_UInt8,
    1060             :                               1, TSZ * TSZ);
    1061        1230 :                 poBlock->DropLock();
    1062             :             }
    1063             :         }
    1064             :         else
    1065             :         {
    1066         410 :             GDALCopyWords(buffer.data() + iBand - 1, GDT_UInt8, parent->nBands,
    1067             :                           pData, GDT_UInt8, 1, TSZ * TSZ);
    1068             :         }
    1069             :     }
    1070             : 
    1071         410 :     return CE_None;
    1072             : }  // IReadBlock
    1073             : 
    1074             : }  // namespace ESRIC
    1075             : 
    1076        2135 : void CPL_DLL GDALRegister_ESRIC()
    1077             : {
    1078        2135 :     if (GDALGetDriverByName("ESRIC") != nullptr)
    1079         263 :         return;
    1080             : 
    1081        1872 :     auto poDriver = new GDALDriver;
    1082             : 
    1083        1872 :     poDriver->SetDescription("ESRIC");
    1084        1872 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    1085        1872 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    1086        1872 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Esri Compact Cache");
    1087             : 
    1088        1872 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "json tpkx");
    1089             : 
    1090        1872 :     poDriver->SetMetadataItem(
    1091             :         GDAL_DMD_OPENOPTIONLIST,
    1092             :         "<OpenOptionList>"
    1093             :         "  <Option name='EXTENT_SOURCE' type='string-select' "
    1094             :         "description='Which source is used to determine the extent' "
    1095             :         "default='FULL_EXTENT'>"
    1096             :         "    <Value>FULL_EXTENT</Value>"
    1097             :         "    <Value>INITIAL_EXTENT</Value>"
    1098             :         "    <Value>TILING_SCHEME</Value>"
    1099             :         "  </Option>"
    1100             :         "  <Option name='IGNORE_OVERSIZED_LODS' type='boolean' "
    1101             :         "description='Whether to silently ignore LODs that exceed the "
    1102             :         "maximum size supported by GDAL (INT32_MAX)' "
    1103             :         "default='NO'>"
    1104             :         "  </Option>"
    1105        1872 :         "</OpenOptionList>");
    1106        1872 :     poDriver->pfnIdentify = ESRIC::Identify;
    1107        1872 :     poDriver->pfnOpen = ESRIC::ECDataset::Open;
    1108        1872 :     poDriver->pfnDelete = ESRIC::Delete;
    1109             : 
    1110        1872 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1111             : }

Generated by: LCOV version 1.14