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

Generated by: LCOV version 1.14