Coverage Report

Created: 2026-11-12 08:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/snap_tiff/snaptiffdriver.cpp
Line
Count
Source
1
// SPDX-License-Identifier: MIT
2
// Copyright 2024, Even Rouault <even.rouault at spatialys.com>
3
4
#include "cpl_port.h"
5
#include "cpl_minixml.h"
6
#include "cpl_vsi_virtual.h"
7
#include "gdal_frmts.h"
8
#include "gdal_pam.h"
9
#include "gdal_driver.h"
10
#include "gdal_drivermanager.h"
11
#include "gdal_openinfo.h"
12
#include "gdal_cpp_functions.h"
13
#include "rawdataset.h"
14
15
341
#define LIBERTIFF_NS GDAL_libertiff
16
#include "libertiff.hpp"
17
18
#include <algorithm>
19
#include <cassert>
20
#include <cmath>
21
22
constexpr const char *SNAP_TIFF_PREFIX = "SNAP_TIFF:";
23
24
// Non-standard TIFF tag holding DIMAP XML for SNAP TIFF products
25
constexpr uint16_t DIMAP_TAG = 65000;
26
27
// Number of values per GCP in the GeoTIFFTiePoints tag
28
constexpr int VALUES_PER_GCP = 6;
29
30
constexpr int GCP_PIXEL = 0;
31
constexpr int GCP_LINE = 1;
32
// constexpr int GCP_DEPTH = 2;
33
constexpr int GCP_X = 3;
34
constexpr int GCP_Y = 4;
35
constexpr int GCP_Z = 5;
36
37
/************************************************************************/
38
/*                           SNAPTIFFDataset                            */
39
/************************************************************************/
40
41
class SNAPTIFFDataset final : public GDALPamDataset
42
{
43
  public:
44
0
    SNAPTIFFDataset() = default;
45
46
    static int Identify(GDALOpenInfo *poOpenInfo);
47
    static GDALDataset *Open(GDALOpenInfo *poOpenInfo);
48
49
    char **GetMetadataDomainList() override;
50
    CSLConstList GetMetadata(const char *pszDomain = "") override;
51
    const char *GetMetadataItem(const char *pszName,
52
                                const char *pszDomain = "") override;
53
54
    const OGRSpatialReference *GetGCPSpatialRef() const override
55
0
    {
56
0
        return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
57
0
    }
58
59
    int GetGCPCount() override
60
0
    {
61
0
        return int(m_asGCPs.size());
62
0
    }
63
64
    const GDAL_GCP *GetGCPs() override
65
0
    {
66
0
        return gdal::GCP::c_ptr(m_asGCPs);
67
0
    }
68
69
  private:
70
    VSIVirtualHandleUniquePtr m_poFile{};
71
    std::unique_ptr<const LIBERTIFF_NS::Image> m_poImage{};
72
73
    //! Whether this dataset is actually the geolocation array
74
    bool m_bIsGeolocArray = false;
75
76
    //! Content of "xml:DIMAP" metadata domain
77
    CPLStringList m_aosDIMAPMetadata{};
78
79
    CPLStringList m_aosGEOLOCATION{};
80
    int m_nGeolocArrayWidth = 0;
81
    int m_nGeolocArrayHeight = 0;
82
83
    CPLStringList m_aosSUBDATASETS{};
84
85
    std::vector<gdal::GCP> m_asGCPs{};
86
    OGRSpatialReference m_oSRS{};
87
88
    bool GetGeolocationMetadata();
89
90
    void ReadSRS();
91
};
92
93
/************************************************************************/
94
/*                              Identify()                              */
95
/************************************************************************/
96
97
int SNAPTIFFDataset::Identify(GDALOpenInfo *poOpenInfo)
98
568k
{
99
568k
    if (STARTS_WITH(poOpenInfo->pszFilename, SNAP_TIFF_PREFIX))
100
0
        return true;
101
102
568k
    if (poOpenInfo->fpL == nullptr || poOpenInfo->nHeaderBytes < 16 ||
103
        // BigEndian classic TIFF
104
134k
        memcmp(poOpenInfo->pabyHeader, "\x4D\x4D\x00\x2A", 4) != 0)
105
568k
    {
106
568k
        return false;
107
568k
    }
108
109
248
    struct MyFileReader final : public LIBERTIFF_NS::FileReader
110
248
    {
111
248
        const GDALOpenInfo *m_poOpenInfo;
112
113
248
        explicit MyFileReader(const GDALOpenInfo *poOpenInfoIn)
114
248
            : m_poOpenInfo(poOpenInfoIn)
115
248
        {
116
248
        }
117
118
248
        uint64_t size() const override
119
248
        {
120
203
            return m_poOpenInfo->nHeaderBytes;
121
203
        }
122
123
248
        size_t read(uint64_t offset, size_t count, void *buffer) const override
124
35.1k
        {
125
35.1k
            if (offset + count >
126
35.1k
                static_cast<uint64_t>(m_poOpenInfo->nHeaderBytes))
127
185
                return 0;
128
34.9k
            memcpy(buffer,
129
34.9k
                   m_poOpenInfo->pabyHeader + static_cast<size_t>(offset),
130
34.9k
                   count);
131
34.9k
            return count;
132
35.1k
        }
133
134
248
        CPL_DISALLOW_COPY_ASSIGN(MyFileReader)
135
248
    };
136
137
248
    auto f = std::make_shared<const MyFileReader>(poOpenInfo);
138
#ifdef DEBUG
139
    // Just to increase coverage testing
140
    CPLAssert(f->size() == uint64_t(poOpenInfo->nHeaderBytes));
141
    char dummy = 0;
142
    CPLAssert(f->read(poOpenInfo->nHeaderBytes, 1, &dummy) == 0);
143
    CPL_IGNORE_RET_VAL(dummy);
144
#endif
145
248
    auto image = LIBERTIFF_NS::open</*acceptBigTIFF = */ false>(std::move(f));
146
    // Checks that it is a single-band Float32 uncompressed dataset, made
147
    // of a single strip.
148
248
    if (!image || image->nextImageOffset() != 0 ||
149
52
        image->compression() != LIBERTIFF_NS::Compression::None ||
150
41
        image->sampleFormat() != LIBERTIFF_NS::SampleFormat::IEEEFP ||
151
0
        image->samplesPerPixel() != 1 || image->bitsPerSample() != 32 ||
152
0
        image->isTiled() || image->strileCount() != 1 || image->width() == 0 ||
153
0
        image->width() > INT_MAX / sizeof(float) || image->height() == 0 ||
154
0
        image->height() > INT_MAX || image->rowsPerStrip() != image->height() ||
155
0
        !image->tag(LIBERTIFF_NS::TagCode::GeoTIFFPixelScale) ||
156
0
        !image->tag(LIBERTIFF_NS::TagCode::GeoTIFFTiePoints) ||
157
0
        !image->tag(LIBERTIFF_NS::TagCode::GeoTIFFGeoKeyDirectory) ||
158
0
        !image->tag(DIMAP_TAG))
159
248
    {
160
248
        return false;
161
248
    }
162
163
0
    return true;
164
248
}
165
166
/************************************************************************/
167
/*                                Open()                                */
168
/************************************************************************/
169
170
GDALDataset *SNAPTIFFDataset::Open(GDALOpenInfo *poOpenInfo)
171
0
{
172
0
    if (poOpenInfo->eAccess == GA_Update || !Identify(poOpenInfo))
173
0
        return nullptr;
174
175
0
    bool bIsGeolocation = false;
176
    // Check if it is SNAP_TIFF:"filename":{subdataset_component} syntax
177
0
    if (STARTS_WITH(poOpenInfo->pszFilename, SNAP_TIFF_PREFIX))
178
0
    {
179
0
        const CPLStringList aosTokens(CSLTokenizeString2(
180
0
            poOpenInfo->pszFilename, ":", CSLT_HONOURSTRINGS));
181
0
        if (aosTokens.size() != 3)
182
0
            return nullptr;
183
0
        bIsGeolocation = EQUAL(aosTokens[2], GDAL_MDD_GEOLOCATION);
184
0
        if (!bIsGeolocation && !EQUAL(aosTokens[2], "MAIN"))
185
0
            return nullptr;
186
0
        GDALOpenInfo oOpenInfo(aosTokens[1], GA_ReadOnly);
187
0
        if (!Identify(&oOpenInfo))
188
0
            return nullptr;
189
0
        std::swap(poOpenInfo->fpL, oOpenInfo.fpL);
190
0
        if (!poOpenInfo->fpL)
191
0
            return nullptr;
192
0
    }
193
194
0
    struct MyFileReader final : public LIBERTIFF_NS::FileReader
195
0
    {
196
0
        VSILFILE *m_fp;
197
0
        mutable uint64_t m_nFileSize = 0;
198
199
0
        explicit MyFileReader(VSILFILE *fp) : m_fp(fp)
200
0
        {
201
0
        }
202
203
0
        uint64_t size() const override
204
0
        {
205
0
            if (m_nFileSize == 0)
206
0
            {
207
0
                m_fp->Seek(0, SEEK_END);
208
0
                m_nFileSize = m_fp->Tell();
209
0
            }
210
0
            return m_nFileSize;
211
0
        }
212
213
0
        size_t read(uint64_t offset, size_t count, void *buffer) const override
214
0
        {
215
0
            return m_fp->Seek(offset, SEEK_SET) == 0
216
0
                       ? m_fp->Read(buffer, 1, count)
217
0
                       : 0;
218
0
        }
219
220
0
        CPL_DISALLOW_COPY_ASSIGN(MyFileReader)
221
0
    };
222
223
0
    auto f = std::make_shared<const MyFileReader>(poOpenInfo->fpL);
224
#ifdef DEBUG
225
    // Just to increase coverage testing
226
    char dummy = 0;
227
    CPLAssert(f->read(f->size(), 1, &dummy) == 0);
228
    CPL_IGNORE_RET_VAL(dummy);
229
#endif
230
231
0
    auto poDS = std::make_unique<SNAPTIFFDataset>();
232
233
0
    poDS->m_poImage = LIBERTIFF_NS::open(std::move(f));
234
0
    if (!poDS->m_poImage)
235
0
        return nullptr;
236
0
    poDS->m_poFile.reset(poOpenInfo->fpL);
237
0
    poOpenInfo->fpL = nullptr;
238
239
0
    poDS->nRasterXSize = static_cast<int>(poDS->m_poImage->width());
240
0
    poDS->nRasterYSize = static_cast<int>(poDS->m_poImage->height());
241
0
    poDS->SetDescription(poOpenInfo->pszFilename);
242
243
0
    if (bIsGeolocation)
244
0
    {
245
0
        poDS->m_bIsGeolocArray = true;
246
0
        if (!poDS->GetGeolocationMetadata())
247
0
        {
248
0
            return nullptr;
249
0
        }
250
0
        poDS->nRasterXSize = poDS->m_nGeolocArrayWidth;
251
0
        poDS->nRasterYSize = poDS->m_nGeolocArrayHeight;
252
253
0
        const auto *psTag =
254
0
            poDS->m_poImage->tag(LIBERTIFF_NS::TagCode::GeoTIFFTiePoints);
255
0
        assert(psTag);
256
257
0
        for (int iBand = 1; iBand <= 2; ++iBand)
258
0
        {
259
0
            auto poBand = std::make_unique<RawRasterBand>(
260
0
                poDS->m_poFile.get(),
261
0
                psTag->value_offset +
262
0
                    (iBand == 1 ? GCP_X : GCP_Y) * sizeof(double),
263
0
                int(sizeof(double)) * VALUES_PER_GCP,
264
0
                int(sizeof(double)) * VALUES_PER_GCP * poDS->nRasterXSize,
265
0
                GDT_Float64, !poDS->m_poImage->mustByteSwap(),
266
0
                poDS->nRasterXSize, poDS->nRasterYSize,
267
0
                RawRasterBand::OwnFP::NO);
268
0
            if (!poBand->IsValid())
269
0
                return nullptr;
270
0
            if (iBand == 1)
271
0
                poBand->SetDescription("Longitude");
272
0
            else
273
0
                poBand->SetDescription("Latitude");
274
0
            poDS->SetBand(iBand, std::move(poBand));
275
0
        }
276
277
0
        return poDS.release();
278
0
    }
279
280
0
    poDS->ReadSRS();
281
0
    poDS->GetGeolocationMetadata();
282
283
0
    {
284
0
        bool okStrileOffset = false;
285
0
        auto poBand = std::make_unique<RawRasterBand>(
286
0
            poDS->m_poFile.get(),
287
0
            poDS->m_poImage->strileOffset(0, okStrileOffset),
288
0
            int(sizeof(float)), int(sizeof(float)) * poDS->nRasterXSize,
289
0
            GDT_Float32, !poDS->m_poImage->mustByteSwap(), poDS->nRasterXSize,
290
0
            poDS->nRasterYSize, RawRasterBand::OwnFP::NO);
291
0
        if (!poBand->IsValid())
292
0
            return nullptr;
293
0
        poDS->SetBand(1, std::move(poBand));
294
0
    }
295
0
    auto poBand = poDS->papoBands[0];
296
297
0
    const auto &psImageDescription =
298
0
        poDS->m_poImage->tag(LIBERTIFF_NS::TagCode::ImageDescription);
299
0
    if (psImageDescription &&
300
0
        psImageDescription->type == LIBERTIFF_NS::TagType::ASCII &&
301
0
        !psImageDescription->invalid_value_offset &&
302
        // Sanity check
303
0
        psImageDescription->count < 100 * 1000)
304
0
    {
305
0
        bool ok = true;
306
0
        const std::string s =
307
0
            poDS->m_poImage->readTagAsString(*psImageDescription, ok);
308
0
        if (ok)
309
0
        {
310
0
            poDS->GDALDataset::SetMetadataItem("IMAGE_DESCRIPTION", s.c_str());
311
0
        }
312
0
    }
313
314
0
    const auto *psDimapTag = poDS->m_poImage->tag(DIMAP_TAG);
315
0
    if (psDimapTag && psDimapTag->type == LIBERTIFF_NS::TagType::ASCII &&
316
0
        !psDimapTag->invalid_value_offset)
317
0
    {
318
0
        try
319
0
        {
320
0
            bool ok = true;
321
            // Just read the first 10 kB max to fetch essential band metadata
322
0
            const std::string s = poDS->m_poImage->readContext()->readString(
323
0
                psDimapTag->value_offset,
324
0
                static_cast<size_t>(
325
0
                    std::min<uint64_t>(psDimapTag->count, 10000)),
326
0
                ok);
327
0
            const auto posStart = s.find("<Spectral_Band_Info>");
328
0
            if (posStart != std::string::npos)
329
0
            {
330
0
                const char *markerEnd = "</Spectral_Band_Info>";
331
0
                const auto posEnd = s.find(markerEnd, posStart);
332
0
                if (posEnd != std::string::npos)
333
0
                {
334
0
                    const std::string osSubstr = s.substr(
335
0
                        posStart, posEnd - posStart + strlen(markerEnd));
336
0
                    CPLXMLTreeCloser oRoot(CPLParseXMLString(osSubstr.c_str()));
337
0
                    if (oRoot)
338
0
                    {
339
0
                        const char *pszNoDataValueUsed = CPLGetXMLValue(
340
0
                            oRoot.get(), "NO_DATA_VALUE_USED", nullptr);
341
0
                        const char *pszNoDataValue = CPLGetXMLValue(
342
0
                            oRoot.get(), "NO_DATA_VALUE", nullptr);
343
0
                        if (pszNoDataValueUsed && pszNoDataValue &&
344
0
                            CPLTestBool(pszNoDataValueUsed))
345
0
                        {
346
0
                            poBand->SetNoDataValue(CPLAtof(pszNoDataValue));
347
0
                        }
348
349
0
                        if (const char *pszScalingFactor = CPLGetXMLValue(
350
0
                                oRoot.get(), "SCALING_FACTOR", nullptr))
351
0
                        {
352
0
                            poBand->SetScale(CPLAtof(pszScalingFactor));
353
0
                        }
354
355
0
                        if (const char *pszScalingOffset = CPLGetXMLValue(
356
0
                                oRoot.get(), "SCALING_OFFSET", nullptr))
357
0
                        {
358
0
                            poBand->SetOffset(CPLAtof(pszScalingOffset));
359
0
                        }
360
361
0
                        if (const char *pszBandName = CPLGetXMLValue(
362
0
                                oRoot.get(), "BAND_NAME", nullptr))
363
0
                        {
364
0
                            poBand->SetDescription(pszBandName);
365
0
                        }
366
367
0
                        if (const char *pszUnit = CPLGetXMLValue(
368
0
                                oRoot.get(), "PHYSICAL_UNIT", nullptr))
369
0
                        {
370
0
                            poBand->SetUnitType(pszUnit);
371
0
                        }
372
0
                    }
373
0
                }
374
0
            }
375
0
        }
376
0
        catch (const std::exception &)
377
0
        {
378
0
        }
379
0
    }
380
381
    // Initialize PAM
382
0
    poDS->SetDescription(poOpenInfo->pszFilename);
383
0
    poDS->TryLoadXML();
384
385
    // Check for overviews.
386
0
    poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
387
388
0
    return poDS.release();
389
0
}
390
391
/************************************************************************/
392
/*                       GetMetadataDomainList()                        */
393
/************************************************************************/
394
395
char **SNAPTIFFDataset::GetMetadataDomainList()
396
0
{
397
0
    return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
398
0
                                   TRUE, GDAL_MDD_GEOLOCATION,
399
0
                                   GDAL_MDD_SUBDATASETS, "xml:DIMAP", nullptr);
