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