LCOV - code coverage report
Current view: top level - frmts/raw - lcpdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 701 823 85.2 %
Date: 2024-11-21 22:18:42 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       51708 : int LCPDataset::Identify(GDALOpenInfo *poOpenInfo)
     161             : 
     162             : {
     163             :     /* -------------------------------------------------------------------- */
     164             :     /*      Verify that this is a FARSITE v.4 LCP file                      */
     165             :     /* -------------------------------------------------------------------- */
     166       51708 :     if (poOpenInfo->nHeaderBytes < 50)
     167       48121 :         return FALSE;
     168             : 
     169             :     /* check if first three fields have valid data */
     170        3587 :     if ((CPL_LSBSINT32PTR(poOpenInfo->pabyHeader) != 20 &&
     171        3573 :          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        3419 :         return FALSE;
     178             :     }
     179             : 
     180             : /* -------------------------------------------------------------------- */
     181             : /*      Check file extension                                            */
     182             : /* -------------------------------------------------------------------- */
     183             : #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
     184         168 :     const char *pszFileExtension = CPLGetExtension(poOpenInfo->pszFilename);
     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         268 :                     for (int i = 0; i <= nTemp; i++)
     461             :                     {
     462         193 :                         const int nTemp2 = CPL_LSBSINT32PTR(poDS->pachHeader +
     463             :                                                             (1292 + (i * 4)));
     464         193 :                         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          75 :     char *const pszDirname = CPLStrdup(CPLGetPath(poOpenInfo->pszFilename));
     744             :     char *const pszBasename =
     745          75 :         CPLStrdup(CPLGetBasename(poOpenInfo->pszFilename));
     746             : 
     747          75 :     poDS->osPrjFilename = CPLFormFilename(pszDirname, pszBasename, "prj");
     748             :     VSIStatBufL sStatBuf;
     749          75 :     int nRet = VSIStatL(poDS->osPrjFilename, &sStatBuf);
     750             : 
     751          75 :     if (nRet != 0 && VSIIsCaseSensitiveFS(poDS->osPrjFilename))
     752             :     {
     753          72 :         poDS->osPrjFilename = CPLFormFilename(pszDirname, pszBasename, "PRJ");
     754          72 :         nRet = VSIStatL(poDS->osPrjFilename, &sStatBuf);
     755             :     }
     756             : 
     757          75 :     if (nRet == 0)
     758             :     {
     759           3 :         char **papszPrj = CSLLoad(poDS->osPrjFilename);
     760             : 
     761           3 :         CPLDebug("LCP", "Loaded SRS from %s", poDS->osPrjFilename.c_str());
     762             : 
     763           3 :         poDS->m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     764           3 :         if (poDS->m_oSRS.importFromESRI(papszPrj) != OGRERR_NONE)
     765             :         {
     766           0 :             poDS->m_oSRS.Clear();
     767             :         }
     768             : 
     769           3 :         CSLDestroy(papszPrj);
     770             :     }
     771             : 
     772          75 :     CPLFree(pszDirname);
     773          75 :     CPLFree(pszBasename);
     774             : 
     775             :     /* -------------------------------------------------------------------- */
     776             :     /*      Initialize any PAM information.                                 */
     777             :     /* -------------------------------------------------------------------- */
     778          75 :     poDS->SetDescription(poOpenInfo->pszFilename);
     779          75 :     poDS->TryLoadXML();
     780             : 
     781             :     /* -------------------------------------------------------------------- */
     782             :     /*      Check for external overviews.                                   */
     783             :     /* -------------------------------------------------------------------- */
     784         150 :     poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename,
     785          75 :                                 poOpenInfo->GetSiblingFiles());
     786             : 
     787          75 :     return poDS.release();
     788             : }
     789             : 
     790             : /************************************************************************/
     791             : /*                          ClassifyBandData()                          */
     792             : /*  Classify a band and store 99 or fewer unique values.  If there are  */
     793             : /*  more than 99 unique values, then set pnNumClasses to -1 as a flag   */
     794             : /*  that represents this.  These are legacy values in the header, and   */
     795             : /*  while we should never deprecate them, we could possibly not         */
     796             : /*  calculate them by default.                                          */
     797             : /************************************************************************/
     798             : 
     799         320 : CPLErr LCPDataset::ClassifyBandData(GDALRasterBand *poBand, GInt32 &nNumClasses,
     800             :                                     GInt32 *panClasses)
     801             : {
     802         320 :     CPLAssert(poBand);
     803         320 :     CPLAssert(panClasses);
     804             : 
     805         320 :     const int nXSize = poBand->GetXSize();
     806         320 :     const int nYSize = poBand->GetYSize();
     807             : 
     808             :     GInt16 *panValues =
     809         320 :         static_cast<GInt16 *>(CPLMalloc(sizeof(GInt16) * nXSize));
     810         320 :     constexpr int MIN_VAL = std::numeric_limits<GInt16>::min();
     811         320 :     constexpr int MAX_VAL = std::numeric_limits<GInt16>::max();
     812         320 :     constexpr int RANGE_VAL = MAX_VAL - MIN_VAL + 1;
     813         320 :     GByte *pabyFlags = static_cast<GByte *>(CPLCalloc(1, RANGE_VAL));
     814             : 
     815             :     /* so that values in [-32768,32767] are mapped to [0,65535] in pabyFlags */
     816         320 :     constexpr int OFFSET = -MIN_VAL;
     817             : 
     818         320 :     int nFound = 0;
     819         320 :     bool bTooMany = false;
     820         320 :     CPLErr eErr = CE_None;
     821        3770 :     for (int iLine = 0; iLine < nYSize; iLine++)
     822             :     {
     823        3451 :         eErr = poBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, panValues, nXSize,
     824             :                                 1, GDT_Int16, 0, 0, nullptr);
     825        3451 :         if (eErr != CE_None)
     826           0 :             break;
     827       37531 :         for (int iPixel = 0; iPixel < nXSize; iPixel++)
     828             :         {
     829       34081 :             if (panValues[iPixel] == -9999)
     830             :             {
     831           0 :                 continue;
     832             :             }
     833       34081 :             if (nFound == LCP_MAX_CLASSES)
     834             :             {
     835           1 :                 CPLDebug("LCP",
     836             :                          "Found more that %d unique values in "
     837             :                          "band %d.  Not 'classifying' the data.",
     838             :                          LCP_MAX_CLASSES - 1, poBand->GetBand());
     839           1 :                 nFound = -1;
     840           1 :                 bTooMany = true;
     841           1 :                 break;
     842             :             }
     843       34080 :             if (pabyFlags[panValues[iPixel] + OFFSET] == 0)
     844             :             {
     845         577 :                 pabyFlags[panValues[iPixel] + OFFSET] = 1;
     846         577 :                 nFound++;
     847             :             }
     848             :         }
     849        3451 :         if (bTooMany)
     850           1 :             break;
     851             :     }
     852         320 :     if (!bTooMany)
     853             :     {
     854             :         // The classes are always padded with a leading 0.  This was for aligning
     855             :         // offsets, or making it a 1-based array instead of 0-based.
     856         319 :         panClasses[0] = 0;
     857    20906300 :         for (int j = 0, nIndex = 1; j < RANGE_VAL; j++)
     858             :         {
     859    20906000 :             if (pabyFlags[j] == 1)
     860             :             {
     861         477 :                 panClasses[nIndex++] = j;
     862             :             }
     863             :         }
     864             :     }
     865         320 :     nNumClasses = nFound;
     866         320 :     CPLFree(pabyFlags);
     867         320 :     CPLFree(panValues);
     868             : 
     869         320 :     return eErr;
     870             : }
     871             : 
     872             : /************************************************************************/
     873             : /*                          CreateCopy()                                */
     874             : /************************************************************************/
     875             : 
     876          68 : GDALDataset *LCPDataset::CreateCopy(const char *pszFilename,
     877             :                                     GDALDataset *poSrcDS, int bStrict,
     878             :                                     char **papszOptions,
     879             :                                     GDALProgressFunc pfnProgress,
     880             :                                     void *pProgressData)
     881             : 
     882             : {
     883             :     /* -------------------------------------------------------------------- */
     884             :     /*      Verify input options.                                           */
     885             :     /* -------------------------------------------------------------------- */
     886          68 :     const int nBands = poSrcDS->GetRasterCount();
     887          68 :     if (nBands != 5 && nBands != 7 && nBands != 8 && nBands != 10)
     888             :     {
     889          25 :         CPLError(CE_Failure, CPLE_NotSupported,
     890             :                  "LCP driver doesn't support %d bands.  Must be 5, 7, 8 "
     891             :                  "or 10 bands.",
     892             :                  nBands);
     893          25 :         return nullptr;
     894             :     }
     895             : 
     896          43 :     GDALDataType eType = poSrcDS->GetRasterBand(1)->GetRasterDataType();
     897          43 :     if (eType != GDT_Int16 && bStrict)
     898             :     {
     899           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     900             :                  "LCP only supports 16-bit signed integer data types.");
     901           1 :         return nullptr;
     902             :     }
     903          42 :     else if (eType != GDT_Int16)
     904             :     {
     905           0 :         CPLError(CE_Warning, CPLE_AppDefined,
     906             :                  "Setting data type to 16-bit integer.");
     907             :     }
     908             : 
     909             :     /* -------------------------------------------------------------------- */
     910             :     /*      What schema do we have (ground/crown fuels)                     */
     911             :     /* -------------------------------------------------------------------- */
     912             : 
     913          42 :     const bool bHaveCrownFuels = nBands == 8 || nBands == 10;
     914          42 :     const bool bHaveGroundFuels = nBands == 7 || nBands == 10;
     915             : 
     916             :     // Since units are 'configurable', we should check for user
     917             :     // defined units.  This is a bit cumbersome, but the user should
     918             :     // be allowed to specify none to get default units/options.  Use
     919             :     // default units every chance we get.
     920          42 :     GInt16 panMetadata[LCP_MAX_BANDS] = {
     921             :         0,  // 0 ELEVATION_UNIT
     922             :         0,  // 1 SLOPE_UNIT
     923             :         2,  // 2 ASPECT_UNIT
     924             :         0,  // 3 FUEL_MODEL_OPTION
     925             :         1,  // 4 CANOPY_COV_UNIT
     926             :         3,  // 5 CANOPY_HT_UNIT
     927             :         3,  // 6 CBH_UNIT
     928             :         3,  // 7 CBD_UNIT
     929             :         1,  // 8 DUFF_UNIT
     930             :         0,  // 9 CWD_OPTION
     931             :     };
     932             : 
     933             :     // Check the units/options for user overrides.
     934             :     const char *pszTemp =
     935          42 :         CSLFetchNameValueDef(papszOptions, "ELEVATION_UNIT", "METERS");
     936          42 :     if (STARTS_WITH_CI(pszTemp, "METER"))
     937             :     {
     938          40 :         panMetadata[0] = 0;
     939             :     }
     940           2 :     else if (EQUAL(pszTemp, "FEET") || EQUAL(pszTemp, "FOOT"))
     941             :     {
     942           1 :         panMetadata[0] = 1;
     943             :     }
     944             :     else
     945             :     {
     946           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     947             :                  "Invalid value (%s) for ELEVATION_UNIT.", pszTemp);
     948           1 :         return nullptr;
     949             :     }
     950             : 
     951          41 :     pszTemp = CSLFetchNameValueDef(papszOptions, "SLOPE_UNIT", "DEGREES");
     952          41 :     if (EQUAL(pszTemp, "DEGREES"))
     953             :     {
     954          39 :         panMetadata[1] = 0;
     955             :     }
     956           2 :     else if (EQUAL(pszTemp, "PERCENT"))
     957             :     {
     958           1 :         panMetadata[1] = 1;
     959             :     }
     960             :     else
     961             :     {
     962           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     963             :                  "Invalid value (%s) for SLOPE_UNIT.", pszTemp);
     964           1 :         return nullptr;
     965             :     }
     966             : 
     967             :     pszTemp =
     968          40 :         CSLFetchNameValueDef(papszOptions, "ASPECT_UNIT", "AZIMUTH_DEGREES");
     969          40 :     if (EQUAL(pszTemp, "GRASS_CATEGORIES"))
     970             :     {
     971           1 :         panMetadata[2] = 0;
     972             :     }
     973          39 :     else if (EQUAL(pszTemp, "GRASS_DEGREES"))
     974             :     {
     975           1 :         panMetadata[2] = 1;
     976             :     }
     977          38 :     else if (EQUAL(pszTemp, "AZIMUTH_DEGREES"))
     978             :     {
     979          37 :         panMetadata[2] = 2;
     980             :     }
     981             :     else
     982             :     {
     983           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     984             :                  "Invalid value (%s) for ASPECT_UNIT.", pszTemp);
     985           1 :         return nullptr;
     986             :     }
     987             : 
     988          39 :     pszTemp = CSLFetchNameValueDef(papszOptions, "FUEL_MODEL_OPTION",
     989             :                                    "NO_CUSTOM_AND_NO_FILE");
     990          39 :     if (EQUAL(pszTemp, "NO_CUSTOM_AND_NO_FILE"))
     991             :     {
     992          38 :         panMetadata[3] = 0;
     993             :     }
     994           1 :     else if (EQUAL(pszTemp, "CUSTOM_AND_NO_FILE"))
     995             :     {
     996           0 :         panMetadata[3] = 1;
     997             :     }
     998           1 :     else if (EQUAL(pszTemp, "NO_CUSTOM_AND_FILE"))
     999             :     {
    1000           0 :         panMetadata[3] = 2;
    1001             :     }
    1002           1 :     else if (EQUAL(pszTemp, "CUSTOM_AND_FILE"))
    1003             :     {
    1004           0 :         panMetadata[3] = 3;
    1005             :     }
    1006             :     else
    1007             :     {
    1008           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1009             :                  "Invalid value (%s) for FUEL_MODEL_OPTION.", pszTemp);
    1010           1 :         return nullptr;
    1011             :     }
    1012             : 
    1013          38 :     pszTemp = CSLFetchNameValueDef(papszOptions, "CANOPY_COV_UNIT", "PERCENT");
    1014          38 :     if (EQUAL(pszTemp, "CATEGORIES"))
    1015             :     {
    1016           1 :         panMetadata[4] = 0;
    1017             :     }
    1018          37 :     else if (EQUAL(pszTemp, "PERCENT"))
    1019             :     {
    1020          36 :         panMetadata[4] = 1;
    1021             :     }
    1022             :     else
    1023             :     {
    1024           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1025             :                  "Invalid value (%s) for CANOPY_COV_UNIT.", pszTemp);
    1026           1 :         return nullptr;
    1027             :     }
    1028             : 
    1029          37 :     if (bHaveCrownFuels)
    1030             :     {
    1031             :         pszTemp =
    1032          35 :             CSLFetchNameValueDef(papszOptions, "CANOPY_HT_UNIT", "METERS_X_10");
    1033          35 :         if (EQUAL(pszTemp, "METERS") || EQUAL(pszTemp, "METER"))
    1034             :         {
    1035           1 :             panMetadata[5] = 1;
    1036             :         }
    1037          34 :         else if (EQUAL(pszTemp, "FEET") || EQUAL(pszTemp, "FOOT"))
    1038             :         {
    1039           1 :             panMetadata[5] = 2;
    1040             :         }
    1041          33 :         else if (EQUAL(pszTemp, "METERS_X_10") || EQUAL(pszTemp, "METER_X_10"))
    1042             :         {
    1043          31 :             panMetadata[5] = 3;
    1044             :         }
    1045           2 :         else if (EQUAL(pszTemp, "FEET_X_10") || EQUAL(pszTemp, "FOOT_X_10"))
    1046             :         {
    1047           1 :             panMetadata[5] = 4;
    1048             :         }
    1049             :         else
    1050             :         {
    1051           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1052             :                      "Invalid value (%s) for CANOPY_HT_UNIT.", pszTemp);
    1053           1 :             return nullptr;
    1054             :         }
    1055             : 
    1056          34 :         pszTemp = CSLFetchNameValueDef(papszOptions, "CBH_UNIT", "METERS_X_10");
    1057          34 :         if (EQUAL(pszTemp, "METERS") || EQUAL(pszTemp, "METER"))
    1058             :         {
    1059           1 :             panMetadata[6] = 1;
    1060             :         }
    1061          33 :         else if (EQUAL(pszTemp, "FEET") || EQUAL(pszTemp, "FOOT"))
    1062             :         {
    1063           1 :             panMetadata[6] = 2;
    1064             :         }
    1065          32 :         else if (EQUAL(pszTemp, "METERS_X_10") || EQUAL(pszTemp, "METER_X_10"))
    1066             :         {
    1067          30 :             panMetadata[6] = 3;
    1068             :         }
    1069           2 :         else if (EQUAL(pszTemp, "FEET_X_10") || EQUAL(pszTemp, "FOOT_X_10"))
    1070             :         {
    1071           1 :             panMetadata[6] = 4;
    1072             :         }
    1073             :         else
    1074             :         {
    1075           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1076             :                      "Invalid value (%s) for CBH_UNIT.", pszTemp);
    1077           1 :             return nullptr;
    1078             :         }
    1079             : 
    1080          33 :         pszTemp = CSLFetchNameValueDef(papszOptions, "CBD_UNIT",
    1081             :                                        "KG_PER_CUBIC_METER_X_100");
    1082          33 :         if (EQUAL(pszTemp, "KG_PER_CUBIC_METER"))
    1083             :         {
    1084           1 :             panMetadata[7] = 1;
    1085             :         }
    1086          32 :         else if (EQUAL(pszTemp, "POUND_PER_CUBIC_FOOT"))
    1087             :         {
    1088           1 :             panMetadata[7] = 2;
    1089             :         }
    1090          31 :         else if (EQUAL(pszTemp, "KG_PER_CUBIC_METER_X_100"))
    1091             :         {
    1092          29 :             panMetadata[7] = 3;
    1093             :         }
    1094           2 :         else if (EQUAL(pszTemp, "POUND_PER_CUBIC_FOOT_X_1000"))
    1095             :         {
    1096           1 :             panMetadata[7] = 4;
    1097             :         }
    1098             :         else
    1099             :         {
    1100           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1101             :                      "Invalid value (%s) for CBD_UNIT.", pszTemp);
    1102           1 :             return nullptr;
    1103             :         }
    1104             :     }
    1105             : 
    1106          34 :     if (bHaveGroundFuels)
    1107             :     {
    1108          32 :         pszTemp = CSLFetchNameValueDef(papszOptions, "DUFF_UNIT",
    1109             :                                        "MG_PER_HECTARE_X_10");
    1110          32 :         if (EQUAL(pszTemp, "MG_PER_HECTARE_X_10"))
    1111             :         {
    1112          30 :             panMetadata[8] = 1;
    1113             :         }
    1114           2 :         else if (EQUAL(pszTemp, "TONS_PER_ACRE_X_10"))
    1115             :         {
    1116           1 :             panMetadata[8] = 2;
    1117             :         }
    1118             :         else
    1119             :         {
    1120           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1121             :                      "Invalid value (%s) for DUFF_UNIT.", pszTemp);
    1122           1 :             return nullptr;
    1123             :         }
    1124             : 
    1125          31 :         panMetadata[9] = 1;
    1126             :     }
    1127             : 
    1128             :     // Calculate the stats for each band.  The binary file carries along
    1129             :     // these metadata for display purposes(?).
    1130          33 :     bool bCalculateStats = CPLFetchBool(papszOptions, "CALCULATE_STATS", true);
    1131             : 
    1132             :     const bool bClassifyData =
    1133          33 :         CPLFetchBool(papszOptions, "CLASSIFY_DATA", true);
    1134             : 
    1135             :     // We should have stats if we classify, we'll get them anyway.
    1136          33 :     if (bClassifyData && !bCalculateStats)
    1137             :     {
    1138           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    1139             :                  "Ignoring request to not calculate statistics, "
    1140             :                  "because CLASSIFY_DATA was set to ON");
    1141           0 :         bCalculateStats = true;
    1142             :     }
    1143             : 
    1144          33 :     pszTemp = CSLFetchNameValueDef(papszOptions, "LINEAR_UNIT", "SET_FROM_SRS");
    1145          33 :     int nLinearUnits = 0;
    1146          33 :     bool bSetLinearUnits = false;
    1147          33 :     if (EQUAL(pszTemp, "SET_FROM_SRS"))
    1148             :     {
    1149           0 :         bSetLinearUnits = true;
    1150             :     }
    1151          33 :     else if (STARTS_WITH_CI(pszTemp, "METER"))
    1152             :     {
    1153          32 :         nLinearUnits = 0;
    1154             :     }
    1155           1 :     else if (EQUAL(pszTemp, "FOOT") || EQUAL(pszTemp, "FEET"))
    1156             :     {
    1157           1 :         nLinearUnits = 1;
    1158             :     }
    1159           0 :     else if (STARTS_WITH_CI(pszTemp, "KILOMETER"))
    1160             :     {
    1161           0 :         nLinearUnits = 2;
    1162             :     }
    1163          33 :     bool bCalculateLatitude = true;
    1164          33 :     int nLatitude = 0;
    1165          33 :     if (CSLFetchNameValue(papszOptions, "LATITUDE") != nullptr)
    1166             :     {
    1167          33 :         bCalculateLatitude = false;
    1168          33 :         nLatitude = atoi(CSLFetchNameValue(papszOptions, "LATITUDE"));
    1169          33 :         if (nLatitude > 90 || nLatitude < -90)
    1170             :         {
    1171           0 :             CPLError(CE_Failure, CPLE_OpenFailed,
    1172             :                      "Invalid value (%d) for LATITUDE.", nLatitude);
    1173           0 :             return nullptr;
    1174             :         }
    1175             :     }
    1176             : 
    1177             :     // If no latitude is supplied, attempt to extract the central latitude
    1178             :     // from the image.  It must be set either manually or here, otherwise
    1179             :     // we fail.
    1180          33 :     double adfSrcGeoTransform[6] = {0.0};
    1181          33 :     poSrcDS->GetGeoTransform(adfSrcGeoTransform);
    1182          33 :     const OGRSpatialReference *poSrcSRS = poSrcDS->GetSpatialRef();
    1183          33 :     double dfLongitude = 0.0;
    1184          33 :     double dfLatitude = 0.0;
    1185             : 
    1186          33 :     const int nYSize = poSrcDS->GetRasterYSize();
    1187             : 
    1188          33 :     if (!bCalculateLatitude)
    1189             :     {
    1190          33 :         dfLatitude = nLatitude;
    1191             :     }
    1192           0 :     else if (poSrcSRS)
    1193             :     {
    1194           0 :         OGRSpatialReference oDstSRS;
    1195           0 :         oDstSRS.importFromEPSG(4269);
    1196           0 :         oDstSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1197             :         OGRCoordinateTransformation *poCT =
    1198           0 :             reinterpret_cast<OGRCoordinateTransformation *>(
    1199             :                 OGRCreateCoordinateTransformation(poSrcSRS, &oDstSRS));
    1200           0 :         if (poCT != nullptr)
    1201             :         {
    1202           0 :             dfLatitude =
    1203           0 :                 adfSrcGeoTransform[3] + adfSrcGeoTransform[5] * nYSize / 2;
    1204             :             const int nErr =
    1205           0 :                 static_cast<int>(poCT->Transform(1, &dfLongitude, &dfLatitude));
    1206           0 :             if (!nErr)
    1207             :             {
    1208           0 :                 dfLatitude = 0.0;
    1209             :                 // For the most part, this is an invalid LCP, but it is a
    1210             :                 // changeable value in Flammap/Farsite, etc.  We should
    1211             :                 // probably be strict here all the time.
    1212           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1213             :                          "Could not calculate latitude from spatial "
    1214             :                          "reference and LATITUDE was not set.");
    1215           0 :                 return nullptr;
    1216             :             }
    1217             :         }
    1218           0 :         OGRCoordinateTransformation::DestroyCT(poCT);
    1219             :     }
    1220             :     else
    1221             :     {
    1222             :         // See comment above on failure to transform.
    1223           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1224             :                  "Could not calculate latitude from spatial reference "
    1225             :                  "and LATITUDE was not set.");
    1226           0 :         return nullptr;
    1227             :     }
    1228             :     // Set the linear units if the metadata item was not already set, and we
    1229             :     // have an SRS.
    1230          33 :     if (bSetLinearUnits && poSrcSRS)
    1231             :     {
    1232           0 :         const char *pszUnit = poSrcSRS->GetAttrValue("UNIT", 0);
    1233           0 :         if (pszUnit == nullptr)
    1234             :         {
    1235           0 :             if (bStrict)
    1236             :             {
    1237           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1238             :                          "Could not parse linear unit.");
    1239           0 :                 return nullptr;
    1240             :             }
    1241             :             else
    1242             :             {
    1243           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1244             :                          "Could not parse linear unit, using meters");
    1245           0 :                 nLinearUnits = 0;
    1246             :             }
    1247             :         }
    1248             :         else
    1249             :         {
    1250           0 :             CPLDebug("LCP", "Setting linear unit to %s", pszUnit);
    1251           0 :             if (EQUAL(pszUnit, "meter") || EQUAL(pszUnit, "metre"))
    1252             :             {
    1253           0 :                 nLinearUnits = 0;
    1254             :             }
    1255           0 :             else if (EQUAL(pszUnit, "feet") || EQUAL(pszUnit, "foot"))
    1256             :             {
    1257           0 :                 nLinearUnits = 1;
    1258             :             }
    1259           0 :             else if (STARTS_WITH_CI(pszUnit, "kilomet"))
    1260             :             {
    1261           0 :                 nLinearUnits = 2;
    1262             :             }
    1263             :             else
    1264             :             {
    1265           0 :                 if (bStrict)
    1266           0 :                     nLinearUnits = 0;
    1267             :             }
    1268           0 :             pszUnit = poSrcSRS->GetAttrValue("UNIT", 1);
    1269           0 :             if (pszUnit != nullptr)
    1270             :             {
    1271           0 :                 const double dfScale = CPLAtof(pszUnit);
    1272           0 :                 if (dfScale != 1.0)
    1273             :                 {
    1274           0 :                     if (bStrict)
    1275             :                     {
    1276           0 :                         CPLError(CE_Failure, CPLE_AppDefined,
    1277             :                                  "Unit scale is %lf (!=1.0). It is not "
    1278             :                                  "supported.",
    1279             :                                  dfScale);
    1280           0 :                         return nullptr;
    1281             :                     }
    1282             :                     else
    1283             :                     {
    1284           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
    1285             :                                  "Unit scale is %lf (!=1.0). It is not "
    1286             :                                  "supported, ignoring.",
    1287             :                                  dfScale);
    1288             :                     }
    1289             :                 }
    1290             :             }
    1291           0 :         }
    1292             :     }
    1293          33 :     else if (bSetLinearUnits)
    1294             :     {
    1295             :         // This can be defaulted if it is not a strict creation.
    1296           0 :         if (bStrict)
    1297             :         {
    1298           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1299             :                      "Could not parse linear unit from spatial reference "
    1300             :                      "and LINEAR_UNIT was not set.");
    1301           0 :             return nullptr;
    1302             :         }
    1303             :         else
    1304             :         {
    1305           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1306             :                      "Could not parse linear unit from spatial reference "
    1307             :                      "and LINEAR_UNIT was not set, defaulting to meters.");
    1308           0 :             nLinearUnits = 0;
    1309             :         }
    1310             :     }
    1311             : 
    1312          33 :     const char *pszDescription = CSLFetchNameValueDef(
    1313             :         papszOptions, "DESCRIPTION", "LCP file created by GDAL.");
    1314             : 
    1315             :     // Loop through and get the stats for the bands if we need to calculate
    1316             :     // them.  This probably should be done when we copy the data over to the
    1317             :     // destination dataset, since we load the values into memory, but this is
    1318             :     // much simpler code using GDALRasterBand->GetStatistics().  We also may
    1319             :     // need to classify the data (number of unique values and a list of those
    1320             :     // values if the number of unique values is > 100.  It is currently unclear
    1321             :     // how these data are used though, so we will implement that at some point
    1322             :     // if need be.
    1323          33 :     double *padfMin = static_cast<double *>(CPLMalloc(sizeof(double) * nBands));
    1324          33 :     double *padfMax = static_cast<double *>(CPLMalloc(sizeof(double) * nBands));
    1325             : 
    1326             :     // Initialize these arrays to zeros
    1327             :     GInt32 *panFound =
    1328          33 :         static_cast<GInt32 *>(VSIMalloc2(sizeof(GInt32), nBands));
    1329          33 :     memset(panFound, 0, sizeof(GInt32) * nBands);
    1330             :     GInt32 *panClasses = static_cast<GInt32 *>(
    1331          33 :         VSIMalloc3(sizeof(GInt32), nBands, LCP_MAX_CLASSES));
    1332          33 :     memset(panClasses, 0, sizeof(GInt32) * nBands * LCP_MAX_CLASSES);
    1333             : 
    1334          33 :     if (bCalculateStats)
    1335             :     {
    1336             : 
    1337         353 :         for (int i = 0; i < nBands; i++)
    1338             :         {
    1339         320 :             GDALRasterBand *poBand = poSrcDS->GetRasterBand(i + 1);
    1340         320 :             double dfDummy = 0.0;
    1341         640 :             CPLErr eErr = poBand->GetStatistics(
    1342         320 :                 FALSE, TRUE, &padfMin[i], &padfMax[i], &dfDummy, &dfDummy);
    1343         320 :             if (eErr != CE_None)
    1344             :             {
    1345           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1346             :                          "Failed to properly "
    1347             :                          "calculate statistics "
    1348             :                          "on band %d",
    1349             :                          i);
    1350           0 :                 padfMin[i] = 0.0;
    1351           0 :                 padfMax[i] = 0.0;
    1352             :             }
    1353             : 
    1354             :             // See comment above.
    1355         320 :             if (bClassifyData)
    1356             :             {
    1357         640 :                 eErr = ClassifyBandData(poBand, panFound[i],
    1358         320 :                                         panClasses + (i * LCP_MAX_CLASSES));
    1359         320 :                 if (eErr != CE_None)
    1360             :                 {
    1361           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1362             :                              "Failed to classify band data on band %d.", i);
    1363             :                 }
    1364             :             }
    1365             :         }
    1366             :     }
    1367             : 
    1368          33 :     VSILFILE *fp = VSIFOpenL(pszFilename, "wb");
    1369          33 :     if (fp == nullptr)
    1370             :     {
    1371           0 :         CPLError(CE_Failure, CPLE_OpenFailed, "Unable to create lcp file %s.",
    1372             :                  pszFilename);
    1373           0 :         CPLFree(padfMin);
    1374           0 :         CPLFree(padfMax);
    1375           0 :         CPLFree(panFound);
    1376           0 :         CPLFree(panClasses);
    1377           0 :         return nullptr;
    1378             :     }
    1379             : 
    1380             :     /* -------------------------------------------------------------------- */
    1381             :     /*      Write the header                                                */
    1382             :     /* -------------------------------------------------------------------- */
    1383             : 
    1384          33 :     GInt32 nTemp = bHaveCrownFuels ? 21 : 20;
    1385          33 :     CPL_LSBPTR32(&nTemp);
    1386          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&nTemp, 4, 1, fp));
    1387          33 :     nTemp = bHaveGroundFuels ? 21 : 20;
    1388          33 :     CPL_LSBPTR32(&nTemp);
    1389          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&nTemp, 4, 1, fp));
    1390             : 
    1391          33 :     const int nXSize = poSrcDS->GetRasterXSize();
    1392          33 :     nTemp = static_cast<GInt32>(dfLatitude + 0.5);
    1393          33 :     CPL_LSBPTR32(&nTemp);
    1394          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&nTemp, 4, 1, fp));
    1395          33 :     dfLongitude = adfSrcGeoTransform[0] + adfSrcGeoTransform[1] * nXSize;
    1396          33 :     CPL_LSBPTR64(&dfLongitude);
    1397          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&dfLongitude, 8, 1, fp));
    1398          33 :     dfLongitude = adfSrcGeoTransform[0];
    1399          33 :     CPL_LSBPTR64(&dfLongitude);
    1400          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&dfLongitude, 8, 1, fp));
    1401          33 :     dfLatitude = adfSrcGeoTransform[3];
    1402          33 :     CPL_LSBPTR64(&dfLatitude);
    1403          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&dfLatitude, 8, 1, fp));
    1404          33 :     dfLatitude = adfSrcGeoTransform[3] + adfSrcGeoTransform[5] * nYSize;
    1405          33 :     CPL_LSBPTR64(&dfLatitude);
    1406          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&dfLatitude, 8, 1, fp));
    1407             : 
    1408             :     // Swap the two classification arrays if we are writing them, and they need
    1409             :     // to be swapped.
    1410             : #ifdef CPL_MSB
    1411             :     if (bClassifyData)
    1412             :     {
    1413             :         GDALSwapWords(panFound, 2, nBands, 2);
    1414             :         GDALSwapWords(panClasses, 2, LCP_MAX_CLASSES, 2);
    1415             :     }
    1416             : #endif
    1417             : 
    1418          33 :     if (bCalculateStats)
    1419             :     {
    1420         353 :         for (int i = 0; i < nBands; i++)
    1421             :         {
    1422             :             // If we don't have Crown fuels, but do have Ground fuels, we
    1423             :             // have to 'fast forward'.
    1424         320 :             if (i == 5 && !bHaveCrownFuels && bHaveGroundFuels)
    1425             :             {
    1426           1 :                 CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 3340, SEEK_SET));
    1427             :             }
    1428         320 :             nTemp = static_cast<GInt32>(padfMin[i]);
    1429         320 :             CPL_LSBPTR32(&nTemp);
    1430         320 :             CPL_IGNORE_RET_VAL(VSIFWriteL(&nTemp, 4, 1, fp));
    1431         320 :             nTemp = static_cast<GInt32>(padfMax[i]);
    1432         320 :             CPL_LSBPTR32(&nTemp);
    1433         320 :             CPL_IGNORE_RET_VAL(VSIFWriteL(&nTemp, 4, 1, fp));
    1434         320 :             if (bClassifyData)
    1435             :             {
    1436             :                 // These two arrays were swapped in their entirety above.
    1437         320 :                 CPL_IGNORE_RET_VAL(VSIFWriteL(panFound + i, 4, 1, fp));
    1438         320 :                 CPL_IGNORE_RET_VAL(
    1439         320 :                     VSIFWriteL(panClasses + (i * LCP_MAX_CLASSES), 4,
    1440             :                                LCP_MAX_CLASSES, fp));
    1441             :             }
    1442             :             else
    1443             :             {
    1444           0 :                 nTemp = -1;
    1445           0 :                 CPL_LSBPTR32(&nTemp);
    1446           0 :                 CPL_IGNORE_RET_VAL(VSIFWriteL(&nTemp, 4, 1, fp));
    1447           0 :                 CPL_IGNORE_RET_VAL(
    1448           0 :                     VSIFSeekL(fp, 4 * LCP_MAX_CLASSES, SEEK_CUR));
    1449             :             }
    1450             :         }
    1451             :     }
    1452             :     else
    1453             :     {
    1454           0 :         CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 4164, SEEK_SET));
    1455             :     }
    1456          33 :     CPLFree(padfMin);
    1457          33 :     CPLFree(padfMax);
    1458          33 :     CPLFree(panFound);
    1459          33 :     CPLFree(panClasses);
    1460             : 
    1461             :     // Should be at one of 3 locations, 2104, 3340, or 4164.
    1462          33 :     CPLAssert(VSIFTellL(fp) == 2104 || VSIFTellL(fp) == 3340 ||
    1463             :               VSIFTellL(fp) == 4164);
    1464          33 :     CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 4164, SEEK_SET));
    1465             : 
    1466             :     /* Image size */
    1467          33 :     nTemp = static_cast<GInt32>(nXSize);
    1468          33 :     CPL_LSBPTR32(&nTemp);
    1469          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&nTemp, 4, 1, fp));
    1470          33 :     nTemp = static_cast<GInt32>(nYSize);
    1471          33 :     CPL_LSBPTR32(&nTemp);
    1472          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&nTemp, 4, 1, fp));
    1473             : 
    1474             :     // X and Y boundaries.
    1475             :     // Max x.
    1476          33 :     double dfTemp = adfSrcGeoTransform[0] + adfSrcGeoTransform[1] * nXSize;
    1477          33 :     CPL_LSBPTR64(&dfTemp);
    1478          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&dfTemp, 8, 1, fp));
    1479             :     // Min x.
    1480          33 :     dfTemp = adfSrcGeoTransform[0];
    1481          33 :     CPL_LSBPTR64(&dfTemp);
    1482          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&dfTemp, 8, 1, fp));
    1483             :     // Max y.
    1484          33 :     dfTemp = adfSrcGeoTransform[3];
    1485          33 :     CPL_LSBPTR64(&dfTemp);
    1486          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&dfTemp, 8, 1, fp));
    1487             :     // Min y.
    1488          33 :     dfTemp = adfSrcGeoTransform[3] + adfSrcGeoTransform[5] * nYSize;
    1489          33 :     CPL_LSBPTR64(&dfTemp);
    1490          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&dfTemp, 8, 1, fp));
    1491             : 
    1492          33 :     nTemp = nLinearUnits;
    1493          33 :     CPL_LSBPTR32(&nTemp);
    1494          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&nTemp, 4, 1, fp));
    1495             : 
    1496             :     // Resolution.
    1497             :     // X resolution.
    1498          33 :     dfTemp = adfSrcGeoTransform[1];
    1499          33 :     CPL_LSBPTR64(&dfTemp);
    1500          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&dfTemp, 8, 1, fp));
    1501             :     // Y resolution.
    1502          33 :     dfTemp = fabs(adfSrcGeoTransform[5]);
    1503          33 :     CPL_LSBPTR64(&dfTemp);
    1504          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(&dfTemp, 8, 1, fp));
    1505             : 
    1506             : #ifdef CPL_MSB
    1507             :     GDALSwapWords(panMetadata, 2, LCP_MAX_BANDS, 2);
    1508             : #endif
    1509          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(panMetadata, 2, LCP_MAX_BANDS, fp));
    1510             : 
    1511             :     // Write the source filenames.
    1512          33 :     char **papszFileList = poSrcDS->GetFileList();
    1513          33 :     if (papszFileList != nullptr)
    1514             :     {
    1515         308 :         for (int i = 0; i < nBands; i++)
    1516             :         {
    1517         280 :             if (i == 5 && !bHaveCrownFuels && bHaveGroundFuels)
    1518             :             {
    1519           0 :                 CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 6292, SEEK_SET));
    1520             :             }
    1521         280 :             CPL_IGNORE_RET_VAL(
    1522         280 :                 VSIFWriteL(papszFileList[0], 1,
    1523             :                            CPLStrnlen(papszFileList[0], LCP_MAX_PATH), fp));
    1524         280 :             CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 4244 + (256 * (i + 1)), SEEK_SET));
    1525             :         }
    1526             :     }
    1527             :     // No file list, mem driver, etc.
    1528             :     else
    1529             :     {
    1530           5 :         CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 6804, SEEK_SET));
    1531             :     }
    1532          33 :     CSLDestroy(papszFileList);
    1533             :     // Should be at location 5524, 6292 or 6804.
    1534          33 :     CPLAssert(VSIFTellL(fp) == 5524 || VSIFTellL(fp) == 6292 ||
    1535             :               VSIFTellL(fp) == 6804);
    1536          33 :     CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 6804, SEEK_SET));
    1537             : 
    1538             :     // Description .
    1539          33 :     CPL_IGNORE_RET_VAL(VSIFWriteL(
    1540             :         pszDescription, 1, CPLStrnlen(pszDescription, LCP_MAX_DESC), fp));
    1541             : 
    1542             :     // Should be at or below location 7316, all done with the header.
    1543          33 :     CPLAssert(VSIFTellL(fp) <= 7316);
    1544          33 :     CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 7316, SEEK_SET));
    1545             : 
    1546             :     /* -------------------------------------------------------------------- */
    1547             :     /*      Loop over image, copying image data.                            */
    1548             :     /* -------------------------------------------------------------------- */
    1549             : 
    1550          33 :     GInt16 *panScanline = static_cast<GInt16 *>(VSIMalloc3(2, nBands, nXSize));
    1551             : 
    1552          33 :     if (!pfnProgress(0.0, nullptr, pProgressData))
    1553             :     {
    1554           0 :         CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
    1555           0 :         VSIFree(panScanline);
    1556           0 :         return nullptr;
    1557             :     }
    1558         399 :     for (int iLine = 0; iLine < nYSize; iLine++)
    1559             :     {
    1560        3826 :         for (int iBand = 0; iBand < nBands; iBand++)
    1561             :         {
    1562        3460 :             GDALRasterBand *poBand = poSrcDS->GetRasterBand(iBand + 1);
    1563        6920 :             CPLErr eErr = poBand->RasterIO(
    1564        3460 :                 GF_Read, 0, iLine, nXSize, 1, panScanline + iBand, nXSize, 1,
    1565        3460 :                 GDT_Int16, nBands * 2, static_cast<size_t>(nBands) * nXSize * 2,
    1566             :                 nullptr);
    1567             :             // Not sure what to do here.
    1568        3460 :             if (eErr != CE_None)
    1569             :             {
    1570           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1571             :                          "Error reported in "
    1572             :                          "RasterIO");
    1573             :             }
    1574             :         }
    1575             : #ifdef CPL_MSB
    1576             :         GDALSwapWordsEx(panScanline, 2, static_cast<size_t>(nBands) * nXSize,
    1577             :                         2);
    1578             : #endif
    1579         366 :         CPL_IGNORE_RET_VAL(VSIFWriteL(
    1580         366 :             panScanline, 2, static_cast<size_t>(nBands) * nXSize, fp));
    1581             : 
    1582         366 :         if (!pfnProgress(iLine / static_cast<double>(nYSize), nullptr,
    1583             :                          pProgressData))
    1584             :         {
    1585           0 :             VSIFree(reinterpret_cast<void *>(panScanline));
    1586           0 :             CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
    1587           0 :             return nullptr;
    1588             :         }
    1589             :     }
    1590          33 :     VSIFree(panScanline);
    1591          33 :     CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
    1592          33 :     if (!pfnProgress(1.0, nullptr, pProgressData))
    1593             :     {
    1594           0 :         return nullptr;
    1595             :     }
    1596             : 
    1597             :     // Try to write projection file.  *Most* landfire data follows ESRI
    1598             :     // style projection files, so we use the same code as the AAIGrid driver.
    1599          33 :     if (poSrcSRS)
    1600             :     {
    1601           0 :         char *pszESRIProjection = nullptr;
    1602           0 :         const char *const apszOptions[] = {"FORMAT=WKT1_ESRI", nullptr};
    1603           0 :         poSrcSRS->exportToWkt(&pszESRIProjection, apszOptions);
    1604           0 :         if (pszESRIProjection)
    1605             :         {
    1606           0 :             char *const pszDirname = CPLStrdup(CPLGetPath(pszFilename));
    1607           0 :             char *const pszBasename = CPLStrdup(CPLGetBasename(pszFilename));
    1608             :             char *pszPrjFilename =
    1609           0 :                 CPLStrdup(CPLFormFilename(pszDirname, pszBasename, "prj"));
    1610           0 :             fp = VSIFOpenL(pszPrjFilename, "wt");
    1611           0 :             if (fp != nullptr)
    1612             :             {
    1613           0 :                 CPL_IGNORE_RET_VAL(VSIFWriteL(pszESRIProjection, 1,
    1614             :                                               strlen(pszESRIProjection), fp));
    1615           0 :                 CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
    1616             :             }
    1617             :             else
    1618             :             {
    1619           0 :                 CPLError(CE_Failure, CPLE_FileIO, "Unable to create file %s.",
    1620             :                          pszPrjFilename);
    1621             :             }
    1622           0 :             CPLFree(pszDirname);
    1623           0 :             CPLFree(pszBasename);
    1624           0 :             CPLFree(pszPrjFilename);
    1625             :         }
    1626           0 :         CPLFree(pszESRIProjection);
    1627             :     }
    1628          33 :     return static_cast<GDALDataset *>(GDALOpen(pszFilename, GA_ReadOnly));
    1629             : }
    1630             : 
    1631             : /************************************************************************/
    1632             : /*                         GDALRegister_LCP()                           */
    1633             : /************************************************************************/
    1634             : 
    1635        1595 : void GDALRegister_LCP()
    1636             : 
    1637             : {
    1638        1595 :     if (GDALGetDriverByName("LCP") != nullptr)
    1639         302 :         return;
    1640             : 
    1641        1293 :     GDALDriver *poDriver = new GDALDriver();
    1642             : 
    1643        1293 :     poDriver->SetDescription("LCP");
    1644        1293 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    1645        1293 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
    1646        1293 :                               "FARSITE v.4 Landscape File (.lcp)");
    1647        1293 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "lcp");
    1648        1293 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/lcp.html");
    1649             : 
    1650        1293 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    1651             : 
    1652        1293 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES, "Int16");
    1653        1293 :     poDriver->SetMetadataItem(
    1654             :         GDAL_DMD_CREATIONOPTIONLIST,
    1655             :         "<CreationOptionList>"
    1656             :         "   <Option name='ELEVATION_UNIT' type='string-select' "
    1657             :         "default='METERS' description='Elevation units'>"
    1658             :         "       <Value>METERS</Value>"
    1659             :         "       <Value>FEET</Value>"
    1660             :         "   </Option>"
    1661             :         "   <Option name='SLOPE_UNIT' type='string-select' default='DEGREES' "
    1662             :         "description='Slope units'>"
    1663             :         "       <Value>DEGREES</Value>"
    1664             :         "       <Value>PERCENT</Value>"
    1665             :         "   </Option>"
    1666             :         "   <Option name='ASPECT_UNIT' type='string-select' "
    1667             :         "default='AZIMUTH_DEGREES'>"
    1668             :         "       <Value>GRASS_CATEGORIES</Value>"
    1669             :         "       <Value>AZIMUTH_DEGREES</Value>"
    1670             :         "       <Value>GRASS_DEGREES</Value>"
    1671             :         "   </Option>"
    1672             :         "   <Option name='FUEL_MODEL_OPTION' type='string-select' "
    1673             :         "default='NO_CUSTOM_AND_NO_FILE'>"
    1674             :         "       <Value>NO_CUSTOM_AND_NO_FILE</Value>"
    1675             :         "       <Value>CUSTOM_AND_NO_FILE</Value>"
    1676             :         "       <Value>NO_CUSTOM_AND_FILE</Value>"
    1677             :         "       <Value>CUSTOM_AND_FILE</Value>"
    1678             :         "   </Option>"
    1679             :         "   <Option name='CANOPY_COV_UNIT' type='string-select' "
    1680             :         "default='PERCENT'>"
    1681             :         "       <Value>CATEGORIES</Value>"
    1682             :         "       <Value>PERCENT</Value>"
    1683             :         "   </Option>"
    1684             :         "   <Option name='CANOPY_HT_UNIT' type='string-select' "
    1685             :         "default='METERS_X_10'>"
    1686             :         "       <Value>METERS</Value>"
    1687             :         "       <Value>FEET</Value>"
    1688             :         "       <Value>METERS_X_10</Value>"
    1689             :         "       <Value>FEET_X_10</Value>"
    1690             :         "   </Option>"
    1691             :         "   <Option name='CBH_UNIT' type='string-select' default='METERS_X_10'>"
    1692             :         "       <Value>METERS</Value>"
    1693             :         "       <Value>FEET</Value>"
    1694             :         "       <Value>METERS_X_10</Value>"
    1695             :         "       <Value>FEET_X_10</Value>"
    1696             :         "   </Option>"
    1697             :         "   <Option name='CBD_UNIT' type='string-select' "
    1698             :         "default='KG_PER_CUBIC_METER_X_100'>"
    1699             :         "       <Value>KG_PER_CUBIC_METER</Value>"
    1700             :         "       <Value>POUND_PER_CUBIC_FOOT</Value>"
    1701             :         "       <Value>KG_PER_CUBIC_METER_X_100</Value>"
    1702             :         "       <Value>POUND_PER_CUBIC_FOOT_X_1000</Value>"
    1703             :         "   </Option>"
    1704             :         "   <Option name='DUFF_UNIT' type='string-select' "
    1705             :         "default='MG_PER_HECTARE_X_10'>"
    1706             :         "       <Value>MG_PER_HECTARE_X_10</Value>"
    1707             :         "       <Value>TONS_PER_ACRE_X_10</Value>"
    1708             :         "   </Option>"
    1709             :         // Kyle does not think we need to override this, but maybe?
    1710             :         // "   <Option name='CWD_OPTION' type='boolean' default='FALSE'
    1711             :         // description='Override logic for setting the coarse woody presence'/>"
    1712             :         // */
    1713             :         "   <Option name='CALCULATE_STATS' type='boolean' default='YES' "
    1714             :         "description='Write the stats to the lcp'/>"
    1715             :         "   <Option name='CLASSIFY_DATA' type='boolean' default='YES' "
    1716             :         "description='Write the stats to the lcp'/>"
    1717             :         "   <Option name='LINEAR_UNIT' type='string-select' "
    1718             :         "default='SET_FROM_SRS' description='Set the linear units in the lcp'>"
    1719             :         "       <Value>SET_FROM_SRS</Value>"
    1720             :         "       <Value>METER</Value>"
    1721             :         "       <Value>FOOT</Value>"
    1722             :         "       <Value>KILOMETER</Value>"
    1723             :         "   </Option>"
    1724             :         "   <Option name='LATITUDE' type='int' default='' description='Set the "
    1725             :         "latitude for the dataset, this overrides the driver trying to set it "
    1726             :         "programmatically in EPSG:4269'/>"
    1727             :         "   <Option name='DESCRIPTION' type='string' default='LCP file created "
    1728             :         "by GDAL' description='A short description of the lcp file'/>"
    1729        1293 :         "</CreationOptionList>");
    1730             : 
    1731        1293 :     poDriver->pfnOpen = LCPDataset::Open;
    1732        1293 :     poDriver->pfnCreateCopy = LCPDataset::CreateCopy;
    1733        1293 :     poDriver->pfnIdentify = LCPDataset::Identify;
    1734             : 
    1735        1293 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1736             : }

Generated by: LCOV version 1.14