Coverage Report

Created: 2026-06-30 08:33

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