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

Generated by: LCOV version 1.14