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