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