LCOV - code coverage report
Current view: top level - frmts/esric - esric_dataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 350 419 83.5 %
Date: 2024-05-15 13:15:52 Functions: 24 26 92.3 %

          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             :  * Permission is hereby granted, free of charge, to any person obtaining a
      14             :  * copy of this softwareand associated documentation files(the "Software"),
      15             :  * to deal in the Software without restriction, including without limitation
      16             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      17             :  * and /or sell copies of the Software, and to permit persons to whom the
      18             :  * Software is furnished to do so, subject to the following conditions :
      19             :  *
      20             :  * The above copyright noticeand this permission notice shall be included
      21             :  * in all copies or substantial portions of the Software.
      22             :  *
      23             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      24             :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      25             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL
      26             :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      27             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      28             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      29             :  * DEALINGS IN THE SOFTWARE.
      30             :  *****************************************************************************/
      31             : 
      32             : #include "gdal_priv.h"
      33             : #include <cassert>
      34             : #include <vector>
      35             : #include <algorithm>
      36             : #include "cpl_json.h"
      37             : 
      38             : using namespace std;
      39             : 
      40             : CPL_C_START
      41             : void CPL_DLL GDALRegister_ESRIC();
      42             : CPL_C_END
      43             : 
      44             : namespace ESRIC
      45             : {
      46             : 
      47             : #define ENDS_WITH_CI(a, b)                                                     \
      48             :     (strlen(a) >= strlen(b) && EQUAL(a + strlen(a) - strlen(b), b))
      49             : 
      50             : // ESRI tpkx files use root.json
      51       53967 : static int IdentifyJSON(GDALOpenInfo *poOpenInfo)
      52             : {
      53       53967 :     if (poOpenInfo->eAccess != GA_ReadOnly || poOpenInfo->nHeaderBytes < 512)
      54       48595 :         return false;
      55             : 
      56             :     // Recognize .tpkx file directly passed
      57        5372 :     if (!STARTS_WITH(poOpenInfo->pszFilename, "/vsizip/") &&
      58             : #if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
      59        5357 :         ENDS_WITH_CI(poOpenInfo->pszFilename, ".tpkx") &&
      60             : #endif
      61           6 :         memcmp(poOpenInfo->pabyHeader, "PK\x03\x04", 4) == 0)
      62             :     {
      63           6 :         return true;
      64             :     }
      65             : 
      66             : #if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
      67        5366 :     if (!ENDS_WITH_CI(poOpenInfo->pszFilename, "root.json"))
      68        5363 :         return false;
      69             : #endif
      70           3 :     CPLString header(reinterpret_cast<char *>(poOpenInfo->pabyHeader),
      71           3 :                      poOpenInfo->nHeaderBytes);
      72           3 :     return (CPLString::npos != header.find("tileBundlesPath"));
      73             : }
      74             : 
      75             : // Without full XML parsing, weak, might still fail
      76       53973 : static int IdentifyXML(GDALOpenInfo *poOpenInfo)
      77             : {
      78       53973 :     if (poOpenInfo->eAccess != GA_ReadOnly
      79             : #if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
      80       53301 :         || !ENDS_WITH_CI(poOpenInfo->pszFilename, "conf.xml")
      81             : #endif
      82           6 :         || poOpenInfo->nHeaderBytes < 512)
      83       53967 :         return false;
      84           6 :     CPLString header(reinterpret_cast<char *>(poOpenInfo->pabyHeader),
      85           6 :                      poOpenInfo->nHeaderBytes);
      86           6 :     return (CPLString::npos != header.find("<CacheInfo"));
      87             : }
      88             : 
      89       53963 : static int Identify(GDALOpenInfo *poOpenInfo)
      90             : {
      91       53963 :     return (IdentifyXML(poOpenInfo) || IdentifyJSON(poOpenInfo));
      92             : }
      93             : 
      94             : // Stub default delete, don't delete a tile cache from GDAL
      95           0 : static CPLErr Delete(const char *)
      96             : {
      97           0 :     return CE_None;
      98             : }
      99             : 
     100             : // Read a 32bit unsigned integer stored in little endian
     101             : // Same as CPL_LSBUINT32PTR
     102          16 : static inline GUInt32 u32lat(void *data)
     103             : {
     104             :     GUInt32 val;
     105          16 :     memcpy(&val, data, 4);
     106          16 :     return CPL_LSBWORD32(val);
     107             : }
     108             : 
     109             : struct Bundle
     110             : {
     111          24 :     Bundle() : fh(nullptr), isV2(true), isTpkx(false)
     112             :     {
     113          24 :     }
     114             : 
     115          24 :     ~Bundle()
     116          24 :     {
     117          24 :         if (fh)
     118           3 :             VSIFCloseL(fh);
     119          24 :         fh = nullptr;
     120          24 :     }
     121             : 
     122           4 :     void Init(const char *filename)
     123             :     {
     124           4 :         if (fh)
     125           0 :             VSIFCloseL(fh);
     126           4 :         name = filename;
     127           4 :         fh = VSIFOpenL(name.c_str(), "rb");
     128           4 :         if (nullptr == fh)
     129           1 :             return;
     130           3 :         GByte header[64] = {0};
     131             :         // Check a few header locations, then read the index
     132           3 :         VSIFReadL(header, 1, 64, fh);
     133           3 :         index.resize(BSZ * BSZ);
     134           9 :         if (3 != u32lat(header) || 5 != u32lat(header + 12) ||
     135           6 :             40 != u32lat(header + 32) || 0 != u32lat(header + 36) ||
     136           3 :             (!isTpkx &&
     137           1 :              BSZ * BSZ != u32lat(header + 4)) || /* skip this check for tpkx */
     138           9 :             BSZ * BSZ * 8 != u32lat(header + 60) ||
     139           3 :             index.size() != VSIFReadL(index.data(), 8, index.size(), fh))
     140             :         {
     141           0 :             VSIFCloseL(fh);
     142           0 :             fh = nullptr;
     143             :         }
     144             : 
     145             : #if !CPL_IS_LSB
     146             :         for (auto &v : index)
     147             :             CPL_LSBPTR64(&v);
     148             :         return;
     149             : #endif
     150             :     }
     151             : 
     152             :     std::vector<GUInt64> index;
     153             :     VSILFILE *fh;
     154             :     bool isV2;
     155             :     bool isTpkx;
     156             :     CPLString name;
     157             :     const size_t BSZ = 128;
     158             : };
     159             : 
     160             : class ECDataset final : public GDALDataset
     161             : {
     162             :     friend class ECBand;
     163             : 
     164             :   public:
     165             :     ECDataset();
     166             : 
     167          12 :     virtual ~ECDataset()
     168           6 :     {
     169          12 :     }
     170             : 
     171           2 :     CPLErr GetGeoTransform(double *gt) override
     172             :     {
     173           2 :         memcpy(gt, GeoTransform, sizeof(GeoTransform));
     174           2 :         return CE_None;
     175             :     }
     176             : 
     177           2 :     virtual const OGRSpatialReference *GetSpatialRef() const override
     178             :     {
     179           2 :         return &oSRS;
     180             :     }
     181             : 
     182             :     static GDALDataset *Open(GDALOpenInfo *poOpenInfo);
     183             : 
     184             :   protected:
     185             :     double GeoTransform[6];
     186             :     CPLString dname;
     187             :     int isV2;  // V2 bundle format
     188             :     int BSZ;   // Bundle size in tiles
     189             :     int TSZ;   // Tile size in pixels
     190             :     std::vector<Bundle> bundles;
     191             : 
     192             :     Bundle &GetBundle(const char *fname);
     193             : 
     194             :   private:
     195             :     CPLErr Initialize(CPLXMLNode *CacheInfo);
     196             :     CPLErr InitializeFromJSON(const CPLJSONObject &oRoot);
     197             :     CPLString compression;
     198             :     std::vector<double> resolutions;
     199             :     OGRSpatialReference oSRS;
     200             :     std::vector<GByte> tilebuffer;  // Last read tile, decompressed
     201             :     std::vector<GByte> filebuffer;  // raw tile buffer
     202             : };
     203             : 
     204             : class ECBand final : public GDALRasterBand
     205             : {
     206             :     friend class ECDataset;
     207             : 
     208             :   public:
     209             :     ECBand(ECDataset *parent, int b, int level = 0);
     210             :     virtual ~ECBand();
     211             : 
     212             :     virtual CPLErr IReadBlock(int xblk, int yblk, void *buffer) override;
     213             : 
     214           0 :     virtual GDALColorInterp GetColorInterpretation() override
     215             :     {
     216           0 :         return ci;
     217             :     }
     218             : 
     219          56 :     virtual int GetOverviewCount() override
     220             :     {
     221          56 :         return static_cast<int>(overviews.size());
     222             :     }
     223             : 
     224          54 :     virtual GDALRasterBand *GetOverview(int n) override
     225             :     {
     226          54 :         return (n >= 0 && n < GetOverviewCount()) ? overviews[n] : nullptr;
     227             :     }
     228             : 
     229             :   protected:
     230             :   private:
     231             :     int lvl;
     232             :     GDALColorInterp ci;
     233             : 
     234             :     // Image image;
     235             :     void AddOverviews();
     236             :     std::vector<ECBand *> overviews;
     237             : };
     238             : 
     239           6 : ECDataset::ECDataset() : isV2(true), BSZ(128), TSZ(256)
     240             : {
     241           6 :     double gt[6] = {0, 1, 0, 0, 0, 1};
     242           6 :     memcpy(GeoTransform, gt, sizeof(gt));
     243           6 : }
     244             : 
     245           3 : CPLErr ECDataset::Initialize(CPLXMLNode *CacheInfo)
     246             : {
     247           3 :     CPLErr error = CE_None;
     248             :     try
     249             :     {
     250           3 :         CPLXMLNode *CSI = CPLGetXMLNode(CacheInfo, "CacheStorageInfo");
     251           3 :         CPLXMLNode *TCI = CPLGetXMLNode(CacheInfo, "TileCacheInfo");
     252           3 :         if (!CSI || !TCI)
     253           0 :             throw CPLString("Error parsing cache configuration");
     254           3 :         auto format = CPLGetXMLValue(CSI, "StorageFormat", "");
     255           3 :         isV2 = EQUAL(format, "esriMapCacheStorageModeCompactV2");
     256           3 :         if (!isV2)
     257           0 :             throw CPLString("Not recognized as esri V2 bundled cache");
     258           3 :         if (BSZ != CPLAtof(CPLGetXMLValue(CSI, "PacketSize", "128")))
     259           0 :             throw CPLString("Only PacketSize of 128 is supported");
     260           3 :         TSZ = static_cast<int>(CPLAtof(CPLGetXMLValue(TCI, "TileCols", "256")));
     261           3 :         if (TSZ != CPLAtof(CPLGetXMLValue(TCI, "TileRows", "256")))
     262           0 :             throw CPLString("Non-square tiles are not supported");
     263             : 
     264           3 :         CPLXMLNode *LODInfo = CPLGetXMLNode(TCI, "LODInfos.LODInfo");
     265           3 :         double res = 0;
     266          15 :         while (LODInfo)
     267             :         {
     268          12 :             res = CPLAtof(CPLGetXMLValue(LODInfo, "Resolution", "0"));
     269          12 :             if (!(res > 0))
     270           0 :                 throw CPLString("Can't parse resolution for LOD");
     271          12 :             resolutions.push_back(res);
     272          12 :             LODInfo = LODInfo->psNext;
     273             :         }
     274           3 :         sort(resolutions.begin(), resolutions.end());
     275           3 :         if (resolutions.empty())
     276           0 :             throw CPLString("Can't parse LODInfos");
     277             : 
     278             :         CPLString RawProj(
     279           6 :             CPLGetXMLValue(TCI, "SpatialReference.WKT", "EPSG:4326"));
     280           3 :         if (OGRERR_NONE != oSRS.SetFromUserInput(RawProj.c_str()))
     281           0 :             throw CPLString("Invalid Spatial Reference");
     282           3 :         oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     283             : 
     284             :         // resolution is the smallest figure
     285           3 :         res = resolutions[0];
     286           3 :         double gt[6] = {0, 1, 0, 0, 0, 1};
     287           3 :         gt[0] = CPLAtof(CPLGetXMLValue(TCI, "TileOrigin.X", "-180"));
     288           3 :         gt[3] = CPLAtof(CPLGetXMLValue(TCI, "TileOrigin.Y", "90"));
     289           3 :         gt[1] = res;
     290           3 :         gt[5] = -res;
     291           3 :         memcpy(GeoTransform, gt, sizeof(gt));
     292             : 
     293             :         // Assume symmetric coverage, check custom end
     294           3 :         double maxx = -gt[0];
     295           3 :         double miny = -gt[3];
     296           3 :         const char *pszmaxx = CPLGetXMLValue(TCI, "TileEnd.X", nullptr);
     297           3 :         const char *pszminy = CPLGetXMLValue(TCI, "TileEnd.Y", nullptr);
     298           3 :         if (pszmaxx && pszminy)
     299             :         {
     300           3 :             maxx = CPLAtof(pszmaxx);
     301           3 :             miny = CPLAtof(pszminy);
     302             :         }
     303             : 
     304           3 :         double dxsz = (maxx - gt[0]) / res;
     305           3 :         double dysz = (gt[3] - miny) / res;
     306           3 :         if (dxsz < 1 || dxsz > INT32_MAX || dysz < 1 || dysz > INT32_MAX)
     307             :             throw CPLString("Too many levels, resulting raster size exceeds "
     308           0 :                             "the GDAL limit");
     309             : 
     310           3 :         nRasterXSize = int(dxsz);
     311           3 :         nRasterYSize = int(dysz);
     312             : 
     313           3 :         SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
     314             :         compression =
     315           3 :             CPLGetXMLValue(CacheInfo, "TileImageInfo.CacheTileFormat", "JPEG");
     316           3 :         SetMetadataItem("COMPRESS", compression.c_str(), "IMAGE_STRUCTURE");
     317             : 
     318           3 :         nBands = EQUAL(compression, "JPEG") ? 3 : 4;
     319          15 :         for (int i = 1; i <= nBands; i++)
     320             :         {
     321          12 :             ECBand *band = new ECBand(this, i);
     322          12 :             SetBand(i, band);
     323             :         }
     324             :         // Keep 4 bundle files open
     325           3 :         bundles.resize(4);
     326             :     }
     327           0 :     catch (CPLString &err)
     328             :     {
     329           0 :         error = CE_Failure;
     330           0 :         CPLError(error, CPLE_OpenFailed, "%s", err.c_str());
     331             :     }
     332           3 :     return error;
     333             : }
     334             : 
     335           3 : CPLErr ECDataset::InitializeFromJSON(const CPLJSONObject &oRoot)
     336             : {
     337           3 :     CPLErr error = CE_None;
     338             :     try
     339             :     {
     340           9 :         auto format = oRoot.GetString("storageInfo/storageFormat");
     341           3 :         isV2 = EQUAL(format.c_str(), "esriMapCacheStorageModeCompactV2");
     342           3 :         if (!isV2)
     343           0 :             throw CPLString("Not recognized as esri V2 bundled cache");
     344           3 :         if (BSZ != oRoot.GetInteger("storageInfo/packetSize"))
     345           0 :             throw CPLString("Only PacketSize of 128 is supported");
     346             : 
     347           3 :         TSZ = oRoot.GetInteger("tileInfo/rows");
     348           3 :         if (TSZ != oRoot.GetInteger("tileInfo/cols"))
     349           0 :             throw CPLString("Non-square tiles are not supported");
     350             : 
     351           9 :         auto oLODs = oRoot.GetArray("tileInfo/lods");
     352           3 :         double res = 0;
     353             :         // we need to skip levels that don't have bundle files
     354           3 :         int minLOD = oRoot.GetInteger("minLOD");
     355           3 :         int maxLOD = oRoot.GetInteger("maxLOD");
     356           3 :         int level = 0;
     357          75 :         for (const auto &oLOD : oLODs)
     358             :         {
     359          72 :             res = oLOD.GetDouble("resolution");
     360          72 :             if (!(res > 0))
     361           0 :                 throw CPLString("Can't parse resolution for LOD");
     362          72 :             level = oLOD.GetInteger("level");
     363          72 :             if (level >= minLOD && level <= maxLOD)
     364             :             {
     365          18 :                 resolutions.push_back(res);
     366             :             }
     367             :         }
     368           3 :         sort(resolutions.begin(), resolutions.end());
     369           3 :         if (resolutions.empty())
     370           0 :             throw CPLString("Can't parse lods");
     371             : 
     372           3 :         bool bSuccess = false;
     373           3 :         const int nCode = oRoot.GetInteger("spatialReference/wkid");
     374             :         // The concept of LatestWKID is explained in
     375             :         // https://support.esri.com/en/technical-article/000013950
     376           3 :         const int nLatestCode = oRoot.GetInteger("spatialReference/latestWkid");
     377             : 
     378             :         // Try first with nLatestWKID as there is a higher chance it is a
     379             :         // EPSG code and not an ESRI one.
     380           3 :         if (nLatestCode > 0)
     381             :         {
     382           3 :             if (nLatestCode > 32767)
     383             :             {
     384           0 :                 if (oSRS.SetFromUserInput(CPLSPrintf("ESRI:%d", nLatestCode)) ==
     385             :                     OGRERR_NONE)
     386             :                 {
     387           0 :                     bSuccess = true;
     388             :                 }
     389             :             }
     390           3 :             else if (oSRS.importFromEPSG(nLatestCode) == OGRERR_NONE)
     391             :             {
     392           3 :                 bSuccess = true;
     393             :             }
     394             :         }
     395           3 :         if (!bSuccess && nCode > 0)
     396             :         {
     397           0 :             if (nCode > 32767)
     398             :             {
     399           0 :                 if (oSRS.SetFromUserInput(CPLSPrintf("ESRI:%d", nCode)) ==
     400             :                     OGRERR_NONE)
     401             :                 {
     402           0 :                     bSuccess = true;
     403             :                 }
     404             :             }
     405           0 :             else if (oSRS.importFromEPSG(nCode) == OGRERR_NONE)
     406             :             {
     407           0 :                 bSuccess = true;
     408             :             }
     409             :         }
     410           3 :         if (!bSuccess)
     411             :         {
     412           0 :             throw CPLString("Invalid Spatial Reference");
     413             :         }
     414             : 
     415           3 :         oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     416             : 
     417             :         // resolution is the smallest figure
     418           3 :         res = resolutions[0];
     419           3 :         double gt[6] = {0, 1, 0, 0, 0, 1};
     420           3 :         gt[0] = oRoot.GetDouble("tileInfo/origin/x");
     421           3 :         gt[3] = oRoot.GetDouble("tileInfo/origin/y");
     422           3 :         gt[1] = res;
     423           3 :         gt[5] = -res;
     424           3 :         memcpy(GeoTransform, gt, sizeof(gt));
     425             : 
     426             :         // Assume symmetric coverage
     427           3 :         double maxx = -gt[0];
     428           3 :         double miny = -gt[3];
     429             : 
     430           3 :         double dxsz = (maxx - gt[0]) / res;
     431           3 :         double dysz = (gt[3] - miny) / res;
     432           3 :         if (dxsz < 1 || dxsz > INT32_MAX || dysz < 1 || dysz > INT32_MAX)
     433             :             throw CPLString("Too many levels, resulting raster size exceeds "
     434           0 :                             "the GDAL limit");
     435             : 
     436           3 :         nRasterXSize = int(dxsz);
     437           3 :         nRasterYSize = int(dysz);
     438             : 
     439           3 :         SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
     440           3 :         compression = oRoot.GetString("tileImageInfo/format");
     441           3 :         SetMetadataItem("COMPRESS", compression.c_str(), "IMAGE_STRUCTURE");
     442             : 
     443           3 :         nBands = EQUAL(compression, "JPEG") ? 3 : 4;
     444          15 :         for (int i = 1; i <= nBands; i++)
     445             :         {
     446          12 :             ECBand *band = new ECBand(this, i);
     447          12 :             SetBand(i, band);
     448             :         }
     449             :         // Keep 4 bundle files open
     450           3 :         bundles.resize(4);
     451             :         // Set the tile package flag in the bundles
     452          15 :         for (auto &bundle : bundles)
     453             :         {
     454          12 :             bundle.isTpkx = true;
     455             :         }
     456             :     }
     457           0 :     catch (CPLString &err)
     458             :     {
     459           0 :         error = CE_Failure;
     460           0 :         CPLError(error, CPLE_OpenFailed, "%s", err.c_str());
     461             :     }
     462           3 :     return error;
     463             : }
     464             : 
     465           9 : GDALDataset *ECDataset::Open(GDALOpenInfo *poOpenInfo)
     466             : {
     467           9 :     if (IdentifyXML(poOpenInfo))
     468             :     {
     469           3 :         CPLXMLNode *config = CPLParseXMLFile(poOpenInfo->pszFilename);
     470           3 :         if (!config)  // Error was reported from parsing XML
     471           0 :             return nullptr;
     472           3 :         CPLXMLNode *CacheInfo = CPLGetXMLNode(config, "=CacheInfo");
     473           3 :         if (!CacheInfo)
     474             :         {
     475           0 :             CPLError(
     476             :                 CE_Warning, CPLE_OpenFailed,
     477             :                 "Error parsing configuration, can't find CacheInfo element");
     478           0 :             CPLDestroyXMLNode(config);
     479           0 :             return nullptr;
     480             :         }
     481           3 :         auto ds = new ECDataset();
     482             :         ds->dname.Printf("%s/_alllayers",
     483           3 :                          CPLGetDirname(poOpenInfo->pszFilename));
     484           3 :         CPLErr error = ds->Initialize(CacheInfo);
     485           3 :         CPLDestroyXMLNode(config);
     486           3 :         if (CE_None != error)
     487             :         {
     488           0 :             delete ds;
     489           0 :             ds = nullptr;
     490             :         }
     491           3 :         return ds;
     492             :     }
     493           6 :     else if (IdentifyJSON(poOpenInfo))
     494             :     {
     495             :         // Recognize .tpkx file directly passed
     496           6 :         if (!STARTS_WITH(poOpenInfo->pszFilename, "/vsizip/") &&
     497             : #if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
     498           3 :             ENDS_WITH_CI(poOpenInfo->pszFilename, ".tpkx") &&
     499             : #endif
     500           3 :             memcmp(poOpenInfo->pabyHeader, "PK\x03\x04", 4) == 0)
     501             :         {
     502           6 :             GDALOpenInfo oOpenInfo((std::string("/vsizip/{") +
     503           9 :                                     poOpenInfo->pszFilename + "}/root.json")
     504             :                                        .c_str(),
     505           3 :                                    GA_ReadOnly);
     506           3 :             auto poDS = Open(&oOpenInfo);
     507           3 :             if (poDS)
     508           3 :                 poDS->SetDescription(poOpenInfo->pszFilename);
     509           3 :             return poDS;
     510             :         }
     511             : 
     512           6 :         CPLJSONDocument oJSONDocument;
     513           3 :         if (!oJSONDocument.Load(poOpenInfo->pszFilename))
     514             :         {
     515           0 :             CPLError(CE_Warning, CPLE_OpenFailed,
     516             :                      "Error parsing configuration");
     517           0 :             return nullptr;
     518             :         }
     519             : 
     520           6 :         const CPLJSONObject &oRoot = oJSONDocument.GetRoot();
     521           3 :         if (!oRoot.IsValid())
     522             :         {
     523           0 :             CPLError(CE_Warning, CPLE_OpenFailed, "Invalid json document root");
     524           0 :             return nullptr;
     525             :         }
     526             : 
     527           3 :         auto ds = new ECDataset();
     528           6 :         auto tileBundlesPath = oRoot.GetString("tileBundlesPath");
     529             :         // Strip leading relative path indicator (if present)
     530           3 :         if (tileBundlesPath.substr(0, 2) == "./")
     531             :         {
     532           3 :             tileBundlesPath.erase(0, 2);
     533             :         }
     534             : 
     535           3 :         ds->dname.Printf("%s/%s", CPLGetDirname(poOpenInfo->pszFilename),
     536           3 :                          tileBundlesPath.c_str());
     537           3 :         CPLErr error = ds->InitializeFromJSON(oRoot);
     538           3 :         if (CE_None != error)
     539             :         {
     540           0 :             delete ds;
     541           0 :             ds = nullptr;
     542             :         }
     543           3 :         return ds;
     544             :     }
     545           0 :     return nullptr;
     546             : }
     547             : 
     548             : // Fetch a reference to an initialized bundle, based on file name
     549             : // The returned bundle could still have an invalid file handle, if the
     550             : // target bundle is not valid
     551        3931 : Bundle &ECDataset::GetBundle(const char *fname)
     552             : {
     553        3947 :     for (auto &bundle : bundles)
     554             :     {
     555             :         // If a bundle is missing, it still occupies a slot, with fh == nullptr
     556        3943 :         if (EQUAL(bundle.name.c_str(), fname))
     557        3927 :             return bundle;
     558             :     }
     559             :     // Not found, look for an empty // missing slot
     560           4 :     for (auto &bundle : bundles)
     561             :     {
     562           4 :         if (nullptr == bundle.fh)
     563             :         {
     564           4 :             bundle.Init(fname);
     565           4 :             return bundle;
     566             :         }
     567             :     }
     568             :     // No empties, eject one
     569             :     // coverity[dont_call]
     570           0 :     Bundle &bundle = bundles[rand() % bundles.size()];
     571           0 :     bundle.Init(fname);
     572           0 :     return bundle;
     573             : }
     574             : 
     575         240 : ECBand::~ECBand()
     576             : {
     577         216 :     for (auto ovr : overviews)
     578          96 :         if (ovr)
     579          96 :             delete ovr;
     580         120 :     overviews.clear();
     581         240 : }
     582             : 
     583         120 : ECBand::ECBand(ECDataset *parent, int b, int level)
     584         120 :     : lvl(level), ci(GCI_Undefined)
     585             : {
     586             :     static const GDALColorInterp rgba[4] = {GCI_RedBand, GCI_GreenBand,
     587             :                                             GCI_BlueBand, GCI_AlphaBand};
     588             :     static const GDALColorInterp la[2] = {GCI_GrayIndex, GCI_AlphaBand};
     589         120 :     poDS = parent;
     590         120 :     nBand = b;
     591             : 
     592         120 :     double factor = parent->resolutions[0] / parent->resolutions[lvl];
     593         120 :     nRasterXSize = static_cast<int>(parent->nRasterXSize * factor + 0.5);
     594         120 :     nRasterYSize = static_cast<int>(parent->nRasterYSize * factor + 0.5);
     595         120 :     nBlockXSize = nBlockYSize = 256;
     596             : 
     597             :     // Default color interpretation
     598         120 :     assert(b - 1 >= 0);
     599         120 :     if (parent->nBands >= 3)
     600             :     {
     601         120 :         assert(b - 1 < static_cast<int>(CPL_ARRAYSIZE(rgba)));
     602         120 :         ci = rgba[b - 1];
     603             :     }
     604             :     else
     605             :     {
     606           0 :         assert(b - 1 < static_cast<int>(CPL_ARRAYSIZE(la)));
     607           0 :         ci = la[b - 1];
     608             :     }
     609         120 :     if (0 == lvl)
     610          24 :         AddOverviews();
     611         120 : }
     612             : 
     613          24 : void ECBand::AddOverviews()
     614             : {
     615          24 :     auto parent = reinterpret_cast<ECDataset *>(poDS);
     616         120 :     for (size_t i = 1; i < parent->resolutions.size(); i++)
     617             :     {
     618          96 :         ECBand *ovl = new ECBand(parent, nBand, int(i));
     619          96 :         if (!ovl)
     620           0 :             break;
     621          96 :         overviews.push_back(ovl);
     622             :     }
     623          24 : }
     624             : 
     625        3931 : CPLErr ECBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pData)
     626             : {
     627        3931 :     auto parent = reinterpret_cast<ECDataset *>(poDS);
     628        3931 :     auto &buffer = parent->tilebuffer;
     629        3931 :     auto TSZ = parent->TSZ;
     630        3931 :     auto BSZ = parent->BSZ;
     631        3931 :     size_t nBytes = size_t(TSZ) * TSZ;
     632             : 
     633        3931 :     buffer.resize(nBytes * parent->nBands);
     634             : 
     635        3931 :     int lxx = static_cast<int>(parent->resolutions.size() - lvl - 1);
     636             :     int bx, by;
     637        3931 :     bx = (nBlockXOff / BSZ) * BSZ;
     638        3931 :     by = (nBlockYOff / BSZ) * BSZ;
     639        7862 :     CPLString fname;
     640        7862 :     fname = CPLString().Printf("%s/L%02d/R%04xC%04x.bundle",
     641        3931 :                                parent->dname.c_str(), lxx, by, bx);
     642        3931 :     Bundle &bundle = parent->GetBundle(fname);
     643        3931 :     if (nullptr == bundle.fh)
     644             :     {  // This is not an error in general, bundles can be missing
     645          64 :         CPLDebug("ESRIC", "Can't open bundle %s", fname.c_str());
     646          64 :         memset(pData, 0, nBytes);
     647          64 :         return CE_None;
     648             :     }
     649        3867 :     int block = static_cast<int>((nBlockYOff % BSZ) * BSZ + (nBlockXOff % BSZ));
     650        3867 :     GUInt64 offset = bundle.index[block] & 0xffffffffffull;
     651        3867 :     GUInt64 size = bundle.index[block] >> 40;
     652        3867 :     if (0 == size)
     653             :     {
     654        3755 :         memset(pData, 0, nBytes);
     655        3755 :         return CE_None;
     656             :     }
     657         112 :     auto &fbuffer = parent->filebuffer;
     658         112 :     fbuffer.resize(size_t(size));
     659         112 :     VSIFSeekL(bundle.fh, offset, SEEK_SET);
     660         112 :     if (size != VSIFReadL(fbuffer.data(), size_t(1), size_t(size), bundle.fh))
     661             :     {
     662           0 :         CPLError(CE_Failure, CPLE_FileIO,
     663             :                  "Error reading tile, reading " CPL_FRMT_GUIB
     664             :                  " at " CPL_FRMT_GUIB,
     665             :                  GUInt64(size), GUInt64(offset));
     666           0 :         return CE_Failure;
     667             :     }
     668         224 :     CPLString magic;
     669             :     // Should use some sort of unique
     670         112 :     magic.Printf("/vsimem/esric_%p.tmp", this);
     671         112 :     auto mfh = VSIFileFromMemBuffer(magic.c_str(), fbuffer.data(), size, false);
     672         112 :     VSIFCloseL(mfh);
     673             :     // Can't open a raster by handle?
     674         112 :     auto inds = GDALOpen(magic.c_str(), GA_ReadOnly);
     675         112 :     if (!inds)
     676             :     {
     677           0 :         VSIUnlink(magic.c_str());
     678           0 :         CPLError(CE_Failure, CPLE_FileIO, "Error opening tile");
     679           0 :         return CE_Failure;
     680             :     }
     681             :     // Duplicate first band if not sufficient bands are provided
     682         112 :     auto inbands = GDALGetRasterCount(inds);
     683         112 :     int ubands[4] = {1, 1, 1, 1};
     684         112 :     int *usebands = nullptr;
     685         112 :     int bandcount = parent->nBands;
     686         112 :     GDALColorTableH hCT = nullptr;
     687         112 :     if (inbands != bandcount)
     688             :     {
     689             :         // Opaque if output expects alpha channel
     690          69 :         if (0 == bandcount % 2)
     691             :         {
     692          69 :             fill(buffer.begin(), buffer.end(), GByte(255));
     693          69 :             bandcount--;
     694             :         }
     695          69 :         if (3 == inbands)
     696             :         {
     697             :             // Lacking opacity, copy the first three bands
     698           4 :             ubands[1] = 2;
     699           4 :             ubands[2] = 3;
     700           4 :             usebands = ubands;
     701             :         }
     702          65 :         else if (1 == inbands)
     703             :         {
     704             :             // Grayscale, expecting color
     705          65 :             usebands = ubands;
     706             :             // Check for the color table of 1 band rasters
     707          65 :             hCT = GDALGetRasterColorTable(GDALGetRasterBand(inds, 1));
     708             :         }
     709             :     }
     710             : 
     711         112 :     auto errcode = CE_None;
     712         112 :     if (nullptr != hCT)
     713             :     {
     714             :         // Expand color indexed to RGB(A)
     715          64 :         errcode = GDALDatasetRasterIO(
     716          64 :             inds, GF_Read, 0, 0, TSZ, TSZ, buffer.data(), TSZ, TSZ, GDT_Byte, 1,
     717          64 :             usebands, parent->nBands, parent->nBands * TSZ, 1);
     718          64 :         if (CE_None == errcode)
     719             :         {
     720             :             GByte abyCT[4 * 256];
     721          64 :             GByte *pabyTileData = buffer.data();
     722          64 :             const int nEntries = std::min(256, GDALGetColorEntryCount(hCT));
     723         540 :             for (int i = 0; i < nEntries; i++)
     724             :             {
     725         476 :                 const GDALColorEntry *psEntry = GDALGetColorEntry(hCT, i);
     726         476 :                 abyCT[4 * i] = static_cast<GByte>(psEntry->c1);
     727         476 :                 abyCT[4 * i + 1] = static_cast<GByte>(psEntry->c2);
     728         476 :                 abyCT[4 * i + 2] = static_cast<GByte>(psEntry->c3);
     729         476 :                 abyCT[4 * i + 3] = static_cast<GByte>(psEntry->c4);
     730             :             }
     731       15972 :             for (int i = nEntries; i < 256; i++)
     732             :             {
     733       15908 :                 abyCT[4 * i] = 0;
     734       15908 :                 abyCT[4 * i + 1] = 0;
     735       15908 :                 abyCT[4 * i + 2] = 0;
     736       15908 :                 abyCT[4 * i + 3] = 0;
     737             :             }
     738             : 
     739          64 :             if (parent->nBands == 4)
     740             :             {
     741     4194370 :                 for (size_t i = 0; i < nBytes; i++)
     742             :                 {
     743     4194300 :                     const GByte byVal = pabyTileData[4 * i];
     744     4194300 :                     pabyTileData[4 * i] = abyCT[4 * byVal];
     745     4194300 :                     pabyTileData[4 * i + 1] = abyCT[4 * byVal + 1];
     746     4194300 :                     pabyTileData[4 * i + 2] = abyCT[4 * byVal + 2];
     747     4194300 :                     pabyTileData[4 * i + 3] = abyCT[4 * byVal + 3];
     748             :                 }
     749             :             }
     750           0 :             else if (parent->nBands == 3)
     751             :             {
     752           0 :                 for (size_t i = 0; i < nBytes; i++)
     753             :                 {
     754           0 :                     const GByte byVal = pabyTileData[3 * i];
     755           0 :                     pabyTileData[3 * i] = abyCT[4 * byVal];
     756           0 :                     pabyTileData[3 * i + 1] = abyCT[4 * byVal + 1];
     757           0 :                     pabyTileData[3 * i + 2] = abyCT[4 * byVal + 2];
     758             :                 }
     759             :             }
     760             :             else
     761             :             {
     762             :                 // Assuming grayscale output
     763           0 :                 for (size_t i = 0; i < nBytes; i++)
     764             :                 {
     765           0 :                     const GByte byVal = pabyTileData[i];
     766           0 :                     pabyTileData[i] = abyCT[4 * byVal];
     767             :                 }
     768             :             }
     769             :         }
     770             :     }
     771             :     else
     772             :     {
     773          48 :         errcode = GDALDatasetRasterIO(
     774          48 :             inds, GF_Read, 0, 0, TSZ, TSZ, buffer.data(), TSZ, TSZ, GDT_Byte,
     775          48 :             bandcount, usebands, parent->nBands, parent->nBands * TSZ, 1);
     776             :     }
     777         112 :     GDALClose(inds);
     778         112 :     VSIUnlink(magic.c_str());
     779             :     // Error while unpacking tile
     780         112 :     if (CE_None != errcode)
     781           0 :         return errcode;
     782             : 
     783         560 :     for (int iBand = 1; iBand <= parent->nBands; iBand++)
     784             :     {
     785         448 :         auto band = parent->GetRasterBand(iBand);
     786         448 :         if (lvl)
     787          52 :             band = band->GetOverview(lvl - 1);
     788         448 :         GDALRasterBlock *poBlock = nullptr;
     789         448 :         if (band != this)
     790             :         {
     791         336 :             poBlock = band->GetLockedBlockRef(nBlockXOff, nBlockYOff, 1);
     792         336 :             if (poBlock != nullptr)
     793             :             {
     794         336 :                 GDALCopyWords(buffer.data() + iBand - 1, GDT_Byte,
     795             :                               parent->nBands, poBlock->GetDataRef(), GDT_Byte,
     796             :                               1, TSZ * TSZ);
     797         336 :                 poBlock->DropLock();
     798             :             }
     799             :         }
     800             :         else
     801             :         {
     802         112 :             GDALCopyWords(buffer.data() + iBand - 1, GDT_Byte, parent->nBands,
     803             :                           pData, GDT_Byte, 1, TSZ * TSZ);
     804             :         }
     805             :     }
     806             : 
     807         112 :     return CE_None;
     808             : }  // IReadBlock
     809             : 
     810             : }  // namespace ESRIC
     811             : 
     812        1523 : void CPL_DLL GDALRegister_ESRIC()
     813             : {
     814        1523 :     if (GDALGetDriverByName("ESRIC") != nullptr)
     815         301 :         return;
     816             : 
     817        1222 :     auto poDriver = new GDALDriver;
     818             : 
     819        1222 :     poDriver->SetDescription("ESRIC");
     820        1222 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
     821        1222 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
     822        1222 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Esri Compact Cache");
     823             : 
     824        1222 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "json tpkx");
     825             : 
     826        1222 :     poDriver->pfnIdentify = ESRIC::Identify;
     827        1222 :     poDriver->pfnOpen = ESRIC::ECDataset::Open;
     828        1222 :     poDriver->pfnDelete = ESRIC::Delete;
     829             : 
     830        1222 :     GetGDALDriverManager()->RegisterDriver(poDriver);
     831             : }

Generated by: LCOV version 1.14