LCOV - code coverage report
Current view: top level - frmts/raw - lcpdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 702 825 85.1 %
Date: 2025-01-18 12:42:00 Functions: 12 12 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  LCP Driver
       4             :  * Purpose:  FARSITE v.4 Landscape file (.lcp) reader for GDAL
       5             :  * Author:   Chris Toney
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2008, Chris Toney
       9             :  * Copyright (c) 2008-2011, Even Rouault <even dot rouault at spatialys.com>
      10             :  * Copyright (c) 2013, Kyle Shannon <kyle at pobox dot com>
      11             :  *
      12             :  * SPDX-License-Identifier: MIT
      13             :  ****************************************************************************/
      14             : 
      15             : #include "cpl_port.h"
      16             : #include "cpl_string.h"
      17             : #include "gdal_frmts.h"
      18             : #include "ogr_spatialref.h"
      19             : #include "rawdataset.h"
      20             : 
      21             : #include <algorithm>
      22             : #include <limits>
      23             : 
      24             : constexpr size_t LCP_HEADER_SIZE = 7316;
      25             : constexpr int LCP_MAX_BANDS = 10;
      26             : constexpr int LCP_MAX_PATH = 256;
      27             : constexpr int LCP_MAX_DESC = 512;
      28             : constexpr int LCP_MAX_CLASSES = 100;
      29             : 
      30             : /************************************************************************/
      31             : /* ==================================================================== */
      32             : /*                              LCPDataset                              */
      33             : /* ==================================================================== */
      34             : /************************************************************************/
      35             : 
      36             : class LCPDataset final : public RawDataset
      37             : {
      38             :     VSILFILE *fpImage;  // image data file.
      39             :     char pachHeader[LCP_HEADER_SIZE];
      40             : 
      41             :     CPLString osPrjFilename{};
      42             :     OGRSpatialReference m_oSRS{};
      43             : 
      44             :     static CPLErr ClassifyBandData(GDALRasterBand *poBand, GInt32 &nNumClasses,
      45             :                                    GInt32 *panClasses);
      46             : 
      47             :     CPL_DISALLOW_COPY_ASSIGN(LCPDataset)
      48             : 
      49             :     CPLErr Close() override;
      50             : 
      51             :   public:
      52             :     LCPDataset();
      53             :     ~LCPDataset() override;
      54             : 
      55             :     char **GetFileList(void) override;
      56             : 
      57             :     CPLErr GetGeoTransform(double *) override;
      58             : 
      59             :     static int Identify(GDALOpenInfo *);
      60             :     static GDALDataset *Open(GDALOpenInfo *);
      61             :     static GDALDataset *CreateCopy(const char *pszFilename,
      62             :                                    GDALDataset *poSrcDS, int bStrict,
      63             :                                    char **papszOptions,
      64             :                                    GDALProgressFunc pfnProgress,
      65             :                                    void *pProgressData);
      66             : 
      67           3 :     const OGRSpatialReference *GetSpatialRef() const override
      68             :     {
      69           3 :         return &m_oSRS;
      70             :     }
      71             : };
      72             : 
      73             : /************************************************************************/
      74             : /*                            LCPDataset()                              */
      75             : /************************************************************************/
      76             : 
      77          75 : LCPDataset::LCPDataset() : fpImage(nullptr)
      78             : {
      79          75 :     memset(pachHeader, 0, sizeof(pachHeader));
      80          75 : }
      81             : 
      82             : /************************************************************************/
      83             : /*                            ~LCPDataset()                             */
      84             : /************************************************************************/
      85             : 
      86         150 : LCPDataset::~LCPDataset()
      87             : 
      88             : {
      89          75 :     LCPDataset::Close();
      90         150 : }
      91             : 
      92             : /************************************************************************/
      93             : /*                              Close()                                 */
      94             : /************************************************************************/
      95             : 
      96         132 : CPLErr LCPDataset::Close()
      97             : {
      98         132 :     CPLErr eErr = CE_None;
      99         132 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
     100             :     {
     101          75 :         if (LCPDataset::FlushCache(true) != CE_None)
     102           0 :             eErr = CE_Failure;
     103             : 
     104          75 :         if (fpImage)
     105             :         {
     106          75 :             if (VSIFCloseL(fpImage) != 0)
     107             :             {
     108           0 :                 CPLError(CE_Failure, CPLE_FileIO, "I/O error");
     109           0 :                 eErr = CE_Failure;
     110             :             }
     111             :         }
     112             : 
     113          75 :         if (GDALPamDataset::Close() != CE_None)
     114           0 :             eErr = CE_Failure;
     115             :     }
     116         132 :     return eErr;
     117             : }
     118             : 
     119             : /************************************************************************/
     120             : /*                          GetGeoTransform()                           */
     121             : /************************************************************************/
     122             : 
     123           2 : CPLErr LCPDataset::GetGeoTransform(double *padfTransform)
     124             : {
     125           2 :     double dfEast = 0.0;
     126           2 :     double dfWest = 0.0;
     127           2 :     double dfNorth = 0.0;
     128           2 :     double dfSouth = 0.0;
     129           2 :     double dfCellX = 0.0;
     130           2 :     double dfCellY = 0.0;
     131             : 
     132           2 :     memcpy(&dfEast, pachHeader + 4172, sizeof(double));
     133           2 :     memcpy(&dfWest, pachHeader + 4180, sizeof(double));
     134           2 :     memcpy(&dfNorth, pachHeader + 4188, sizeof(double));
     135           2 :     memcpy(&dfSouth, pachHeader + 4196, sizeof(double));
     136           2 :     memcpy(&dfCellX, pachHeader + 4208, sizeof(double));
     137           2 :     memcpy(&dfCellY, pachHeader + 4216, sizeof(double));
     138           2 :     CPL_LSBPTR64(&dfEast);
     139           2 :     CPL_LSBPTR64(&dfWest);
     140           2 :     CPL_LSBPTR64(&dfNorth);
     141           2 :     CPL_LSBPTR64(&dfSouth);
     142           2 :     CPL_LSBPTR64(&dfCellX);
     143           2 :     CPL_LSBPTR64(&dfCellY);
     144             : 
     145           2 :     padfTransform[0] = dfWest;
     146           2 :     padfTransform[3] = dfNorth;
     147           2 :     padfTransform[1] = dfCellX;
     148           2 :     padfTransform[2] = 0.0;
     149             : 
     150           2 :     padfTransform[4] = 0.0;
     151           2 :     padfTransform[5] = -1 * dfCellY;
     152             : 
     153           2 :     return CE_None;
     154             : }
     155             : 
     156             : /************************************************************************/
     157             : /*                              Identify()                              */
     158             : /************************************************************************/
     159             : 
     160       52272 : int LCPDataset::Identify(GDALOpenInfo *poOpenInfo)
     161             : 
     162             : {
     163             :     /* -------------------------------------------------------------------- */
     164             :     /*      Verify that this is a FARSITE v.4 LCP file                      */
     165             :     /* -------------------------------------------------------------------- */
     166       52272 :     if (poOpenInfo->nHeaderBytes < 50)
     167       48594 :         return FALSE;
     168             : 
     169             :     /* check if first three fields have valid data */
     170        3678 :     if ((CPL_LSBSINT32PTR(poOpenInfo->pabyHeader) != 20 &&
     171        3664 :          CPL_LSBSINT32PTR(poOpenInfo->pabyHeader) != 21) ||
     172         168 :         (CPL_LSBSINT32PTR(poOpenInfo->pabyHeader + 4) != 20 &&
     173         142 :          CPL_LSBSINT32PTR(poOpenInfo->pabyHeader + 4) != 21) ||
     174         168 :         (CPL_LSBSINT32PTR(poOpenInfo->pabyHeader + 8) < -90 ||
     175         168 :          CPL_LSBSINT32PTR(poOpenInfo->pabyHeader + 8) > 90))
     176             :     {
     177        3510 :         return FALSE;
     178             :     }
     179             : 
     180             : /* -------------------------------------------------------------------- */
     181             : /*      Check file extension                                            */
     182             : /* -------------------------------------------------------------------- */
     183             : #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
     184         168 :     const char *pszFileExtension = poOpenInfo->osExtension.c_str();
     185         168 :     if (!EQUAL(pszFileExtension, "lcp"))
     186             :     {
     187           0 :         return FALSE;
     188             :     }
     189             : #endif
     190             : 
     191         168 :     return TRUE;
     192             : }
     193             : 
     194             : /************************************************************************/
     195             : /*                            GetFileList()                             */
     196             : /************************************************************************/
     197             : 
     198          38 : char **LCPDataset::GetFileList()
     199             : 
     200             : {
     201          38 :     char **papszFileList = GDALPamDataset::GetFileList();
     202             : 
     203          38 :     if (!m_oSRS.IsEmpty())
     204             :     {
     205           1 :         papszFileList = CSLAddString(papszFileList, osPrjFilename);
     206             :     }
     207             : 
     208          38 :     return papszFileList;
     209             : }
     210             : 
     211             : /************************************************************************/
     212             : /*                                Open()                                */
     213             : /************************************************************************/
     214             : 
     215          75 : GDALDataset *LCPDataset::Open(GDALOpenInfo *poOpenInfo)
     216             : 
     217             : {
     218             :     /* -------------------------------------------------------------------- */
     219             :     /*      Verify that this is a FARSITE LCP file                          */
     220             :     /* -------------------------------------------------------------------- */
     221          75 :     if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr)
     222           0 :         return nullptr;
     223             : 
     224             :     /* -------------------------------------------------------------------- */
     225             :     /*      Confirm the requested access is supported.                      */
     226             :     /* -------------------------------------------------------------------- */
     227          75 :     if (poOpenInfo->eAccess == GA_Update)
     228             :     {
     229           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     230             :                  "The LCP driver does not support update access to existing"
     231             :                  " datasets.");
     232           0 :         return nullptr;
     233             :     }
     234             :     /* -------------------------------------------------------------------- */
     235             :     /*      Create a corresponding GDALDataset.                             */
     236             :     /* -------------------------------------------------------------------- */
     237         150 :     auto poDS = std::make_unique<LCPDataset>();
     238          75 :     std::swap(poDS->fpImage, poOpenInfo->fpL);
     239             : 
     240             :     /* -------------------------------------------------------------------- */
     241             :     /*      Read the header and extract some information.                   */
     242             :     /* -------------------------------------------------------------------- */
     243         150 :     if (VSIFSeekL(poDS->fpImage, 0, SEEK_SET) < 0 ||
     244          75 :         VSIFReadL(poDS->pachHeader, 1, LCP_HEADER_SIZE, poDS->fpImage) !=
     245             :             LCP_HEADER_SIZE)
     246             :     {
     247           0 :         CPLError(CE_Failure, CPLE_FileIO, "File too short");
     248           0 :         return nullptr;
     249             :     }
     250             : 
     251          75 :     const int nWidth = CPL_LSBSINT32PTR(poDS->pachHeader + 4164);
     252          75 :     const int nHeight = CPL_LSBSINT32PTR(poDS->pachHeader + 4168);
     253             : 
     254          75 :     poDS->nRasterXSize = nWidth;
     255          75 :     poDS->nRasterYSize = nHeight;
     256             : 
     257          75 :     if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize))
     258             :     {
     259           0 :         return nullptr;
     260             :     }
     261             : 
     262             :     // Crown fuels = canopy height, canopy base height, canopy bulk density.
     263             :     // 21 = have them, 20 = do not have them
     264             :     const bool bHaveCrownFuels =
     265          75 :         CPL_TO_BOOL(CPL_LSBSINT32PTR(poDS->pachHeader + 0) - 20);
     266             :     // Ground fuels = duff loading, coarse woody.
     267             :     const bool bHaveGroundFuels =
     268          75 :         CPL_TO_BOOL(CPL_LSBSINT32PTR(poDS->pachHeader + 4) - 20);
     269             : 
     270          75 :     int nBands = 0;
     271          75 :     if (bHaveCrownFuels)
     272             :     {
     273          69 :         if (bHaveGroundFuels)
     274          60 :             nBands = 10;
     275             :         else
     276           9 :             nBands = 8;
     277             :     }
     278             :     else
     279             :     {
     280           6 :         if (bHaveGroundFuels)
     281           3 :             nBands = 7;
     282             :         else
     283           3 :             nBands = 5;
     284             :     }
     285             : 
     286             :     // Add dataset-level metadata.
     287             : 
     288          75 :     int nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 8);
     289          75 :     char szTemp[32] = {'\0'};
     290          75 :     snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     291          75 :     poDS->SetMetadataItem("LATITUDE", szTemp);
     292             : 
     293          75 :     nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 4204);
     294          75 :     if (nTemp == 0)
     295          74 :         poDS->SetMetadataItem("LINEAR_UNIT", "Meters");
     296          75 :     if (nTemp == 1)
     297           1 :         poDS->SetMetadataItem("LINEAR_UNIT", "Feet");
     298             : 
     299          75 :     poDS->pachHeader[LCP_HEADER_SIZE - 1] = '\0';
     300          75 :     poDS->SetMetadataItem("DESCRIPTION", poDS->pachHeader + 6804);
     301             : 
     302             :     /* -------------------------------------------------------------------- */
     303             :     /*      Create band information objects.                                */
     304             :     /* -------------------------------------------------------------------- */
     305             : 
     306          75 :     const int iPixelSize = nBands * 2;
     307             : 
     308          75 :     if (nWidth > INT_MAX / iPixelSize)
     309             :     {
     310           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Int overflow occurred");
     311           0 :         return nullptr;
     312             :     }
     313             : 
     314         783 :     for (int iBand = 1; iBand <= nBands; iBand++)
     315             :     {
     316             :         auto poBand =
     317        1416 :             RawRasterBand::Create(poDS.get(), iBand, poDS->fpImage,
     318         708 :                                   LCP_HEADER_SIZE + ((iBand - 1) * 2),
     319             :                                   iPixelSize, iPixelSize * nWidth, GDT_Int16,
     320             :                                   RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN,
     321         708 :                                   RawRasterBand::OwnFP::NO);
     322         708 :         if (!poBand)
     323           0 :             return nullptr;
     324             : 
     325         708 :         switch (iBand)
     326             :         {
     327          75 :             case 1:
     328          75 :                 poBand->SetDescription("Elevation");
     329             : 
     330          75 :                 nTemp = CPL_LSBUINT16PTR(poDS->pachHeader + 4224);
     331          75 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     332          75 :                 poBand->SetMetadataItem("ELEVATION_UNIT", szTemp);
     333             : 
     334          75 :                 if (nTemp == 0)
     335          74 :                     poBand->SetMetadataItem("ELEVATION_UNIT_NAME", "Meters");
     336          75 :                 if (nTemp == 1)
     337           1 :                     poBand->SetMetadataItem("ELEVATION_UNIT_NAME", "Feet");
     338             : 
     339          75 :                 nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 44);
     340          75 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     341          75 :                 poBand->SetMetadataItem("ELEVATION_MIN", szTemp);
     342             : 
     343          75 :                 nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 48);
     344          75 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     345          75 :                 poBand->SetMetadataItem("ELEVATION_MAX", szTemp);
     346             : 
     347          75 :                 nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 52);
     348          75 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     349          75 :                 poBand->SetMetadataItem("ELEVATION_NUM_CLASSES", szTemp);
     350             : 
     351          75 :                 *(poDS->pachHeader + 4244 + 255) = '\0';
     352          75 :                 poBand->SetMetadataItem("ELEVATION_FILE",
     353          75 :                                         poDS->pachHeader + 4244);
     354             : 
     355          75 :                 break;
     356             : 
     357          75 :             case 2:
     358          75 :                 poBand->SetDescription("Slope");
     359             : 
     360          75 :                 nTemp = CPL_LSBUINT16PTR(poDS->pachHeader + 4226);
     361          75 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     362          75 :                 poBand->SetMetadataItem("SLOPE_UNIT", szTemp);
     363             : 
     364          75 :                 if (nTemp == 0)
     365          74 :                     poBand->SetMetadataItem("SLOPE_UNIT_NAME", "Degrees");
     366          75 :                 if (nTemp == 1)
     367           1 :                     poBand->SetMetadataItem("SLOPE_UNIT_NAME", "Percent");
     368             : 
     369          75 :                 nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 456);
     370          75 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     371          75 :                 poBand->SetMetadataItem("SLOPE_MIN", szTemp);
     372             : 
     373          75 :                 nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 460);
     374          75 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     375          75 :                 poBand->SetMetadataItem("SLOPE_MAX", szTemp);
     376             : 
     377          75 :                 nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 464);
     378          75 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     379          75 :                 poBand->SetMetadataItem("SLOPE_NUM_CLASSES", szTemp);
     380             : 
     381          75 :                 *(poDS->pachHeader + 4500 + 255) = '\0';
     382          75 :                 poBand->SetMetadataItem("SLOPE_FILE", poDS->pachHeader + 4500);
     383             : 
     384          75 :                 break;
     385             : 
     386          75 :             case 3:
     387          75 :                 poBand->SetDescription("Aspect");
     388             : 
     389          75 :                 nTemp = CPL_LSBUINT16PTR(poDS->pachHeader + 4228);
     390          75 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     391          75 :                 poBand->SetMetadataItem("ASPECT_UNIT", szTemp);
     392             : 
     393          75 :                 if (nTemp == 0)
     394           3 :                     poBand->SetMetadataItem("ASPECT_UNIT_NAME",
     395           3 :                                             "Grass categories");
     396          75 :                 if (nTemp == 1)
     397           1 :                     poBand->SetMetadataItem("ASPECT_UNIT_NAME",
     398           1 :                                             "Grass degrees");
     399          75 :                 if (nTemp == 2)
     400          71 :                     poBand->SetMetadataItem("ASPECT_UNIT_NAME",
     401          71 :                                             "Azimuth degrees");
     402             : 
     403          75 :                 nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 868);
     404          75 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     405          75 :                 poBand->SetMetadataItem("ASPECT_MIN", szTemp);
     406             : 
     407          75 :                 nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 872);
     408          75 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     409          75 :                 poBand->SetMetadataItem("ASPECT_MAX", szTemp);
     410             : 
     411          75 :                 nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 876);
     412          75 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     413          75 :                 poBand->SetMetadataItem("ASPECT_NUM_CLASSES", szTemp);
     414             : 
     415          75 :                 *(poDS->pachHeader + 4756 + 255) = '\0';
     416          75 :                 poBand->SetMetadataItem("ASPECT_FILE", poDS->pachHeader + 4756);
     417             : 
     418          75 :                 break;
     419             : 
     420          75 :             case 4:
     421             :             {
     422          75 :                 poBand->SetDescription("Fuel models");
     423             : 
     424          75 :                 nTemp = CPL_LSBUINT16PTR(poDS->pachHeader + 4230);
     425          75 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     426          75 :                 poBand->SetMetadataItem("FUEL_MODEL_OPTION", szTemp);
     427             : 
     428          75 :                 if (nTemp == 0)
     429          75 :                     poBand->SetMetadataItem(
     430             :                         "FUEL_MODEL_OPTION_DESC",
     431          75 :                         "no custom models AND no conversion file needed");
     432          75 :                 if (nTemp == 1)
     433           0 :                     poBand->SetMetadataItem(
     434             :                         "FUEL_MODEL_OPTION_DESC",
     435           0 :                         "custom models BUT no conversion file needed");
     436          75 :                 if (nTemp == 2)
     437           0 :                     poBand->SetMetadataItem(
     438             :                         "FUEL_MODEL_OPTION_DESC",
     439           0 :                         "no custom models BUT conversion file needed");
     440          75 :                 if (nTemp == 3)
     441           0 :                     poBand->SetMetadataItem(
     442             :                         "FUEL_MODEL_OPTION_DESC",
     443           0 :                         "custom models AND conversion file needed");
     444             : 
     445          75 :                 const int nMinFM = CPL_LSBSINT32PTR(poDS->pachHeader + 1280);
     446          75 :                 snprintf(szTemp, sizeof(szTemp), "%d", nMinFM);
     447          75 :                 poBand->SetMetadataItem("FUEL_MODEL_MIN", szTemp);
     448             : 
     449          75 :                 const int nMaxFM = CPL_LSBSINT32PTR(poDS->pachHeader + 1284);
     450          75 :                 snprintf(szTemp, sizeof(szTemp), "%d", nMaxFM);
     451          75 :                 poBand->SetMetadataItem("FUEL_MODEL_MAX", szTemp);
     452             : 
     453          75 :                 nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 1288);
     454          75 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     455          75 :                 poBand->SetMetadataItem("FUEL_MODEL_NUM_CLASSES", szTemp);
     456             : 
     457         150 :                 std::string osValues;
     458          75 :                 if (nTemp > 0 && nTemp <= 100)
     459             :                 {
     460         267 :                     for (int i = 0; i <= nTemp; i++)
     461             :                     {
     462         192 :                         const int nTemp2 = CPL_LSBSINT32PTR(poDS->pachHeader +
     463             :                                                             (1292 + (i * 4)));
     464         192 :                         if (nTemp2 >= nMinFM && nTemp2 <= nMaxFM)
     465             :                         {
     466         100 :                             snprintf(szTemp, sizeof(szTemp), "%d", nTemp2);
     467         100 :                             if (!osValues.empty())
     468          27 :                                 osValues += ',';
     469         100 :                             osValues += szTemp;
     470             :                         }
     471             :                     }
     472             :                 }
     473          75 :                 poBand->SetMetadataItem("FUEL_MODEL_VALUES", osValues.c_str());
     474             : 
     475          75 :                 *(poDS->pachHeader + 5012 + 255) = '\0';
     476          75 :                 poBand->SetMetadataItem("FUEL_MODEL_FILE",
     477          75 :                                         poDS->pachHeader + 5012);
     478             : 
     479          75 :                 break;
     480             :             }
     481          75 :             case 5:
     482          75 :                 poBand->SetDescription("Canopy cover");
     483             : 
     484          75 :                 nTemp = CPL_LSBUINT16PTR(poDS->pachHeader + 4232);
     485          75 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     486          75 :                 poBand->SetMetadataItem("CANOPY_COV_UNIT", szTemp);
     487             : 
     488          75 :                 if (nTemp == 0)
     489           4 :                     poBand->SetMetadataItem("CANOPY_COV_UNIT_NAME",
     490           4 :                                             "Categories (0-4)");
     491          75 :                 if (nTemp == 1)
     492          71 :                     poBand->SetMetadataItem("CANOPY_COV_UNIT_NAME", "Percent");
     493             : 
     494          75 :                 nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 1692);
     495          75 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     496          75 :                 poBand->SetMetadataItem("CANOPY_COV_MIN", szTemp);
     497             : 
     498          75 :                 nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 1696);
     499          75 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     500          75 :                 poBand->SetMetadataItem("CANOPY_COV_MAX", szTemp);
     501             : 
     502          75 :                 nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 1700);
     503          75 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     504          75 :                 poBand->SetMetadataItem("CANOPY_COV_NUM_CLASSES", szTemp);
     505             : 
     506          75 :                 *(poDS->pachHeader + 5268 + 255) = '\0';
     507          75 :                 poBand->SetMetadataItem("CANOPY_COV_FILE",
     508          75 :                                         poDS->pachHeader + 5268);
     509             : 
     510          75 :                 break;
     511             : 
     512          72 :             case 6:
     513          72 :                 if (bHaveCrownFuels)
     514             :                 {
     515          69 :                     poBand->SetDescription("Canopy height");
     516             : 
     517          69 :                     nTemp = CPL_LSBUINT16PTR(poDS->pachHeader + 4234);
     518          69 :                     snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     519          69 :                     poBand->SetMetadataItem("CANOPY_HT_UNIT", szTemp);
     520             : 
     521          69 :                     if (nTemp == 1)
     522           3 :                         poBand->SetMetadataItem("CANOPY_HT_UNIT_NAME",
     523           3 :                                                 "Meters");
     524          69 :                     if (nTemp == 2)
     525           3 :                         poBand->SetMetadataItem("CANOPY_HT_UNIT_NAME", "Feet");
     526          69 :                     if (nTemp == 3)
     527          62 :                         poBand->SetMetadataItem("CANOPY_HT_UNIT_NAME",
     528          62 :                                                 "Meters x 10");
     529          69 :                     if (nTemp == 4)
     530           1 :                         poBand->SetMetadataItem("CANOPY_HT_UNIT_NAME",
     531           1 :                                                 "Feet x 10");
     532             : 
     533          69 :                     nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 2104);
     534          69 :                     snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     535          69 :                     poBand->SetMetadataItem("CANOPY_HT_MIN", szTemp);
     536             : 
     537          69 :                     nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 2108);
     538          69 :                     snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     539          69 :                     poBand->SetMetadataItem("CANOPY_HT_MAX", szTemp);
     540             : 
     541          69 :                     nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 2112);
     542          69 :                     snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     543          69 :                     poBand->SetMetadataItem("CANOPY_HT_NUM_CLASSES", szTemp);
     544             : 
     545          69 :                     *(poDS->pachHeader + 5524 + 255) = '\0';
     546          69 :                     poBand->SetMetadataItem("CANOPY_HT_FILE",
     547          69 :                                             poDS->pachHeader + 5524);
     548             :                 }
     549             :                 else
     550             :                 {
     551           3 :                     poBand->SetDescription("Duff");
     552             : 
     553           3 :                     nTemp = CPL_LSBUINT16PTR(poDS->pachHeader + 4240);
     554           3 :                     snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     555           3 :                     poBand->SetMetadataItem("DUFF_UNIT", szTemp);
     556             : 
     557           3 :                     if (nTemp == 1)
     558           3 :                         poBand->SetMetadataItem("DUFF_UNIT_NAME", "Mg/ha");
     559           3 :                     if (nTemp == 2)
     560           0 :                         poBand->SetMetadataItem("DUFF_UNIT_NAME", "t/ac");
     561             : 
     562           3 :                     nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 3340);
     563           3 :                     snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     564           3 :                     poBand->SetMetadataItem("DUFF_MIN", szTemp);
     565             : 
     566           3 :                     nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 3344);
     567           3 :                     snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     568           3 :                     poBand->SetMetadataItem("DUFF_MAX", szTemp);
     569             : 
     570           3 :                     nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 3348);
     571           3 :                     snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     572           3 :                     poBand->SetMetadataItem("DUFF_NUM_CLASSES", szTemp);
     573             : 
     574           3 :                     *(poDS->pachHeader + 6292 + 255) = '\0';
     575           3 :                     poBand->SetMetadataItem("DUFF_FILE",
     576           3 :                                             poDS->pachHeader + 6292);
     577             :                 }
     578          72 :                 break;
     579             : 
     580          72 :             case 7:
     581          72 :                 if (bHaveCrownFuels)
     582             :                 {
     583          69 :                     poBand->SetDescription("Canopy base height");
     584             : 
     585          69 :                     nTemp = CPL_LSBUINT16PTR(poDS->pachHeader + 4236);
     586          69 :                     snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     587          69 :                     poBand->SetMetadataItem("CBH_UNIT", szTemp);
     588             : 
     589          69 :                     if (nTemp == 1)
     590           3 :                         poBand->SetMetadataItem("CBH_UNIT_NAME", "Meters");
     591          69 :                     if (nTemp == 2)
     592           3 :                         poBand->SetMetadataItem("CBH_UNIT_NAME", "Feet");
     593          69 :                     if (nTemp == 3)
     594          62 :                         poBand->SetMetadataItem("CBH_UNIT_NAME", "Meters x 10");
     595          69 :                     if (nTemp == 4)
     596           1 :                         poBand->SetMetadataItem("CBH_UNIT_NAME", "Feet x 10");
     597             : 
     598          69 :                     nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 2516);
     599          69 :                     snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     600          69 :                     poBand->SetMetadataItem("CBH_MIN", szTemp);
     601             : 
     602          69 :                     nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 2520);
     603          69 :                     snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     604          69 :                     poBand->SetMetadataItem("CBH_MAX", szTemp);
     605             : 
     606          69 :                     nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 2524);
     607          69 :                     snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     608          69 :                     poBand->SetMetadataItem("CBH_NUM_CLASSES", szTemp);
     609             : 
     610          69 :                     *(poDS->pachHeader + 5780 + 255) = '\0';
     611          69 :                     poBand->SetMetadataItem("CBH_FILE",
     612          69 :                                             poDS->pachHeader + 5780);
     613             :                 }
     614             :                 else
     615             :                 {
     616           3 :                     poBand->SetDescription("Coarse woody debris");
     617             : 
     618           3 :                     nTemp = CPL_LSBUINT16PTR(poDS->pachHeader + 4242);
     619           3 :                     snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     620           3 :                     poBand->SetMetadataItem("CWD_OPTION", szTemp);
     621             : 
     622             :                     // if ( nTemp == 1 )
     623             :                     //    poBand->SetMetadataItem( "CWD_UNIT_DESC", "?" );
     624             :                     // if ( nTemp == 2 )
     625             :                     //    poBand->SetMetadataItem( "CWD_UNIT_DESC", "?" );
     626             : 
     627           3 :                     nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 3752);
     628           3 :                     snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     629           3 :                     poBand->SetMetadataItem("CWD_MIN", szTemp);
     630             : 
     631           3 :                     nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 3756);
     632           3 :                     snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     633           3 :                     poBand->SetMetadataItem("CWD_MAX", szTemp);
     634             : 
     635           3 :                     nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 3760);
     636           3 :                     snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     637           3 :                     poBand->SetMetadataItem("CWD_NUM_CLASSES", szTemp);
     638             : 
     639           3 :                     *(poDS->pachHeader + 6548 + 255) = '\0';
     640           3 :                     poBand->SetMetadataItem("CWD_FILE",
     641           3 :                                             poDS->pachHeader + 6548);
     642             :                 }
     643          72 :                 break;
     644             : 
     645          69 :             case 8:
     646          69 :                 poBand->SetDescription("Canopy bulk density");
     647             : 
     648          69 :                 nTemp = CPL_LSBUINT16PTR(poDS->pachHeader + 4238);
     649          69 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     650          69 :                 poBand->SetMetadataItem("CBD_UNIT", szTemp);
     651             : 
     652          69 :                 if (nTemp == 1)
     653           3 :                     poBand->SetMetadataItem("CBD_UNIT_NAME", "kg/m^3");
     654          69 :                 if (nTemp == 2)
     655           3 :                     poBand->SetMetadataItem("CBD_UNIT_NAME", "lb/ft^3");
     656          69 :                 if (nTemp == 3)
     657          62 :                     poBand->SetMetadataItem("CBD_UNIT_NAME", "kg/m^3 x 100");
     658          69 :                 if (nTemp == 4)
     659           1 :                     poBand->SetMetadataItem("CBD_UNIT_NAME", "lb/ft^3 x 1000");
     660             : 
     661          69 :                 nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 2928);
     662          69 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     663          69 :                 poBand->SetMetadataItem("CBD_MIN", szTemp);
     664             : 
     665          69 :                 nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 2932);
     666          69 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     667          69 :                 poBand->SetMetadataItem("CBD_MAX", szTemp);
     668             : 
     669          69 :                 nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 2936);
     670          69 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     671          69 :                 poBand->SetMetadataItem("CBD_NUM_CLASSES", szTemp);
     672             : 
     673          69 :                 *(poDS->pachHeader + 6036 + 255) = '\0';
     674          69 :                 poBand->SetMetadataItem("CBD_FILE", poDS->pachHeader + 6036);
     675             : 
     676          69 :                 break;
     677             : 
     678          60 :             case 9:
     679          60 :                 poBand->SetDescription("Duff");
     680             : 
     681          60 :                 nTemp = CPL_LSBUINT16PTR(poDS->pachHeader + 4240);
     682          60 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     683          60 :                 poBand->SetMetadataItem("DUFF_UNIT", szTemp);
     684             : 
     685          60 :                 if (nTemp == 1)
     686          59 :                     poBand->SetMetadataItem("DUFF_UNIT_NAME", "Mg/ha");
     687          60 :                 if (nTemp == 2)
     688           1 :                     poBand->SetMetadataItem("DUFF_UNIT_NAME", "t/ac");
     689             : 
     690          60 :                 nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 3340);
     691          60 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     692          60 :                 poBand->SetMetadataItem("DUFF_MIN", szTemp);
     693             : 
     694          60 :                 nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 3344);
     695          60 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     696          60 :                 poBand->SetMetadataItem("DUFF_MAX", szTemp);
     697             : 
     698          60 :                 nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 3348);
     699          60 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     700          60 :                 poBand->SetMetadataItem("DUFF_NUM_CLASSES", szTemp);
     701             : 
     702          60 :                 *(poDS->pachHeader + 6292 + 255) = '\0';
     703          60 :                 poBand->SetMetadataItem("DUFF_FILE", poDS->pachHeader + 6292);
     704             : 
     705          60 :                 break;
     706             : 
     707          60 :             case 10:
     708          60 :                 poBand->SetDescription("Coarse woody debris");
     709             : 
     710          60 :                 nTemp = CPL_LSBUINT16PTR(poDS->pachHeader + 4242);
     711          60 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     712          60 :                 poBand->SetMetadataItem("CWD_OPTION", szTemp);
     713             : 
     714             :                 // if ( nTemp == 1 )
     715             :                 //    poBand->SetMetadataItem( "CWD_UNIT_DESC", "?" );
     716             :                 // if ( nTemp == 2 )
     717             :                 //    poBand->SetMetadataItem( "CWD_UNIT_DESC", "?" );
     718             : 
     719          60 :                 nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 3752);
     720          60 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     721          60 :                 poBand->SetMetadataItem("CWD_MIN", szTemp);
     722             : 
     723          60 :                 nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 3756);
     724          60 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     725          60 :                 poBand->SetMetadataItem("CWD_MAX", szTemp);
     726             : 
     727          60 :                 nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 3760);
     728          60 :                 snprintf(szTemp, sizeof(szTemp), "%d", nTemp);
     729          60 :                 poBand->SetMetadataItem("CWD_NUM_CLASSES", szTemp);
     730             : 
     731          60 :                 *(poDS->pachHeader + 6548 + 255) = '\0';
     732          60 :                 poBand->SetMetadataItem("CWD_FILE", poDS->pachHeader + 6548);
     733             : 
     734          60 :                 break;
     735             :         }
     736             : 
     737         708 :         poDS->SetBand(iBand, std::move(poBand));
     738             :     }
     739             : 
     740             :     /* -------------------------------------------------------------------- */
     741             :     /*      Try to read projection file.                                    */
     742             :     /* -------------------------------------------------------------------- */
     743             :     char *const pszDirname =
     744          75 :         CPLStrdup(CPLGetPathSafe(poOpenInfo->pszFilename).c_str());
     745             :     char *const pszBasename =
     746          75 :         CPLStrdup(CPLGetBasenameSafe(poOpenInfo->pszFilename).c_str());
     747             : 
     748          75 :     poDS->osPrjFilename = CPLFormFilenameSafe(pszDirname, pszBasename, "prj");
     749             :     VSIStatBufL sStatBuf;
     750          75 :     int nRet = VSIStatL(poDS->osPrjFilename, &sStatBuf);
     751             : 
     752          75 :     if (nRet != 0 && VSIIsCaseSensitiveFS(poDS->osPrjFilename))
     753             :     {
     754          72 :         poDS->osPrjFilename =
     755         144 :             CPLFormFilenameSafe(pszDirname, pszBasename, "PRJ");
     756          72 :         nRet = VSIStatL(poDS->osPrjFilename, &sStatBuf);
     757             :     }
     758             : 
     759          75 :     if (nRet == 0)
     760             :     {
     761           3 :         char **papszPrj = CSLLoad(poDS->osPrjFilename);
     762             : 
     763           3 :         CPLDebug("LCP", "Loaded SRS from %s", poDS->osPrjFilename.c_str());
     764             : 
     765           3 :         poDS->m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     766           3 :         if (poDS->m_oSRS.importFromESRI(papszPrj) != OGRERR_NONE)
     767             :         {
     768           0 :             poDS->m_oSRS.Clear();
     769             :         }
     770             : 
     771           3 :         CSLDestroy(papszPrj);
     772             :     }
     773             : 
     774          75 :     CPLFree(pszDirname);
     775          75 :     CPLFree(pszBasename);
     776             : 
     777             :     /* -------------------------------------------------------------------- */
     778             :     /*      Initialize any PAM information.                                 */
     779             :     /* -------------------------------------------------------------------- */
     780          75 :     poDS->SetDescription(poOpenInfo->pszFilename);
     781          75 :     poDS->TryLoadXML();
     782             : 
     783             :     /* -------------------------------------------------------------------- */
     784             :     /*      Check for external overviews.                                   */
     785             :     /* -------------------------------------------------------------------- */
     786         150 :     poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename,
     787          75 :                                 poOpenInfo->GetSiblingFiles());
     788             : 
     789          75 :     return poDS.release();
     790             : }
     791             : 
     792             : /************************************************************************/
     793             : /*                          ClassifyBandData()                          */
     794             : /*  Classify a band and store 99 or fewer unique values.  If there are  */
     795             : /*  more than 99 unique values, then set pnNumClasses to -1 as a flag   */
     796             : /*  that represents this.  These are legacy values in the header, and   */
     797             : /*  while we should never deprecate them, we could possibly not         */
     798             : /*  calculate them by default.                                          */
     799             : /************************************************************************/
     800             : 
     801         320 : CPLErr LCPDataset::ClassifyBandData(GDALRasterBand *poBand, GInt32 &nNumClasses,
     802             :                                     GInt32 *panClasses)
     803             : {
     804         320 :     CPLAssert(poBand);
     805         320 :     CPLAssert(panClasses);
     806             : 
     807         320 :     const int nXSize = poBand->GetXSize();
     808         320 :     const int nYSize = poBand->GetYSize();
     809             : 
     810             :     GInt16 *panValues =
     811         320 :         static_cast<GInt16 *>(CPLMalloc(sizeof(GInt16) * nXSize));
     812         320 :     constexpr int MIN_VAL = std::numeric_limits<GInt16>::min();
     813         320 :     constexpr int MAX_VAL = std::numeric_limits<GInt16>::max();
     814         320 :     constexpr int RANGE_VAL = MAX_VAL - MIN_VAL + 1;
     815         320 :     GByte *pabyFlags = static_cast<GByte *>(CPLCalloc(1, RANGE_VAL));
     816             : 
     817             :     /* so that values in [-32768,32767] are mapped to [0,65535] in pabyFlags */
     818         320 :     constexpr int OFFSET = -MIN_VAL;
     819             : 
     820         320 :     int nFound = 0;
     821         320 :     bool bTooMany = false;
     822         320 :     CPLErr eErr = CE_None;
     823        3770 :     for (int iLine = 0; iLine < nYSize; iLine++)
     824             :     {
     825        3451 :         eErr = poBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, panValues, nXSize,
     826             :                                 1, GDT_Int16, 0, 0, nullptr);
     827        3451 :         if (eErr != CE_None)
     828           0 :             break;
     829       37531 :         for (int iPixel = 0; iPixel < nXSize; iPixel++)
     830             :         {
     831       34081 :             if (panValues[iPixel] == -9999)
     832             :             {
     833           0 :                 continue;
     834             :             }
     835       34081 :             if (nFound == LCP_MAX_CLASSES)
     836             :             {
     837           1 :                 CPLDebug("LCP",
     838             :                          "Found more that %d unique values in "
     839             :                          "band %d.  Not 'classifying' the data.",
     840             :                          LCP_MAX_CLASSES - 1, poBand->GetBand());
     841           1 :                 nFound = -1;
     842           1 :                 bTooMany = true;
     843           1 :                 break;
     844             :             }
     845       34080 :             if (pabyFlags[panValues[iPixel] + OFFSET] == 0)
     846             :             {
     847         569 :                 pabyFlags[panValues[iPixel] + OFFSET] = 1;
     848         569 :                 nFound++;
     849             :             }
     850             :         }
     851        3451 :         if (bTooMany)
     852           1 :             break;
     853             :     }
     854         320 :     if (!bTooMany)
     855             :     {
     856             :         // The classes are always padded with a leading 0.  This was for aligning
     857             :         // offsets, or making it a 1-based array instead of 0-based.
     858         319 :         panClasses[0] = 0;
     859    20906300 :         for (int j = 0, nIndex = 1; j < RANGE_VAL; j++)
     860             :         {
     861    20906000 :             if (pabyFlags[j] == 1)
     862             :             {
     863         469 :                 panClasses[nIndex++] = j;
     864             :             }
     865             :         }
     866             :     }
     867         320 :     nNumClasses = nFound;
     868         320 :     CPLFree(pabyFlags);
     869         320 :     CPLFree(panValues);
     870             : 
     871         320 :     return eErr;
     872             : }
     873             : 
     874             : /************************************************************************/
     875             : /*                          CreateCopy()                                */
     876             : /************************************************************************/
     877             : 
     878          68 : GDALDataset *LCPDataset::CreateCopy(const char *pszFilename,
     879             :                                     GDALDataset *poSrcDS, int bStrict,
     880             :                                     char **papszOptions,
     881             :                                     GDALProgressFunc pfnProgress,
     882             :                                     void *pProgressData)
     883             : 
     884             : {
     885             :     /* -------------------------------------------------------------------- */
     886             :     /*      Verify input options.                                           */
     887             :     /* -------------------------------------------------------------------- */
     888          68 :     const int nBands = poSrcDS->GetRasterCount();
     889          68 :     if (nBands != 5 && nBands != 7 && nBands != 8 && nBands != 10)
     890             :     {
     891          25 :         CPLError(CE_Failure, CPLE_NotSupported,
     892             :                  "LCP driver doesn't support %d bands.  Must be 5, 7, 8 "
     893             :                  "or 10 bands.",
     894             :                  nBands);
     895          25 :         return nullptr;
     896             :     }
     897             : 
     898          43 :     GDALDataType eType = poSrcDS->GetRasterBand(1)->GetRasterDataType();
     899          43 :     if (eType != GDT_Int16 && bStrict)
     900             :     {
     901           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     902             :                  "LCP only supports 16-bit signed integer data types.");
     903           1 :         return nullptr;
     904             :     }
     905          42 :     else if (eType != GDT_Int16)
     906             :     {
     907           0 :         CPLError(CE_Warning, CPLE_AppDefined,
     908             :                  "Setting data type to 16-bit integer.");
     909             :     }
     910             : 
     911             :     /* -------------------------------------------------------------------- */
     912             :     /*      What schema do we have (ground/crown fuels)                     */
     913             :     /* -------------------------------------------------------------------- */
     914             : 
     915          42 :     const bool bHaveCrownFuels = nBands == 8 || nBands == 10;
     916          42 :     const bool bHaveGroundFuels = nBands == 7 || nBands == 10;
     917             : 
     918             :     // Since units are 'configurable', we should check for user
     919             :     // defined units.  This is a bit cumbersome, but the user should
     920             :     // be allowed to specify none to get default units/options.  Use
     921             :     // default units every chance we get.
     922          42 :     GInt16 panMetadata[LCP_MAX_BANDS] = {
     923             :         0,  // 0 ELEVATION_UNIT
     924             :         0,  // 1 SLOPE_UNIT
     925             :         2,  // 2 ASPECT_UNIT
     926             :         0,  // 3 FUEL_MODEL_OPTION
     927             :         1,  // 4 CANOPY_COV_UNIT
     928             :         3,  // 5 CANOPY_HT_UNIT
     929             :         3,  // 6 CBH_UNIT
     930             :         3,  // 7 CBD_UNIT
     931             :         1,  // 8 DUFF_UNIT
     932             :         0,  // 9 CWD_OPTION
     933             :     };
     934             : 
     935             :     // Check the units/options for user overrides.
     936             :     const char *pszTemp =
     937          42 :         CSLFetchNameValueDef(papszOptions, "ELEVATION_UNIT", "METERS");
     938          42 :     if (STARTS_WITH_CI(pszTemp, "METER"))
     939             :     {
     940          40 :         panMetadata[0] = 0;
     941             :     }
     942           2 :     else if (EQUAL(pszTemp, "FEET") || EQUAL(pszTemp, "FOOT"))
     943             :     {
     944           1 :         panMetadata[0] = 1;
     945             :     }
     946             :     else
     947             :     {
     948           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     949             :                  "Invalid value (%s) for ELEVATION_UNIT.", pszTemp);
     950           1 :         return nullptr;
     951             :     }
     952             : 
     953          41 :     pszTemp = CSLFetchNameValueDef(papszOptions, "SLOPE_UNIT", "DEGREES");
     954          41 :     if (EQUAL(pszTemp, "DEGREES"))
     955             :     {
     956          39 :         panMetadata[1] = 0;
     957             :     }
     958           2 :     else if (EQUAL(pszTemp, "PERCENT"))
     959             :     {
     960           1 :         panMetadata[1] = 1;
     961             :     }
     962             :     else
     963             :     {
     964           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     965             :                  "Invalid value (%s) for SLOPE_UNIT.", pszTemp);
     966           1 :         return nullptr;
     967             :     }
     968             : 
     969             :     pszTemp =
     970          40 :         CSLFetchNameValueDef(papszOptions, "ASPECT_UNIT", "AZIMUTH_DEGREES");
     971          40 :     if (EQUAL(pszTemp, "GRASS_CATEGORIES"))
     972             :     {
     973           1 :         panMetadata[2] = 0;
     974             :     }
     975          39 :     else if (EQUAL(pszTemp, "GRASS_DEGREES"))
     976             :     {
     977           1 :         panMetadata[2] = 1;
     978             :     }
     979          38 :     else if (EQUAL(pszTemp, "AZIMUTH_DEGREES"))
     980             :     {
     981          37 :         panMetadata[2] = 2;
     982             :     }
     983             :     else
     984             :     {
     985           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     986             :                  "Invalid value (%s) for ASPECT_UNIT.", pszTemp);
     987           1 :         return nullptr;
     988             :     }
     989             : 
     990          39 :     pszTemp = CSLFetchNameValueDef(papszOptions, "FUEL_MODEL_OPTION",
     991             :                                    "NO_CUSTOM_AND_NO_FILE");
     992          39 :     if (EQUAL(pszTemp, "NO_CUSTOM_AND_NO_FILE"))
     993             :     {
     994          38 :         panMetadata[3] = 0;
     995             :     }
     996           1 :     else if (EQUAL(pszTemp, "CUSTOM_AND_NO_FILE"))
     997             :     {
     998           0 :         panMetadata[3] = 1;
     999             :     }
    1000           1 :     else if (EQUAL(pszTemp, "NO_CUSTOM_AND_FILE"))
    1001             :     {
    1002           0 :         panMetadata[3] = 2;
    1003             :     }
    1004           1 :     else if (EQUAL(pszTemp, "CUSTOM_AND_FILE"))
    1005             :     {
    1006           0 :         panMetadata[3] = 3;
    1007             :     }
    1008             :     else
    1009             :     {
    1010           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1011             :                  "Invalid value (%s) for FUEL_MODEL_OPTION.", pszTemp);
    1012           1 :         return nullptr;
    1013             :     }
    1014             : 
    1015          38 :     pszTemp = CSLFetchNameValueDef(papszOptions, "CANOPY_COV_UNIT", "PERCENT");
    1016          38 :     if (EQUAL(pszTemp, "CATEGORIES"))
    1017             :     {
    1018           1 :         panMetadata[4] = 0;
    1019             :     }
    1020          37 :     else if (EQUAL(pszTemp, "PERCENT"))
    1021             :     {
    1022          36 :         panMetadata[4] = 1;
    1023             :     }
    1024             :     else
    1025             :     {
    1026           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1027             :                  "Invalid value (%s) for CANOPY_COV_UNIT.", pszTemp);
    1028           1 :         return nullptr;
    1029             :     }
    1030             : 
    1031          37 :     if (bHaveCrownFuels)
    1032             :     {
    1033             :         pszTemp =
    1034          35 :             CSLFetchNameValueDef(papszOptions, "CANOPY_HT_UNIT", "METERS_X_10");
    1035          35 :         if (EQUAL(pszTemp, "METERS") || EQUAL(pszTemp, "METER"))
    1036             :         {
    1037           1 :             panMetadata[5] = 1;
    1038             :         }
    1039          34 :         else if (EQUAL(pszTemp, "FEET") || EQUAL(pszTemp, "FOOT"))
    1040             :         {
    1041           1 :             panMetadata[5] = 2;
    1042             :         }
    1043          33 :         else if (EQUAL(pszTemp, "METERS_X_10") || EQUAL(pszTemp, "METER_X_10"))
    1044             :         {
    1045          31 :             panMetadata[5] = 3;
    1046             :         }
    1047           2 :         else if (EQUAL(pszTemp, "FEET_X_10") || EQUAL(pszTemp, "FOOT_X_10"))
    1048             :         {
    1049           1 :             panMetadata[5] = 4;
    1050             :         }
    1051             :         else
    1052             :         {
    1053           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1054             :                      "Invalid value (%s) for CANOPY_HT_UNIT.", pszTemp);
    1055           1 :             return nullptr;
    1056             :         }
    1057             : 
    1058          34 :         pszTemp = CSLFetchNameValueDef(papszOptions, "CBH_UNIT", "METERS_X_10");
    1059          34 :         if (EQUAL(pszTemp, "METERS") || EQUAL(pszTemp, "METER"))
    1060             :         {
    1061           1 :             panMetadata[6] = 1;
    1062             :         }
    1063          33 :         else if (EQUAL(pszTemp, "FEET") || EQUAL(pszTemp, "FOOT"))
    1064             :         {
    1065           1 :             panMetadata[6] = 2;
    1066             :         }
    1067          32 :         else if (EQUAL(pszTemp, "METERS_X_10") || EQUAL(pszTemp, "METER_X_10"))
    1068             :         {
    1069          30 :             panMetadata[6] = 3;
    1070             :         }
    1071           2 :         else if (EQUAL(pszTemp, "FEET_X_10") || EQUAL(pszTemp, "FOOT_X_10"))
    1072             :         {
    1073           1 :             panMetadata[6] = 4;
    1074             :         }
    1075             :         else
    1076             :         {
    1077           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1078             :                      "Invalid value (%s) for CBH_UNIT.", pszTemp);
    1079           1 :             return nullptr;
    1080             :         }
    1081             : 
    1082          33 :         pszTemp = CSLFetchNameValueDef(papszOptions, "CBD_UNIT",
    1083             :                                        "KG_PER_CUBIC_METER_X_100");
    1084          33 :         if (EQUAL(pszTemp, "KG_PER_CUBIC_METER"))
    1085             :         {
    1086           1 :             panMetadata[7] = 1;
    1087             :         }
    1088          32 :         else if (EQUAL(pszTemp, "POUND_PER_CUBIC_FOOT"))
    1089             :         {
    1090           1 :             panMetadata[7] = 2;
    1091             :         }
    1092          31 :         else if (EQUAL(pszTemp, "KG_PER_CUBIC_METER_X_100"))
    1093             :         {
    1094          29 :             panMetadata[7] = 3;
    1095             :         }
    1096           2 :         else if (EQUAL(pszTemp, "POUND_PER_CUBIC_FOOT_X_1000"))
    1097             :         {
    1098           1 :             panMetadata[7] = 4;
    1099             :         }
    1100             :         else
    1101             :         {
    1102           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1103             :                      "Invalid value (%s) for CBD_UNIT.", pszTemp);
    1104           1 :             return nullptr;
    1105             :         }
    1106             :     }
    1107             : 
    1108          34 :     if (bHaveGroundFuels)
    1109             :     {
    1110          32 :         pszTemp = CSLFetchNameValueDef(papszOptions, "DUFF_UNIT",
    1111             :                                        "MG_PER_HECTARE_X_10");
    1112          32 :         if (EQUAL(pszTemp, "MG_PER_HECTARE_X_10"))
    1113             :         {
    1114          30 :             panMetadata[8] = 1;
    1115             :         }
    1116           2 :         else if (EQUAL(pszTemp, "TONS_PER_ACRE_X_10"))
    1117             :         {
    1118           1 :             panMetadata[8] = 2;
    1119             :         }
    1120             :         else
    1121             :         {
    1122           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1123             :                      "Invalid value (%s) for DUFF_UNIT.", pszTemp);
    1124           1 :             return nullptr;
    1125             :         }
    1126             : 
    1127          31 :         panMetadata[9] = 1;
    1128             :     }
    1129             : 
    1130             :     // Calculate the stats for each band.  The binary file carries along
    1131             :     // these metadata for display purposes(?).
    1132          33 :     bool bCalculateStats = CPLFetchBool(papszOptions, "CALCULATE_STATS", true);
    1133             : 
    1134             :     const bool bClassifyData =
    1135          33 :         CPLFetchBool(papszOptions, "CLASSIFY_DATA", true);
    1136             : 
    1137             :     // We should have stats if we classify, we'll get them anyway.
    1138          33 :     if (bClassifyData && !bCalculateStats)
    1139             :     {
    1140           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    1141             :                  "Ignoring request to not calculate statistics, "
    1142             :                  "because CLASSIFY_DATA was set to ON");
    1143           0 :         bCalculateStats = true;
    1144             :     }
    1145             : 
    1146          33 :     pszTemp = CSLFetchNameValueDef(papszOptions, "LINEAR_UNIT", "SET_FROM_SRS");
    1147          33 :     int nLinearUnits = 0;
    1148          33 :     bool bSetLinearUnits = false;
    1149          33 :     if (EQUAL(pszTemp, "SET_FROM_SRS"))
    1150             :     {
    1151           0 :         bSetLinearUnits = true;
    1152             :     }
    1153          33 :     else if (STARTS_WITH_CI(pszTemp, "METER"))
    1154             :     {
    1155          32 :         nLinearUnits = 0;
    1156             :     }
    1157           1 :     else if (EQUAL(pszTemp, "FOOT") || EQUAL(pszTemp, "FEET"))
    1158             :     {
    1159           1 :         nLinearUnits = 1;
    1160             :     }
    1161           0 :     else if (STARTS_WITH_CI(pszTemp, "KILOMETER"))
    1162             :     {
    1163           0 :         nLinearUnits = 2;
    1164             :     }
    1165          33 :     bool bCalculateLatitude = true;
    1166          33 :     int nLatitude = 0;
    1167          33 :     if (CSLFetchNameValue(papszOptions, "LATITUDE") != nullptr)
    1168             :     {
    1169          33 :         bCalculateLatitude = false;
    1170          33 :         nLatitude = atoi(CSLFetchNameValue(papszOptions, "LATITUDE"));
    1171          33 :         if (nLatitude > 90 || nLatitude < -90)
    1172             :         {
    1173           0 :             CPLError(CE_Failure, CPLE_OpenFailed,
    1174             :                      "Invalid value (%d) for LATITUDE.", nLatitude);
    1175           0 :             return nullptr;
    1176             :         }
    1177             :     }
    1178             : 
    1179             :     // If no latitude is supplied, attempt to extract the central latitude
    1180             :     // from the image.  It must be set either manually or here, otherwise
    1181             :     // we fail.
    1182          33 :     double adfSrcGeoTransform[6] = {0.0};
    1183          33 :     poSrcDS->GetGeoTransform(adfSrcGeoTransform);
    1184          33 :     const OGRSpatialReference *poSrcSRS = poSrcDS->GetSpatialRef();
    1185          33 :     double dfLongitude = 0.0;
    1186          33 :     double dfLatitude = 0.0;
    1187             : 
    1188          33 :     const int nYSize = poSrcDS->GetRasterYSize();
    1189             : 
    1190          33 :     if (!bCalculateLatitude)
    1191             :     {
    1192          33 :         dfLatitude = nLatitude;
    1193             :     }
    1194           0 :     else if (poSrcSRS)
    1195             :     {
    1196           0 :         OGRSpatialReference oDstSRS;
    1197           0 :         oDstSRS.importFromEPSG(4269);
    1198           0 :         oDstSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1199             :         OGRCoordinateTransformation *poCT =
    1200           0 :             reinterpret_cast<OGRCoordinateTransformation *>(
    1201             :                 OGRCreateCoordinateTransformation(poSrcSRS, &oDstSRS));
    1202           0 :         if (poCT != nullptr)
    1203             :         {
    1204           0 :             dfLatitude =
    1205           0 :                 adfSrcGeoTransform[3] + adfSrcGeoTransform[5] * nYSize / 2;
    1206             :             const int nErr =
    1207           0 :                 static_cast<int>(poCT->Transform(1, &dfLongitude, &dfLatitude));
    1208           0 :             if (!nErr)
    1209             :             {
    1210           0 :                 dfLatitude = 0.0;
    1211             :                 // For the most part, this is an invalid LCP, but it is a
    1212             :                 // changeable value in Flammap/Farsite, etc.  We should
    1213             :                 // probably be strict here all the time.
    1214           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1215             :                          "Could not calculate latitude from spatial "
    1216             :                          "reference and LATITUDE was not set.");
    1217           0 :                 return nullptr;
    1218             :             }
    1219             :         }
    1220           0 :         OGRCoordinateTransformation::DestroyCT(poCT);
    1221             :     }
    1222             :     else
    1223             :     {
    1224             :         // See comment above on failure to transform.
    1225           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1226             :                  "Could not calculate latitude from spatial reference "
    1227             :                  "and LATITUDE was not set.");
    1228           0 :         return nullptr;
    1229             :     }
    1230             :     // Set the linear units if the metadata item was not already set, and we
    1231             :     // have an SRS.
    1232          33 :     if (bSetLinearUnits && poSrcSRS)
    1233             :     {
    1234           0 :         const char *pszUnit = poSrcSRS->GetAttrValue("UNIT", 0);
    1235           0 :         if (pszUnit == nullptr)
    1236             :         {
    1237           0 :             if (bStrict)
    1238             :             {
    1239           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1240             :                          "Could not parse linear unit.");
    1241           0 :                 return nullptr;
    1242             :             }
    1243             :             else
    1244             :             {
    1245           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1246             :                          "Could not parse linear unit, using meters");
    1247           0 :                 nLinearUnits = 0;
    1248             :             }
    1249             :         }
    1250             :         else
    1251             :         {
    1252           0 :             CPLDebug("LCP", "Setting linear unit to %s", pszUnit);
    1253           0 :             if (EQUAL(pszUnit, "meter") || EQUAL(pszUnit, "metre"))
    1254             :             {
    1255           0 :                 nLinearUnits = 0;
    1256             :             }
    1257           0 :             else if (EQUAL(pszUnit, "feet") || EQUAL(pszUnit, "foot"))
    1258             :             {
    1259           0 :                 nLinearUnits = 1;
    1260             :             }
    1261           0 :             else if (STARTS_WITH_CI(pszUnit, "kilomet"))
    1262             :             {
    1263           0 :                 nLinearUnits = 2;
    1264             :             }
    1265             :             else
    1266             :             {
    1267           0 :                 if (bStrict)
    1268           0 :                     nLinearUnits = 0;
    1269             :             }
    1270           0 :             pszUnit = poSrcSRS->GetAttrValue("UNIT", 1);
    1271           0 :             if (pszUnit != nullptr)
    1272             :             {
    1273           0 :                 const double dfScale = CPLAtof(pszUnit);
    1274           0 :                 if (dfScale != 1.0)
    1275             :                 {
    1276           0 :                     if (bStrict)
    1277             :                     {
    1278           0 :                         CPLError(CE_Failure, CPLE_AppDefined,
    1279             :                                  "Unit scale is %lf (!=1.0). It is not "
    1280             :                                  "supported.",
    1281             :                                  dfScale);
    1282           0 :                         return nullptr;
    1283             :                     }
    1284             :                     else
    1285             :                     {
    1286           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
    1287             :                                  "Unit scale is %lf (!=1.0). It is not "
    1288             :                                  "supported, ignoring.",
    1289             :                                  dfScale);
    1290             :                     }
    1291             :                 }
    1292             :             }
    1293           0 :         }
    1294             :     }
    1295          33 :     else if (bSetLinearUnits)
    1296             :     {
    1297             :         // This can be defaulted if it is not a strict creation.
    1298           0 :         if (bStrict)
    1299             :         {
    1300           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1301             :                      "Could not parse linear unit from spatial reference "
    1302             :                      "and LINEAR_UNIT was not set.");
    1303           0 :             return nullptr;
    1304             :         }
    1305             :         else
    1306             :         {
    1307           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1308             :                      "Could not parse linear unit from spatial reference "
    1309             :                      "and LINEAR_UNIT was not set, defaulting to meters.");
    1310           0 :             nLinearUnits = 0;
    1311             :         }
    1312             :     }
    1313             : 
    1314          33 :     const char *pszDescription = CSLFetchNameValueDef(
    1315             :         papszOptions, "DESCRIPTION", "LCP file created by GDAL.");
    1316             : 
    1317             :     // Loop through and get the stats for the bands if we need to calculate
    1318             :     // them.  This probably should be done when we copy the data over to the
    1319             :     // destination dataset, since we load the values into memory, but this is
    1320             :     // much simpler code using GDALRasterBand->GetStatistics().  We also may
    1321             :     // need to classify the data (number of unique values and a list of those
    1322             :     // values if the number of unique values is > 100.  It is currently unclear
    1323             :     // how these data are used though, so we will implement that at some point
    1324             :     // if need be.
    1325          33 :     double *padfMin = static_cast<double *>(CPLMalloc(sizeof(double) * nBands));
    1326          33 :     double *padfMax = static_cast<double *>(CPLMalloc(sizeof(double) * nBands));
    1327             : 
    1328             :     // Initialize these arrays to zeros
    1329             :     GInt32 *panFound =
    1330          33 :         static_cast<GInt32 *>(VSIMalloc2(sizeof(GInt32), nBands));
    1331          33 :     memset(panFound, 0, sizeof(GInt32) * nBands);
    1332             :     GInt32 *panClasses = static_cast<GInt32 *>(
    1333          33 :         VSIMalloc3(sizeof(GInt32), nBands, LCP_MAX_CLASSES));
    1334          33 :     memset(panClasses, 0, sizeof(GInt32) * nBands * LCP_MAX_CLASSES);
    1335             : 
    1336          33 :     if (bCalculateStats)
    1337             :     {
    1338             : 
    1339         353 :         for (int i = 0; i < nBands; i++)
    1340             :         {
    1341         320 :             GDALRasterBand *poBand = poSrcDS->GetRasterBand(i + 1);
    1342         320 :             double dfDummy = 0.0;
    1343         640 :             CPLErr eErr = poBand->GetStatistics(
    1344         320 :                 FALSE, TRUE, &padfMin[i], &padfMax[i], &dfDummy, &dfDummy);
    1345         320 :             if (eErr != CE_None)
    1346             :             {
    1347           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1348             :                          "Failed to properly "
    1349             :                          "calculate statistics "
    1350             :                          "on band %d",
    1351             :                          i);
    1352           0 :                 padfMin[i] = 0.0;
    1353           0 :                 padfMax[i] = 0.0;
    1354             :             }
    1355             : 
    1356             :             // See comment above.
    1357         320 :             if (bClassifyData)
    1358             :             {
    1359         640 :                 eErr = ClassifyBandData(poBand, panFound[i],
    1360         320 :                                         panClasses + (i * LCP_MAX_CLASSES));
    1361         320 :                 if (eErr != CE_None)
    1362             :                 {
    1363           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1364             :                              "Failed to classify band data on band %d.", i);
    1365             :                 }
    1366             :             }
    1367             :         }
    1368             :     }
    1369             : 
    1370          33 :     VSILFILE *fp = VSIFOpenL(pszFilename, "wb");
    1371          33 :     if (fp == nullptr)
    1372             :     {
    1373           0 :         CPLError(CE_Failure, CPLE_OpenFailed, "Unable to create lcp file %s.",
    1374             :                  pszFilename);
    1375           0 :         CPLFree(padfMin);
    1376           0 :         CPLFree(padfMax);
    1377           0 :         CPLFree(panFound);
    1378           0 :         CPLFree(panClasses);
    1379           0 :         return nullptr;
    1380             :     }
    1381             : 
    1382             :     /* -------------------------------------------------------------------- */
    1383             :     /*      Write the header                                                */
    1384             :     /* -------------------------------------------------------------------- */
    1385             : 
    1386          33 :     GInt32 nTemp = bHaveCrownFuels ? 21 : 20;
    1387          33 :     CPL_LSBPTR32(&nTemp);
    1388          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&nTemp, 4, 1, fp));
    1389          33 :     nTemp = bHaveGroundFuels ? 21 : 20;
    1390          33 :     CPL_LSBPTR32(&nTemp);
    1391          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&nTemp, 4, 1, fp));
    1392             : 
    1393          33 :     const int nXSize = poSrcDS->GetRasterXSize();
    1394          33 :     nTemp = static_cast<GInt32>(dfLatitude + 0.5);
    1395          33 :     CPL_LSBPTR32(&nTemp);
    1396          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&nTemp, 4, 1, fp));
    1397          33 :     dfLongitude = adfSrcGeoTransform[0] + adfSrcGeoTransform[1] * nXSize;
    1398          33 :     CPL_LSBPTR64(&dfLongitude);
    1399          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&dfLongitude, 8, 1, fp));
    1400          33 :     dfLongitude = adfSrcGeoTransform[0];
    1401          33 :     CPL_LSBPTR64(&dfLongitude);
    1402          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&dfLongitude, 8, 1, fp));
    1403          33 :     dfLatitude = adfSrcGeoTransform[3];
    1404          33 :     CPL_LSBPTR64(&dfLatitude);
    1405          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&dfLatitude, 8, 1, fp));
    1406          33 :     dfLatitude = adfSrcGeoTransform[3] + adfSrcGeoTransform[5] * nYSize;
    1407          33 :     CPL_LSBPTR64(&dfLatitude);
    1408          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&dfLatitude, 8, 1, fp));
    1409             : 
    1410             :     // Swap the two classification arrays if we are writing them, and they need
    1411             :     // to be swapped.
    1412             : #ifdef CPL_MSB
    1413             :     if (bClassifyData)
    1414             :     {
    1415             :         GDALSwapWords(panFound, 2, nBands, 2);
    1416             :         GDALSwapWords(panClasses, 2, LCP_MAX_CLASSES, 2);
    1417             :     }
    1418             : #endif
    1419             : 
    1420          33 :     if (bCalculateStats)
    1421             :     {
    1422         353 :         for (int i = 0; i < nBands; i++)
    1423             :         {
    1424             :             // If we don't have Crown fuels, but do have Ground fuels, we
    1425             :             // have to 'fast forward'.
    1426         320 :             if (i == 5 && !bHaveCrownFuels && bHaveGroundFuels)
    1427             :             {
    1428           1 :                 CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 3340, SEEK_SET));
    1429             :             }
    1430         320 :             nTemp = static_cast<GInt32>(padfMin[i]);
    1431         320 :             CPL_LSBPTR32(&nTemp);
    1432         320 :             CPL_IGNORE_RET_VAL(VSIFWriteL(&nTemp, 4, 1, fp));
    1433         320 :             nTemp = static_cast<GInt32>(padfMax[i]);
    1434         320 :             CPL_LSBPTR32(&nTemp);
    1435         320 :             CPL_IGNORE_RET_VAL(VSIFWriteL(&nTemp, 4, 1, fp));
    1436         320 :             if (bClassifyData)
    1437             :             {
    1438             :                 // These two arrays were swapped in their entirety above.
    1439         320 :                 CPL_IGNORE_RET_VAL(VSIFWriteL(panFound + i, 4, 1, fp));
    1440         320 :                 CPL_IGNORE_RET_VAL(
    1441         320 :                     VSIFWriteL(panClasses + (i * LCP_MAX_CLASSES), 4,
    1442             :                                LCP_MAX_CLASSES, fp));
    1443             :             }
    1444             :             else
    1445             :             {
    1446           0 :                 nTemp = -1;
    1447           0 :                 CPL_LSBPTR32(&nTemp);
    1448           0 :                 CPL_IGNORE_RET_VAL(VSIFWriteL(&nTemp, 4, 1, fp));
    1449           0 :                 CPL_IGNORE_RET_VAL(
    1450           0 :                     VSIFSeekL(fp, 4 * LCP_MAX_CLASSES, SEEK_CUR));
    1451             :             }
    1452             :         }
    1453             :     }
    1454             :     else
    1455             :     {
    1456           0 :         CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 4164, SEEK_SET));
    1457             :     }
    1458          33 :     CPLFree(padfMin);
    1459          33 :     CPLFree(padfMax);
    1460          33 :     CPLFree(panFound);
    1461          33 :     CPLFree(panClasses);
    1462             : 
    1463             :     // Should be at one of 3 locations, 2104, 3340, or 4164.
    1464          33 :     CPLAssert(VSIFTellL(fp) == 2104 || VSIFTellL(fp) == 3340 ||
    1465             :               VSIFTellL(fp) == 4164);
    1466          33 :     CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 4164, SEEK_SET));
    1467             : 
    1468             :     /* Image size */
    1469          33 :     nTemp = static_cast<GInt32>(nXSize);
    1470          33 :     CPL_LSBPTR32(&nTemp);
    1471          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&nTemp, 4, 1, fp));
    1472          33 :     nTemp = static_cast<GInt32>(nYSize);
    1473          33 :     CPL_LSBPTR32(&nTemp);
    1474          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&nTemp, 4, 1, fp));
    1475             : 
    1476             :     // X and Y boundaries.
    1477             :     // Max x.
    1478          33 :     double dfTemp = adfSrcGeoTransform[0] + adfSrcGeoTransform[1] * nXSize;
    1479          33 :     CPL_LSBPTR64(&dfTemp);
    1480          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&dfTemp, 8, 1, fp));
    1481             :     // Min x.
    1482          33 :     dfTemp = adfSrcGeoTransform[0];
    1483          33 :     CPL_LSBPTR64(&dfTemp);
    1484          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&dfTemp, 8, 1, fp));
    1485             :     // Max y.
    1486          33 :     dfTemp = adfSrcGeoTransform[3];
    1487          33 :     CPL_LSBPTR64(&dfTemp);
    1488          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&dfTemp, 8, 1, fp));
    1489             :     // Min y.
    1490          33 :     dfTemp = adfSrcGeoTransform[3] + adfSrcGeoTransform[5] * nYSize;
    1491          33 :     CPL_LSBPTR64(&dfTemp);
    1492          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&dfTemp, 8, 1, fp));
    1493             : 
    1494          33 :     nTemp = nLinearUnits;
    1495          33 :     CPL_LSBPTR32(&nTemp);
    1496          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&nTemp, 4, 1, fp));
    1497             : 
    1498             :     // Resolution.
    1499             :     // X resolution.
    1500          33 :     dfTemp = adfSrcGeoTransform[1];
    1501          33 :     CPL_LSBPTR64(&dfTemp);
    1502          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&dfTemp, 8, 1, fp));
    1503             :     // Y resolution.
    1504          33 :     dfTemp = fabs(adfSrcGeoTransform[5]);
    1505          33 :     CPL_LSBPTR64(&dfTemp);
    1506          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&dfTemp, 8, 1, fp));
    1507             : 
    1508             : #ifdef CPL_MSB
    1509             :     GDALSwapWords(panMetadata, 2, LCP_MAX_BANDS, 2);
    1510             : #endif
    1511          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(panMetadata, 2, LCP_MAX_BANDS, fp));
    1512             : 
    1513             :     // Write the source filenames.
    1514          33 :     char **papszFileList = poSrcDS->GetFileList();
    1515          33 :     if (papszFileList != nullptr)
    1516             :     {
    1517         308 :         for (int i = 0; i < nBands; i++)
    1518             :         {
    1519         280 :             if (i == 5 && !bHaveCrownFuels && bHaveGroundFuels)
    1520             :             {
    1521           0 :                 CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 6292, SEEK_SET));
    1522             :             }
    1523         280 :             CPL_IGNORE_RET_VAL(
    1524         280 :                 VSIFWriteL(papszFileList[0], 1,
    1525             :                            CPLStrnlen(papszFileList[0], LCP_MAX_PATH), fp));
    1526         280 :             CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 4244 + (256 * (i + 1)), SEEK_SET));
    1527             :         }
    1528             :     }
    1529             :     // No file list, mem driver, etc.
    1530             :     else
    1531             :     {
    1532           5 :         CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 6804, SEEK_SET));
    1533             :     }
    1534          33 :     CSLDestroy(papszFileList);
    1535             :     // Should be at location 5524, 6292 or 6804.
    1536          33 :     CPLAssert(VSIFTellL(fp) == 5524 || VSIFTellL(fp) == 6292 ||
    1537             :               VSIFTellL(fp) == 6804);
    1538          33 :     CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 6804, SEEK_SET));
    1539             : 
    1540             :     // Description .
    1541          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(
    1542             :         pszDescription, 1, CPLStrnlen(pszDescription, LCP_MAX_DESC), fp));
    1543             : 
    1544             :     // Should be at or below location 7316, all done with the header.
    1545          33 :     CPLAssert(VSIFTellL(fp) <= 7316);
    1546          33 :     CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 7316, SEEK_SET));
    1547             : 
    1548             :     /* -------------------------------------------------------------------- */
    1549             :     /*      Loop over image, copying image data.                            */
    1550             :     /* -------------------------------------------------------------------- */
    1551             : 
    1552          33 :     GInt16 *panScanline = static_cast<GInt16 *>(VSIMalloc3(2, nBands, nXSize));
    1553             : 
    1554          33 :     if (!pfnProgress(0.0, nullptr, pProgressData))
    1555             :     {
    1556           0 :         CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
    1557           0 :         VSIFree(panScanline);
    1558           0 :         return nullptr;
    1559             :     }
    1560         399 :     for (int iLine = 0; iLine < nYSize; iLine++)
    1561             :     {
    1562        3826 :         for (int iBand = 0; iBand < nBands; iBand++)
    1563             :         {
    1564        3460 :             GDALRasterBand *poBand = poSrcDS->GetRasterBand(iBand + 1);
    1565        6920 :             CPLErr eErr = poBand->RasterIO(
    1566        3460 :                 GF_Read, 0, iLine, nXSize, 1, panScanline + iBand, nXSize, 1,
    1567        3460 :                 GDT_Int16, nBands * 2, static_cast<size_t>(nBands) * nXSize * 2,
    1568             :                 nullptr);
    1569             :             // Not sure what to do here.
    1570        3460 :             if (eErr != CE_None)
    1571             :             {
    1572           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1573             :                          "Error reported in "
    1574             :                          "RasterIO");
    1575             :             }
    1576             :         }
    1577             : #ifdef CPL_MSB
    1578             :         GDALSwapWordsEx(panScanline, 2, static_cast<size_t>(nBands) * nXSize,
    1579             :                         2);
    1580             : #endif
    1581         366 :         CPL_IGNORE_RET_VAL(VSIFWriteL(
    1582         366 :             panScanline, 2, static_cast<size_t>(nBands) * nXSize, fp));
    1583             : 
    1584         366 :         if (!pfnProgress(iLine / static_cast<double>(nYSize), nullptr,
    1585             :                          pProgressData))
    1586             :         {
    1587           0 :             VSIFree(reinterpret_cast<void *>(panScanline));
    1588           0 :             CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
    1589           0 :             return nullptr;
    1590             :         }
    1591             :     }
    1592          33 :     VSIFree(panScanline);
    1593          33 :     CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
    1594          33 :     if (!pfnProgress(1.0, nullptr, pProgressData))
    1595             :     {
    1596           0 :         return nullptr;
    1597             :     }
    1598             : 
    1599             :     // Try to write projection file.  *Most* landfire data follows ESRI
    1600             :     // style projection files, so we use the same code as the AAIGrid driver.
    1601          33 :     if (poSrcSRS)
    1602             :     {
    1603           0 :         char *pszESRIProjection = nullptr;
    1604           0 :         const char *const apszOptions[] = {"FORMAT=WKT1_ESRI", nullptr};
    1605           0 :         poSrcSRS->exportToWkt(&pszESRIProjection, apszOptions);
    1606           0 :         if (pszESRIProjection)
    1607             :         {
    1608             :             char *const pszDirname =
    1609           0 :                 CPLStrdup(CPLGetPathSafe(pszFilename).c_str());
    1610             :             char *const pszBasename =
    1611           0 :                 CPLStrdup(CPLGetBasenameSafe(pszFilename).c_str());
    1612           0 :             char *pszPrjFilename = CPLStrdup(
    1613           0 :                 CPLFormFilenameSafe(pszDirname, pszBasename, "prj").c_str());
    1614           0 :             fp = VSIFOpenL(pszPrjFilename, "wt");
    1615           0 :             if (fp != nullptr)
    1616             :             {
    1617           0 :                 CPL_IGNORE_RET_VAL(VSIFWriteL(pszESRIProjection, 1,
    1618             :                                               strlen(pszESRIProjection), fp));
    1619           0 :                 CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
    1620             :             }
    1621             :             else
    1622             :             {
    1623           0 :                 CPLError(CE_Failure, CPLE_FileIO, "Unable to create file %s.",
    1624             :                          pszPrjFilename);
    1625             :             }
    1626           0 :             CPLFree(pszDirname);
    1627           0 :             CPLFree(pszBasename);
    1628           0 :             CPLFree(pszPrjFilename);
    1629             :         }
    1630           0 :         CPLFree(pszESRIProjection);
    1631             :     }
    1632          33 :     return static_cast<GDALDataset *>(GDALOpen(pszFilename, GA_ReadOnly));
    1633             : }
    1634             : 
    1635             : /************************************************************************/
    1636             : /*                         GDALRegister_LCP()                           */
    1637             : /************************************************************************/
    1638             : 
    1639        1682 : void GDALRegister_LCP()
    1640             : 
    1641             : {
    1642        1682 :     if (GDALGetDriverByName("LCP") != nullptr)
    1643         301 :         return;
    1644             : 
    1645        1381 :     GDALDriver *poDriver = new GDALDriver();
    1646             : 
    1647        1381 :     poDriver->SetDescription("LCP");
    1648        1381 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    1649        1381 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
    1650        1381 :                               "FARSITE v.4 Landscape File (.lcp)");
    1651        1381 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "lcp");
    1652        1381 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/lcp.html");
    1653             : 
    1654        1381 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    1655             : 
    1656        1381 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES, "Int16");
    1657        1381 :     poDriver->SetMetadataItem(
    1658             :         GDAL_DMD_CREATIONOPTIONLIST,
    1659             :         "<CreationOptionList>"
    1660             :         "   <Option name='ELEVATION_UNIT' type='string-select' "
    1661             :         "default='METERS' description='Elevation units'>"
    1662             :         "       <Value>METERS</Value>"
    1663             :         "       <Value>FEET</Value>"
    1664             :         "   </Option>"
    1665             :         "   <Option name='SLOPE_UNIT' type='string-select' default='DEGREES' "
    1666             :         "description='Slope units'>"
    1667             :         "       <Value>DEGREES</Value>"
    1668             :         "       <Value>PERCENT</Value>"
    1669             :         "   </Option>"
    1670             :         "   <Option name='ASPECT_UNIT' type='string-select' "
    1671             :         "default='AZIMUTH_DEGREES'>"
    1672             :         "       <Value>GRASS_CATEGORIES</Value>"
    1673             :         "       <Value>AZIMUTH_DEGREES</Value>"
    1674             :         "       <Value>GRASS_DEGREES</Value>"
    1675             :         "   </Option>"
    1676             :         "   <Option name='FUEL_MODEL_OPTION' type='string-select' "
    1677             :         "default='NO_CUSTOM_AND_NO_FILE'>"
    1678             :         "       <Value>NO_CUSTOM_AND_NO_FILE</Value>"
    1679             :         "       <Value>CUSTOM_AND_NO_FILE</Value>"
    1680             :         "       <Value>NO_CUSTOM_AND_FILE</Value>"
    1681             :         "       <Value>CUSTOM_AND_FILE</Value>"
    1682             :         "   </Option>"
    1683             :         "   <Option name='CANOPY_COV_UNIT' type='string-select' "
    1684             :         "default='PERCENT'>"
    1685             :         "       <Value>CATEGORIES</Value>"
    1686             :         "       <Value>PERCENT</Value>"
    1687             :         "   </Option>"
    1688             :         "   <Option name='CANOPY_HT_UNIT' type='string-select' "
    1689             :         "default='METERS_X_10'>"
    1690             :         "       <Value>METERS</Value>"
    1691             :         "       <Value>FEET</Value>"
    1692             :         "       <Value>METERS_X_10</Value>"
    1693             :         "       <Value>FEET_X_10</Value>"
    1694             :         "   </Option>"
    1695             :         "   <Option name='CBH_UNIT' type='string-select' default='METERS_X_10'>"
    1696             :         "       <Value>METERS</Value>"
    1697             :         "       <Value>FEET</Value>"
    1698             :         "       <Value>METERS_X_10</Value>"
    1699             :         "       <Value>FEET_X_10</Value>"
    1700             :         "   </Option>"
    1701             :         "   <Option name='CBD_UNIT' type='string-select' "
    1702             :         "default='KG_PER_CUBIC_METER_X_100'>"
    1703             :         "       <Value>KG_PER_CUBIC_METER</Value>"
    1704             :         "       <Value>POUND_PER_CUBIC_FOOT</Value>"
    1705             :         "       <Value>KG_PER_CUBIC_METER_X_100</Value>"
    1706             :         "       <Value>POUND_PER_CUBIC_FOOT_X_1000</Value>"
    1707             :         "   </Option>"
    1708             :         "   <Option name='DUFF_UNIT' type='string-select' "
    1709             :         "default='MG_PER_HECTARE_X_10'>"
    1710             :         "       <Value>MG_PER_HECTARE_X_10</Value>"
    1711             :         "       <Value>TONS_PER_ACRE_X_10</Value>"
    1712             :         "   </Option>"
    1713             :         // Kyle does not think we need to override this, but maybe?
    1714             :         // "   <Option name='CWD_OPTION' type='boolean' default='FALSE'
    1715             :         // description='Override logic for setting the coarse woody presence'/>"
    1716             :         // */
    1717             :         "   <Option name='CALCULATE_STATS' type='boolean' default='YES' "
    1718             :         "description='Write the stats to the lcp'/>"
    1719             :         "   <Option name='CLASSIFY_DATA' type='boolean' default='YES' "
    1720             :         "description='Write the stats to the lcp'/>"
    1721             :         "   <Option name='LINEAR_UNIT' type='string-select' "
    1722             :         "default='SET_FROM_SRS' description='Set the linear units in the lcp'>"
    1723             :         "       <Value>SET_FROM_SRS</Value>"
    1724             :         "       <Value>METER</Value>"
    1725             :         "       <Value>FOOT</Value>"
    1726             :         "       <Value>KILOMETER</Value>"
    1727             :         "   </Option>"
    1728             :         "   <Option name='LATITUDE' type='int' default='' description='Set the "
    1729             :         "latitude for the dataset, this overrides the driver trying to set it "
    1730             :         "programmatically in EPSG:4269'/>"
    1731             :         "   <Option name='DESCRIPTION' type='string' default='LCP file created "
    1732             :         "by GDAL' description='A short description of the lcp file'/>"
    1733        1381 :         "</CreationOptionList>");
    1734             : 
    1735        1381 :     poDriver->pfnOpen = LCPDataset::Open;
    1736        1381 :     poDriver->pfnCreateCopy = LCPDataset::CreateCopy;
    1737        1381 :     poDriver->pfnIdentify = LCPDataset::Identify;
    1738             : 
    1739        1381 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1740             : }

Generated by: LCOV version 1.14