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 : }
|