LCOV - code coverage report
Current view: top level - frmts/esric - esric_dataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 463 534 86.7 %
Date: 2026-01-23 20:24:11 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       66777 : static int IdentifyJSON(GDALOpenInfo *poOpenInfo)
      39             : {
      40       66777 :     if (poOpenInfo->eAccess != GA_ReadOnly || poOpenInfo->nHeaderBytes < 512)
      41       59946 :         return false;
      42             : 
      43             :     // Recognize .tpkx file directly passed
      44        6831 :     if (!STARTS_WITH(poOpenInfo->pszFilename, "/vsizip/") &&
      45             : #if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
      46        6813 :         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        6817 :     if (!ENDS_WITH_CI(poOpenInfo->pszFilename, "root.json"))
      55        6802 :         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       66789 : static int IdentifyXML(GDALOpenInfo *poOpenInfo)
      84             : {
      85       66789 :     if (poOpenInfo->eAccess != GA_ReadOnly
      86             : #if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
      87       66283 :         || !ENDS_WITH_CI(poOpenInfo->pszFilename, "conf.xml")
      88             : #endif
      89          12 :         || poOpenInfo->nHeaderBytes < 512)
      90       66777 :         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       66765 : static int Identify(GDALOpenInfo *poOpenInfo)
      97             : {
      98       66765 :     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[0] = minx;
     325           4 :         m_gt[3] = maxy;
     326           4 :         m_gt[1] = res;
     327           4 :         m_gt[5] = -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("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
     336             :         compression =
     337           4 :             CPLGetXMLValue(CacheInfo, "TileImageInfo.CacheTileFormat", "JPEG");
     338           4 :         SetMetadataItem("COMPRESS", compression.c_str(), "IMAGE_STRUCTURE");
     339             : 
     340           4 :         nBands = EQUAL(compression, "JPEG") ? 3 : 4;
     341          20 :         for (int i = 1; i <= nBands; i++)
     342             :         {
     343          16 :             ECBand *band = new ECBand(this, i);
     344          16 :             SetBand(i, band);
     345             :         }
     346             :         // Keep 4 bundle files open
     347           4 :         bundles.resize(4);
     348             :     }
     349           4 :     catch (CPLString &err)
     350             :     {
     351           2 :         error = CE_Failure;
     352           2 :         CPLError(error, CPLE_OpenFailed, "%s", err.c_str());
     353             :     }
     354           6 :     return error;
     355             : }
     356             : 
     357             : static std::unique_ptr<OGRSpatialReference>
     358          27 : CreateSRS(const CPLJSONObject &oSRSRoot)
     359             : {
     360          54 :     auto poSRS = std::make_unique<OGRSpatialReference>();
     361             : 
     362          27 :     bool bSuccess = false;
     363          27 :     const int nCode = oSRSRoot.GetInteger("wkid");
     364             :     // The concept of LatestWKID is explained in
     365             :     // https://support.esri.com/en/technical-article/000013950
     366          27 :     const int nLatestCode = oSRSRoot.GetInteger("latestWkid");
     367             : 
     368             :     // Try first with nLatestWKID as there is a higher chance it is a
     369             :     // EPSG code and not an ESRI one.
     370          27 :     if (nLatestCode > 0)
     371             :     {
     372          27 :         if (nLatestCode > 32767)
     373             :         {
     374           0 :             if (poSRS->SetFromUserInput(CPLSPrintf("ESRI:%d", nLatestCode)) ==
     375             :                 OGRERR_NONE)
     376             :             {
     377           0 :                 bSuccess = true;
     378             :             }
     379             :         }
     380          27 :         else if (poSRS->importFromEPSG(nLatestCode) == OGRERR_NONE)
     381             :         {
     382          27 :             bSuccess = true;
     383             :         }
     384             :     }
     385          27 :     if (!bSuccess && nCode > 0)
     386             :     {
     387           0 :         if (nCode > 32767)
     388             :         {
     389           0 :             if (poSRS->SetFromUserInput(CPLSPrintf("ESRI:%d", nCode)) ==
     390             :                 OGRERR_NONE)
     391             :             {
     392           0 :                 bSuccess = true;
     393             :             }
     394             :         }
     395           0 :         else if (poSRS->importFromEPSG(nCode) == OGRERR_NONE)
     396             :         {
     397           0 :             bSuccess = true;
     398             :         }
     399             :     }
     400          27 :     if (!bSuccess)
     401             :     {
     402           0 :         return nullptr;
     403             :     }
     404             : 
     405          27 :     poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     406          27 :     return poSRS;
     407             : }
     408             : 
     409          11 : CPLErr ECDataset::InitializeFromJSON(const CPLJSONObject &oRoot,
     410             :                                      bool ignoreOversizedLods)
     411             : {
     412          11 :     CPLErr error = CE_None;
     413             :     try
     414             :     {
     415          33 :         auto format = oRoot.GetString("storageInfo/storageFormat");
     416          11 :         isV2 = EQUAL(format.c_str(), "esriMapCacheStorageModeCompactV2");
     417          11 :         if (!isV2)
     418           0 :             throw CPLString("Not recognized as esri V2 bundled cache");
     419          11 :         if (BSZ != oRoot.GetInteger("storageInfo/packetSize"))
     420           0 :             throw CPLString("Only PacketSize of 128 is supported");
     421             : 
     422          11 :         TSZ = oRoot.GetInteger("tileInfo/rows");
     423          11 :         if (TSZ != oRoot.GetInteger("tileInfo/cols"))
     424           0 :             throw CPLString("Non-square tiles are not supported");
     425          11 :         if (TSZ < 0 || TSZ > 8192)
     426           0 :             throw CPLString("Unsupported tileInfo/rows value");
     427             : 
     428          11 :         double minx = oRoot.GetDouble("tileInfo/origin/x");
     429          11 :         double maxy = oRoot.GetDouble("tileInfo/origin/y");
     430             :         // Assume symmetric coverage
     431          11 :         double maxx = -minx;
     432          11 :         double miny = -maxy;
     433             : 
     434          33 :         const auto oLODs = oRoot.GetArray("tileInfo/lods");
     435          11 :         double res = 0;
     436             :         // we need to skip levels that don't have bundle files
     437          11 :         m_nMinLOD = oRoot.GetInteger("minLOD");
     438          11 :         if (m_nMinLOD < 0 || m_nMinLOD >= 31)
     439           0 :             throw CPLString("Invalid minLOD");
     440          11 :         const int maxLOD = std::min(oRoot.GetInteger("maxLOD"), 31);
     441         280 :         for (const auto &oLOD : oLODs)
     442             :         {
     443         267 :             const int level = oLOD.GetInteger("level");
     444         267 :             if (level < m_nMinLOD || level > maxLOD)
     445         149 :                 continue;
     446             : 
     447         118 :             res = oLOD.GetDouble("resolution");
     448         118 :             if (!(res > 0))
     449           0 :                 throw CPLString("Can't parse resolution for LOD");
     450             : 
     451         118 :             double dxsz = (maxx - minx) / res;
     452         118 :             double dysz = (maxy - miny) / res;
     453             :             // Allow size just above INT32_MAX to handle FP rounding. Actual size is later clamped to INT32_MAX
     454         118 :             double maxRasterSize = static_cast<double>(INT32_MAX) + 2;
     455         118 :             if (dxsz < 1 || dxsz > maxRasterSize || dysz < 1 ||
     456             :                 dysz > maxRasterSize)
     457             :             {
     458           3 :                 if (ignoreOversizedLods)
     459             :                 {
     460           1 :                     CPLDebug("ESRIC",
     461             :                              "Skipping LOD with resolution %.10f: raster size "
     462             :                              "exceeds the GDAL limit",
     463             :                              res);
     464           1 :                     continue;
     465             :                 }
     466             :                 else
     467             :                 {
     468             :                     throw CPLString(
     469             :                         "Too many levels, resulting raster size exceeds "
     470             :                         "the GDAL limit. Open with IGNORE_OVERSIZED_LODS=YES "
     471           2 :                         "to ignore this");
     472             :                 }
     473             :             }
     474             : 
     475         115 :             resolutions.push_back(res);
     476             :         }
     477           9 :         sort(resolutions.begin(), resolutions.end());
     478           9 :         if (resolutions.empty())
     479           0 :             throw CPLString("Can't parse lods");
     480             : 
     481             :         {
     482          27 :             auto poSRS = CreateSRS(oRoot.GetObj("spatialReference"));
     483           9 :             if (!poSRS)
     484             :             {
     485           0 :                 throw CPLString("Invalid Spatial Reference");
     486             :             }
     487           9 :             oSRS = std::move(*poSRS);
     488             :         }
     489             : 
     490             :         // resolution is the smallest figure
     491           9 :         res = resolutions[0];
     492           9 :         m_gt = GDALGeoTransform();
     493           9 :         m_gt[0] = minx;
     494           9 :         m_gt[3] = maxy;
     495           9 :         m_gt[1] = res;
     496           9 :         m_gt[5] = -res;
     497             : 
     498           9 :         double dxsz = (maxx - minx) / res;
     499           9 :         double dysz = (maxy - miny) / res;
     500             : 
     501           9 :         nRasterXSize = int(std::min(dxsz, double(INT32_MAX)));
     502           9 :         nRasterYSize = int(std::min(dysz, double(INT32_MAX)));
     503             : 
     504           9 :         SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
     505           9 :         compression = oRoot.GetString("tileImageInfo/format");
     506           9 :         SetMetadataItem("COMPRESS", compression.c_str(), "IMAGE_STRUCTURE");
     507             : 
     508          27 :         auto oInitialExtent = oRoot.GetObj("initialExtent");
     509          18 :         if (oInitialExtent.IsValid() &&
     510           9 :             oInitialExtent.GetType() == CPLJSONObject::Type::Object)
     511             :         {
     512           9 :             m_sInitialExtent.MinX = oInitialExtent.GetDouble("xmin");
     513           9 :             m_sInitialExtent.MinY = oInitialExtent.GetDouble("ymin");
     514           9 :             m_sInitialExtent.MaxX = oInitialExtent.GetDouble("xmax");
     515           9 :             m_sInitialExtent.MaxY = oInitialExtent.GetDouble("ymax");
     516          27 :             auto oSRSRoot = oInitialExtent.GetObj("spatialReference");
     517           9 :             if (oSRSRoot.IsValid())
     518             :             {
     519          18 :                 auto poSRS = CreateSRS(oSRSRoot);
     520           9 :                 if (!poSRS)
     521             :                 {
     522             :                     throw CPLString(
     523           0 :                         "Invalid Spatial Reference in initialExtent");
     524             :                 }
     525           9 :                 if (!poSRS->IsSame(&oSRS))
     526             :                 {
     527           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
     528             :                              "Ignoring initialExtent, because its SRS is "
     529             :                              "different from the main one");
     530           0 :                     m_sInitialExtent = OGREnvelope();
     531             :                 }
     532             :             }
     533             :         }
     534             : 
     535          27 :         auto oFullExtent = oRoot.GetObj("fullExtent");
     536          18 :         if (oFullExtent.IsValid() &&
     537           9 :             oFullExtent.GetType() == CPLJSONObject::Type::Object)
     538             :         {
     539           9 :             m_sFullExtent.MinX = oFullExtent.GetDouble("xmin");
     540           9 :             m_sFullExtent.MinY = oFullExtent.GetDouble("ymin");
     541           9 :             m_sFullExtent.MaxX = oFullExtent.GetDouble("xmax");
     542           9 :             m_sFullExtent.MaxY = oFullExtent.GetDouble("ymax");
     543          27 :             auto oSRSRoot = oFullExtent.GetObj("spatialReference");
     544           9 :             if (oSRSRoot.IsValid())
     545             :             {
     546          18 :                 auto poSRS = CreateSRS(oSRSRoot);
     547           9 :                 if (!poSRS)
     548             :                 {
     549           0 :                     throw CPLString("Invalid Spatial Reference in fullExtent");
     550             :                 }
     551           9 :                 if (!poSRS->IsSame(&oSRS))
     552             :                 {
     553           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
     554             :                              "Ignoring fullExtent, because its SRS is "
     555             :                              "different from the main one");
     556           0 :                     m_sFullExtent = OGREnvelope();
     557             :                 }
     558             :             }
     559             :         }
     560             : 
     561           9 :         nBands = EQUAL(compression, "JPEG") ? 3 : 4;
     562          45 :         for (int i = 1; i <= nBands; i++)
     563             :         {
     564          36 :             ECBand *band = new ECBand(this, i);
     565          36 :             SetBand(i, band);
     566             :         }
     567             :         // Keep 4 bundle files open
     568           9 :         bundles.resize(4);
     569             :     }
     570           4 :     catch (CPLString &err)
     571             :     {
     572           2 :         error = CE_Failure;
     573           2 :         CPLError(error, CPLE_OpenFailed, "%s", err.c_str());
     574             :     }
     575          11 :     return error;
     576             : }
     577             : 
     578             : class ESRICProxyRasterBand final : public GDALProxyRasterBand
     579             : {
     580             :   private:
     581             :     GDALRasterBand *m_poUnderlyingBand = nullptr;
     582             : 
     583             :     CPL_DISALLOW_COPY_ASSIGN(ESRICProxyRasterBand)
     584             : 
     585             :   protected:
     586             :     GDALRasterBand *RefUnderlyingRasterBand(bool /*bForceOpen*/) const override;
     587             : 
     588             :   public:
     589          20 :     explicit ESRICProxyRasterBand(GDALRasterBand *poUnderlyingBand)
     590          20 :         : m_poUnderlyingBand(poUnderlyingBand)
     591             :     {
     592          20 :         nBand = poUnderlyingBand->GetBand();
     593          20 :         eDataType = poUnderlyingBand->GetRasterDataType();
     594          20 :         nRasterXSize = poUnderlyingBand->GetXSize();
     595          20 :         nRasterYSize = poUnderlyingBand->GetYSize();
     596          20 :         poUnderlyingBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
     597          20 :     }
     598             : };
     599             : 
     600             : GDALRasterBand *
     601          28 : ESRICProxyRasterBand::RefUnderlyingRasterBand(bool /*bForceOpen*/) const
     602             : {
     603          28 :     return m_poUnderlyingBand;
     604             : }
     605             : 
     606             : class ESRICProxyDataset final : public GDALProxyDataset
     607             : {
     608             :   private:
     609             :     // m_poSrcDS must be placed before m_poUnderlyingDS for proper destruction
     610             :     // as m_poUnderlyingDS references m_poSrcDS
     611             :     std::unique_ptr<GDALDataset> m_poSrcDS{};
     612             :     std::unique_ptr<GDALDataset> m_poUnderlyingDS{};
     613             :     CPLStringList m_aosFileList{};
     614             : 
     615             :   protected:
     616             :     GDALDataset *RefUnderlyingDataset() const override;
     617             : 
     618             :   public:
     619           5 :     ESRICProxyDataset(GDALDataset *poSrcDS, GDALDataset *poUnderlyingDS,
     620             :                       const char *pszDescription)
     621           5 :         : m_poSrcDS(poSrcDS), m_poUnderlyingDS(poUnderlyingDS)
     622             :     {
     623           5 :         nRasterXSize = poUnderlyingDS->GetRasterXSize();
     624           5 :         nRasterYSize = poUnderlyingDS->GetRasterYSize();
     625          25 :         for (int i = 0; i < poUnderlyingDS->GetRasterCount(); ++i)
     626          20 :             SetBand(i + 1, new ESRICProxyRasterBand(
     627          20 :                                poUnderlyingDS->GetRasterBand(i + 1)));
     628           5 :         m_aosFileList.AddString(pszDescription);
     629           5 :     }
     630             : 
     631           3 :     GDALDriver *GetDriver() override
     632             :     {
     633           3 :         return GDALDriver::FromHandle(GDALGetDriverByName("ESRIC"));
     634             :     }
     635             : 
     636           3 :     char **GetFileList() override
     637             :     {
     638           3 :         return CSLDuplicate(m_aosFileList.List());
     639             :     }
     640             : };
     641             : 
     642          12 : GDALDataset *ESRICProxyDataset::RefUnderlyingDataset() const
     643             : {
     644          12 :     return m_poUnderlyingDS.get();
     645             : }
     646             : 
     647          17 : GDALDataset *ECDataset::Open(GDALOpenInfo *poOpenInfo)
     648             : {
     649          17 :     return Open(poOpenInfo, poOpenInfo->pszFilename);
     650             : }
     651             : 
     652          24 : GDALDataset *ECDataset::Open(GDALOpenInfo *poOpenInfo,
     653             :                              const char *pszDescription)
     654             : {
     655          24 :     bool ignoreOversizedLods = CSLFetchBoolean(poOpenInfo->papszOpenOptions,
     656          24 :                                                "IGNORE_OVERSIZED_LODS", FALSE);
     657          24 :     if (IdentifyXML(poOpenInfo))
     658             :     {
     659           6 :         CPLXMLNode *config = CPLParseXMLFile(poOpenInfo->pszFilename);
     660           6 :         if (!config)  // Error was reported from parsing XML
     661           0 :             return nullptr;
     662           6 :         CPLXMLNode *CacheInfo = CPLGetXMLNode(config, "=CacheInfo");
     663           6 :         if (!CacheInfo)
     664             :         {
     665           0 :             CPLError(
     666             :                 CE_Warning, CPLE_OpenFailed,
     667             :                 "Error parsing configuration, can't find CacheInfo element");
     668           0 :             CPLDestroyXMLNode(config);
     669           0 :             return nullptr;
     670             :         }
     671           6 :         auto ds = new ECDataset();
     672           6 :         ds->dname = CPLGetDirnameSafe(poOpenInfo->pszFilename) + "/_alllayers";
     673           6 :         CPLErr error = ds->Initialize(CacheInfo, ignoreOversizedLods);
     674           6 :         CPLDestroyXMLNode(config);
     675           6 :         if (CE_None != error)
     676             :         {
     677           2 :             delete ds;
     678           2 :             ds = nullptr;
     679             :         }
     680           6 :         return ds;
     681             :     }
     682          18 :     else if (IdentifyJSON(poOpenInfo))
     683             :     {
     684             :         // Recognize .tpkx file directly passed
     685          18 :         if (!STARTS_WITH(poOpenInfo->pszFilename, "/vsizip/") &&
     686             : #if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
     687          11 :             ENDS_WITH_CI(poOpenInfo->pszFilename, ".tpkx") &&
     688             : #endif
     689           7 :             memcmp(poOpenInfo->pabyHeader, "PK\x03\x04", 4) == 0)
     690             :         {
     691          14 :             GDALOpenInfo oOpenInfo((std::string("/vsizip/{") +
     692          21 :                                     poOpenInfo->pszFilename + "}/root.json")
     693             :                                        .c_str(),
     694          14 :                                    GA_ReadOnly);
     695           7 :             oOpenInfo.papszOpenOptions = poOpenInfo->papszOpenOptions;
     696           7 :             return Open(&oOpenInfo, pszDescription);
     697             :         }
     698             : 
     699          22 :         CPLJSONDocument oJSONDocument;
     700          11 :         if (!oJSONDocument.Load(poOpenInfo->pszFilename))
     701             :         {
     702           0 :             CPLError(CE_Warning, CPLE_OpenFailed,
     703             :                      "Error parsing configuration");
     704           0 :             return nullptr;
     705             :         }
     706             : 
     707          22 :         const CPLJSONObject &oRoot = oJSONDocument.GetRoot();
     708          11 :         if (!oRoot.IsValid())
     709             :         {
     710           0 :             CPLError(CE_Warning, CPLE_OpenFailed, "Invalid json document root");
     711           0 :             return nullptr;
     712             :         }
     713             : 
     714          22 :         auto ds = std::make_unique<ECDataset>();
     715          33 :         auto tileBundlesPath = oRoot.GetString("tileBundlesPath");
     716             :         // Strip leading relative path indicator (if present)
     717          11 :         if (tileBundlesPath.substr(0, 2) == "./")
     718             :         {
     719          11 :             tileBundlesPath.erase(0, 2);
     720             :         }
     721             : 
     722          11 :         ds->dname.Printf("%s/%s",
     723          22 :                          CPLGetDirnameSafe(poOpenInfo->pszFilename).c_str(),
     724          22 :                          tileBundlesPath.c_str());
     725          11 :         CPLErr error = ds->InitializeFromJSON(oRoot, ignoreOversizedLods);
     726          11 :         if (CE_None != error)
     727             :         {
     728           2 :             return nullptr;
     729             :         }
     730             : 
     731             :         const bool bIsFullExtentValid =
     732           9 :             (ds->m_sFullExtent.IsInit() &&
     733          17 :              ds->m_sFullExtent.MinX < ds->m_sFullExtent.MaxX &&
     734           8 :              ds->m_sFullExtent.MinY < ds->m_sFullExtent.MaxY);
     735             :         const char *pszExtentSource =
     736           9 :             CSLFetchNameValue(poOpenInfo->papszOpenOptions, "EXTENT_SOURCE");
     737             : 
     738          18 :         CPLStringList aosOptions;
     739           9 :         if ((!pszExtentSource && bIsFullExtentValid) ||
     740           5 :             (pszExtentSource && EQUAL(pszExtentSource, "FULL_EXTENT")))
     741             :         {
     742           4 :             if (!bIsFullExtentValid)
     743             :             {
     744           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     745             :                          "fullExtent is not valid");
     746           0 :                 return nullptr;
     747             :             }
     748           4 :             aosOptions.AddString("-projwin");
     749           4 :             aosOptions.AddString(CPLSPrintf("%.17g", ds->m_sFullExtent.MinX));
     750           4 :             aosOptions.AddString(CPLSPrintf("%.17g", ds->m_sFullExtent.MaxY));
     751           4 :             aosOptions.AddString(CPLSPrintf("%.17g", ds->m_sFullExtent.MaxX));
     752           4 :             aosOptions.AddString(CPLSPrintf("%.17g", ds->m_sFullExtent.MinY));
     753             :         }
     754           5 :         else if (pszExtentSource && EQUAL(pszExtentSource, "INITIAL_EXTENT"))
     755             :         {
     756             :             const bool bIsInitialExtentValid =
     757           1 :                 (ds->m_sInitialExtent.IsInit() &&
     758           2 :                  ds->m_sInitialExtent.MinX < ds->m_sInitialExtent.MaxX &&
     759           1 :                  ds->m_sInitialExtent.MinY < ds->m_sInitialExtent.MaxY);
     760           1 :             if (!bIsInitialExtentValid)
     761             :             {
     762           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     763             :                          "initialExtent is not valid");
     764           0 :                 return nullptr;
     765             :             }
     766           1 :             aosOptions.AddString("-projwin");
     767             :             aosOptions.AddString(
     768           1 :                 CPLSPrintf("%.17g", ds->m_sInitialExtent.MinX));
     769             :             aosOptions.AddString(
     770           1 :                 CPLSPrintf("%.17g", ds->m_sInitialExtent.MaxY));
     771             :             aosOptions.AddString(
     772           1 :                 CPLSPrintf("%.17g", ds->m_sInitialExtent.MaxX));
     773             :             aosOptions.AddString(
     774           1 :                 CPLSPrintf("%.17g", ds->m_sInitialExtent.MinY));
     775             :         }
     776             : 
     777           9 :         if (!aosOptions.empty())
     778             :         {
     779           5 :             aosOptions.AddString("-of");
     780           5 :             aosOptions.AddString("VRT");
     781           5 :             aosOptions.AddString("-co");
     782           5 :             aosOptions.AddString(CPLSPrintf("BLOCKXSIZE=%d", ds->TSZ));
     783           5 :             aosOptions.AddString("-co");
     784           5 :             aosOptions.AddString(CPLSPrintf("BLOCKYSIZE=%d", ds->TSZ));
     785             :             auto psOptions =
     786           5 :                 GDALTranslateOptionsNew(aosOptions.List(), nullptr);
     787           5 :             auto hDS = GDALTranslate("", GDALDataset::ToHandle(ds.get()),
     788             :                                      psOptions, nullptr);
     789           5 :             GDALTranslateOptionsFree(psOptions);
     790           5 :             if (!hDS)
     791             :             {
     792           0 :                 return nullptr;
     793             :             }
     794             :             return new ESRICProxyDataset(
     795           5 :                 ds.release(), GDALDataset::FromHandle(hDS), pszDescription);
     796             :         }
     797           4 :         return ds.release();
     798             :     }
     799           0 :     return nullptr;
     800             : }
     801             : 
     802             : // Fetch a reference to an initialized bundle, based on file name
     803             : // The returned bundle could still have an invalid file handle, if the
     804             : // target bundle is not valid
     805        4229 : Bundle &ECDataset::GetBundle(const char *fname)
     806             : {
     807        4261 :     for (auto &bundle : bundles)
     808             :     {
     809             :         // If a bundle is missing, it still occupies a slot, with fh == nullptr
     810        4253 :         if (EQUAL(bundle.name.c_str(), fname))
     811        4221 :             return bundle;
     812             :     }
     813             :     // Not found, look for an empty // missing slot
     814           8 :     for (auto &bundle : bundles)
     815             :     {
     816           8 :         if (nullptr == bundle.fh)
     817             :         {
     818           8 :             bundle.Init(fname);
     819           8 :             return bundle;
     820             :         }
     821             :     }
     822             :     // No empties, eject one
     823             :     Bundle &bundle = bundles[
     824             : #ifndef __COVERITY__
     825           0 :         rand() % bundles.size()
     826             : #else
     827             :         0
     828             : #endif
     829           0 :     ];
     830           0 :     bundle.Init(fname);
     831           0 :     return bundle;
     832             : }
     833             : 
     834         824 : ECBand::~ECBand()
     835             : {
     836         772 :     for (auto ovr : overviews)
     837         360 :         if (ovr)
     838         360 :             delete ovr;
     839         412 :     overviews.clear();
     840         824 : }
     841             : 
     842         412 : ECBand::ECBand(ECDataset *parent, int b, int level)
     843         412 :     : lvl(level), ci(GCI_Undefined)
     844             : {
     845             :     static const GDALColorInterp rgba[4] = {GCI_RedBand, GCI_GreenBand,
     846             :                                             GCI_BlueBand, GCI_AlphaBand};
     847             :     static const GDALColorInterp la[2] = {GCI_GrayIndex, GCI_AlphaBand};
     848         412 :     poDS = parent;
     849         412 :     nBand = b;
     850             : 
     851         412 :     double factor = parent->resolutions[0] / parent->resolutions[lvl];
     852         412 :     nRasterXSize = static_cast<int>(parent->nRasterXSize * factor + 0.5);
     853         412 :     nRasterYSize = static_cast<int>(parent->nRasterYSize * factor + 0.5);
     854         412 :     nBlockXSize = nBlockYSize = parent->TSZ;
     855             : 
     856             :     // Default color interpretation
     857         412 :     assert(b - 1 >= 0);
     858         412 :     if (parent->nBands >= 3)
     859             :     {
     860         412 :         assert(b - 1 < static_cast<int>(CPL_ARRAYSIZE(rgba)));
     861         412 :         ci = rgba[b - 1];
     862             :     }
     863             :     else
     864             :     {
     865           0 :         assert(b - 1 < static_cast<int>(CPL_ARRAYSIZE(la)));
     866           0 :         ci = la[b - 1];
     867             :     }
     868         412 :     if (0 == lvl)
     869          52 :         AddOverviews();
     870         412 : }
     871             : 
     872          52 : void ECBand::AddOverviews()
     873             : {
     874          52 :     auto parent = cpl::down_cast<ECDataset *>(poDS);
     875         412 :     for (size_t i = 1; i < parent->resolutions.size(); i++)
     876             :     {
     877         360 :         ECBand *ovl = new ECBand(parent, nBand, int(i));
     878         360 :         if (!ovl)
     879           0 :             break;
     880         360 :         overviews.push_back(ovl);
     881             :     }
     882          52 : }
     883             : 
     884        4229 : CPLErr ECBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pData)
     885             : {
     886        4229 :     auto parent = cpl::down_cast<ECDataset *>(poDS);
     887        4229 :     auto &buffer = parent->tilebuffer;
     888        4229 :     auto TSZ = parent->TSZ;
     889        4229 :     auto BSZ = parent->BSZ;
     890        4229 :     size_t nBytes = size_t(TSZ) * TSZ;
     891             : 
     892        4229 :     buffer.resize(nBytes * parent->nBands);
     893             : 
     894        8458 :     const int lxx = parent->m_nMinLOD +
     895        4229 :                     static_cast<int>(parent->resolutions.size() - lvl - 1);
     896             :     int bx, by;
     897        4229 :     bx = (nBlockXOff / BSZ) * BSZ;
     898        4229 :     by = (nBlockYOff / BSZ) * BSZ;
     899        8458 :     CPLString fname;
     900        8458 :     fname = CPLString().Printf("%s/L%02d/R%04xC%04x.bundle",
     901        4229 :                                parent->dname.c_str(), lxx, by, bx);
     902        4229 :     Bundle &bundle = parent->GetBundle(fname);
     903        4229 :     if (nullptr == bundle.fh)
     904             :     {  // This is not an error in general, bundles can be missing
     905          64 :         CPLDebug("ESRIC", "Can't open bundle %s", fname.c_str());
     906          64 :         memset(pData, 0, nBytes);
     907          64 :         return CE_None;
     908             :     }
     909        4165 :     int block = static_cast<int>((nBlockYOff % BSZ) * BSZ + (nBlockXOff % BSZ));
     910        4165 :     GUInt64 offset = bundle.index[block] & 0xffffffffffull;
     911        4165 :     GUInt64 size = bundle.index[block] >> 40;
     912        4165 :     if (0 == size)
     913             :     {
     914        3755 :         memset(pData, 0, nBytes);
     915        3755 :         return CE_None;
     916             :     }
     917         410 :     auto &fbuffer = parent->filebuffer;
     918         410 :     fbuffer.resize(size_t(size));
     919         410 :     bundle.fh->Seek(offset, SEEK_SET);
     920         410 :     if (size != bundle.fh->Read(fbuffer.data(), size_t(1), size_t(size)))
     921             :     {
     922           0 :         CPLError(CE_Failure, CPLE_FileIO,
     923             :                  "Error reading tile, reading " CPL_FRMT_GUIB
     924             :                  " at " CPL_FRMT_GUIB,
     925             :                  GUInt64(size), GUInt64(offset));
     926           0 :         return CE_Failure;
     927             :     }
     928         820 :     const CPLString magic(VSIMemGenerateHiddenFilename("esric.tmp"));
     929         410 :     auto mfh = VSIFileFromMemBuffer(magic.c_str(), fbuffer.data(), size, false);
     930         410 :     VSIFCloseL(mfh);
     931             :     // Can't open a raster by handle?
     932         410 :     auto inds = GDALOpen(magic.c_str(), GA_ReadOnly);
     933         410 :     if (!inds)
     934             :     {
     935           0 :         VSIUnlink(magic.c_str());
     936           0 :         CPLError(CE_Failure, CPLE_FileIO, "Error opening tile");
     937           0 :         return CE_Failure;
     938             :     }
     939             :     // Duplicate first band if not sufficient bands are provided
     940         410 :     auto inbands = GDALGetRasterCount(inds);
     941         410 :     int ubands[4] = {1, 1, 1, 1};
     942         410 :     int *usebands = nullptr;
     943         410 :     int bandcount = parent->nBands;
     944         410 :     GDALColorTableH hCT = nullptr;
     945         410 :     if (inbands != bandcount)
     946             :     {
     947             :         // Opaque if output expects alpha channel
     948         259 :         if (0 == bandcount % 2)
     949             :         {
     950         259 :             fill(buffer.begin(), buffer.end(), GByte(255));
     951         259 :             bandcount--;
     952             :         }
     953         259 :         if (3 == inbands)
     954             :         {
     955             :             // Lacking opacity, copy the first three bands
     956           8 :             ubands[1] = 2;
     957           8 :             ubands[2] = 3;
     958           8 :             usebands = ubands;
     959             :         }
     960         251 :         else if (1 == inbands)
     961             :         {
     962             :             // Grayscale, expecting color
     963         251 :             usebands = ubands;
     964             :             // Check for the color table of 1 band rasters
     965         251 :             hCT = GDALGetRasterColorTable(GDALGetRasterBand(inds, 1));
     966             :         }
     967             :     }
     968             : 
     969         410 :     auto errcode = CE_None;
     970         410 :     if (nullptr != hCT)
     971             :     {
     972             :         // Expand color indexed to RGB(A)
     973         250 :         errcode = GDALDatasetRasterIO(
     974         250 :             inds, GF_Read, 0, 0, TSZ, TSZ, buffer.data(), TSZ, TSZ, GDT_UInt8,
     975         250 :             1, usebands, parent->nBands, parent->nBands * TSZ, 1);
     976         250 :         if (CE_None == errcode)
     977             :         {
     978             :             GByte abyCT[4 * 256];
     979         250 :             GByte *pabyTileData = buffer.data();
     980         250 :             const int nEntries = std::min(256, GDALGetColorEntryCount(hCT));
     981        2148 :             for (int i = 0; i < nEntries; i++)
     982             :             {
     983        1898 :                 const GDALColorEntry *psEntry = GDALGetColorEntry(hCT, i);
     984        1898 :                 abyCT[4 * i] = static_cast<GByte>(psEntry->c1);
     985        1898 :                 abyCT[4 * i + 1] = static_cast<GByte>(psEntry->c2);
     986        1898 :                 abyCT[4 * i + 2] = static_cast<GByte>(psEntry->c3);
     987        1898 :                 abyCT[4 * i + 3] = static_cast<GByte>(psEntry->c4);
     988             :             }
     989       62352 :             for (int i = nEntries; i < 256; i++)
     990             :             {
     991       62102 :                 abyCT[4 * i] = 0;
     992       62102 :                 abyCT[4 * i + 1] = 0;
     993       62102 :                 abyCT[4 * i + 2] = 0;
     994       62102 :                 abyCT[4 * i + 3] = 0;
     995             :             }
     996             : 
     997         250 :             if (parent->nBands == 4)
     998             :             {
     999    16384200 :                 for (size_t i = 0; i < nBytes; i++)
    1000             :                 {
    1001    16384000 :                     const GByte byVal = pabyTileData[4 * i];
    1002    16384000 :                     pabyTileData[4 * i] = abyCT[4 * byVal];
    1003    16384000 :                     pabyTileData[4 * i + 1] = abyCT[4 * byVal + 1];
    1004    16384000 :                     pabyTileData[4 * i + 2] = abyCT[4 * byVal + 2];
    1005    16384000 :                     pabyTileData[4 * i + 3] = abyCT[4 * byVal + 3];
    1006             :                 }
    1007             :             }
    1008           0 :             else if (parent->nBands == 3)
    1009             :             {
    1010           0 :                 for (size_t i = 0; i < nBytes; i++)
    1011             :                 {
    1012           0 :                     const GByte byVal = pabyTileData[3 * i];
    1013           0 :                     pabyTileData[3 * i] = abyCT[4 * byVal];
    1014           0 :                     pabyTileData[3 * i + 1] = abyCT[4 * byVal + 1];
    1015           0 :                     pabyTileData[3 * i + 2] = abyCT[4 * byVal + 2];
    1016             :                 }
    1017             :             }
    1018             :             else
    1019             :             {
    1020             :                 // Assuming grayscale output
    1021           0 :                 for (size_t i = 0; i < nBytes; i++)
    1022             :                 {
    1023           0 :                     const GByte byVal = pabyTileData[i];
    1024           0 :                     pabyTileData[i] = abyCT[4 * byVal];
    1025             :                 }
    1026             :             }
    1027             :         }
    1028             :     }
    1029             :     else
    1030             :     {
    1031         160 :         errcode = GDALDatasetRasterIO(
    1032         160 :             inds, GF_Read, 0, 0, TSZ, TSZ, buffer.data(), TSZ, TSZ, GDT_UInt8,
    1033         160 :             bandcount, usebands, parent->nBands, parent->nBands * TSZ, 1);
    1034             :     }
    1035         410 :     GDALClose(inds);
    1036         410 :     VSIUnlink(magic.c_str());
    1037             :     // Error while unpacking tile
    1038         410 :     if (CE_None != errcode)
    1039           0 :         return errcode;
    1040             : 
    1041        2050 :     for (int iBand = 1; iBand <= parent->nBands; iBand++)
    1042             :     {
    1043        1640 :         auto band = parent->GetRasterBand(iBand);
    1044        1640 :         if (lvl)
    1045          52 :             band = band->GetOverview(lvl - 1);
    1046        1640 :         GDALRasterBlock *poBlock = nullptr;
    1047        1640 :         if (band != this)
    1048             :         {
    1049        1230 :             poBlock = band->GetLockedBlockRef(nBlockXOff, nBlockYOff, 1);
    1050        1230 :             if (poBlock != nullptr)
    1051             :             {
    1052        1230 :                 GDALCopyWords(buffer.data() + iBand - 1, GDT_UInt8,
    1053             :                               parent->nBands, poBlock->GetDataRef(), GDT_UInt8,
    1054             :                               1, TSZ * TSZ);
    1055        1230 :                 poBlock->DropLock();
    1056             :             }
    1057             :         }
    1058             :         else
    1059             :         {
    1060         410 :             GDALCopyWords(buffer.data() + iBand - 1, GDT_UInt8, parent->nBands,
    1061             :                           pData, GDT_UInt8, 1, TSZ * TSZ);
    1062             :         }
    1063             :     }
    1064             : 
    1065         410 :     return CE_None;
    1066             : }  // IReadBlock
    1067             : 
    1068             : }  // namespace ESRIC
    1069             : 
    1070        2058 : void CPL_DLL GDALRegister_ESRIC()
    1071             : {
    1072        2058 :     if (GDALGetDriverByName("ESRIC") != nullptr)
    1073         283 :         return;
    1074             : 
    1075        1775 :     auto poDriver = new GDALDriver;
    1076             : 
    1077        1775 :     poDriver->SetDescription("ESRIC");
    1078        1775 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    1079        1775 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    1080        1775 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Esri Compact Cache");
    1081             : 
    1082        1775 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "json tpkx");
    1083             : 
    1084        1775 :     poDriver->SetMetadataItem(
    1085             :         GDAL_DMD_OPENOPTIONLIST,
    1086             :         "<OpenOptionList>"
    1087             :         "  <Option name='EXTENT_SOURCE' type='string-select' "
    1088             :         "description='Which source is used to determine the extent' "
    1089             :         "default='FULL_EXTENT'>"
    1090             :         "    <Value>FULL_EXTENT</Value>"
    1091             :         "    <Value>INITIAL_EXTENT</Value>"
    1092             :         "    <Value>TILING_SCHEME</Value>"
    1093             :         "  </Option>"
    1094             :         "  <Option name='IGNORE_OVERSIZED_LODS' type='boolean' "
    1095             :         "description='Whether to silently ignore LODs that exceed the "
    1096             :         "maximum size supported by GDAL (INT32_MAX)' "
    1097             :         "default='NO'>"
    1098             :         "  </Option>"
    1099        1775 :         "</OpenOptionList>");
    1100        1775 :     poDriver->pfnIdentify = ESRIC::Identify;
    1101        1775 :     poDriver->pfnOpen = ESRIC::ECDataset::Open;
    1102        1775 :     poDriver->pfnDelete = ESRIC::Delete;
    1103             : 
    1104        1775 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1105             : }

Generated by: LCOV version 1.14