Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: Driver for ASTM E57 3D file format (image part)
5 : * Author: Even Rouault <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "gdal_frmts.h"
14 : #include "gdal_pam.h"
15 : #include "gdal_proxy.h"
16 : #include "cpl_minixml.h"
17 : #include "cpl_string.h"
18 : #include "cpl_vsi_virtual.h"
19 :
20 : #include <algorithm>
21 : #include <array>
22 : #include <cinttypes>
23 : #include <limits>
24 : #include <set>
25 :
26 : // Useful links:
27 : // - https://paulbourke.net/dataformats/e57/
28 : // - https://paulbourke.net/dataformats/e57/2011-huber-e57-v3.pdf
29 : // - http://www.libe57.org/data.html
30 : // - https://github.com/asmaloney/libE57Format
31 : // - https://store.astm.org/e2807-11r19e01.html
32 :
33 : namespace
34 : {
35 :
36 : constexpr const char *E57_PREFIX = "E57:";
37 :
38 : /* EndOfPage size */
39 : constexpr int E57_EOP_SIZE = 4;
40 :
41 : /************************************************************************/
42 : /* E57ImageDesc() */
43 : /************************************************************************/
44 :
45 : struct E57ImageDesc
46 : {
47 : std::string osDriverName{};
48 : int nWidth = 0;
49 : int nHeight = 0;
50 : uint64_t nOffset = 0;
51 : uint64_t nLength = 0;
52 : uint64_t nMaskOffset = 0;
53 : uint64_t nMaskLength = 0;
54 : CPLStringList aosExtraMD{};
55 : };
56 :
57 : /************************************************************************/
58 : /* IsValidPhysicalOffsetForBeginningOfSection() */
59 : /************************************************************************/
60 :
61 3016 : static bool IsValidPhysicalOffsetForBeginningOfSection(uint64_t nOffset,
62 : uint64_t pageSize)
63 : {
64 : // The start of a section cannot be one of the last 3 bytes of a page
65 3016 : return (nOffset % pageSize) < pageSize - (E57_EOP_SIZE - 1);
66 : }
67 :
68 : /************************************************************************/
69 : /* ConvertE57LogicalOffsetToPhysical() */
70 : /************************************************************************/
71 :
72 : // Convert nLogicalOffset (measured from nBasePhysicalOffset) to physical offset
73 : // E57 files are divided into physical pages. The last 4 bytes of every page
74 : // are a CRC32 checksum. This function calculates the physical jump required
75 : // to skip these checksums when moving through a logical stream of data.
76 9035 : static uint64_t ConvertE57LogicalOffsetToPhysical(uint64_t nBasePhysicalOffset,
77 : uint64_t nLogicalOffset,
78 : uint64_t nPhysicalPageSize)
79 : {
80 9035 : const auto nLogicalPageSize = nPhysicalPageSize - E57_EOP_SIZE;
81 9035 : const auto numPagesCrossed =
82 9035 : ((nBasePhysicalOffset % nPhysicalPageSize) + nLogicalOffset) /
83 : nLogicalPageSize;
84 9035 : return nBasePhysicalOffset + nLogicalOffset +
85 9035 : numPagesCrossed * E57_EOP_SIZE;
86 : }
87 :
88 : /************************************************************************/
89 : /* GDAL_E57Dataset */
90 : /************************************************************************/
91 :
92 : class GDAL_E57RasterBand;
93 :
94 : class GDAL_E57Dataset final : public GDALProxyDataset
95 : {
96 : public:
97 : GDAL_E57Dataset(std::unique_ptr<GDALDataset> poUnderlyingDataset,
98 : std::unique_ptr<GDALDataset> poMaskDS,
99 : const E57ImageDesc &sE57ImageDesc, std::string osXML);
100 :
101 : GDALDriver *GetDriver() override;
102 :
103 1 : char **GetMetadataDomainList() override
104 : {
105 1 : char **papszList = GDALProxyDataset::GetMetadataDomainList();
106 1 : papszList = CSLAddString(papszList, "xml:E57");
107 1 : return papszList;
108 : }
109 :
110 6 : CSLConstList GetMetadata(const char *pszDomain) override
111 : {
112 6 : if (!pszDomain || pszDomain[0] == 0)
113 : {
114 2 : if (!m_bMDSet)
115 : {
116 1 : m_bMDSet = true;
117 : m_aosMD.Assign(
118 : CSLDuplicate(GDALProxyDataset::GetMetadata(pszDomain)),
119 1 : true);
120 16 : for (const auto &[pszKey, pszValue] :
121 17 : cpl::IterateNameValue(m_sE57ImageDesc.aosExtraMD))
122 8 : m_aosMD.SetNameValue(pszKey, pszValue);
123 : }
124 2 : return m_aosMD.List();
125 : }
126 4 : else if (EQUAL(pszDomain, "xml:E57"))
127 : {
128 3 : return const_cast<char **>(m_apszXMLE57.data());
129 : }
130 :
131 1 : return GDALProxyDataset::GetMetadata(pszDomain);
132 : }
133 :
134 1 : const char *GetMetadataItem(const char *pszName,
135 : const char *pszDomain) override
136 : {
137 1 : return CSLFetchNameValue(GetMetadata(pszDomain), pszName);
138 : }
139 :
140 : static int Identify(GDALOpenInfo *poOpenInfo);
141 : static GDALDataset *Open(GDALOpenInfo *poOpenInfo);
142 :
143 : protected:
144 1006 : GDALDataset *RefUnderlyingDataset() const override
145 : {
146 1006 : return m_poUnderlyingDataset.get();
147 : }
148 :
149 : private:
150 : friend class GDAL_E57RasterBand;
151 : std::unique_ptr<GDALDataset> m_poUnderlyingDataset{};
152 : std::unique_ptr<GDALDataset> m_poMaskDS{};
153 : const E57ImageDesc m_sE57ImageDesc;
154 : CPLStringList m_aosMD{};
155 : const std::string m_osXML;
156 : std::array<const char *, 2> m_apszXMLE57{nullptr, nullptr};
157 : bool m_bMDSet = false;
158 : };
159 :
160 0 : GDALDriver *GDAL_E57Dataset::GetDriver()
161 : {
162 : // Short-circuit proxying, so as not to get the PNG/JPEG driver
163 0 : return GDALDataset::GetDriver();
164 : }
165 :
166 : /************************************************************************/
167 : /* GDAL_E57FileHandle */
168 : /************************************************************************/
169 :
170 : class GDAL_E57FileHandle final : public VSIVirtualHandle
171 : {
172 : public:
173 3002 : GDAL_E57FileHandle(VSIVirtualHandleUniquePtr poRawFP,
174 : uint64_t nBasePhysicalOffset, uint64_t nLength,
175 : uint64_t nPageSize, int nSectionHeaderSize)
176 6004 : : m_poRawFP(std::move(poRawFP)),
177 : m_nBasePhysicalOffset(nBasePhysicalOffset), m_nLength(nLength),
178 3002 : m_nPageSize(nPageSize), m_nSectionHeaderSize(nSectionHeaderSize)
179 : {
180 3002 : }
181 :
182 1002 : VSIVirtualHandleUniquePtr ReacquireRawFP()
183 : {
184 1002 : VSIVirtualHandleUniquePtr ret;
185 1002 : std::swap(ret, m_poRawFP);
186 1002 : return ret;
187 : }
188 :
189 4052 : int Seek(vsi_l_offset nOffset, int nWhence) override
190 : {
191 4052 : m_bEOF = false;
192 4052 : if (nWhence == SEEK_SET)
193 4041 : m_nPos = nOffset;
194 11 : else if (nWhence == SEEK_CUR)
195 9 : m_nPos += nOffset;
196 : else
197 : {
198 2 : CPLAssert(nWhence == SEEK_END);
199 2 : CPLAssert(nOffset == 0);
200 2 : m_nPos = m_nLength;
201 : }
202 4052 : return 0;
203 : }
204 :
205 : vsi_l_offset Tell() override;
206 :
207 14140 : size_t Read(void *pBuffer, size_t nToReadTotal) override
208 : {
209 10042 : if (m_bEOF || m_nPos > m_nLength ||
210 10042 : m_nBasePhysicalOffset >
211 34224 : std::numeric_limits<uint64_t>::max() - m_nPos ||
212 10042 : m_nPos >
213 10042 : std::numeric_limits<uint64_t>::max() - m_nSectionHeaderSize)
214 : {
215 4098 : m_bEOF = true;
216 4098 : return 0;
217 : }
218 10042 : if (nToReadTotal == 0)
219 1007 : return 0;
220 :
221 : // Align our raw file pointer to the physical location of the current
222 : // logical position, taking the E57 page/CRC overhead into account.
223 9035 : const auto nPhysicalOffset = ConvertE57LogicalOffsetToPhysical(
224 9035 : m_nBasePhysicalOffset, m_nPos + m_nSectionHeaderSize, m_nPageSize);
225 :
226 9035 : if (m_poRawFP->Seek(nPhysicalOffset, SEEK_SET) != 0)
227 0 : return 0;
228 :
229 9035 : GByte *pabyBuffer = static_cast<GByte *>(pBuffer);
230 : // Ingest number of requested bytes, by skipping the last 4 bytes of
231 : // each physical page.
232 9035 : size_t nReadTotal = 0;
233 17081 : while (nReadTotal < nToReadTotal)
234 : {
235 11040 : const uint64_t nCurPos = m_poRawFP->Tell();
236 : const uint64_t nEndOfPagePhysicalOffset =
237 11040 : cpl::div_round_up(nCurPos + 1, m_nPageSize) * m_nPageSize;
238 11040 : CPLAssert(nEndOfPagePhysicalOffset - nCurPos >= E57_EOP_SIZE);
239 :
240 : const size_t nToReadChunk = static_cast<size_t>(
241 22080 : std::min(static_cast<uint64_t>(nToReadTotal - nReadTotal),
242 11040 : nEndOfPagePhysicalOffset - nCurPos - E57_EOP_SIZE));
243 : const size_t nReadChunk =
244 11040 : m_poRawFP->Read(pabyBuffer + nReadTotal, nToReadChunk);
245 11040 : m_nPos += nReadChunk;
246 11040 : nReadTotal += nReadChunk;
247 11040 : if (m_nPos > m_nLength)
248 : {
249 1998 : nReadTotal -= static_cast<size_t>(m_nPos - m_nLength);
250 1998 : m_nPos = m_nLength;
251 1998 : m_bEOF = true;
252 1998 : break;
253 : }
254 9042 : if (nReadChunk != nToReadChunk)
255 : {
256 996 : m_bEOF = true;
257 996 : break;
258 : }
259 8046 : if (nReadTotal < nToReadTotal)
260 : {
261 : // Skip 4 bytes of CRC
262 : GByte abyCRC32[E57_EOP_SIZE];
263 2005 : if (m_poRawFP->Read(abyCRC32, sizeof(abyCRC32)) !=
264 : sizeof(abyCRC32))
265 : {
266 0 : CPLDebug("E57", "Cannot read CRC");
267 0 : break;
268 : }
269 : }
270 : }
271 9035 : return nReadTotal;
272 : }
273 :
274 6137 : int Eof() override
275 : {
276 6137 : return m_bEOF;
277 : }
278 :
279 6137 : int Error() override
280 : {
281 6137 : return m_poRawFP->Error();
282 : }
283 :
284 1995 : int Close() override
285 : {
286 1995 : int nRet = 0;
287 1995 : if (m_poRawFP)
288 1995 : nRet = m_poRawFP->Close();
289 1995 : m_poRawFP.reset();
290 1995 : return nRet;
291 : }
292 :
293 6137 : void ClearErr() override
294 : {
295 6137 : m_poRawFP->ClearErr();
296 6137 : }
297 :
298 1007 : size_t Write(const void *, size_t) override
299 : {
300 1007 : return 0;
301 : }
302 :
303 : private:
304 : VSIVirtualHandleUniquePtr m_poRawFP{};
305 : // physical offset of the start of the subfile
306 : const uint64_t m_nBasePhysicalOffset;
307 : const uint64_t m_nLength; // logical length of the subfile
308 : const uint64_t m_nPageSize;
309 : const int m_nSectionHeaderSize;
310 : uint64_t m_nPos = 0; // logical offset within the subfile
311 : bool m_bEOF = false;
312 : };
313 :
314 : // Offline definition to please -Wweak-vtables
315 6142 : vsi_l_offset GDAL_E57FileHandle::Tell()
316 : {
317 6142 : return m_nPos;
318 : }
319 :
320 : /************************************************************************/
321 : /* GDAL_E57RasterBand */
322 : /************************************************************************/
323 :
324 : class GDAL_E57RasterBand final : public GDALProxyRasterBand
325 : {
326 : public:
327 1001 : explicit GDAL_E57RasterBand(GDALRasterBand *poUnderlyingBand)
328 1001 : : m_poUnderlyingBand(poUnderlyingBand)
329 : {
330 1001 : nRasterXSize = m_poUnderlyingBand->GetXSize();
331 1001 : nRasterYSize = m_poUnderlyingBand->GetYSize();
332 1001 : eDataType = m_poUnderlyingBand->GetRasterDataType();
333 1001 : m_poUnderlyingBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
334 1001 : }
335 :
336 2 : int GetMaskFlags() override
337 : {
338 2 : auto poGDS = cpl::down_cast<GDAL_E57Dataset *>(poDS);
339 2 : if (poGDS->m_poMaskDS)
340 1 : return GMF_PER_DATASET;
341 1 : return GDALProxyRasterBand::GetMaskFlags();
342 : }
343 :
344 2 : GDALRasterBand *GetMaskBand() override
345 : {
346 2 : auto poGDS = cpl::down_cast<GDAL_E57Dataset *>(poDS);
347 2 : if (poGDS->m_poMaskDS)
348 1 : return poGDS->m_poMaskDS->GetRasterBand(1);
349 1 : return GDALProxyRasterBand::GetMaskBand();
350 : }
351 :
352 : protected:
353 : GDALRasterBand *RefUnderlyingRasterBand(bool bForceOpen) const override;
354 :
355 : private:
356 : GDALRasterBand *const m_poUnderlyingBand;
357 : CPL_DISALLOW_COPY_ASSIGN(GDAL_E57RasterBand)
358 : };
359 :
360 47 : GDALRasterBand *GDAL_E57RasterBand::RefUnderlyingRasterBand(bool) const
361 : {
362 47 : return m_poUnderlyingBand;
363 : }
364 :
365 1001 : GDAL_E57Dataset::GDAL_E57Dataset(
366 : std::unique_ptr<GDALDataset> poUnderlyingDataset,
367 : std::unique_ptr<GDALDataset> poMaskDS, const E57ImageDesc &sE57ImageDesc,
368 1001 : std::string osXML)
369 1001 : : m_poUnderlyingDataset(std::move(poUnderlyingDataset)),
370 1001 : m_poMaskDS(std::move(poMaskDS)), m_sE57ImageDesc(sE57ImageDesc),
371 1001 : m_osXML(std::move(osXML))
372 : {
373 1001 : nRasterXSize = m_poUnderlyingDataset->GetRasterXSize();
374 1001 : nRasterYSize = m_poUnderlyingDataset->GetRasterYSize();
375 2002 : for (int i = 0; i < m_poUnderlyingDataset->GetRasterCount(); ++i)
376 : {
377 1001 : SetBand(i + 1, std::make_unique<GDAL_E57RasterBand>(
378 2002 : m_poUnderlyingDataset->GetRasterBand(i + 1)));
379 : }
380 1001 : m_apszXMLE57[0] = m_osXML.c_str();
381 1001 : }
382 :
383 : /************************************************************************/
384 : /* Identify() */
385 : /************************************************************************/
386 :
387 : /* static */
388 61185 : int GDAL_E57Dataset::Identify(GDALOpenInfo *poOpenInfo)
389 : {
390 64950 : return (poOpenInfo->nHeaderBytes >= 1024 &&
391 3765 : memcmp(poOpenInfo->pabyHeader, "ASTM-E57", 8) == 0 &&
392 122370 : poOpenInfo->fpL != nullptr) ||
393 120321 : STARTS_WITH_CI(poOpenInfo->pszFilename, E57_PREFIX);
394 : }
395 :
396 : /************************************************************************/
397 : /* asMDDescriptors */
398 : /************************************************************************/
399 :
400 : static const struct
401 : {
402 : const char *pszXMLPath;
403 : const char *pszMDItem;
404 : } asMDDescriptors[] = {
405 : {"name", "NAME"},
406 : {"description", "DESCRIPTION"},
407 : {"sensorVendor", "SENSOR_VENDOR"},
408 : {"sensorModel", "SENSOR_MODEL"},
409 : {"sensorSerialNumber", "SENSOR_SERIAL_NUMBER"},
410 : {"associatedData3DGuid", "ASSOCIATED_DATA_3D_GUID"},
411 : {"acquisitionDateTime.dateTimeValue", "ACQUISITION_DATE_TIME"},
412 : {"pose.rotation.w", "POSE_ROTATION_W"},
413 : {"pose.rotation.x", "POSE_ROTATION_X"},
414 : {"pose.rotation.y", "POSE_ROTATION_Y"},
415 : {"pose.rotation.z", "POSE_ROTATION_Z"},
416 : {"pose.translation.x", "POSE_TRANSLATION_X"},
417 : {"pose.translation.y", "POSE_TRANSLATION_Y"},
418 : {"pose.translation.z", "POSE_TRANSLATION_Z"},
419 : {"{rep}.pixelWidth", "PIXEL_WIDTH"},
420 : {"{rep}.pixelHeight", "PIXEL_HEIGHT"},
421 : {"{rep}.focalLength", "FOCAL_LENGTH"},
422 : {"{rep}.principalPointX", "PRINCIPAL_POINT_X"},
423 : {"{rep}.principalPointY", "PRINCIPAL_POINT_Y"},
424 : {"{rep}.radius", "RADIUS"},
425 : };
426 :
427 : /************************************************************************/
428 : /* Open() */
429 : /************************************************************************/
430 :
431 : /* static */
432 1032 : GDALDataset *GDAL_E57Dataset::Open(GDALOpenInfo *poOpenInfo)
433 : {
434 1032 : if (!Identify(poOpenInfo))
435 0 : return nullptr;
436 1032 : if (poOpenInfo->eAccess == GA_Update)
437 : {
438 1 : CPLError(CE_Failure, CPLE_NotSupported,
439 : "E57 driver does not support updates");
440 1 : return nullptr;
441 : }
442 :
443 2062 : std::string osSubDSName;
444 1031 : std::unique_ptr<GDALOpenInfo> poOpenInfoSubDS; // keep in that scope
445 2062 : std::string osPhysicalFilename(poOpenInfo->pszFilename);
446 1031 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, E57_PREFIX))
447 : {
448 : const CPLStringList aosTokens(
449 12 : CSLTokenizeString2(poOpenInfo->pszFilename + strlen(E57_PREFIX),
450 12 : ":", CSLT_HONOURSTRINGS));
451 12 : if (aosTokens.size() != 2)
452 : {
453 2 : CPLError(CE_Failure, CPLE_AppDefined,
454 : "Invalid E57 subdataset syntax");
455 2 : return nullptr;
456 : }
457 :
458 10 : osPhysicalFilename = aosTokens[0];
459 10 : poOpenInfoSubDS = std::make_unique<GDALOpenInfo>(
460 10 : osPhysicalFilename.c_str(), GA_ReadOnly);
461 10 : osSubDSName = aosTokens[1];
462 10 : poOpenInfo = poOpenInfoSubDS.get();
463 : // cppcheck-suppress knownConditionTrueFalse
464 10 : if (!Identify(poOpenInfo) || !poOpenInfo->fpL)
465 1 : return nullptr;
466 : }
467 :
468 2056 : VSIVirtualHandleUniquePtr fp(poOpenInfo->fpL);
469 1028 : poOpenInfo->fpL = nullptr;
470 :
471 : // Parse E57 header
472 1028 : const uint32_t nMajorVersion = CPL_LSBUINT32PTR(poOpenInfo->pabyHeader + 8);
473 1028 : const uint32_t nMinorVersion =
474 1028 : CPL_LSBUINT32PTR(poOpenInfo->pabyHeader + 12);
475 1028 : CPLDebug("E57", "E57 v%d.%d file", nMajorVersion, nMinorVersion);
476 :
477 : uint64_t filePhysicalLength;
478 1028 : memcpy(&filePhysicalLength, poOpenInfo->pabyHeader + 16,
479 : sizeof(filePhysicalLength));
480 1028 : CPL_LSBPTR64(&filePhysicalLength);
481 1028 : CPLDebugOnly("E57", "filePhysicalLength = %" PRIu64, filePhysicalLength);
482 :
483 : uint64_t xmlPhysicalOffset;
484 1028 : memcpy(&xmlPhysicalOffset, poOpenInfo->pabyHeader + 24,
485 : sizeof(xmlPhysicalOffset));
486 1028 : CPL_LSBPTR64(&xmlPhysicalOffset);
487 1028 : CPLDebugOnly("E57", "xmlPhysicalOffset = %" PRIu64, xmlPhysicalOffset);
488 :
489 : uint64_t xmlLogicalLength;
490 1028 : memcpy(&xmlLogicalLength, poOpenInfo->pabyHeader + 32,
491 : sizeof(xmlLogicalLength));
492 1028 : CPL_LSBPTR64(&xmlLogicalLength);
493 1028 : CPLDebugOnly("E57", "xmlLogicalLength = %" PRIu64, xmlLogicalLength);
494 :
495 : uint64_t pageSize;
496 1028 : memcpy(&pageSize, poOpenInfo->pabyHeader + 40, sizeof(pageSize));
497 1028 : CPL_LSBPTR64(&pageSize);
498 1028 : CPLDebugOnly("E57", "pageSize = %" PRIu64, pageSize);
499 : // The page size NEEDS to be strictly greater than E57_EOP_SIZE (4), and
500 : // the nominal page size is 1024 bytes
501 1028 : constexpr uint64_t NOMINAL_PAGE_SIZE = 1024;
502 1028 : constexpr uint64_t MAX_LARGE_PAGE_SIZE = 1024 * 1024; // arbitrary
503 1028 : if (pageSize < NOMINAL_PAGE_SIZE || pageSize > MAX_LARGE_PAGE_SIZE ||
504 1022 : (pageSize % 4) != 0)
505 : {
506 7 : CPLError(CE_Failure, CPLE_NotSupported,
507 : "E57: invalid page size: %" PRIu64, pageSize);
508 7 : return nullptr;
509 : }
510 :
511 1021 : if (!IsValidPhysicalOffsetForBeginningOfSection(xmlPhysicalOffset,
512 : pageSize))
513 : {
514 0 : CPLError(CE_Failure, CPLE_NotSupported,
515 : "E57: invalid xmlPhysicalOffset: %" PRIu64, xmlPhysicalOffset);
516 0 : return nullptr;
517 : }
518 :
519 1021 : constexpr size_t SIZEOF_E57_FILE_HEADER = 48;
520 3056 : if (xmlLogicalLength > filePhysicalLength ||
521 1014 : xmlLogicalLength > std::numeric_limits<size_t>::max() - 1 ||
522 3049 : xmlPhysicalOffset < SIZEOF_E57_FILE_HEADER ||
523 1014 : xmlPhysicalOffset > filePhysicalLength - xmlLogicalLength)
524 : {
525 14 : CPLError(CE_Failure, CPLE_AppDefined,
526 : "E57: invalid "
527 : "filePhysicalLength/xmlPhysicalOffset/xmlLogicalLength");
528 14 : return nullptr;
529 : }
530 :
531 : // Arbitrary threshold above which we check the consistency of the declared
532 : // size of the XML section w.r.t. whole file size
533 1007 : constexpr size_t XML_THRESHOLD_SIZE = 100 * 1024 * 1024;
534 1007 : if (xmlLogicalLength > XML_THRESHOLD_SIZE &&
535 0 : !(fp->Seek(0, SEEK_END) == 0 && fp->Tell() == filePhysicalLength &&
536 1007 : fp->Seek(0, SEEK_SET) == 0))
537 : {
538 0 : CPLError(CE_Failure, CPLE_AppDefined, "E57: file too short");
539 0 : return nullptr;
540 : }
541 :
542 2014 : std::string osXML;
543 : try
544 : {
545 1007 : osXML.resize(static_cast<size_t>(xmlLogicalLength));
546 : }
547 0 : catch (const std::exception &)
548 : {
549 0 : CPLError(CE_Failure, CPLE_OutOfMemory, "E57: out of memory");
550 0 : return nullptr;
551 : }
552 :
553 : auto poE57XmlFile = std::make_unique<GDAL_E57FileHandle>(
554 2014 : std::move(fp), xmlPhysicalOffset, xmlLogicalLength, pageSize, 0);
555 :
556 : #ifdef DEBUG
557 : {
558 : // Quick actions just to increase test coverage
559 1007 : CPLAssert(poE57XmlFile->Tell() == 0);
560 1007 : char chDummy = 0;
561 1007 : CPLAssert(poE57XmlFile->Read(&chDummy, 0) == 0);
562 1007 : CPL_IGNORE_RET_VAL(chDummy);
563 1007 : CPLAssert(!poE57XmlFile->Eof());
564 1007 : CPLAssert(!poE57XmlFile->Error());
565 1007 : poE57XmlFile->ClearErr();
566 1007 : CPLAssert(poE57XmlFile->Write("", 0) == 0);
567 : }
568 : #endif
569 :
570 1007 : if (poE57XmlFile->Read(osXML.data(),
571 1007 : static_cast<size_t>(xmlLogicalLength)) !=
572 : static_cast<size_t>(xmlLogicalLength))
573 : {
574 0 : CPLError(CE_Failure, CPLE_AppDefined, "E57: cannot read XML");
575 0 : return nullptr;
576 : }
577 :
578 : #ifdef DEBUG
579 1007 : if (EQUAL(CPLGetConfigOption("CPL_DEBUG", ""), "E57"))
580 : {
581 0 : fprintf(stderr, "XML: %s\n", osXML.c_str()); /*ok*/
582 : }
583 : #endif
584 :
585 2014 : CPLXMLTreeCloser poRoot(CPLParseXMLString(osXML.c_str()));
586 : const CPLXMLNode *psImages2D =
587 1007 : CPLGetXMLNode(poRoot.get(), "=e57Root.images2D");
588 2014 : std::vector<E57ImageDesc> asE57ImageDesc;
589 2014 : std::set<std::string> aoSetNames;
590 1007 : size_t iCounter = 0;
591 : // Iterate through images
592 1007 : const CPLXMLNode *psIter = psImages2D;
593 1007 : if (psIter)
594 1004 : psIter = psIter->psChild;
595 4029 : for (/* */; psIter; psIter = psIter->psNext)
596 : {
597 3022 : if (psIter->eType == CXT_Element &&
598 1014 : strcmp(psIter->pszValue, "vectorChild") == 0)
599 : {
600 1014 : const CPLXMLNode *psRep = nullptr;
601 10 : for (const char *pszNodeName :
602 : {"sphericalRepresentation", "pinholeRepresentation",
603 1024 : "cylindricalRepresentation", "visualReferenceRepresentation"})
604 : {
605 1024 : psRep = CPLGetXMLNode(psIter, pszNodeName);
606 1024 : if (psRep)
607 1014 : break;
608 : }
609 1014 : if (psRep)
610 : {
611 1014 : const CPLXMLNode *psImage = CPLGetXMLNode(psRep, "jpegImage");
612 1014 : const bool bIsJPEG = psImage != nullptr;
613 1014 : if (!psImage)
614 10 : psImage = CPLGetXMLNode(psRep, "pngImage");
615 1014 : if (psImage)
616 : {
617 : const char *pszFileOffset =
618 1014 : CPLGetXMLValue(psImage, "fileOffset", nullptr);
619 : const char *pszLength =
620 1014 : CPLGetXMLValue(psImage, "length", nullptr);
621 1014 : if (pszFileOffset && pszLength)
622 : {
623 1014 : E57ImageDesc desc;
624 1014 : desc.osDriverName = bIsJPEG ? "JPEG" : "PNG";
625 : desc.aosExtraMD.SetNameValue(
626 : "REPRESENTATION_TYPE",
627 1014 : CPLString(psRep->pszValue)
628 2028 : .replaceAll("Representation", "")
629 1014 : .c_str());
630 21294 : for (const auto &sMDDescriptor : asMDDescriptors)
631 : {
632 20280 : if (const char *pszValue = CPLGetXMLValue(
633 : psIter,
634 40560 : CPLString(sMDDescriptor.pszXMLPath)
635 40560 : .replaceAll("{rep}", psRep->pszValue)
636 : .c_str(),
637 : nullptr))
638 : desc.aosExtraMD.SetNameValue(
639 6988 : sMDDescriptor.pszMDItem, pszValue);
640 : }
641 : const char *pszName =
642 1014 : desc.aosExtraMD.FetchNameValue("NAME");
643 1014 : if (pszName)
644 1014 : aoSetNames.insert(pszName);
645 1014 : desc.nOffset =
646 1014 : std::strtoull(pszFileOffset, nullptr, 10);
647 1014 : desc.nLength = std::strtoull(pszLength, nullptr, 10);
648 1014 : desc.nWidth =
649 1014 : atoi(CPLGetXMLValue(psRep, "imageWidth", ""));
650 1014 : desc.nHeight =
651 1014 : atoi(CPLGetXMLValue(psRep, "imageHeight", ""));
652 :
653 : const CPLXMLNode *psMask =
654 1014 : CPLGetXMLNode(psRep, "imageMask");
655 1014 : if (psMask)
656 : {
657 : const char *pszMaskFileOffset =
658 994 : CPLGetXMLValue(psMask, "fileOffset", nullptr);
659 : const char *pszMaskLength =
660 994 : CPLGetXMLValue(psMask, "length", nullptr);
661 994 : if (pszMaskFileOffset && pszMaskLength)
662 : {
663 994 : desc.nMaskOffset = std::strtoull(
664 : pszMaskFileOffset, nullptr, 10);
665 994 : desc.nMaskLength =
666 994 : std::strtoull(pszMaskLength, nullptr, 10);
667 : }
668 : }
669 :
670 1014 : ++iCounter;
671 1014 : constexpr size_t MAX_IMAGES = 10000;
672 1014 : if (iCounter > MAX_IMAGES)
673 : {
674 0 : CPLError(CE_Failure, CPLE_NotSupported,
675 : "Too many images");
676 0 : break;
677 : }
678 1032 : if (osSubDSName.empty() ||
679 28 : ((pszName && osSubDSName == pszName) ||
680 1024 : (osSubDSName == std::to_string(iCounter))))
681 : {
682 1004 : asE57ImageDesc.push_back(std::move(desc));
683 : }
684 : }
685 : }
686 : }
687 : }
688 : }
689 :
690 1007 : if (asE57ImageDesc.empty())
691 : {
692 4 : if (osSubDSName.empty())
693 : {
694 3 : CPLDebug("E57", "No image found");
695 : }
696 : else
697 : {
698 1 : CPLError(CE_Failure, CPLE_AppDefined, "Subdataset %s not found",
699 : osSubDSName.c_str());
700 : }
701 4 : return nullptr;
702 : }
703 1003 : else if (asE57ImageDesc.size() == 1)
704 : {
705 1002 : if (!IsValidPhysicalOffsetForBeginningOfSection(
706 1002 : asE57ImageDesc[0].nOffset, pageSize))
707 : {
708 0 : CPLError(CE_Failure, CPLE_NotSupported,
709 : "E57: invalid image offset: %" PRIu64,
710 0 : asE57ImageDesc[0].nOffset);
711 0 : return nullptr;
712 : }
713 :
714 1002 : CPLDebugOnly("E57", "Image physical offset: %" PRIu64,
715 : asE57ImageDesc[0].nOffset);
716 1002 : CPLDebugOnly("E57", "Image logical length: %" PRIu64,
717 : asE57ImageDesc[0].nLength);
718 1002 : constexpr int E57_SIZEOF_BINARY_SECTION_HEADER = 16;
719 : // Create a file handle that implements physical-to-logical translation
720 : // by skipping CRCs transparently.
721 : auto poE57File = std::make_unique<GDAL_E57FileHandle>(
722 1002 : poE57XmlFile->ReacquireRawFP(), asE57ImageDesc[0].nOffset,
723 1002 : asE57ImageDesc[0].nLength, pageSize,
724 2004 : E57_SIZEOF_BINARY_SECTION_HEADER);
725 :
726 : GDALOpenInfo oOpenInfo(osPhysicalFilename.c_str(),
727 : GDAL_OF_RASTER | GDAL_OF_INTERNAL,
728 2004 : std::move(poE57File));
729 1002 : const char *const apszAllowedDrivers[] = {
730 1002 : asE57ImageDesc[0].osDriverName.c_str(), nullptr};
731 2004 : CPLStringList aosOpenOptions;
732 1002 : if (!osSubDSName.empty())
733 : {
734 : aosOpenOptions.SetNameValue("@PHYSICAL_FILENAME",
735 8 : osPhysicalFilename.c_str());
736 : aosOpenOptions.SetNameValue("@SUBDATASET_NAME",
737 8 : osSubDSName.c_str());
738 : }
739 : auto poImageDS =
740 2004 : GDALDataset::Open(&oOpenInfo, apszAllowedDrivers, aosOpenOptions);
741 1002 : std::unique_ptr<GDALDataset> poRetDS;
742 1002 : if (poImageDS)
743 : {
744 : // Open the mask if present
745 0 : std::unique_ptr<GDALDataset> poMaskDS;
746 1001 : if (asE57ImageDesc[0].nMaskLength &&
747 1994 : asE57ImageDesc[0].nMaskOffset &&
748 993 : IsValidPhysicalOffsetForBeginningOfSection(
749 993 : asE57ImageDesc[0].nMaskOffset, pageSize))
750 : {
751 : VSIVirtualHandleUniquePtr poNewRawFP(
752 1986 : VSIFOpenL(osPhysicalFilename.c_str(), "rb"));
753 993 : if (poNewRawFP)
754 : {
755 : auto poE57MaskHandle = std::make_unique<GDAL_E57FileHandle>(
756 993 : std::move(poNewRawFP), asE57ImageDesc[0].nMaskOffset,
757 993 : asE57ImageDesc[0].nMaskLength, pageSize,
758 1986 : E57_SIZEOF_BINARY_SECTION_HEADER);
759 : GDALOpenInfo oMaskOpenInfo(osPhysicalFilename.c_str(),
760 : GDAL_OF_RASTER |
761 : GDAL_OF_INTERNAL,
762 1986 : std::move(poE57MaskHandle));
763 993 : const char *const apszAllowedDriversPNG[] = {"PNG",
764 : nullptr};
765 : auto poMaskDSTmp = GDALDataset::Open(
766 1986 : &oMaskOpenInfo, apszAllowedDriversPNG, nullptr);
767 1986 : if (poMaskDSTmp &&
768 993 : poMaskDSTmp->GetRasterXSize() ==
769 1986 : poImageDS->GetRasterXSize() &&
770 993 : poMaskDSTmp->GetRasterYSize() ==
771 2979 : poImageDS->GetRasterYSize() &&
772 993 : poMaskDSTmp->GetRasterCount() == 1)
773 : {
774 993 : poMaskDS = std::move(poMaskDSTmp);
775 : }
776 : }
777 : }
778 :
779 2002 : poRetDS = std::make_unique<GDAL_E57Dataset>(
780 1001 : std::move(poImageDS), std::move(poMaskDS), asE57ImageDesc[0],
781 2002 : std::move(osXML));
782 : }
783 1002 : return poRetDS.release();
784 : }
785 : else
786 : {
787 : class GDAL_E57DatasetMultipleSDS final : public GDALDataset
788 : {
789 : public:
790 1 : GDAL_E57DatasetMultipleSDS()
791 1 : {
792 1 : nRasterXSize = 0;
793 1 : nRasterYSize = 0;
794 1 : }
795 : };
796 :
797 1 : const bool bUniqueNames = aoSetNames.size() == asE57ImageDesc.size();
798 2 : auto poDS = std::make_unique<GDAL_E57DatasetMultipleSDS>();
799 2 : CPLStringList aosSubDS;
800 3 : for (unsigned i = 0; i < asE57ImageDesc.size(); ++i)
801 : {
802 : const char *pszName =
803 2 : asE57ImageDesc[i].aosExtraMD.FetchNameValueDef("NAME", "");
804 2 : const CPLString osSubdatasetName = CPLString().Printf(
805 : "%s\"%s\":%s", E57_PREFIX, poOpenInfo->pszFilename,
806 : bUniqueNames ? pszName
807 4 : : CPLString().Printf("%u", i + 1).c_str());
808 : aosSubDS.SetNameValue(
809 4 : CPLString().Printf("SUBDATASET_%u_NAME", i + 1),
810 4 : osSubdatasetName.c_str());
811 2 : CPLString osDesc;
812 2 : if (bUniqueNames)
813 : {
814 4 : osDesc = CPLString().Printf("Image %s (%dx%d)", pszName,
815 2 : asE57ImageDesc[i].nWidth,
816 2 : asE57ImageDesc[i].nHeight);
817 : }
818 0 : else if (pszName[0])
819 : {
820 0 : osDesc = CPLString().Printf("Image %u (%s) (%dx%d)", i + 1,
821 0 : pszName, asE57ImageDesc[i].nWidth,
822 0 : asE57ImageDesc[i].nHeight);
823 : }
824 : else
825 : {
826 0 : osDesc = CPLString().Printf("Image %u (%dx%d)", i + 1,
827 0 : asE57ImageDesc[i].nWidth,
828 0 : asE57ImageDesc[i].nHeight);
829 : }
830 : aosSubDS.SetNameValue(
831 4 : CPLString().Printf("SUBDATASET_%u_DESC", i + 1),
832 4 : osDesc.c_str());
833 : }
834 1 : poDS->SetMetadata(aosSubDS.List(), "SUBDATASETS");
835 2 : CPLStringList aosXMLE57;
836 1 : aosXMLE57.AddString(osXML.c_str());
837 1 : poDS->SetMetadata(aosXMLE57.List(), "xml:E57");
838 1 : return poDS.release();
839 : }
840 : }
841 :
842 : } // namespace
843 :
844 : /************************************************************************/
845 : /* GDALRegister_E57() */
846 : /************************************************************************/
847 :
848 2058 : void GDALRegister_E57()
849 : {
850 2058 : auto poDM = GetGDALDriverManager();
851 2058 : if (poDM->GetDriverByName("E57") != nullptr)
852 283 : return;
853 :
854 3550 : auto poDriver = std::make_unique<GDALDriver>();
855 :
856 1775 : poDriver->SetDescription("E57");
857 1775 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
858 1775 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
859 1775 : "ASTM E57 3D file format (image part)");
860 1775 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "e57");
861 1775 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/e57.html");
862 1775 : poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
863 :
864 1775 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
865 :
866 1775 : poDriver->pfnOpen = GDAL_E57Dataset::Open;
867 1775 : poDriver->pfnIdentify = GDAL_E57Dataset::Identify;
868 :
869 1775 : poDM->RegisterDriver(poDriver.release());
870 : }
|