LCOV - code coverage report
Current view: top level - frmts/esric - esric_dataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 453 536 84.5 %
Date: 2024-11-21 22:18:42 Functions: 33 34 97.1 %

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

Generated by: LCOV version 1.14