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