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

Generated by: LCOV version 1.14