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