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

Generated by: LCOV version 1.14