/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 >) 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 >) 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 | } |