LCOV - code coverage report
Current view: top level - frmts/snap_tiff - snaptiffdriver.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 326 350 93.1 %
Date: 2025-10-27 00:14:23 Functions: 18 18 100.0 %

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

Generated by: LCOV version 1.14