400
0
}
401
402
/************************************************************************/
403
/*                       GetGeolocationMetadata()                       */
404
/************************************************************************/
405
406
// (Partially) read the content of the GeoTIFFTiePoints tag to check if the
407
// tie points form a regular geolocation array, and extract the width, height,
408
// and spacing of that geolocation array. Also fills the m_aosGEOLOCATION
409
// metadata domain
410
bool SNAPTIFFDataset::GetGeolocationMetadata()
411
0
{
412
0
    const auto *psTag = m_poImage->tag(LIBERTIFF_NS::TagCode::GeoTIFFTiePoints);
413
0
    if (psTag && psTag->type == LIBERTIFF_NS::TagType::Double &&
414
0
        !psTag->invalid_value_offset && (psTag->count % VALUES_PER_GCP) == 0 &&
415
        // Sanity check
416
0
        psTag->count <= uint64_t(nRasterXSize) * nRasterYSize * VALUES_PER_GCP)
417
0
    {
418
0
        const int numGCPs = int(psTag->count / VALUES_PER_GCP);
419
420
        // Assume that the geolocation array has the same proportion as the
421
        // main array
422
0
        const double dfGCPArrayWidth =
423
0
            sqrt(double(nRasterXSize) * numGCPs / nRasterYSize);
424
0
        const double dfGCPArrayHeight =
425
0
            sqrt(double(nRasterYSize) * numGCPs / nRasterXSize);
426
0
        if (dfGCPArrayWidth > INT_MAX || dfGCPArrayHeight > INT_MAX)
427
0
        {
428
0
            return false;
429
0
        }
430
431
0
        const int nGCPArrayWidth = int(std::round(dfGCPArrayWidth));
432
0
        const int nGCPArrayHeight = int(std::round(dfGCPArrayHeight));
433
0
        constexpr int NUM_LINES = 3;
434
0
        if (nGCPArrayWidth * nGCPArrayHeight != numGCPs ||
435
0
            nGCPArrayHeight < NUM_LINES)
436
0
        {
437
0
            return false;
438
0
        }
439
440
0
        bool ok = true;
441
        // Just read the first 3 lines of the geolocation array
442
0
        const auto numValuesPerLine = nGCPArrayWidth * VALUES_PER_GCP;
443
0
        const auto values = m_poImage->readContext()->readArray<double>(
444
0
            psTag->value_offset, numValuesPerLine * NUM_LINES, ok);
445
0
        if (!ok || values.empty())
446
0
            return false;
447
448
0
        if (values[GCP_LINE] != 0.5 && values[GCP_PIXEL] != 0.5)
449
0
            return false;
450
451
0
        constexpr double RELATIVE_TOLERANCE = 1e-5;
452
0
        constexpr double PIXEL_TOLERANCE = 1e-3;
453
454
        // Check that pixel spacing is constant on all three first lines
455
0
        const double pixelSpacing =
456
0
            values[VALUES_PER_GCP + GCP_PIXEL] - values[GCP_PIXEL];
457
0
        if (!(pixelSpacing >= 1))
458
0
            return false;
459
0
        if (std::fabs(pixelSpacing * (nGCPArrayWidth - 1) -
460
0
                      (nRasterXSize - 1)) > PIXEL_TOLERANCE)
461
0
            return false;
462
463
0
        double adfY[NUM_LINES];
464
0
        for (int iLine = 0; iLine < NUM_LINES; ++iLine)
465
0
        {
466
0
            adfY[iLine] = values[iLine * numValuesPerLine + GCP_LINE];
467
0
            for (int i = iLine * numValuesPerLine + VALUES_PER_GCP;
468
0
                 i < (iLine + 1) * numValuesPerLine; i += VALUES_PER_GCP)
469
0
            {
470
0
                if (values[i + GCP_LINE] !=
471
0
                    values[i - VALUES_PER_GCP + GCP_LINE])
472
0
                {
473
0
                    return false;
474
0
                }
475
0
                const double pixelSpacingNew =
476
0
                    values[i + GCP_PIXEL] -
477
0
                    values[i - VALUES_PER_GCP + GCP_PIXEL];
478
0
                if (std::fabs(pixelSpacingNew - pixelSpacing) >
479
0
                    RELATIVE_TOLERANCE * std::fabs(pixelSpacing))
480
0
                {
481
0
                    return false;
482
0
                }
483
0
            }
484
0
        }
485
486
        // Check that line spacing is constant on the three first lines
487
0
        const double lineSpacing = adfY[1] - adfY[0];
488
0
        if (!(lineSpacing >= 1))
489
0
            return false;
490
0
        if (std::fabs(lineSpacing * (nGCPArrayHeight - 1) -
491
0
                      (nRasterYSize - 1)) > PIXEL_TOLERANCE)
492
0
            return false;
493
494
0
        for (int iLine = 1; iLine + 1 < NUM_LINES; ++iLine)
495
0
        {
496
0
            const double lineSpacingNew = adfY[iLine + 1] - adfY[iLine];
497
0
            if (std::fabs(lineSpacingNew - lineSpacing) >
498
0
                RELATIVE_TOLERANCE * std::fabs(lineSpacing))
499
0
            {
500
0
                return false;
501
0
            }
502
0
        }
503
504
        // Read last line
505
0
        const auto lastLineValue = m_poImage->readContext()->readArray<double>(
506
0
            psTag->value_offset + uint64_t(nGCPArrayHeight - 1) *
507
0
                                      numValuesPerLine * sizeof(double),
508
0
            numValuesPerLine, ok);
509
0
        if (!ok)
510
0
            return false;
511
512
0
        if (!m_bIsGeolocArray)
513
0
        {
514
            // Expose the 4 corner GCPs for rough georeferencing.
515
516
0
            const uint32_t nShift = numValuesPerLine - VALUES_PER_GCP;
517
0
            m_asGCPs.emplace_back("TL", "Top Left", values[GCP_PIXEL],
518
0
                                  values[GCP_LINE], values[GCP_X],
519
0
                                  values[GCP_Y], values[GCP_Z]);
520
0
            m_asGCPs.emplace_back(
521
0
                "TR", "Top Right", values[nShift + GCP_PIXEL],
522
0
                values[nShift + GCP_LINE], values[nShift + GCP_X],
523
0
                values[nShift + GCP_Y], values[nShift + GCP_Z]);
524
0
            m_asGCPs.emplace_back("BL", "Bottom Left", lastLineValue[GCP_PIXEL],
525
0
                                  lastLineValue[GCP_LINE], lastLineValue[GCP_X],
526
0
                                  lastLineValue[GCP_Y], lastLineValue[GCP_Z]);
527
0
            m_asGCPs.emplace_back(
528
0
                "BR", "Bottom Right", lastLineValue[nShift + GCP_PIXEL],
529
0
                lastLineValue[nShift + GCP_LINE], lastLineValue[nShift + GCP_X],
530
0
                lastLineValue[nShift + GCP_Y], lastLineValue[nShift + GCP_Z]);
531
0
        }
532
533
0
        m_nGeolocArrayWidth = nGCPArrayWidth;
534
0
        m_nGeolocArrayHeight = nGCPArrayHeight;
535
536
0
        if (!m_bIsGeolocArray)
537
0
        {
538
0
            if (!m_oSRS.IsEmpty())
539
0
            {
540
0
                char *pszWKT = nullptr;
541
0
                m_oSRS.exportToWkt(&pszWKT);
542
0
                m_aosGEOLOCATION.SetNameValue("SRS", pszWKT);
543
0
                CPLFree(pszWKT);
544
0
            }
545
546
0
            m_aosGEOLOCATION.SetNameValue(
547
0
                "X_DATASET", CPLSPrintf("%s\"%s\":GEOLOCATION",
548
0
                                        SNAP_TIFF_PREFIX, GetDescription()));
549
0
            m_aosGEOLOCATION.SetNameValue("X_BAND", "1");
550
0
            m_aosGEOLOCATION.SetNameValue(
551
0
                "Y_DATASET", CPLSPrintf("%s\"%s\":GEOLOCATION",
552
0
                                        SNAP_TIFF_PREFIX, GetDescription()));
553
0
            m_aosGEOLOCATION.SetNameValue("Y_BAND", "2");
554
0
            m_aosGEOLOCATION.SetNameValue("PIXEL_OFFSET", "0");
555
0
            m_aosGEOLOCATION.SetNameValue("PIXEL_STEP",
556
0
                                          CPLSPrintf("%.17g", pixelSpacing));
557
0
            m_aosGEOLOCATION.SetNameValue("LINE_OFFSET", "0");
558
0
            m_aosGEOLOCATION.SetNameValue("LINE_STEP",
559
0
                                          CPLSPrintf("%.17g", lineSpacing));
560
0
        }
561
562
0
        return true;
563
0
    }
564
0
    return false;
565
0
}
566
567
/************************************************************************/
568
/*                              ReadSRS()                               */
569
/************************************************************************/
570
571
// Simplified GeoTIFF SRS reader, assuming the SRS is encoded as a EPSG code
572
void SNAPTIFFDataset::ReadSRS()
573
0
{
574
0
    const auto &psGeoKeysTag =
575
0
        m_poImage->tag(LIBERTIFF_NS::TagCode::GeoTIFFGeoKeyDirectory);
576
0
    constexpr int VALUES_PER_GEOKEY = 4;
577
0
    if (psGeoKeysTag && psGeoKeysTag->type == LIBERTIFF_NS::TagType::Short &&
578
0
        !psGeoKeysTag->invalid_value_offset &&
579
0
        psGeoKeysTag->count >= VALUES_PER_GEOKEY &&
580
0
        (psGeoKeysTag->count % VALUES_PER_GEOKEY) == 0 &&
581
        // Sanity check
582
0
        psGeoKeysTag->count < 1000)
583
0
    {
584
0
        bool ok = true;
585
0
        const auto values =
586
0
            m_poImage->readTagAsVector<uint16_t>(*psGeoKeysTag, ok);
587
0
        if (values.size() >= 4)
588
0
        {
589
0
            const uint16_t geokeysCount = values[3];
590
0
            constexpr uint16_t GEOTIFF_KEY_DIRECTORY_VERSION_V1 = 1;
591
0
            constexpr uint16_t GEOTIFF_KEY_VERSION_MAJOR_V1 = 1;
592
0
            if (values[0] == GEOTIFF_KEY_DIRECTORY_VERSION_V1 &&
593
                // GeoTIFF 1.x
594
0
                values[1] == GEOTIFF_KEY_VERSION_MAJOR_V1 &&
595
0
                geokeysCount == psGeoKeysTag->count / VALUES_PER_GEOKEY - 1)
596
0
            {
597
0
                constexpr uint16_t GeoTIFFTypeShort = 0;
598
0
                constexpr uint16_t GeodeticCRSGeoKey = 2048;
599
0
                constexpr uint16_t ProjectedCRSGeoKey = 3072;
600
0
                uint16_t nEPSGCode = 0;
601
0
                for (uint32_t i = 1; i <= geokeysCount; ++i)
602
0
                {
603
0
                    const auto geokey = values[VALUES_PER_GEOKEY * i];
604
0
                    const auto geokeyType = values[VALUES_PER_GEOKEY * i + 1];
605
0
                    const auto geokeyCount = values[VALUES_PER_GEOKEY * i + 2];
606
0
                    const auto geokeyValue = values[VALUES_PER_GEOKEY * i + 3];
607
0
                    if ((geokey == GeodeticCRSGeoKey ||
608
0
                         geokey == ProjectedCRSGeoKey) &&
609
0
                        geokeyType == GeoTIFFTypeShort && geokeyCount == 1 &&
610
0
                        geokeyValue > 0)
611
0
                    {
612
0
                        nEPSGCode = geokeyValue;
613
0
                        if (geokey == ProjectedCRSGeoKey)
614
0
                            break;
615
0
                    }
616
0
                }
617
0
                if (nEPSGCode > 0)
618
0
                {
619
0
                    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
620
0
                    m_oSRS.importFromEPSG(nEPSGCode);
621
0
                }
622
0
            }
623
0
        }
624
0
    }
625
0
}
626
627
/************************************************************************/
628
/*                            GetMetadata()                             */
629
/************************************************************************/
630
631
CSLConstList SNAPTIFFDataset::GetMetadata(const char *pszDomain)
632
0
{
633
0
    if (!m_bIsGeolocArray)
634
0
    {
635
0
        if (pszDomain && EQUAL(pszDomain, "xml:DIMAP"))
636
0
        {
637
0
            if (m_aosDIMAPMetadata.empty())
638
0
            {
639
0
                const auto *psDimapTag = m_poImage->tag(DIMAP_TAG);
640
0
                if (psDimapTag &&
641
0
                    psDimapTag->type == LIBERTIFF_NS::TagType::ASCII &&
642
0
                    !psDimapTag->invalid_value_offset &&
643
                    // Sanity check
644
0
                    psDimapTag->count < 100 * 1000 * 1000)
645
0
                {
646
0
                    try
647
0
                    {
648
0
                        bool ok = true;
649
0
                        const std::string s =
650
0
                            m_poImage->readTagAsString(*psDimapTag, ok);
651
0
                        if (ok)
652
0
                        {
653
0
                            m_aosDIMAPMetadata.AddString(s.c_str());
654
0
                        }
655
0
                    }
656
0
                    catch (const std::exception &)
657
0
                    {
658
0
                        CPLError(CE_Failure, CPLE_OutOfMemory,
659
0
                                 "Out of memory in GetMetadata()");
660
0
                    }
661
0
                }
662
0
            }
663
0
            return m_aosDIMAPMetadata.List();
664
0
        }
665
0
        else if (pszDomain && EQUAL(pszDomain, GDAL_MDD_GEOLOCATION))
666
0
        {
667
0
            return m_aosGEOLOCATION.List();
668
0
        }
669
0
        else if (pszDomain && EQUAL(pszDomain, GDAL_MDD_SUBDATASETS))
670
0
        {
671
0
            if (m_aosSUBDATASETS.empty() && GetGeolocationMetadata())
672
0
            {
673
0
                m_aosSUBDATASETS.SetNameValue("SUBDATASET_1_NAME",
674
0
                                              CPLSPrintf("%s\"%s\":MAIN",
675
0
                                                         SNAP_TIFF_PREFIX,
676
0
                                                         GetDescription()));
677
0
                m_aosSUBDATASETS.SetNameValue("SUBDATASET_1_DESC",
678
0
                                              std::string("Main content of ")
679
0
                                                  .append(GetDescription())
680
0
                                                  .c_str());
681
682
0
                m_aosSUBDATASETS.SetNameValue(
683
0
                    "SUBDATASET_2_NAME",
684
0
                    m_aosGEOLOCATION.FetchNameValue("X_DATASET"));
685
0
                m_aosSUBDATASETS.SetNameValue(
686
0
                    "SUBDATASET_2_DESC", std::string("Geolocation array of ")
687
0
                                             .append(GetDescription())
688
0
                                             .c_str());
689
0
            }
690
0
            return m_aosSUBDATASETS.List();
691
0
        }
692
0
    }
693
694
0
    return GDALPamDataset::GetMetadata(pszDomain);
695
0
}
696
697
/************************************************************************/
698
/*                          GetMetadataItem()                           */
699
/************************************************************************/
700
701
const char *SNAPTIFFDataset::GetMetadataItem(const char *pszName,
702
                                             const char *pszDomain)
