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