Coverage Report

Created: 2026-02-14 09:00

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