703
0
{
704
0
    if (!m_bIsGeolocArray && pszDomain &&
705
0
        (EQUAL(pszDomain, GDAL_MDD_GEOLOCATION) ||
706
0
         EQUAL(pszDomain, GDAL_MDD_SUBDATASETS)))
707
0
    {
708
0
        return CSLFetchNameValue(GetMetadata(pszDomain), pszName);
709
0
    }
710
0
    return GDALPamDataset::GetMetadataItem(pszName, pszDomain);
711
0
}
712
713
/************************************************************************/
714
/*                       GDALRegister_SNAP_TIFF()                       */
715
/************************************************************************/
716
717
void GDALRegister_SNAP_TIFF()
718
24
{
719
24
    if (GDALGetDriverByName("SNAP_TIFF") != nullptr)
720
0
        return;
721
722
24
    GDALDriver *poDriver = new GDALDriver();
723
24
    poDriver->SetDescription("SNAP_TIFF");
724
24
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
725
24
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
726
24
                              "Sentinel Application Processing GeoTIFF");
727
24
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
728
24
                              "drivers/raster/snap_tiff.html");
729
    // Declaring the tif extension confuses QGIS
730
    // Cf https://github.com/qgis/QGIS/issues/59112
731
    // This driver is of too marginal usage to justify causing chaos downstream.
732
    // poDriver->SetMetadataItem(GDAL_DMD_MIMETYPE, "image/tiff");
733
    // poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "tif tiff");
734
24
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
735
736
24
    poDriver->pfnOpen = SNAPTIFFDataset::Open;
737
24
    poDriver->pfnIdentify = SNAPTIFFDataset::Identify;
738
739
24
    GetGDALDriverManager()->RegisterDriver(poDriver);
740
24
}