LCOV - code coverage report
Current view: top level - frmts/ctg - ctgdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 172 202 85.1 %
Date: 2024-11-21 22:18:42 Functions: 16 16 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  CTG driver
       4             :  * Purpose:  GDALDataset driver for CTG dataset.
       5             :  * Author:   Even Rouault, <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2011, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "gdal_frmts.h"
      14             : #include "gdal_pam.h"
      15             : #include "ogr_spatialref.h"
      16             : 
      17             : constexpr int HEADER_LINE_COUNT = 5;
      18             : 
      19             : typedef struct
      20             : {
      21             :     int nCode;
      22             :     const char *pszDesc;
      23             : } LULCDescStruct;
      24             : 
      25             : static const LULCDescStruct asLULCDesc[] = {
      26             :     {1, "Urban or Built-Up Land"},
      27             :     {2, "Agricultural Land"},
      28             :     {3, "Rangeland"},
      29             :     {4, "Forest Land"},
      30             :     {5, "Water"},
      31             :     {6, "Wetland"},
      32             :     {7, "Barren Land"},
      33             :     {8, "Tundra"},
      34             :     {9, "Perennial Snow and Ice"},
      35             :     {11, "Residential"},
      36             :     {12, "Commercial Services"},
      37             :     {13, "Industrial"},
      38             :     {14, "Transportation, Communications"},
      39             :     {15, "Industrial and Commercial"},
      40             :     {16, "Mixed Urban or Built-Up Land"},
      41             :     {17, "Other Urban or Built-Up Land"},
      42             :     {21, "Cropland and Pasture"},
      43             :     {22, "Orchards, Groves, Vineyards, Nurseries"},
      44             :     {23, "Confined Feeding Operations"},
      45             :     {24, "Other Agricultural Land"},
      46             :     {31, "Herbaceous Rangeland"},
      47             :     {32, "Shrub and Brush Rangeland"},
      48             :     {33, "Mixed Rangeland"},
      49             :     {41, "Deciduous Forest Land"},
      50             :     {42, "Evergreen Forest Land"},
      51             :     {43, "Mixed Forest Land"},
      52             :     {51, "Streams and Canals"},
      53             :     {52, "Lakes"},
      54             :     {53, "Reservoirs"},
      55             :     {54, "Bays and Estuaries"},
      56             :     {61, "Forested Wetlands"},
      57             :     {62, "Nonforested Wetlands"},
      58             :     {71, "Dry Salt Flats"},
      59             :     {72, "Beaches"},
      60             :     {73, "Sandy Areas Other than Beaches"},
      61             :     {74, "Bare Exposed Rock"},
      62             :     {75, "Strip Mines, Quarries, and Gravel Pits"},
      63             :     {76, "Transitional Areas"},
      64             :     {77, "Mixed Barren Land"},
      65             :     {81, "Shrub and Brush Tundra"},
      66             :     {82, "Herbaceous Tundra"},
      67             :     {83, "Bare Ground"},
      68             :     {84, "Wet Tundra"},
      69             :     {85, "Mixed Tundra"},
      70             :     {91, "Perennial Snowfields"},
      71             :     {92, "Glaciers"}};
      72             : 
      73             : static const char *const apszBandDescription[] = {
      74             :     "Land Use and Land Cover",
      75             :     "Political units",
      76             :     "Census county subdivisions and SMSA tracts",
      77             :     "Hydrologic units",
      78             :     "Federal land ownership",
      79             :     "State land ownership"};
      80             : 
      81             : /************************************************************************/
      82             : /* ==================================================================== */
      83             : /*                              CTGDataset                              */
      84             : /* ==================================================================== */
      85             : /************************************************************************/
      86             : 
      87             : class CTGRasterBand;
      88             : 
      89             : class CTGDataset final : public GDALPamDataset
      90             : {
      91             :     friend class CTGRasterBand;
      92             : 
      93             :     VSILFILE *fp;
      94             : 
      95             :     int nNWEasting, nNWNorthing, nCellSize, nUTMZone;
      96             :     OGRSpatialReference m_oSRS{};
      97             : 
      98             :     int bHasReadImagery;
      99             :     GByte *pabyImage;
     100             : 
     101             :     int ReadImagery();
     102             : 
     103             :     static const char *ExtractField(char *szOutput, const char *pszBuffer,
     104             :                                     int nOffset, int nLength);
     105             : 
     106             :   public:
     107             :     CTGDataset();
     108             :     ~CTGDataset() override;
     109             : 
     110             :     CPLErr GetGeoTransform(double *) override;
     111             : 
     112           1 :     const OGRSpatialReference *GetSpatialRef() const override
     113             :     {
     114           1 :         return &m_oSRS;
     115             :     }
     116             : 
     117             :     static GDALDataset *Open(GDALOpenInfo *);
     118             :     static int Identify(GDALOpenInfo *);
     119             : };
     120             : 
     121             : /************************************************************************/
     122             : /* ==================================================================== */
     123             : /*                            CTGRasterBand                             */
     124             : /* ==================================================================== */
     125             : /************************************************************************/
     126             : 
     127             : class CTGRasterBand final : public GDALPamRasterBand
     128             : {
     129             :     friend class CTGDataset;
     130             : 
     131             :     char **papszCategories;
     132             : 
     133             :   public:
     134             :     CTGRasterBand(CTGDataset *, int);
     135             :     ~CTGRasterBand() override;
     136             : 
     137             :     CPLErr IReadBlock(int, int, void *) override;
     138             :     double GetNoDataValue(int *pbSuccess = nullptr) override;
     139             :     char **GetCategoryNames() override;
     140             : };
     141             : 
     142             : /************************************************************************/
     143             : /*                           CTGRasterBand()                            */
     144             : /************************************************************************/
     145             : 
     146          18 : CTGRasterBand::CTGRasterBand(CTGDataset *poDSIn, int nBandIn)
     147          18 :     : papszCategories(nullptr)
     148             : {
     149          18 :     poDS = poDSIn;
     150          18 :     nBand = nBandIn;
     151             : 
     152          18 :     eDataType = GDT_Int32;
     153             : 
     154          18 :     nBlockXSize = poDS->GetRasterXSize();
     155          18 :     nBlockYSize = poDS->GetRasterYSize();
     156          18 : }
     157             : 
     158             : /************************************************************************/
     159             : /*                          ~CTGRasterBand()                            */
     160             : /************************************************************************/
     161             : 
     162          36 : CTGRasterBand::~CTGRasterBand()
     163             : 
     164             : {
     165          18 :     CSLDestroy(papszCategories);
     166          36 : }
     167             : 
     168             : /************************************************************************/
     169             : /*                             IReadBlock()                             */
     170             : /************************************************************************/
     171             : 
     172           1 : CPLErr CTGRasterBand::IReadBlock(int /* nBlockXOff */, int /* nBlockYOff */,
     173             :                                  void *pImage)
     174             : {
     175           1 :     CTGDataset *poGDS = (CTGDataset *)poDS;
     176             : 
     177           1 :     poGDS->ReadImagery();
     178           1 :     memcpy(pImage,
     179           1 :            poGDS->pabyImage +
     180           1 :                sizeof(int) * (nBand - 1) * nBlockXSize * nBlockYSize,
     181           1 :            sizeof(int) * nBlockXSize * nBlockYSize);
     182             : 
     183           1 :     return CE_None;
     184             : }
     185             : 
     186             : /************************************************************************/
     187             : /*                           GetNoDataValue()                           */
     188             : /************************************************************************/
     189             : 
     190           1 : double CTGRasterBand::GetNoDataValue(int *pbSuccess)
     191             : {
     192           1 :     if (pbSuccess)
     193           1 :         *pbSuccess = TRUE;
     194             : 
     195           1 :     return 0.0;
     196             : }
     197             : 
     198             : /************************************************************************/
     199             : /*                          GetCategoryNames()                          */
     200             : /************************************************************************/
     201             : 
     202           2 : char **CTGRasterBand::GetCategoryNames()
     203             : {
     204           2 :     if (nBand != 1)
     205           1 :         return nullptr;
     206             : 
     207           1 :     if (papszCategories != nullptr)
     208           0 :         return papszCategories;
     209             : 
     210           1 :     int nasLULCDescSize = (int)(sizeof(asLULCDesc) / sizeof(asLULCDesc[0]));
     211           1 :     int nCategoriesSize = asLULCDesc[nasLULCDescSize - 1].nCode;
     212           1 :     papszCategories = (char **)CPLCalloc(nCategoriesSize + 2, sizeof(char *));
     213          47 :     for (int i = 0; i < nasLULCDescSize; i++)
     214             :     {
     215          46 :         papszCategories[asLULCDesc[i].nCode] = CPLStrdup(asLULCDesc[i].pszDesc);
     216             :     }
     217          93 :     for (int i = 0; i < nCategoriesSize; i++)
     218             :     {
     219          92 :         if (papszCategories[i] == nullptr)
     220          47 :             papszCategories[i] = CPLStrdup("");
     221             :     }
     222           1 :     papszCategories[nCategoriesSize + 1] = nullptr;
     223             : 
     224           1 :     return papszCategories;
     225             : }
     226             : 
     227             : /************************************************************************/
     228             : /*                            ~CTGDataset()                            */
     229             : /************************************************************************/
     230             : 
     231           3 : CTGDataset::CTGDataset()
     232             :     : fp(nullptr), nNWEasting(0), nNWNorthing(0), nCellSize(0), nUTMZone(0),
     233           3 :       bHasReadImagery(FALSE), pabyImage(nullptr)
     234             : {
     235           3 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     236           3 : }
     237             : 
     238             : /************************************************************************/
     239             : /*                            ~CTGDataset()                            */
     240             : /************************************************************************/
     241             : 
     242           6 : CTGDataset::~CTGDataset()
     243             : 
     244             : {
     245           3 :     CPLFree(pabyImage);
     246           3 :     if (fp != nullptr)
     247           3 :         VSIFCloseL(fp);
     248           6 : }
     249             : 
     250             : /************************************************************************/
     251             : /*                              ExtractField()                          */
     252             : /************************************************************************/
     253             : 
     254          69 : const char *CTGDataset::ExtractField(char *szField, const char *pszBuffer,
     255             :                                      int nOffset, int nLength)
     256             : {
     257          69 :     CPLAssert(nLength <= 10);
     258          69 :     memcpy(szField, pszBuffer + nOffset, nLength);
     259          69 :     szField[nLength] = 0;
     260          69 :     return szField;
     261             : }
     262             : 
     263             : /************************************************************************/
     264             : /*                            ReadImagery()                             */
     265             : /************************************************************************/
     266             : 
     267           1 : int CTGDataset::ReadImagery()
     268             : {
     269           1 :     if (bHasReadImagery)
     270           0 :         return TRUE;
     271             : 
     272           1 :     bHasReadImagery = TRUE;
     273             : 
     274             :     char szLine[81];
     275             :     char szField[11];
     276           1 :     szLine[80] = 0;
     277           1 :     int nLine = HEADER_LINE_COUNT;
     278           1 :     VSIFSeekL(fp, nLine * 80, SEEK_SET);
     279           1 :     const int nCells = nRasterXSize * nRasterYSize;
     280           2 :     while (VSIFReadL(szLine, 1, 80, fp) == 80)
     281             :     {
     282           1 :         const int nZone = atoi(ExtractField(szField, szLine, 0, 3));
     283           1 :         if (nZone != nUTMZone)
     284             :         {
     285           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     286             :                      "Read error at line %d, %s. Did not expected UTM zone %d",
     287             :                      nLine, szLine, nZone);
     288           0 :             return FALSE;
     289             :         }
     290             :         const int nX =
     291           1 :             atoi(ExtractField(szField, szLine, 3, 8)) - nCellSize / 2;
     292             :         const int nY =
     293           1 :             atoi(ExtractField(szField, szLine, 11, 8)) + nCellSize / 2;
     294           1 :         const GIntBig nDiffX = static_cast<GIntBig>(nX) - nNWEasting;
     295           1 :         const GIntBig nDiffY = static_cast<GIntBig>(nNWNorthing) - nY;
     296           1 :         if (nDiffX < 0 || (nDiffX % nCellSize) != 0 || nDiffY < 0 ||
     297           1 :             (nDiffY % nCellSize) != 0)
     298             :         {
     299           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     300             :                      "Read error at line %d, %s. Unexpected cell coordinates",
     301             :                      nLine, szLine);
     302           0 :             return FALSE;
     303             :         }
     304           1 :         const GIntBig nCellX = nDiffX / nCellSize;
     305           1 :         const GIntBig nCellY = nDiffY / nCellSize;
     306           1 :         if (nCellX >= nRasterXSize || nCellY >= nRasterYSize)
     307             :         {
     308           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     309             :                      "Read error at line %d, %s. Unexpected cell coordinates",
     310             :                      nLine, szLine);
     311           0 :             return FALSE;
     312             :         }
     313           7 :         for (int i = 0; i < 6; i++)
     314             :         {
     315           6 :             int nVal = atoi(ExtractField(szField, szLine, 20 + 10 * i, 10));
     316           6 :             if (nVal >= 2000000000)
     317           0 :                 nVal = 0;
     318             :             ((int *)
     319           6 :                  pabyImage)[i * nCells +
     320           6 :                             static_cast<int>(nCellY) * nRasterXSize + nCellX] =
     321             :                 nVal;
     322             :         }
     323             : 
     324           1 :         nLine++;
     325             :     }
     326             : 
     327           1 :     return TRUE;
     328             : }
     329             : 
     330             : /************************************************************************/
     331             : /*                             Identify()                               */
     332             : /************************************************************************/
     333             : 
     334       50649 : int CTGDataset::Identify(GDALOpenInfo *poOpenInfo)
     335             : {
     336      101294 :     CPLString osFilename;  // let in that scope
     337             : 
     338       50648 :     GDALOpenInfo *poOpenInfoToDelete = nullptr;
     339             :     /*  GZipped grid_cell.gz files are common, so automagically open them */
     340             :     /*  if the /vsigzip/ has not been explicitly passed */
     341       50648 :     const char *pszFilename = CPLGetFilename(poOpenInfo->pszFilename);
     342       50651 :     if ((EQUAL(pszFilename, "grid_cell.gz") ||
     343       50650 :          EQUAL(pszFilename, "grid_cell1.gz") ||
     344       50650 :          EQUAL(pszFilename, "grid_cell2.gz")) &&
     345           1 :         !STARTS_WITH_CI(poOpenInfo->pszFilename, "/vsigzip/"))
     346             :     {
     347           0 :         osFilename = "/vsigzip/";
     348           0 :         osFilename += poOpenInfo->pszFilename;
     349           0 :         poOpenInfo = poOpenInfoToDelete = new GDALOpenInfo(
     350           0 :             osFilename.c_str(), GA_ReadOnly, poOpenInfo->GetSiblingFiles());
     351             :     }
     352             : 
     353       50651 :     if (poOpenInfo->nHeaderBytes < HEADER_LINE_COUNT * 80)
     354             :     {
     355       48450 :         delete poOpenInfoToDelete;
     356       48444 :         return FALSE;
     357             :     }
     358             : 
     359             :     /* -------------------------------------------------------------------- */
     360             :     /*      Check that it looks roughly as a CTG dataset                    */
     361             :     /* -------------------------------------------------------------------- */
     362        2201 :     const char *pszData = (const char *)poOpenInfo->pabyHeader;
     363        4766 :     for (int i = 0; i < 4 * 80; i++)
     364             :     {
     365        4759 :         if (!((pszData[i] >= '0' && pszData[i] <= '9') || pszData[i] == ' ' ||
     366        2196 :               pszData[i] == '-'))
     367             :         {
     368        2194 :             delete poOpenInfoToDelete;
     369        2194 :             return FALSE;
     370             :         }
     371             :     }
     372             : 
     373             :     char szField[11];
     374           7 :     int nRows = atoi(ExtractField(szField, pszData, 0, 10));
     375           7 :     int nCols = atoi(ExtractField(szField, pszData, 20, 10));
     376           7 :     int nMinColIndex = atoi(ExtractField(szField, pszData + 80, 0, 5));
     377           7 :     int nMinRowIndex = atoi(ExtractField(szField, pszData + 80, 5, 5));
     378           7 :     int nMaxColIndex = atoi(ExtractField(szField, pszData + 80, 10, 5));
     379           7 :     int nMaxRowIndex = atoi(ExtractField(szField, pszData + 80, 15, 5));
     380             : 
     381           7 :     if (nRows <= 0 || nCols <= 0 || nMinColIndex != 1 || nMinRowIndex != 1 ||
     382           6 :         nMaxRowIndex != nRows || nMaxColIndex != nCols)
     383             :     {
     384           1 :         delete poOpenInfoToDelete;
     385           1 :         return FALSE;
     386             :     }
     387             : 
     388           6 :     delete poOpenInfoToDelete;
     389           6 :     return TRUE;
     390             : }
     391             : 
     392             : /************************************************************************/
     393             : /*                                Open()                                */
     394             : /************************************************************************/
     395             : 
     396           3 : GDALDataset *CTGDataset::Open(GDALOpenInfo *poOpenInfo)
     397             : 
     398             : {
     399           3 :     if (!Identify(poOpenInfo))
     400           0 :         return nullptr;
     401             : 
     402           6 :     CPLString osFilename(poOpenInfo->pszFilename);
     403             : 
     404             :     /*  GZipped grid_cell.gz files are common, so automagically open them */
     405             :     /*  if the /vsigzip/ has not been explicitly passed */
     406           3 :     const char *pszFilename = CPLGetFilename(poOpenInfo->pszFilename);
     407           3 :     if ((EQUAL(pszFilename, "grid_cell.gz") ||
     408           3 :          EQUAL(pszFilename, "grid_cell1.gz") ||
     409           3 :          EQUAL(pszFilename, "grid_cell2.gz")) &&
     410           0 :         !STARTS_WITH_CI(poOpenInfo->pszFilename, "/vsigzip/"))
     411             :     {
     412           0 :         osFilename = "/vsigzip/";
     413           0 :         osFilename += poOpenInfo->pszFilename;
     414             :     }
     415             : 
     416           3 :     if (poOpenInfo->eAccess == GA_Update)
     417             :     {
     418           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     419             :                  "The CTG driver does not support update access to existing"
     420             :                  " datasets.\n");
     421           0 :         return nullptr;
     422             :     }
     423             : 
     424             :     /* -------------------------------------------------------------------- */
     425             :     /*      Find dataset characteristics                                    */
     426             :     /* -------------------------------------------------------------------- */
     427           3 :     VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "rb");
     428           3 :     if (fp == nullptr)
     429           0 :         return nullptr;
     430             : 
     431             :     char szHeader[HEADER_LINE_COUNT * 80 + 1];
     432           3 :     szHeader[HEADER_LINE_COUNT * 80] = 0;
     433           3 :     if (VSIFReadL(szHeader, 1, HEADER_LINE_COUNT * 80, fp) !=
     434             :         HEADER_LINE_COUNT * 80)
     435             :     {
     436           0 :         VSIFCloseL(fp);
     437           0 :         return nullptr;
     438             :     }
     439             : 
     440         216 :     for (int i = HEADER_LINE_COUNT * 80 - 1; i >= 0; i--)
     441             :     {
     442         216 :         if (szHeader[i] == ' ')
     443         213 :             szHeader[i] = 0;
     444             :         else
     445           3 :             break;
     446             :     }
     447             : 
     448             :     char szField[11];
     449           3 :     int nRows = atoi(ExtractField(szField, szHeader, 0, 10));
     450           3 :     int nCols = atoi(ExtractField(szField, szHeader, 20, 10));
     451             : 
     452             :     /* -------------------------------------------------------------------- */
     453             :     /*      Create a corresponding GDALDataset.                             */
     454             :     /* -------------------------------------------------------------------- */
     455           3 :     CTGDataset *poDS = new CTGDataset();
     456           3 :     poDS->fp = fp;
     457           3 :     fp = nullptr;
     458           3 :     poDS->nRasterXSize = nCols;
     459           3 :     poDS->nRasterYSize = nRows;
     460             : 
     461           3 :     poDS->SetMetadataItem("TITLE", szHeader + 4 * 80);
     462             : 
     463           3 :     poDS->nCellSize = atoi(ExtractField(szField, szHeader, 35, 5));
     464           3 :     if (poDS->nCellSize <= 0 || poDS->nCellSize >= 10000)
     465             :     {
     466           0 :         delete poDS;
     467           0 :         return nullptr;
     468             :     }
     469           3 :     poDS->nNWEasting = atoi(ExtractField(szField, szHeader + 3 * 80, 40, 10));
     470           3 :     poDS->nNWNorthing = atoi(ExtractField(szField, szHeader + 3 * 80, 50, 10));
     471           3 :     poDS->nUTMZone = atoi(ExtractField(szField, szHeader, 50, 5));
     472           3 :     if (poDS->nUTMZone <= 0 || poDS->nUTMZone > 60)
     473             :     {
     474           0 :         delete poDS;
     475           0 :         return nullptr;
     476             :     }
     477             : 
     478           3 :     poDS->m_oSRS.importFromEPSG(32600 + poDS->nUTMZone);
     479             : 
     480           3 :     if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize))
     481             :     {
     482           0 :         delete poDS;
     483           0 :         return nullptr;
     484             :     }
     485             : 
     486             :     /* -------------------------------------------------------------------- */
     487             :     /*      Read the imagery                                                */
     488             :     /* -------------------------------------------------------------------- */
     489             :     GByte *pabyImage =
     490           3 :         (GByte *)VSICalloc(static_cast<size_t>(nCols) * nRows, 6 * sizeof(int));
     491           3 :     if (pabyImage == nullptr)
     492             :     {
     493           0 :         delete poDS;
     494           0 :         return nullptr;
     495             :     }
     496           3 :     poDS->pabyImage = pabyImage;
     497             : 
     498             :     /* -------------------------------------------------------------------- */
     499             :     /*      Create band information objects.                                */
     500             :     /* -------------------------------------------------------------------- */
     501           3 :     poDS->nBands = 6;
     502          21 :     for (int i = 0; i < poDS->nBands; i++)
     503             :     {
     504          18 :         poDS->SetBand(i + 1, new CTGRasterBand(poDS, i + 1));
     505          18 :         poDS->GetRasterBand(i + 1)->SetDescription(apszBandDescription[i]);
     506             :     }
     507             : 
     508             :     /* -------------------------------------------------------------------- */
     509             :     /*      Initialize any PAM information.                                 */
     510             :     /* -------------------------------------------------------------------- */
     511           3 :     poDS->SetDescription(poOpenInfo->pszFilename);
     512           3 :     poDS->TryLoadXML();
     513             : 
     514             :     /* -------------------------------------------------------------------- */
     515             :     /*      Support overviews.                                              */
     516             :     /* -------------------------------------------------------------------- */
     517           3 :     poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename);
     518             : 
     519           3 :     return poDS;
     520             : }
     521             : 
     522             : /************************************************************************/
     523             : /*                          GetGeoTransform()                           */
     524             : /************************************************************************/
     525             : 
     526           1 : CPLErr CTGDataset::GetGeoTransform(double *padfTransform)
     527             : 
     528             : {
     529           1 :     padfTransform[0] = static_cast<double>(nNWEasting) - nCellSize / 2;
     530           1 :     padfTransform[1] = nCellSize;
     531           1 :     padfTransform[2] = 0;
     532           1 :     padfTransform[3] = static_cast<double>(nNWNorthing) + nCellSize / 2;
     533           1 :     padfTransform[4] = 0.;
     534           1 :     padfTransform[5] = -nCellSize;
     535             : 
     536           1 :     return CE_None;
     537             : }
     538             : 
     539             : /************************************************************************/
     540             : /*                         GDALRegister_CTG()                           */
     541             : /************************************************************************/
     542             : 
     543        1595 : void GDALRegister_CTG()
     544             : 
     545             : {
     546        1595 :     if (GDALGetDriverByName("CTG") != nullptr)
     547         302 :         return;
     548             : 
     549        1293 :     GDALDriver *poDriver = new GDALDriver();
     550             : 
     551        1293 :     poDriver->SetDescription("CTG");
     552        1293 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
     553        1293 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
     554        1293 :                               "USGS LULC Composite Theme Grid");
     555        1293 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/ctg.html");
     556             : 
     557        1293 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
     558             : 
     559        1293 :     poDriver->pfnOpen = CTGDataset::Open;
     560        1293 :     poDriver->pfnIdentify = CTGDataset::Identify;
     561             : 
     562        1293 :     GetGDALDriverManager()->RegisterDriver(poDriver);
     563             : }

Generated by: LCOV version 1.14