LCOV - code coverage report
Current view: top level - frmts/lerc - lercdriver.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 210 238 88.2 %
Date: 2026-06-19 21:24:00 Functions: 16 16 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  LERC driver
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2026, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "lercdrivercore.h"
      14             : 
      15             : #include "cpl_vsi.h"
      16             : #include "cpl_vsi_virtual.h"
      17             : 
      18             : #include "gdal_frmts.h"
      19             : #include "gdal_pam.h"
      20             : #include "gdal_priv.h"
      21             : 
      22             : #include "Lerc_c_api.h"
      23             : 
      24             : #include <limits>
      25             : 
      26             : #ifndef LERC_AT_LEAST_VERSION
      27             : #define LERC_AT_LEAST_VERSION(maj, min, patch) 0
      28             : #endif
      29             : 
      30             : namespace gdal
      31             : {
      32             : class LERCBand;
      33             : 
      34             : /************************************************************************/
      35             : /*                             LERCDataset                              */
      36             : /************************************************************************/
      37             : 
      38          22 : class LERCDataset final : public GDALPamDataset
      39             : {
      40             :   public:
      41          11 :     LERCDataset() = default;
      42             :     ~LERCDataset() override;
      43             : 
      44             :     static GDALDataset *Open(GDALOpenInfo *poOpenInfo);
      45             : 
      46             :   private:
      47             :     friend class LERCBand;
      48             :     friend class LERCMaskBand;
      49             : 
      50             :     bool m_bUseNoData = true;
      51             :     double m_dfNoDataValue = std::numeric_limits<double>::quiet_NaN();
      52             :     int m_nLercDataType = 0;
      53             :     int m_nDepth = 0;
      54             :     int m_nLercBands = 0;
      55             :     int m_nMaskCount = 0;
      56             :     unsigned m_nBlobSize = 0;
      57             :     std::unique_ptr<unsigned char, VSIFreeReleaser> m_pabyBlob{};
      58             :     std::unique_ptr<unsigned char, VSIFreeReleaser> m_pabyDecodedImage{};
      59             :     std::unique_ptr<unsigned char, VSIFreeReleaser> m_pabyDecodedMask{};
      60             : 
      61             :     const unsigned char *GetDecodedImage();
      62             :     const unsigned char *GetDecodedMask();
      63             : };
      64             : 
      65             : LERCDataset::~LERCDataset() = default;
      66             : 
      67             : /************************************************************************/
      68             : /*                               LERCBand                               */
      69             : /************************************************************************/
      70             : 
      71             : class LERCBand final : public GDALPamRasterBand
      72             : {
      73             :   public:
      74             :     LERCBand(LERCDataset *poDSIn, int nBandIn, GDALDataType eDT);
      75             : 
      76             :     double GetNoDataValue(int *pbHasNoData) override;
      77             :     int GetMaskFlags() override;
      78             :     GDALRasterBand *GetMaskBand() override;
      79             : 
      80             :   protected:
      81             :     CPLErr IReadBlock(int nBlockXOff, int nBlockYOff, void *pData) override;
      82             : 
      83             :   private:
      84             :     std::unique_ptr<GDALRasterBand> m_poMaskBand{};
      85             : };
      86             : 
      87             : /************************************************************************/
      88             : /*                             LERCMaskBand                             */
      89             : /************************************************************************/
      90             : 
      91             : class LERCMaskBand final : public GDALRasterBand
      92             : {
      93             :   public:
      94           3 :     LERCMaskBand(LERCDataset *poDSIn, int nMaskIdx) : m_nMaskIdx(nMaskIdx)
      95             :     {
      96           3 :         poDS = poDSIn;
      97           3 :         nBand = 0;
      98           3 :         eDataType = GDT_Byte;
      99           3 :         nRasterXSize = poDS->GetRasterXSize();
     100           3 :         nRasterYSize = poDS->GetRasterYSize();
     101           3 :         nBlockXSize = nRasterXSize;
     102           3 :         nBlockYSize = 1;
     103           3 :     }
     104             : 
     105             :   protected:
     106             :     CPLErr IReadBlock(int nBlockXOff, int nBlockYOff, void *pData) override;
     107             : 
     108             :   private:
     109             :     const int m_nMaskIdx;
     110             : };
     111             : 
     112             : /************************************************************************/
     113             : /*                    LERCDataset::GetDecodedImage()                    */
     114             : /************************************************************************/
     115             : 
     116        1466 : const unsigned char *LERCDataset::GetDecodedImage()
     117             : {
     118        1466 :     if (!m_pabyBlob)
     119        1455 :         return m_pabyDecodedImage.get();
     120             : 
     121          11 :     m_pabyDecodedImage.reset(static_cast<unsigned char *>(VSI_MALLOC_VERBOSE(
     122             :         static_cast<size_t>(nRasterXSize) * nRasterYSize * m_nDepth *
     123             :         m_nLercBands *
     124             :         GDALGetDataTypeSizeBytes(GetRasterBand(1)->GetRasterDataType()))));
     125          11 :     if (m_nMaskCount)
     126             :     {
     127           3 :         m_pabyDecodedMask.reset(static_cast<unsigned char *>(VSI_MALLOC_VERBOSE(
     128             :             static_cast<size_t>(nRasterXSize) * nRasterYSize * m_nMaskCount)));
     129           3 :         if (!m_pabyDecodedMask)
     130           0 :             m_pabyDecodedImage.reset();
     131             :     }
     132             : 
     133          11 :     if (m_pabyDecodedImage)
     134             :     {
     135          11 :         const lerc_status status = lerc_decode(
     136          11 :             m_pabyBlob.get(), m_nBlobSize,
     137             : #if LERC_AT_LEAST_VERSION(3, 0, 0)
     138             :             m_nMaskCount,
     139             : #endif
     140             :             m_pabyDecodedMask.get(), m_nDepth, nRasterXSize, nRasterYSize,
     141          11 :             m_nLercBands, m_nLercDataType, m_pabyDecodedImage.get());
     142          11 :         if (status != 0)
     143             :         {
     144           0 :             m_pabyDecodedImage.reset();
     145           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     146             :                      "lerc_decode() failed with status %d", status);
     147             :         }
     148             :     }
     149          11 :     m_pabyBlob.reset();
     150             : 
     151          11 :     return m_pabyDecodedImage.get();
     152             : }
     153             : 
     154             : /************************************************************************/
     155             : /*                    LERCDataset::GetDecodedMask()                     */
     156             : /************************************************************************/
     157             : 
     158         120 : const unsigned char *LERCDataset::GetDecodedMask()
     159             : {
     160         120 :     GetDecodedImage();
     161         120 :     return m_pabyDecodedMask.get();
     162             : }
     163             : 
     164             : /************************************************************************/
     165             : /*                         LERCBand::LERCBand()                         */
     166             : /************************************************************************/
     167             : 
     168          13 : LERCBand::LERCBand(LERCDataset *poDSIn, int nBandIn, GDALDataType eDT)
     169             : {
     170          13 :     poDS = poDSIn;
     171          13 :     nBand = nBandIn;
     172          13 :     eDataType = eDT;
     173          13 :     nRasterXSize = poDS->GetRasterXSize();
     174          13 :     nRasterYSize = poDS->GetRasterYSize();
     175          13 :     nBlockXSize = nRasterXSize;
     176          13 :     nBlockYSize = 1;
     177          13 : }
     178             : 
     179             : /************************************************************************/
     180             : /*                    SetNodataWhereMaskIsInvalid()                     */
     181             : /************************************************************************/
     182             : 
     183             : template <class T>
     184          48 : static void SetNodataWhereMaskIsInvalid(T *pLine, const unsigned char *pSrcMask,
     185             :                                         int nPixels, T nodataValue)
     186             : {
     187        1104 :     for (int i = 0; i < nPixels; ++i)
     188             :     {
     189        1056 :         if (!pSrcMask[i])
     190             :         {
     191         256 :             pLine[i] = nodataValue;
     192             :         }
     193             :     }
     194          48 : }
     195             : 
     196             : /************************************************************************/
     197             : /*                        LERCBand::IReadBlock()                        */
     198             : /************************************************************************/
     199             : 
     200        1346 : CPLErr LERCBand::IReadBlock(int, int nBlockYOff, void *pData)
     201             : {
     202        1346 :     auto poGDS = cpl::down_cast<LERCDataset *>(poDS);
     203        1346 :     const unsigned char *pabyImage = poGDS->GetDecodedImage();
     204        1346 :     if (!pabyImage)
     205           0 :         return CE_Failure;
     206             : 
     207        1346 :     const int nDTSize = GDALGetDataTypeSizeBytes(eDataType);
     208        4038 :     GDALCopyWords64(
     209        2692 :         pabyImage + ((nBand - 1) + static_cast<size_t>(nBlockYOff) *
     210        2692 :                                        nRasterXSize * poGDS->GetRasterCount()) *
     211        1346 :                         nDTSize,
     212        1346 :         eDataType, poGDS->GetRasterCount() * nDTSize, pData, eDataType, nDTSize,
     213        1346 :         nRasterXSize);
     214             : 
     215        1346 :     if (poGDS->m_bUseNoData &&
     216        1322 :         (eDataType == GDT_Float32 || eDataType == GDT_Float64) &&
     217          48 :         poGDS->m_nMaskCount > 0)
     218             :     {
     219          48 :         const unsigned char *pabyMask = poGDS->GetDecodedMask();
     220          48 :         if (pabyMask)
     221             :         {
     222          48 :             const unsigned char *pSrcMask =
     223             :                 pabyMask +
     224          48 :                 static_cast<size_t>(poGDS->m_nMaskCount == 1 ? 0 : nBand - 1) *
     225          48 :                     nRasterXSize * nRasterYSize +
     226          48 :                 static_cast<size_t>(nBlockYOff) * nRasterXSize;
     227          48 :             if (eDataType == GDT_Float32)
     228             :             {
     229          24 :                 SetNodataWhereMaskIsInvalid(
     230             :                     static_cast<float *>(pData), pSrcMask, nRasterXSize,
     231          24 :                     static_cast<float>(poGDS->m_dfNoDataValue));
     232             :             }
     233             :             else
     234             :             {
     235          24 :                 SetNodataWhereMaskIsInvalid(static_cast<double *>(pData),
     236             :                                             pSrcMask, nRasterXSize,
     237             :                                             poGDS->m_dfNoDataValue);
     238             :             }
     239             :         }
     240             :     }
     241             : 
     242        1346 :     return CE_None;
     243             : }
     244             : 
     245             : /************************************************************************/
     246             : /*                      LERCBand::GetNoDataValue()                      */
     247             : /************************************************************************/
     248             : 
     249          17 : double LERCBand::GetNoDataValue(int *pbHasNoData)
     250             : {
     251          17 :     auto poGDS = cpl::down_cast<LERCDataset *>(poDS);
     252          17 :     if (poGDS->m_bUseNoData &&
     253          16 :         (eDataType == GDT_Float32 || eDataType == GDT_Float64) &&
     254           2 :         poGDS->m_nMaskCount > 0)
     255             :     {
     256           2 :         if (pbHasNoData)
     257           2 :             *pbHasNoData = true;
     258           2 :         return std::numeric_limits<double>::quiet_NaN();
     259             :     }
     260          15 :     if (pbHasNoData)
     261          15 :         *pbHasNoData = false;
     262          15 :     return 0;
     263             : }
     264             : 
     265             : /************************************************************************/
     266             : /*                       LERCBand::GetMaskFlags()                       */
     267             : /************************************************************************/
     268             : 
     269          10 : int LERCBand::GetMaskFlags()
     270             : {
     271          10 :     auto poGDS = cpl::down_cast<LERCDataset *>(poDS);
     272          10 :     if (poGDS->m_bUseNoData &&
     273           9 :         (eDataType == GDT_Float32 || eDataType == GDT_Float64) &&
     274           2 :         poGDS->m_nMaskCount > 0)
     275             :     {
     276           2 :         return GMF_NODATA;
     277             :     }
     278           8 :     if (poGDS->m_nMaskCount == 0)
     279           7 :         return GMF_ALL_VALID;
     280           1 :     if (poGDS->m_nMaskCount == 1)
     281           1 :         return GMF_PER_DATASET;
     282           0 :     return 0;
     283             : }
     284             : 
     285             : /************************************************************************/
     286             : /*                       LERCBand::GetMaskBand()                        */
     287             : /************************************************************************/
     288             : 
     289          13 : GDALRasterBand *LERCBand::GetMaskBand()
     290             : {
     291          13 :     auto poGDS = cpl::down_cast<LERCDataset *>(poDS);
     292          13 :     if (poGDS->m_nMaskCount == 0)
     293           7 :         return GDALPamRasterBand::GetMaskBand();
     294           6 :     if (!m_poMaskBand)
     295           3 :         m_poMaskBand = std::make_unique<LERCMaskBand>(
     296           6 :             poGDS, poGDS->m_nMaskCount == 1 ? 0 : nBand - 1);
     297           6 :     return m_poMaskBand.get();
     298             : }
     299             : 
     300             : /************************************************************************/
     301             : /*                      LERCMaskBand::IReadBlock()                      */
     302             : /************************************************************************/
     303             : 
     304          72 : CPLErr LERCMaskBand::IReadBlock(int, int nBlockYOff, void *pData)
     305             : {
     306          72 :     auto poGDS = cpl::down_cast<LERCDataset *>(poDS);
     307          72 :     const unsigned char *pabyMask = poGDS->GetDecodedMask();
     308          72 :     if (!pabyMask)
     309           0 :         return CE_Failure;
     310             : 
     311          72 :     const unsigned char *pSrc =
     312             :         pabyMask +
     313          72 :         static_cast<size_t>(m_nMaskIdx) * nRasterXSize * nRasterYSize +
     314          72 :         static_cast<size_t>(nBlockYOff) * nRasterXSize;
     315          72 :     unsigned char *pDst = static_cast<unsigned char *>(pData);
     316        1656 :     for (int i = 0; i < nRasterXSize; ++i)
     317             :     {
     318        1584 :         pDst[i] = pSrc[i] ? 255 : 0;
     319             :     }
     320             : 
     321          72 :     return CE_None;
     322             : }
     323             : 
     324             : /************************************************************************/
     325             : /*                         LERCDataset::Open()                          */
     326             : /************************************************************************/
     327             : 
     328          11 : GDALDataset *LERCDataset::Open(GDALOpenInfo *poOpenInfo)
     329             : {
     330          11 :     if (poOpenInfo->eAccess == GA_Update || poOpenInfo->fpL == nullptr)
     331           0 :         return nullptr;
     332             : 
     333             :     VSIStatBufL sStat;
     334          11 :     if (VSIStatL(poOpenInfo->pszFilename, &sStat) != 0)
     335           0 :         return nullptr;
     336          22 :     if (static_cast<uint64_t>(sStat.st_size) >
     337          11 :         std::numeric_limits<unsigned>::max())
     338             :     {
     339           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Too large file");
     340           0 :         return nullptr;
     341             :     }
     342             : 
     343          11 :     const auto nRAMSize = CPLGetUsablePhysicalRAM();
     344          11 :     if (nRAMSize > 0 && sStat.st_size > nRAMSize)
     345             :     {
     346           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     347             :                  "Too large file compared to usable RAM");
     348           0 :         return nullptr;
     349             :     }
     350          11 :     const unsigned nBlobSize = static_cast<unsigned>(sStat.st_size);
     351             :     std::unique_ptr<unsigned char, VSIFreeReleaser> pabyBlob(
     352          22 :         static_cast<unsigned char *>(VSI_MALLOC_VERBOSE(nBlobSize)));
     353          11 :     if (!pabyBlob)
     354           0 :         return nullptr;
     355          22 :     if (poOpenInfo->fpL->Seek(0, SEEK_SET) != 0 ||
     356          11 :         poOpenInfo->fpL->Read(pabyBlob.get(), nBlobSize) != nBlobSize)
     357             :     {
     358           0 :         CPLError(CE_Failure, CPLE_FileIO, "Cannot read file");
     359           0 :         return nullptr;
     360             :     }
     361             : 
     362             :     unsigned int infoArray[9];
     363             :     /* Info returned in infoArray is { version, dataType, nDim/nDepth, nCols,
     364             :         nRows, nBands, nValidPixels, blobSize,
     365             :         and starting with liblerc 3.0 nRequestedMasks } */
     366             : 
     367             :     double arrayRange[3];
     368             :     /* Info returned in infoArray is { zMin, zMax, maxZErrUsed } */
     369             : 
     370             :     int lerc_ret =
     371          11 :         lerc_getBlobInfo(pabyBlob.get(), nBlobSize, infoArray, arrayRange,
     372          11 :                          CPL_ARRAYSIZE(infoArray), CPL_ARRAYSIZE(arrayRange));
     373          11 :     if (lerc_ret != 0)
     374             :     {
     375           0 :         CPLError(CE_Failure, CPLE_AppDefined, "lerc_getBlobInfo() failed");
     376           0 :         return nullptr;
     377             :     }
     378             : 
     379          11 :     GDALDataType eDT = GDT_Unknown;
     380          11 :     const unsigned nLercDataType = infoArray[1];
     381          11 :     switch (nLercDataType)
     382             :     {
     383           1 :         case 0:
     384           1 :             eDT = GDT_Int8;
     385           1 :             break;
     386           3 :         case 1:
     387           3 :             eDT = GDT_UInt8;
     388           3 :             break;
     389           1 :         case 2:
     390           1 :             eDT = GDT_Int16;
     391           1 :             break;
     392           1 :         case 3:
     393           1 :             eDT = GDT_UInt16;
     394           1 :             break;
     395           1 :         case 4:
     396           1 :             eDT = GDT_Int32;
     397           1 :             break;
     398           1 :         case 5:
     399           1 :             eDT = GDT_UInt32;
     400           1 :             break;
     401           2 :         case 6:
     402           2 :             eDT = GDT_Float32;
     403           2 :             break;
     404           1 :         case 7:
     405           1 :             eDT = GDT_Float64;
     406           1 :             break;
     407           0 :         default:
     408           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     409             :                      "Unhandled LERC data type %u", nLercDataType);
     410           0 :             return nullptr;
     411             :     }
     412             : 
     413          11 :     const unsigned nDepth = infoArray[2];
     414          11 :     const unsigned nCols = infoArray[3];
     415          11 :     const unsigned nRows = infoArray[4];
     416          11 :     const unsigned nLercBands = infoArray[5];
     417          11 :     if (nCols == 0 || nRows == 0 || nDepth == 0 || nLercBands == 0 ||
     418          11 :         nCols > static_cast<unsigned>(INT_MAX) ||
     419          11 :         nRows > static_cast<unsigned>(INT_MAX) ||
     420          11 :         nDepth > static_cast<unsigned>(INT_MAX) ||
     421          11 :         nLercBands > static_cast<unsigned>(INT_MAX))
     422             :     {
     423           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     424             :                  "Invalid dimensions: cols=%u x rows=%u x depth=%u x bands=%u",
     425             :                  nCols, nRows, nDepth, nLercBands);
     426           0 :         return nullptr;
     427             :     }
     428          11 :     const int nDTSize = GDALGetDataTypeSizeBytes(eDT);
     429             :     // nCols * nRows * nDepth limitation is due to liblerc assuming it fits on int
     430             :     // Cf third_party/LercLib/Lerc.cpp#L381
     431          11 :     if (nCols > std::numeric_limits<int>::max() / nRows ||
     432          11 :         nCols * nRows > std::numeric_limits<int>::max() / nDepth ||
     433          11 :         nDepth > static_cast<unsigned>(std::numeric_limits<int>::max()) /
     434          11 :                      nLercBands ||
     435          11 :         static_cast<size_t>(nCols) * nRows >
     436          11 :             (std::numeric_limits<size_t>::max() / 2) / nDTSize ||
     437          11 :         static_cast<size_t>(nCols) * nRows * nDTSize >
     438          11 :             (std::numeric_limits<size_t>::max() / 2) /
     439          33 :                 (static_cast<size_t>(nDepth) * nLercBands) ||
     440          11 :         (nRAMSize > 0 &&
     441          11 :          static_cast<GIntBig>(nCols) * nRows * nDTSize * nDepth * nLercBands >
     442          11 :              nRAMSize - sStat.st_size))
     443             :     {
     444           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     445             :                  "Too large dimensions cols=%u x rows=%u x depth=%u x bands=%u",
     446             :                  nCols, nRows, nDepth, nLercBands);
     447           0 :         return nullptr;
     448             :     }
     449             : 
     450          11 :     const int nGDALBands =
     451          11 :         static_cast<int>(nDepth) * static_cast<int>(nLercBands);
     452             : 
     453          11 :     if (nLercBands != 1)
     454             :     {
     455           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     456             :                  "LercBands=%u != 1 not supported", nLercBands);
     457           0 :         return nullptr;
     458             :     }
     459             : 
     460          22 :     auto poDS = std::make_unique<LERCDataset>();
     461          11 :     poDS->nRasterXSize = static_cast<int>(nCols);
     462          11 :     poDS->nRasterYSize = static_cast<int>(nRows);
     463          22 :     if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize) ||
     464          11 :         !GDALCheckBandCount(nGDALBands, /* bIsZeroAllowed =*/false))
     465             :     {
     466           0 :         return nullptr;
     467             :     }
     468             : 
     469          11 :     poDS->m_nLercDataType = nLercDataType;
     470          11 :     poDS->m_nBlobSize = nBlobSize;
     471          11 :     poDS->m_nDepth = static_cast<int>(nDepth);
     472          11 :     poDS->m_nLercBands = static_cast<int>(nLercBands);
     473             :     // Use by WMS driver in MRF/LERC mode
     474          11 :     const char *pszNDV = CSLFetchNameValue(poOpenInfo->papszOpenOptions, "NDV");
     475          11 :     if (pszNDV)
     476             :     {
     477           1 :         if (EQUAL(pszNDV, "none"))
     478           1 :             poDS->m_bUseNoData = false;
     479             :         else
     480           0 :             poDS->m_dfNoDataValue = CPLAtof(pszNDV);
     481             :     }
     482             : #if LERC_AT_LEAST_VERSION(3, 0, 0)
     483             :     poDS->m_nMaskCount = static_cast<int>(infoArray[8]);
     484             :     if (poDS->m_nMaskCount != 0 && poDS->m_nMaskCount != nGDALBands)
     485             :     {
     486             :         CPLError(CE_Failure, CPLE_NotSupported,
     487             :                  "Mask count=%d != 0 and != band count=%d not supported",
     488             :                  poDS->m_nMaskCount, nGDALBands);
     489             :         return nullptr;
     490             :     }
     491             : #else
     492          11 :     const unsigned nValidPixels = infoArray[6];
     493          11 :     const size_t nTotalPixels = static_cast<size_t>(nCols) * nRows;
     494          22 :     if (nTotalPixels <= std::numeric_limits<unsigned>::max() &&
     495          11 :         nValidPixels < nTotalPixels)
     496           3 :         poDS->m_nMaskCount = 1;
     497             : #endif
     498          11 :     poDS->m_pabyBlob = std::move(pabyBlob);
     499             : 
     500          24 :     for (int i = 0; i < nGDALBands; ++i)
     501             :     {
     502          13 :         poDS->SetBand(i + 1, std::make_unique<LERCBand>(poDS.get(), i, eDT));
     503             :     }
     504             : 
     505          11 :     if (nGDALBands > 1)
     506             :     {
     507           1 :         poDS->GDALDataset::SetMetadataItem("INTERLEAVE", "PIXEL",
     508             :                                            "IMAGE_STRUCTURE");
     509             :     }
     510          11 :     poDS->GDALDataset::SetMetadataItem("COMPRESSION", "LERC",
     511             :                                        "IMAGE_STRUCTURE");
     512          11 :     poDS->GDALDataset::SetMetadataItem(
     513             :         "MAX_Z_ERROR", CPLSPrintf("%g", arrayRange[2]), "IMAGE_STRUCTURE");
     514             : 
     515             :     // Initialize any PAM information.
     516          11 :     poDS->SetDescription(poOpenInfo->pszFilename);
     517          11 :     poDS->TryLoadXML(poOpenInfo->GetSiblingFiles());
     518          11 :     poDS->oOvManager.Initialize(poDS.get(), poOpenInfo);
     519             : 
     520          11 :     return poDS.release();
     521             : }
     522             : 
     523             : }  // namespace gdal
     524             : 
     525             : /************************************************************************/
     526             : /*                         GDALRegister_LERC()                          */
     527             : /************************************************************************/
     528             : 
     529        2135 : void GDALRegister_LERC()
     530             : {
     531        2135 :     if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
     532         263 :         return;
     533             : 
     534        3744 :     auto poDriver = std::make_unique<GDALDriver>();
     535        1872 :     LERCDriverSetCommonMetadata(poDriver.get());
     536             : 
     537             : #ifdef LERC_VERSION_MAJOR
     538             : #define XSTRINGIFY(X) #X
     539             : #define STRINGIFY(X) XSTRINGIFY(X)
     540             :     poDriver->SetMetadataItem(
     541             :         "LIBLERC_VERSION",
     542             :         STRINGIFY(LERC_VERSION_MAJOR) "." STRINGIFY(
     543             :             LERC_VERSION_MINOR) "." STRINGIFY(LERC_VERSION_PATCH));
     544             : #endif
     545             : 
     546        1872 :     poDriver->pfnOpen = gdal::LERCDataset::Open;
     547        1872 :     GetGDALDriverManager()->RegisterDriver(poDriver.release());
     548             : }

Generated by: LCOV version 1.14