/src/gdal/frmts/raw/lcpdataset.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: LCP Driver |
4 | | * Purpose: FARSITE v.4 Landscape file (.lcp) reader for GDAL |
5 | | * Author: Chris Toney |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2008, Chris Toney |
9 | | * Copyright (c) 2008-2011, Even Rouault <even dot rouault at spatialys.com> |
10 | | * Copyright (c) 2013, Kyle Shannon <kyle at pobox dot com> |
11 | | * |
12 | | * SPDX-License-Identifier: MIT |
13 | | ****************************************************************************/ |
14 | | |
15 | | #include "cpl_port.h" |
16 | | #include "cpl_string.h" |
17 | | #include "gdal_frmts.h" |
18 | | #include "ogr_spatialref.h" |
19 | | #include "rawdataset.h" |
20 | | |
21 | | #include <algorithm> |
22 | | #include <limits> |
23 | | |
24 | | constexpr size_t LCP_HEADER_SIZE = 7316; |
25 | | constexpr int LCP_MAX_BANDS = 10; |
26 | | constexpr int LCP_MAX_PATH = 256; |
27 | | constexpr int LCP_MAX_DESC = 512; |
28 | | constexpr int LCP_MAX_CLASSES = 100; |
29 | | |
30 | | /************************************************************************/ |
31 | | /* ==================================================================== */ |
32 | | /* LCPDataset */ |
33 | | /* ==================================================================== */ |
34 | | /************************************************************************/ |
35 | | |
36 | | class LCPDataset final : public RawDataset |
37 | | { |
38 | | VSILFILE *fpImage; // image data file. |
39 | | char pachHeader[LCP_HEADER_SIZE]; |
40 | | |
41 | | CPLString osPrjFilename{}; |
42 | | OGRSpatialReference m_oSRS{}; |
43 | | |
44 | | static CPLErr ClassifyBandData(GDALRasterBand *poBand, GInt32 &nNumClasses, |
45 | | GInt32 *panClasses); |
46 | | |
47 | | CPL_DISALLOW_COPY_ASSIGN(LCPDataset) |
48 | | |
49 | | CPLErr Close() override; |
50 | | |
51 | | public: |
52 | | LCPDataset(); |
53 | | ~LCPDataset() override; |
54 | | |
55 | | char **GetFileList(void) override; |
56 | | |
57 | | CPLErr GetGeoTransform(double *) override; |
58 | | |
59 | | static int Identify(GDALOpenInfo *); |
60 | | static GDALDataset *Open(GDALOpenInfo *); |
61 | | static GDALDataset *CreateCopy(const char *pszFilename, |
62 | | GDALDataset *poSrcDS, int bStrict, |
63 | | char **papszOptions, |
64 | | GDALProgressFunc pfnProgress, |
65 | | void *pProgressData); |
66 | | |
67 | | const OGRSpatialReference *GetSpatialRef() const override |
68 | 0 | { |
69 | 0 | return &m_oSRS; |
70 | 0 | } |
71 | | }; |
72 | | |
73 | | /************************************************************************/ |
74 | | /* LCPDataset() */ |
75 | | /************************************************************************/ |
76 | | |
77 | 53 | LCPDataset::LCPDataset() : fpImage(nullptr) |
78 | 53 | { |
79 | 53 | memset(pachHeader, 0, sizeof(pachHeader)); |
80 | 53 | } |
81 | | |
82 | | /************************************************************************/ |
83 | | /* ~LCPDataset() */ |
84 | | /************************************************************************/ |
85 | | |
86 | | LCPDataset::~LCPDataset() |
87 | | |
88 | 53 | { |
89 | 53 | LCPDataset::Close(); |
90 | 53 | } |
91 | | |
92 | | /************************************************************************/ |
93 | | /* Close() */ |
94 | | /************************************************************************/ |
95 | | |
96 | | CPLErr LCPDataset::Close() |
97 | 53 | { |
98 | 53 | CPLErr eErr = CE_None; |
99 | 53 | if (nOpenFlags != OPEN_FLAGS_CLOSED) |
100 | 53 | { |
101 | 53 | if (LCPDataset::FlushCache(true) != CE_None) |
102 | 0 | eErr = CE_Failure; |
103 | | |
104 | 53 | if (fpImage) |
105 | 53 | { |
106 | 53 | if (VSIFCloseL(fpImage) != 0) |
107 | 0 | { |
108 | 0 | CPLError(CE_Failure, CPLE_FileIO, "I/O error"); |
109 | 0 | eErr = CE_Failure; |
110 | 0 | } |
111 | 53 | } |
112 | | |
113 | 53 | if (GDALPamDataset::Close() != CE_None) |
114 | 0 | eErr = CE_Failure; |
115 | 53 | } |
116 | 53 | return eErr; |
117 | 53 | } |
118 | | |
119 | | /************************************************************************/ |
120 | | /* GetGeoTransform() */ |
121 | | /************************************************************************/ |
122 | | |
123 | | CPLErr LCPDataset::GetGeoTransform(double *padfTransform) |
124 | 0 | { |
125 | 0 | double dfEast = 0.0; |
126 | 0 | double dfWest = 0.0; |
127 | 0 | double dfNorth = 0.0; |
128 | 0 | double dfSouth = 0.0; |
129 | 0 | double dfCellX = 0.0; |
130 | 0 | double dfCellY = 0.0; |
131 | |
|
132 | 0 | memcpy(&dfEast, pachHeader + 4172, sizeof(double)); |
133 | 0 | memcpy(&dfWest, pachHeader + 4180, sizeof(double)); |
134 | 0 | memcpy(&dfNorth, pachHeader + 4188, sizeof(double)); |
135 | 0 | memcpy(&dfSouth, pachHeader + 4196, sizeof(double)); |
136 | 0 | memcpy(&dfCellX, pachHeader + 4208, sizeof(double)); |
137 | 0 | memcpy(&dfCellY, pachHeader + 4216, sizeof(double)); |
138 | 0 | CPL_LSBPTR64(&dfEast); |
139 | 0 | CPL_LSBPTR64(&dfWest); |
140 | 0 | CPL_LSBPTR64(&dfNorth); |
141 | 0 | CPL_LSBPTR64(&dfSouth); |
142 | 0 | CPL_LSBPTR64(&dfCellX); |
143 | 0 | CPL_LSBPTR64(&dfCellY); |
144 | |
|
145 | 0 | padfTransform[0] = dfWest; |
146 | 0 | padfTransform[3] = dfNorth; |
147 | 0 | padfTransform[1] = dfCellX; |
148 | 0 | padfTransform[2] = 0.0; |
149 | |
|
150 | 0 | padfTransform[4] = 0.0; |
151 | 0 | padfTransform[5] = -1 * dfCellY; |
152 | |
|
153 | 0 | return CE_None; |
154 | 0 | } |
155 | | |
156 | | /************************************************************************/ |
157 | | /* Identify() */ |
158 | | /************************************************************************/ |
159 | | |
160 | | int LCPDataset::Identify(GDALOpenInfo *poOpenInfo) |
161 | | |
162 | 15.8k | { |
163 | | /* -------------------------------------------------------------------- */ |
164 | | /* Verify that this is a FARSITE v.4 LCP file */ |
165 | | /* -------------------------------------------------------------------- */ |
166 | 15.8k | if (poOpenInfo->nHeaderBytes < 50) |
167 | 767 | return FALSE; |
168 | | |
169 | | /* check if first three fields have valid data */ |
170 | 15.0k | if ((CPL_LSBSINT32PTR(poOpenInfo->pabyHeader) != 20 && |
171 | 15.0k | CPL_LSBSINT32PTR(poOpenInfo->pabyHeader) != 21) || |
172 | 15.0k | (CPL_LSBSINT32PTR(poOpenInfo->pabyHeader + 4) != 20 && |
173 | 109 | CPL_LSBSINT32PTR(poOpenInfo->pabyHeader + 4) != 21) || |
174 | 15.0k | (CPL_LSBSINT32PTR(poOpenInfo->pabyHeader + 8) < -90 || |
175 | 108 | CPL_LSBSINT32PTR(poOpenInfo->pabyHeader + 8) > 90)) |
176 | 14.9k | { |
177 | 14.9k | return FALSE; |
178 | 14.9k | } |
179 | | |
180 | | /* -------------------------------------------------------------------- */ |
181 | | /* Check file extension */ |
182 | | /* -------------------------------------------------------------------- */ |
183 | | #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
184 | | const char *pszFileExtension = poOpenInfo->osExtension.c_str(); |
185 | | if (!EQUAL(pszFileExtension, "lcp")) |
186 | | { |
187 | | return FALSE; |
188 | | } |
189 | | #endif |
190 | | |
191 | 106 | return TRUE; |
192 | 15.0k | } |
193 | | |
194 | | /************************************************************************/ |
195 | | /* GetFileList() */ |
196 | | /************************************************************************/ |
197 | | |
198 | | char **LCPDataset::GetFileList() |
199 | | |
200 | 0 | { |
201 | 0 | char **papszFileList = GDALPamDataset::GetFileList(); |
202 | |
|
203 | 0 | if (!m_oSRS.IsEmpty()) |
204 | 0 | { |
205 | 0 | papszFileList = CSLAddString(papszFileList, osPrjFilename); |
206 | 0 | } |
207 | |
|
208 | 0 | return papszFileList; |
209 | 0 | } |
210 | | |
211 | | /************************************************************************/ |
212 | | /* Open() */ |
213 | | /************************************************************************/ |
214 | | |
215 | | GDALDataset *LCPDataset::Open(GDALOpenInfo *poOpenInfo) |
216 | | |
217 | 53 | { |
218 | | /* -------------------------------------------------------------------- */ |
219 | | /* Verify that this is a FARSITE LCP file */ |
220 | | /* -------------------------------------------------------------------- */ |
221 | 53 | if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr) |
222 | 0 | return nullptr; |
223 | | |
224 | | /* -------------------------------------------------------------------- */ |
225 | | /* Confirm the requested access is supported. */ |
226 | | /* -------------------------------------------------------------------- */ |
227 | 53 | if (poOpenInfo->eAccess == GA_Update) |
228 | 0 | { |
229 | 0 | ReportUpdateNotSupportedByDriver("LCP"); |
230 | 0 | return nullptr; |
231 | 0 | } |
232 | | /* -------------------------------------------------------------------- */ |
233 | | /* Create a corresponding GDALDataset. */ |
234 | | /* -------------------------------------------------------------------- */ |
235 | 53 | auto poDS = std::make_unique<LCPDataset>(); |
236 | 53 | std::swap(poDS->fpImage, poOpenInfo->fpL); |
237 | | |
238 | | /* -------------------------------------------------------------------- */ |
239 | | /* Read the header and extract some information. */ |
240 | | /* -------------------------------------------------------------------- */ |
241 | 53 | if (VSIFSeekL(poDS->fpImage, 0, SEEK_SET) < 0 || |
242 | 53 | VSIFReadL(poDS->pachHeader, 1, LCP_HEADER_SIZE, poDS->fpImage) != |
243 | 53 | LCP_HEADER_SIZE) |
244 | 5 | { |
245 | 5 | CPLError(CE_Failure, CPLE_FileIO, "File too short"); |
246 | 5 | return nullptr; |
247 | 5 | } |
248 | | |
249 | 48 | const int nWidth = CPL_LSBSINT32PTR(poDS->pachHeader + 4164); |
250 | 48 | const int nHeight = CPL_LSBSINT32PTR(poDS->pachHeader + 4168); |
251 | | |
252 | 48 | poDS->nRasterXSize = nWidth; |
253 | 48 | poDS->nRasterYSize = nHeight; |
254 | | |
255 | 48 | if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize)) |
256 | 3 | { |
257 | 3 | return nullptr; |
258 | 3 | } |
259 | | |
260 | | // Crown fuels = canopy height, canopy base height, canopy bulk density. |
261 | | // 21 = have them, 20 = do not have them |
262 | 45 | const bool bHaveCrownFuels = |
263 | 45 | CPL_TO_BOOL(CPL_LSBSINT32PTR(poDS->pachHeader + 0) - 20); |
264 | | // Ground fuels = duff loading, coarse woody. |
265 | 45 | const bool bHaveGroundFuels = |
266 | 45 | CPL_TO_BOOL(CPL_LSBSINT32PTR(poDS->pachHeader + 4) - 20); |
267 | | |
268 | 45 | int nBands = 0; |
269 | 45 | if (bHaveCrownFuels) |
270 | 45 | { |
271 | 45 | if (bHaveGroundFuels) |
272 | 45 | nBands = 10; |
273 | 0 | else |
274 | 0 | nBands = 8; |
275 | 45 | } |
276 | 0 | else |
277 | 0 | { |
278 | 0 | if (bHaveGroundFuels) |
279 | 0 | nBands = 7; |
280 | 0 | else |
281 | 0 | nBands = 5; |
282 | 0 | } |
283 | | |
284 | | // Add dataset-level metadata. |
285 | | |
286 | 45 | int nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 8); |
287 | 45 | char szTemp[32] = {'\0'}; |
288 | 45 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
289 | 45 | poDS->SetMetadataItem("LATITUDE", szTemp); |
290 | | |
291 | 45 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 4204); |
292 | 45 | if (nTemp == 0) |
293 | 7 | poDS->SetMetadataItem("LINEAR_UNIT", "Meters"); |
294 | 45 | if (nTemp == 1) |
295 | 2 | poDS->SetMetadataItem("LINEAR_UNIT", "Feet"); |
296 | | |
297 | 45 | poDS->pachHeader[LCP_HEADER_SIZE - 1] = '\0'; |
298 | 45 | poDS->SetMetadataItem("DESCRIPTION", poDS->pachHeader + 6804); |
299 | | |
300 | | /* -------------------------------------------------------------------- */ |
301 | | /* Create band information objects. */ |
302 | | /* -------------------------------------------------------------------- */ |
303 | | |
304 | 45 | const int iPixelSize = nBands * 2; |
305 | | |
306 | 45 | if (nWidth > INT_MAX / iPixelSize) |
307 | 7 | { |
308 | 7 | CPLError(CE_Failure, CPLE_AppDefined, "Int overflow occurred"); |
309 | 7 | return nullptr; |
310 | 7 | } |
311 | | |
312 | 418 | for (int iBand = 1; iBand <= nBands; iBand++) |
313 | 380 | { |
314 | 380 | auto poBand = |
315 | 380 | RawRasterBand::Create(poDS.get(), iBand, poDS->fpImage, |
316 | 380 | LCP_HEADER_SIZE + ((iBand - 1) * 2), |
317 | 380 | iPixelSize, iPixelSize * nWidth, GDT_Int16, |
318 | 380 | RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN, |
319 | 380 | RawRasterBand::OwnFP::NO); |
320 | 380 | if (!poBand) |
321 | 0 | return nullptr; |
322 | | |
323 | 380 | switch (iBand) |
324 | 380 | { |
325 | 38 | case 1: |
326 | 38 | poBand->SetDescription("Elevation"); |
327 | | |
328 | 38 | nTemp = CPL_LSBUINT16PTR(poDS->pachHeader + 4224); |
329 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
330 | 38 | poBand->SetMetadataItem("ELEVATION_UNIT", szTemp); |
331 | | |
332 | 38 | if (nTemp == 0) |
333 | 10 | poBand->SetMetadataItem("ELEVATION_UNIT_NAME", "Meters"); |
334 | 38 | if (nTemp == 1) |
335 | 4 | poBand->SetMetadataItem("ELEVATION_UNIT_NAME", "Feet"); |
336 | | |
337 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 44); |
338 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
339 | 38 | poBand->SetMetadataItem("ELEVATION_MIN", szTemp); |
340 | | |
341 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 48); |
342 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
343 | 38 | poBand->SetMetadataItem("ELEVATION_MAX", szTemp); |
344 | | |
345 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 52); |
346 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
347 | 38 | poBand->SetMetadataItem("ELEVATION_NUM_CLASSES", szTemp); |
348 | | |
349 | 38 | *(poDS->pachHeader + 4244 + 255) = '\0'; |
350 | 38 | poBand->SetMetadataItem("ELEVATION_FILE", |
351 | 38 | poDS->pachHeader + 4244); |
352 | | |
353 | 38 | break; |
354 | | |
355 | 38 | case 2: |
356 | 38 | poBand->SetDescription("Slope"); |
357 | | |
358 | 38 | nTemp = CPL_LSBUINT16PTR(poDS->pachHeader + 4226); |
359 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
360 | 38 | poBand->SetMetadataItem("SLOPE_UNIT", szTemp); |
361 | | |
362 | 38 | if (nTemp == 0) |
363 | 18 | poBand->SetMetadataItem("SLOPE_UNIT_NAME", "Degrees"); |
364 | 38 | if (nTemp == 1) |
365 | 2 | poBand->SetMetadataItem("SLOPE_UNIT_NAME", "Percent"); |
366 | | |
367 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 456); |
368 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
369 | 38 | poBand->SetMetadataItem("SLOPE_MIN", szTemp); |
370 | | |
371 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 460); |
372 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
373 | 38 | poBand->SetMetadataItem("SLOPE_MAX", szTemp); |
374 | | |
375 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 464); |
376 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
377 | 38 | poBand->SetMetadataItem("SLOPE_NUM_CLASSES", szTemp); |
378 | | |
379 | 38 | *(poDS->pachHeader + 4500 + 255) = '\0'; |
380 | 38 | poBand->SetMetadataItem("SLOPE_FILE", poDS->pachHeader + 4500); |
381 | | |
382 | 38 | break; |
383 | | |
384 | 38 | case 3: |
385 | 38 | poBand->SetDescription("Aspect"); |
386 | | |
387 | 38 | nTemp = CPL_LSBUINT16PTR(poDS->pachHeader + 4228); |
388 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
389 | 38 | poBand->SetMetadataItem("ASPECT_UNIT", szTemp); |
390 | | |
391 | 38 | if (nTemp == 0) |
392 | 11 | poBand->SetMetadataItem("ASPECT_UNIT_NAME", |
393 | 11 | "Grass categories"); |
394 | 38 | if (nTemp == 1) |
395 | 3 | poBand->SetMetadataItem("ASPECT_UNIT_NAME", |
396 | 3 | "Grass degrees"); |
397 | 38 | if (nTemp == 2) |
398 | 1 | poBand->SetMetadataItem("ASPECT_UNIT_NAME", |
399 | 1 | "Azimuth degrees"); |
400 | | |
401 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 868); |
402 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
403 | 38 | poBand->SetMetadataItem("ASPECT_MIN", szTemp); |
404 | | |
405 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 872); |
406 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
407 | 38 | poBand->SetMetadataItem("ASPECT_MAX", szTemp); |
408 | | |
409 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 876); |
410 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
411 | 38 | poBand->SetMetadataItem("ASPECT_NUM_CLASSES", szTemp); |
412 | | |
413 | 38 | *(poDS->pachHeader + 4756 + 255) = '\0'; |
414 | 38 | poBand->SetMetadataItem("ASPECT_FILE", poDS->pachHeader + 4756); |
415 | | |
416 | 38 | break; |
417 | | |
418 | 38 | case 4: |
419 | 38 | { |
420 | 38 | poBand->SetDescription("Fuel models"); |
421 | | |
422 | 38 | nTemp = CPL_LSBUINT16PTR(poDS->pachHeader + 4230); |
423 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
424 | 38 | poBand->SetMetadataItem("FUEL_MODEL_OPTION", szTemp); |
425 | | |
426 | 38 | if (nTemp == 0) |
427 | 10 | poBand->SetMetadataItem( |
428 | 10 | "FUEL_MODEL_OPTION_DESC", |
429 | 10 | "no custom models AND no conversion file needed"); |
430 | 38 | if (nTemp == 1) |
431 | 2 | poBand->SetMetadataItem( |
432 | 2 | "FUEL_MODEL_OPTION_DESC", |
433 | 2 | "custom models BUT no conversion file needed"); |
434 | 38 | if (nTemp == 2) |
435 | 1 | poBand->SetMetadataItem( |
436 | 1 | "FUEL_MODEL_OPTION_DESC", |
437 | 1 | "no custom models BUT conversion file needed"); |
438 | 38 | if (nTemp == 3) |
439 | 1 | poBand->SetMetadataItem( |
440 | 1 | "FUEL_MODEL_OPTION_DESC", |
441 | 1 | "custom models AND conversion file needed"); |
442 | | |
443 | 38 | const int nMinFM = CPL_LSBSINT32PTR(poDS->pachHeader + 1280); |
444 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nMinFM); |
445 | 38 | poBand->SetMetadataItem("FUEL_MODEL_MIN", szTemp); |
446 | | |
447 | 38 | const int nMaxFM = CPL_LSBSINT32PTR(poDS->pachHeader + 1284); |
448 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nMaxFM); |
449 | 38 | poBand->SetMetadataItem("FUEL_MODEL_MAX", szTemp); |
450 | | |
451 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 1288); |
452 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
453 | 38 | poBand->SetMetadataItem("FUEL_MODEL_NUM_CLASSES", szTemp); |
454 | | |
455 | 38 | std::string osValues; |
456 | 38 | if (nTemp > 0 && nTemp <= 100) |
457 | 12 | { |
458 | 449 | for (int i = 0; i <= nTemp; i++) |
459 | 437 | { |
460 | 437 | const int nTemp2 = CPL_LSBSINT32PTR(poDS->pachHeader + |
461 | 437 | (1292 + (i * 4))); |
462 | 437 | if (nTemp2 >= nMinFM && nTemp2 <= nMaxFM) |
463 | 45 | { |
464 | 45 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp2); |
465 | 45 | if (!osValues.empty()) |
466 | 40 | osValues += ','; |
467 | 45 | osValues += szTemp; |
468 | 45 | } |
469 | 437 | } |
470 | 12 | } |
471 | 38 | poBand->SetMetadataItem("FUEL_MODEL_VALUES", osValues.c_str()); |
472 | | |
473 | 38 | *(poDS->pachHeader + 5012 + 255) = '\0'; |
474 | 38 | poBand->SetMetadataItem("FUEL_MODEL_FILE", |
475 | 38 | poDS->pachHeader + 5012); |
476 | | |
477 | 38 | break; |
478 | 0 | } |
479 | 38 | case 5: |
480 | 38 | poBand->SetDescription("Canopy cover"); |
481 | | |
482 | 38 | nTemp = CPL_LSBUINT16PTR(poDS->pachHeader + 4232); |
483 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
484 | 38 | poBand->SetMetadataItem("CANOPY_COV_UNIT", szTemp); |
485 | | |
486 | 38 | if (nTemp == 0) |
487 | 10 | poBand->SetMetadataItem("CANOPY_COV_UNIT_NAME", |
488 | 10 | "Categories (0-4)"); |
489 | 38 | if (nTemp == 1) |
490 | 3 | poBand->SetMetadataItem("CANOPY_COV_UNIT_NAME", "Percent"); |
491 | | |
492 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 1692); |
493 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
494 | 38 | poBand->SetMetadataItem("CANOPY_COV_MIN", szTemp); |
495 | | |
496 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 1696); |
497 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
498 | 38 | poBand->SetMetadataItem("CANOPY_COV_MAX", szTemp); |
499 | | |
500 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 1700); |
501 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
502 | 38 | poBand->SetMetadataItem("CANOPY_COV_NUM_CLASSES", szTemp); |
503 | | |
504 | 38 | *(poDS->pachHeader + 5268 + 255) = '\0'; |
505 | 38 | poBand->SetMetadataItem("CANOPY_COV_FILE", |
506 | 38 | poDS->pachHeader + 5268); |
507 | | |
508 | 38 | break; |
509 | | |
510 | 38 | case 6: |
511 | 38 | if (bHaveCrownFuels) |
512 | 38 | { |
513 | 38 | poBand->SetDescription("Canopy height"); |
514 | | |
515 | 38 | nTemp = CPL_LSBUINT16PTR(poDS->pachHeader + 4234); |
516 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
517 | 38 | poBand->SetMetadataItem("CANOPY_HT_UNIT", szTemp); |
518 | | |
519 | 38 | if (nTemp == 1) |
520 | 1 | poBand->SetMetadataItem("CANOPY_HT_UNIT_NAME", |
521 | 1 | "Meters"); |
522 | 38 | if (nTemp == 2) |
523 | 1 | poBand->SetMetadataItem("CANOPY_HT_UNIT_NAME", "Feet"); |
524 | 38 | if (nTemp == 3) |
525 | 2 | poBand->SetMetadataItem("CANOPY_HT_UNIT_NAME", |
526 | 2 | "Meters x 10"); |
527 | 38 | if (nTemp == 4) |
528 | 1 | poBand->SetMetadataItem("CANOPY_HT_UNIT_NAME", |
529 | 1 | "Feet x 10"); |
530 | | |
531 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 2104); |
532 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
533 | 38 | poBand->SetMetadataItem("CANOPY_HT_MIN", szTemp); |
534 | | |
535 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 2108); |
536 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
537 | 38 | poBand->SetMetadataItem("CANOPY_HT_MAX", szTemp); |
538 | | |
539 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 2112); |
540 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
541 | 38 | poBand->SetMetadataItem("CANOPY_HT_NUM_CLASSES", szTemp); |
542 | | |
543 | 38 | *(poDS->pachHeader + 5524 + 255) = '\0'; |
544 | 38 | poBand->SetMetadataItem("CANOPY_HT_FILE", |
545 | 38 | poDS->pachHeader + 5524); |
546 | 38 | } |
547 | 0 | else |
548 | 0 | { |
549 | 0 | poBand->SetDescription("Duff"); |
550 | |
|
551 | 0 | nTemp = CPL_LSBUINT16PTR(poDS->pachHeader + 4240); |
552 | 0 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
553 | 0 | poBand->SetMetadataItem("DUFF_UNIT", szTemp); |
554 | |
|
555 | 0 | if (nTemp == 1) |
556 | 0 | poBand->SetMetadataItem("DUFF_UNIT_NAME", "Mg/ha"); |
557 | 0 | if (nTemp == 2) |
558 | 0 | poBand->SetMetadataItem("DUFF_UNIT_NAME", "t/ac"); |
559 | |
|
560 | 0 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 3340); |
561 | 0 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
562 | 0 | poBand->SetMetadataItem("DUFF_MIN", szTemp); |
563 | |
|
564 | 0 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 3344); |
565 | 0 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
566 | 0 | poBand->SetMetadataItem("DUFF_MAX", szTemp); |
567 | |
|
568 | 0 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 3348); |
569 | 0 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
570 | 0 | poBand->SetMetadataItem("DUFF_NUM_CLASSES", szTemp); |
571 | |
|
572 | 0 | *(poDS->pachHeader + 6292 + 255) = '\0'; |
573 | 0 | poBand->SetMetadataItem("DUFF_FILE", |
574 | 0 | poDS->pachHeader + 6292); |
575 | 0 | } |
576 | 38 | break; |
577 | | |
578 | 38 | case 7: |
579 | 38 | if (bHaveCrownFuels) |
580 | 38 | { |
581 | 38 | poBand->SetDescription("Canopy base height"); |
582 | | |
583 | 38 | nTemp = CPL_LSBUINT16PTR(poDS->pachHeader + 4236); |
584 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
585 | 38 | poBand->SetMetadataItem("CBH_UNIT", szTemp); |
586 | | |
587 | 38 | if (nTemp == 1) |
588 | 3 | poBand->SetMetadataItem("CBH_UNIT_NAME", "Meters"); |
589 | 38 | if (nTemp == 2) |
590 | 1 | poBand->SetMetadataItem("CBH_UNIT_NAME", "Feet"); |
591 | 38 | if (nTemp == 3) |
592 | 1 | poBand->SetMetadataItem("CBH_UNIT_NAME", "Meters x 10"); |
593 | 38 | if (nTemp == 4) |
594 | 1 | poBand->SetMetadataItem("CBH_UNIT_NAME", "Feet x 10"); |
595 | | |
596 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 2516); |
597 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
598 | 38 | poBand->SetMetadataItem("CBH_MIN", szTemp); |
599 | | |
600 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 2520); |
601 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
602 | 38 | poBand->SetMetadataItem("CBH_MAX", szTemp); |
603 | | |
604 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 2524); |
605 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
606 | 38 | poBand->SetMetadataItem("CBH_NUM_CLASSES", szTemp); |
607 | | |
608 | 38 | *(poDS->pachHeader + 5780 + 255) = '\0'; |
609 | 38 | poBand->SetMetadataItem("CBH_FILE", |
610 | 38 | poDS->pachHeader + 5780); |
611 | 38 | } |
612 | 0 | else |
613 | 0 | { |
614 | 0 | poBand->SetDescription("Coarse woody debris"); |
615 | |
|
616 | 0 | nTemp = CPL_LSBUINT16PTR(poDS->pachHeader + 4242); |
617 | 0 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
618 | 0 | poBand->SetMetadataItem("CWD_OPTION", szTemp); |
619 | | |
620 | | // if ( nTemp == 1 ) |
621 | | // poBand->SetMetadataItem( "CWD_UNIT_DESC", "?" ); |
622 | | // if ( nTemp == 2 ) |
623 | | // poBand->SetMetadataItem( "CWD_UNIT_DESC", "?" ); |
624 | |
|
625 | 0 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 3752); |
626 | 0 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
627 | 0 | poBand->SetMetadataItem("CWD_MIN", szTemp); |
628 | |
|
629 | 0 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 3756); |
630 | 0 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
631 | 0 | poBand->SetMetadataItem("CWD_MAX", szTemp); |
632 | |
|
633 | 0 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 3760); |
634 | 0 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
635 | 0 | poBand->SetMetadataItem("CWD_NUM_CLASSES", szTemp); |
636 | |
|
637 | 0 | *(poDS->pachHeader + 6548 + 255) = '\0'; |
638 | 0 | poBand->SetMetadataItem("CWD_FILE", |
639 | 0 | poDS->pachHeader + 6548); |
640 | 0 | } |
641 | 38 | break; |
642 | | |
643 | 38 | case 8: |
644 | 38 | poBand->SetDescription("Canopy bulk density"); |
645 | | |
646 | 38 | nTemp = CPL_LSBUINT16PTR(poDS->pachHeader + 4238); |
647 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
648 | 38 | poBand->SetMetadataItem("CBD_UNIT", szTemp); |
649 | | |
650 | 38 | if (nTemp == 1) |
651 | 1 | poBand->SetMetadataItem("CBD_UNIT_NAME", "kg/m^3"); |
652 | 38 | if (nTemp == 2) |
653 | 0 | poBand->SetMetadataItem("CBD_UNIT_NAME", "lb/ft^3"); |
654 | 38 | if (nTemp == 3) |
655 | 0 | poBand->SetMetadataItem("CBD_UNIT_NAME", "kg/m^3 x 100"); |
656 | 38 | if (nTemp == 4) |
657 | 0 | poBand->SetMetadataItem("CBD_UNIT_NAME", "lb/ft^3 x 1000"); |
658 | | |
659 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 2928); |
660 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
661 | 38 | poBand->SetMetadataItem("CBD_MIN", szTemp); |
662 | | |
663 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 2932); |
664 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
665 | 38 | poBand->SetMetadataItem("CBD_MAX", szTemp); |
666 | | |
667 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 2936); |
668 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
669 | 38 | poBand->SetMetadataItem("CBD_NUM_CLASSES", szTemp); |
670 | | |
671 | 38 | *(poDS->pachHeader + 6036 + 255) = '\0'; |
672 | 38 | poBand->SetMetadataItem("CBD_FILE", poDS->pachHeader + 6036); |
673 | | |
674 | 38 | break; |
675 | | |
676 | 38 | case 9: |
677 | 38 | poBand->SetDescription("Duff"); |
678 | | |
679 | 38 | nTemp = CPL_LSBUINT16PTR(poDS->pachHeader + 4240); |
680 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
681 | 38 | poBand->SetMetadataItem("DUFF_UNIT", szTemp); |
682 | | |
683 | 38 | if (nTemp == 1) |
684 | 3 | poBand->SetMetadataItem("DUFF_UNIT_NAME", "Mg/ha"); |
685 | 38 | if (nTemp == 2) |
686 | 1 | poBand->SetMetadataItem("DUFF_UNIT_NAME", "t/ac"); |
687 | | |
688 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 3340); |
689 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
690 | 38 | poBand->SetMetadataItem("DUFF_MIN", szTemp); |
691 | | |
692 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 3344); |
693 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
694 | 38 | poBand->SetMetadataItem("DUFF_MAX", szTemp); |
695 | | |
696 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 3348); |
697 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
698 | 38 | poBand->SetMetadataItem("DUFF_NUM_CLASSES", szTemp); |
699 | | |
700 | 38 | *(poDS->pachHeader + 6292 + 255) = '\0'; |
701 | 38 | poBand->SetMetadataItem("DUFF_FILE", poDS->pachHeader + 6292); |
702 | | |
703 | 38 | break; |
704 | | |
705 | 38 | case 10: |
706 | 38 | poBand->SetDescription("Coarse woody debris"); |
707 | | |
708 | 38 | nTemp = CPL_LSBUINT16PTR(poDS->pachHeader + 4242); |
709 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
710 | 38 | poBand->SetMetadataItem("CWD_OPTION", szTemp); |
711 | | |
712 | | // if ( nTemp == 1 ) |
713 | | // poBand->SetMetadataItem( "CWD_UNIT_DESC", "?" ); |
714 | | // if ( nTemp == 2 ) |
715 | | // poBand->SetMetadataItem( "CWD_UNIT_DESC", "?" ); |
716 | | |
717 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 3752); |
718 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
719 | 38 | poBand->SetMetadataItem("CWD_MIN", szTemp); |
720 | | |
721 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 3756); |
722 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
723 | 38 | poBand->SetMetadataItem("CWD_MAX", szTemp); |
724 | | |
725 | 38 | nTemp = CPL_LSBSINT32PTR(poDS->pachHeader + 3760); |
726 | 38 | snprintf(szTemp, sizeof(szTemp), "%d", nTemp); |
727 | 38 | poBand->SetMetadataItem("CWD_NUM_CLASSES", szTemp); |
728 | | |
729 | 38 | *(poDS->pachHeader + 6548 + 255) = '\0'; |
730 | 38 | poBand->SetMetadataItem("CWD_FILE", poDS->pachHeader + 6548); |
731 | | |
732 | 38 | break; |
733 | 380 | } |
734 | | |
735 | 380 | poDS->SetBand(iBand, std::move(poBand)); |
736 | 380 | } |
737 | | |
738 | | /* -------------------------------------------------------------------- */ |
739 | | /* Try to read projection file. */ |
740 | | /* -------------------------------------------------------------------- */ |
741 | 38 | char *const pszDirname = |
742 | 38 | CPLStrdup(CPLGetPathSafe(poOpenInfo->pszFilename).c_str()); |
743 | 38 | char *const pszBasename = |
744 | 38 | CPLStrdup(CPLGetBasenameSafe(poOpenInfo->pszFilename).c_str()); |
745 | | |
746 | 38 | poDS->osPrjFilename = CPLFormFilenameSafe(pszDirname, pszBasename, "prj"); |
747 | 38 | VSIStatBufL sStatBuf; |
748 | 38 | int nRet = VSIStatL(poDS->osPrjFilename, &sStatBuf); |
749 | | |
750 | 38 | if (nRet != 0 && VSIIsCaseSensitiveFS(poDS->osPrjFilename)) |
751 | 38 | { |
752 | 38 | poDS->osPrjFilename = |
753 | 38 | CPLFormFilenameSafe(pszDirname, pszBasename, "PRJ"); |
754 | 38 | nRet = VSIStatL(poDS->osPrjFilename, &sStatBuf); |
755 | 38 | } |
756 | | |
757 | 38 | if (nRet == 0) |
758 | 0 | { |
759 | 0 | char **papszPrj = CSLLoad(poDS->osPrjFilename); |
760 | |
|
761 | 0 | CPLDebug("LCP", "Loaded SRS from %s", poDS->osPrjFilename.c_str()); |
762 | |
|
763 | 0 | poDS->m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
764 | 0 | if (poDS->m_oSRS.importFromESRI(papszPrj) != OGRERR_NONE) |
765 | 0 | { |
766 | 0 | poDS->m_oSRS.Clear(); |
767 | 0 | } |
768 | |
|
769 | 0 | CSLDestroy(papszPrj); |
770 | 0 | } |
771 | | |
772 | 38 | CPLFree(pszDirname); |
773 | 38 | CPLFree(pszBasename); |
774 | | |
775 | | /* -------------------------------------------------------------------- */ |
776 | | /* Initialize any PAM information. */ |
777 | | /* -------------------------------------------------------------------- */ |
778 | 38 | poDS->SetDescription(poOpenInfo->pszFilename); |
779 | 38 | poDS->TryLoadXML(); |
780 | | |
781 | | /* -------------------------------------------------------------------- */ |
782 | | /* Check for external overviews. */ |
783 | | /* -------------------------------------------------------------------- */ |
784 | 38 | poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename, |
785 | 38 | poOpenInfo->GetSiblingFiles()); |
786 | | |
787 | 38 | return poDS.release(); |
788 | 38 | } |
789 | | |
790 | | /************************************************************************/ |
791 | | /* ClassifyBandData() */ |
792 | | /* Classify a band and store 99 or fewer unique values. If there are */ |
793 | | /* more than 99 unique values, then set pnNumClasses to -1 as a flag */ |
794 | | /* that represents this. These are legacy values in the header, and */ |
795 | | /* while we should never deprecate them, we could possibly not */ |
796 | | /* calculate them by default. */ |
797 | | /************************************************************************/ |
798 | | |
799 | | CPLErr LCPDataset::ClassifyBandData(GDALRasterBand *poBand, GInt32 &nNumClasses, |
800 | | GInt32 *panClasses) |
801 | 0 | { |
802 | 0 | CPLAssert(poBand); |
803 | 0 | CPLAssert(panClasses); |
804 | |
|
805 | 0 | const int nXSize = poBand->GetXSize(); |
806 | 0 | const int nYSize = poBand->GetYSize(); |
807 | |
|
808 | 0 | GInt16 *panValues = |
809 | 0 | static_cast<GInt16 *>(CPLMalloc(sizeof(GInt16) * nXSize)); |
810 | 0 | constexpr int MIN_VAL = std::numeric_limits<GInt16>::min(); |
811 | 0 | constexpr int MAX_VAL = std::numeric_limits<GInt16>::max(); |
812 | 0 | constexpr int RANGE_VAL = MAX_VAL - MIN_VAL + 1; |
813 | 0 | GByte *pabyFlags = static_cast<GByte *>(CPLCalloc(1, RANGE_VAL)); |
814 | | |
815 | | /* so that values in [-32768,32767] are mapped to [0,65535] in pabyFlags */ |
816 | 0 | constexpr int OFFSET = -MIN_VAL; |
817 | |
|
818 | 0 | int nFound = 0; |
819 | 0 | bool bTooMany = false; |
820 | 0 | CPLErr eErr = CE_None; |
821 | 0 | for (int iLine = 0; iLine < nYSize; iLine++) |
822 | 0 | { |
823 | 0 | eErr = poBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, panValues, nXSize, |
824 | 0 | 1, GDT_Int16, 0, 0, nullptr); |
825 | 0 | if (eErr != CE_None) |
826 | 0 | break; |
827 | 0 | for (int iPixel = 0; iPixel < nXSize; iPixel++) |
828 | 0 | { |
829 | 0 | if (panValues[iPixel] == -9999) |
830 | 0 | { |
831 | 0 | continue; |
832 | 0 | } |
833 | 0 | if (nFound == LCP_MAX_CLASSES) |
834 | 0 | { |
835 | 0 | CPLDebug("LCP", |
836 | 0 | "Found more that %d unique values in " |
837 | 0 | "band %d. Not 'classifying' the data.", |
838 | 0 | LCP_MAX_CLASSES - 1, poBand->GetBand()); |
839 | 0 | nFound = -1; |
840 | 0 | bTooMany = true; |
841 | 0 | break; |
842 | 0 | } |
843 | 0 | if (pabyFlags[panValues[iPixel] + OFFSET] == 0) |
844 | 0 | { |
845 | 0 | pabyFlags[panValues[iPixel] + OFFSET] = 1; |
846 | 0 | nFound++; |
847 | 0 | } |
848 | 0 | } |
849 | 0 | if (bTooMany) |
850 | 0 | break; |
851 | 0 | } |
852 | 0 | if (!bTooMany) |
853 | 0 | { |
854 | | // The classes are always padded with a leading 0. This was for aligning |
855 | | // offsets, or making it a 1-based array instead of 0-based. |
856 | 0 | panClasses[0] = 0; |
857 | 0 | for (int j = 0, nIndex = 1; j < RANGE_VAL; j++) |
858 | 0 | { |
859 | 0 | if (pabyFlags[j] == 1) |
860 | 0 | { |
861 | 0 | panClasses[nIndex++] = j; |
862 | 0 | } |
863 | 0 | } |
864 | 0 | } |
865 | 0 | nNumClasses = nFound; |
866 | 0 | CPLFree(pabyFlags); |
867 | 0 | CPLFree(panValues); |
868 | |
|
869 | 0 | return eErr; |
870 | 0 | } |
871 | | |
872 | | /************************************************************************/ |
873 | | /* CreateCopy() */ |
874 | | /************************************************************************/ |
875 | | |
876 | | GDALDataset *LCPDataset::CreateCopy(const char *pszFilename, |
877 | | GDALDataset *poSrcDS, int bStrict, |
878 | | char **papszOptions, |
879 | | GDALProgressFunc pfnProgress, |
880 | | void *pProgressData) |
881 | | |
882 | 0 | { |
883 | | /* -------------------------------------------------------------------- */ |
884 | | /* Verify input options. */ |
885 | | /* -------------------------------------------------------------------- */ |
886 | 0 | const int nBands = poSrcDS->GetRasterCount(); |
887 | 0 | if (nBands != 5 && nBands != 7 && nBands != 8 && nBands != 10) |
888 | 0 | { |
889 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
890 | 0 | "LCP driver doesn't support %d bands. Must be 5, 7, 8 " |
891 | 0 | "or 10 bands.", |
892 | 0 | nBands); |
893 | 0 | return nullptr; |
894 | 0 | } |
895 | | |
896 | 0 | GDALDataType eType = poSrcDS->GetRasterBand(1)->GetRasterDataType(); |
897 | 0 | if (eType != GDT_Int16 && bStrict) |
898 | 0 | { |
899 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
900 | 0 | "LCP only supports 16-bit signed integer data types."); |
901 | 0 | return nullptr; |
902 | 0 | } |
903 | 0 | else if (eType != GDT_Int16) |
904 | 0 | { |
905 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
906 | 0 | "Setting data type to 16-bit integer."); |
907 | 0 | } |
908 | | |
909 | | /* -------------------------------------------------------------------- */ |
910 | | /* What schema do we have (ground/crown fuels) */ |
911 | | /* -------------------------------------------------------------------- */ |
912 | | |
913 | 0 | const bool bHaveCrownFuels = nBands == 8 || nBands == 10; |
914 | 0 | const bool bHaveGroundFuels = nBands == 7 || nBands == 10; |
915 | | |
916 | | // Since units are 'configurable', we should check for user |
917 | | // defined units. This is a bit cumbersome, but the user should |
918 | | // be allowed to specify none to get default units/options. Use |
919 | | // default units every chance we get. |
920 | 0 | GInt16 panMetadata[LCP_MAX_BANDS] = { |
921 | 0 | 0, // 0 ELEVATION_UNIT |
922 | 0 | 0, // 1 SLOPE_UNIT |
923 | 0 | 2, // 2 ASPECT_UNIT |
924 | 0 | 0, // 3 FUEL_MODEL_OPTION |
925 | 0 | 1, // 4 CANOPY_COV_UNIT |
926 | 0 | 3, // 5 CANOPY_HT_UNIT |
927 | 0 | 3, // 6 CBH_UNIT |
928 | 0 | 3, // 7 CBD_UNIT |
929 | 0 | 1, // 8 DUFF_UNIT |
930 | 0 | 0, // 9 CWD_OPTION |
931 | 0 | }; |
932 | | |
933 | | // Check the units/options for user overrides. |
934 | 0 | const char *pszTemp = |
935 | 0 | CSLFetchNameValueDef(papszOptions, "ELEVATION_UNIT", "METERS"); |
936 | 0 | if (STARTS_WITH_CI(pszTemp, "METER")) |
937 | 0 | { |
938 | 0 | panMetadata[0] = 0; |
939 | 0 | } |
940 | 0 | else if (EQUAL(pszTemp, "FEET") || EQUAL(pszTemp, "FOOT")) |
941 | 0 | { |
942 | 0 | panMetadata[0] = 1; |
943 | 0 | } |
944 | 0 | else |
945 | 0 | { |
946 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
947 | 0 | "Invalid value (%s) for ELEVATION_UNIT.", pszTemp); |
948 | 0 | return nullptr; |
949 | 0 | } |
950 | | |
951 | 0 | pszTemp = CSLFetchNameValueDef(papszOptions, "SLOPE_UNIT", "DEGREES"); |
952 | 0 | if (EQUAL(pszTemp, "DEGREES")) |
953 | 0 | { |
954 | 0 | panMetadata[1] = 0; |
955 | 0 | } |
956 | 0 | else if (EQUAL(pszTemp, "PERCENT")) |
957 | 0 | { |
958 | 0 | panMetadata[1] = 1; |
959 | 0 | } |
960 | 0 | else |
961 | 0 | { |
962 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
963 | 0 | "Invalid value (%s) for SLOPE_UNIT.", pszTemp); |
964 | 0 | return nullptr; |
965 | 0 | } |
966 | | |
967 | 0 | pszTemp = |
968 | 0 | CSLFetchNameValueDef(papszOptions, "ASPECT_UNIT", "AZIMUTH_DEGREES"); |
969 | 0 | if (EQUAL(pszTemp, "GRASS_CATEGORIES")) |
970 | 0 | { |
971 | 0 | panMetadata[2] = 0; |
972 | 0 | } |
973 | 0 | else if (EQUAL(pszTemp, "GRASS_DEGREES")) |
974 | 0 | { |
975 | 0 | panMetadata[2] = 1; |
976 | 0 | } |
977 | 0 | else if (EQUAL(pszTemp, "AZIMUTH_DEGREES")) |
978 | 0 | { |
979 | 0 | panMetadata[2] = 2; |
980 | 0 | } |
981 | 0 | else |
982 | 0 | { |
983 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
984 | 0 | "Invalid value (%s) for ASPECT_UNIT.", pszTemp); |
985 | 0 | return nullptr; |
986 | 0 | } |
987 | | |
988 | 0 | pszTemp = CSLFetchNameValueDef(papszOptions, "FUEL_MODEL_OPTION", |
989 | 0 | "NO_CUSTOM_AND_NO_FILE"); |
990 | 0 | if (EQUAL(pszTemp, "NO_CUSTOM_AND_NO_FILE")) |
991 | 0 | { |
992 | 0 | panMetadata[3] = 0; |
993 | 0 | } |
994 | 0 | else if (EQUAL(pszTemp, "CUSTOM_AND_NO_FILE")) |
995 | 0 | { |
996 | 0 | panMetadata[3] = 1; |
997 | 0 | } |
998 | 0 | else if (EQUAL(pszTemp, "NO_CUSTOM_AND_FILE")) |
999 | 0 | { |
1000 | 0 | panMetadata[3] = 2; |
1001 | 0 | } |
1002 | 0 | else if (EQUAL(pszTemp, "CUSTOM_AND_FILE")) |
1003 | 0 | { |
1004 | 0 | panMetadata[3] = 3; |
1005 | 0 | } |
1006 | 0 | else |
1007 | 0 | { |
1008 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1009 | 0 | "Invalid value (%s) for FUEL_MODEL_OPTION.", pszTemp); |
1010 | 0 | return nullptr; |
1011 | 0 | } |
1012 | | |
1013 | 0 | pszTemp = CSLFetchNameValueDef(papszOptions, "CANOPY_COV_UNIT", "PERCENT"); |
1014 | 0 | if (EQUAL(pszTemp, "CATEGORIES")) |
1015 | 0 | { |
1016 | 0 | panMetadata[4] = 0; |
1017 | 0 | } |
1018 | 0 | else if (EQUAL(pszTemp, "PERCENT")) |
1019 | 0 | { |
1020 | 0 | panMetadata[4] = 1; |
1021 | 0 | } |
1022 | 0 | else |
1023 | 0 | { |
1024 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1025 | 0 | "Invalid value (%s) for CANOPY_COV_UNIT.", pszTemp); |
1026 | 0 | return nullptr; |
1027 | 0 | } |
1028 | | |
1029 | 0 | if (bHaveCrownFuels) |
1030 | 0 | { |
1031 | 0 | pszTemp = |
1032 | 0 | CSLFetchNameValueDef(papszOptions, "CANOPY_HT_UNIT", "METERS_X_10"); |
1033 | 0 | if (EQUAL(pszTemp, "METERS") || EQUAL(pszTemp, "METER")) |
1034 | 0 | { |
1035 | 0 | panMetadata[5] = 1; |
1036 | 0 | } |
1037 | 0 | else if (EQUAL(pszTemp, "FEET") || EQUAL(pszTemp, "FOOT")) |
1038 | 0 | { |
1039 | 0 | panMetadata[5] = 2; |
1040 | 0 | } |
1041 | 0 | else if (EQUAL(pszTemp, "METERS_X_10") || EQUAL(pszTemp, "METER_X_10")) |
1042 | 0 | { |
1043 | 0 | panMetadata[5] = 3; |
1044 | 0 | } |
1045 | 0 | else if (EQUAL(pszTemp, "FEET_X_10") || EQUAL(pszTemp, "FOOT_X_10")) |
1046 | 0 | { |
1047 | 0 | panMetadata[5] = 4; |
1048 | 0 | } |
1049 | 0 | else |
1050 | 0 | { |
1051 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1052 | 0 | "Invalid value (%s) for CANOPY_HT_UNIT.", pszTemp); |
1053 | 0 | return nullptr; |
1054 | 0 | } |
1055 | | |
1056 | 0 | pszTemp = CSLFetchNameValueDef(papszOptions, "CBH_UNIT", "METERS_X_10"); |
1057 | 0 | if (EQUAL(pszTemp, "METERS") || EQUAL(pszTemp, "METER")) |
1058 | 0 | { |
1059 | 0 | panMetadata[6] = 1; |
1060 | 0 | } |
1061 | 0 | else if (EQUAL(pszTemp, "FEET") || EQUAL(pszTemp, "FOOT")) |
1062 | 0 | { |
1063 | 0 | panMetadata[6] = 2; |
1064 | 0 | } |
1065 | 0 | else if (EQUAL(pszTemp, "METERS_X_10") || EQUAL(pszTemp, "METER_X_10")) |
1066 | 0 | { |
1067 | 0 | panMetadata[6] = 3; |
1068 | 0 | } |
1069 | 0 | else if (EQUAL(pszTemp, "FEET_X_10") || EQUAL(pszTemp, "FOOT_X_10")) |
1070 | 0 | { |
1071 | 0 | panMetadata[6] = 4; |
1072 | 0 | } |
1073 | 0 | else |
1074 | 0 | { |
1075 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1076 | 0 | "Invalid value (%s) for CBH_UNIT.", pszTemp); |
1077 | 0 | return nullptr; |
1078 | 0 | } |
1079 | | |
1080 | 0 | pszTemp = CSLFetchNameValueDef(papszOptions, "CBD_UNIT", |
1081 | 0 | "KG_PER_CUBIC_METER_X_100"); |
1082 | 0 | if (EQUAL(pszTemp, "KG_PER_CUBIC_METER")) |
1083 | 0 | { |
1084 | 0 | panMetadata[7] = 1; |
1085 | 0 | } |
1086 | 0 | else if (EQUAL(pszTemp, "POUND_PER_CUBIC_FOOT")) |
1087 | 0 | { |
1088 | 0 | panMetadata[7] = 2; |
1089 | 0 | } |
1090 | 0 | else if (EQUAL(pszTemp, "KG_PER_CUBIC_METER_X_100")) |
1091 | 0 | { |
1092 | 0 | panMetadata[7] = 3; |
1093 | 0 | } |
1094 | 0 | else if (EQUAL(pszTemp, "POUND_PER_CUBIC_FOOT_X_1000")) |
1095 | 0 | { |
1096 | 0 | panMetadata[7] = 4; |
1097 | 0 | } |
1098 | 0 | else |
1099 | 0 | { |
1100 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1101 | 0 | "Invalid value (%s) for CBD_UNIT.", pszTemp); |
1102 | 0 | return nullptr; |
1103 | 0 | } |
1104 | 0 | } |
1105 | | |
1106 | 0 | if (bHaveGroundFuels) |
1107 | 0 | { |
1108 | 0 | pszTemp = CSLFetchNameValueDef(papszOptions, "DUFF_UNIT", |
1109 | 0 | "MG_PER_HECTARE_X_10"); |
1110 | 0 | if (EQUAL(pszTemp, "MG_PER_HECTARE_X_10")) |
1111 | 0 | { |
1112 | 0 | panMetadata[8] = 1; |
1113 | 0 | } |
1114 | 0 | else if (EQUAL(pszTemp, "TONS_PER_ACRE_X_10")) |
1115 | 0 | { |
1116 | 0 | panMetadata[8] = 2; |
1117 | 0 | } |
1118 | 0 | else |
1119 | 0 | { |
1120 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1121 | 0 | "Invalid value (%s) for DUFF_UNIT.", pszTemp); |
1122 | 0 | return nullptr; |
1123 | 0 | } |
1124 | | |
1125 | 0 | panMetadata[9] = 1; |
1126 | 0 | } |
1127 | | |
1128 | | // Calculate the stats for each band. The binary file carries along |
1129 | | // these metadata for display purposes(?). |
1130 | 0 | bool bCalculateStats = CPLFetchBool(papszOptions, "CALCULATE_STATS", true); |
1131 | |
|
1132 | 0 | const bool bClassifyData = |
1133 | 0 | CPLFetchBool(papszOptions, "CLASSIFY_DATA", true); |
1134 | | |
1135 | | // We should have stats if we classify, we'll get them anyway. |
1136 | 0 | if (bClassifyData && !bCalculateStats) |
1137 | 0 | { |
1138 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
1139 | 0 | "Ignoring request to not calculate statistics, " |
1140 | 0 | "because CLASSIFY_DATA was set to ON"); |
1141 | 0 | bCalculateStats = true; |
1142 | 0 | } |
1143 | |
|
1144 | 0 | pszTemp = CSLFetchNameValueDef(papszOptions, "LINEAR_UNIT", "SET_FROM_SRS"); |
1145 | 0 | int nLinearUnits = 0; |
1146 | 0 | bool bSetLinearUnits = false; |
1147 | 0 | if (EQUAL(pszTemp, "SET_FROM_SRS")) |
1148 | 0 | { |
1149 | 0 | bSetLinearUnits = true; |
1150 | 0 | } |
1151 | 0 | else if (STARTS_WITH_CI(pszTemp, "METER")) |
1152 | 0 | { |
1153 | 0 | nLinearUnits = 0; |
1154 | 0 | } |
1155 | 0 | else if (EQUAL(pszTemp, "FOOT") || EQUAL(pszTemp, "FEET")) |
1156 | 0 | { |
1157 | 0 | nLinearUnits = 1; |
1158 | 0 | } |
1159 | 0 | else if (STARTS_WITH_CI(pszTemp, "KILOMETER")) |
1160 | 0 | { |
1161 | 0 | nLinearUnits = 2; |
1162 | 0 | } |
1163 | 0 | bool bCalculateLatitude = true; |
1164 | 0 | int nLatitude = 0; |
1165 | 0 | if (CSLFetchNameValue(papszOptions, "LATITUDE") != nullptr) |
1166 | 0 | { |
1167 | 0 | bCalculateLatitude = false; |
1168 | 0 | nLatitude = atoi(CSLFetchNameValue(papszOptions, "LATITUDE")); |
1169 | 0 | if (nLatitude > 90 || nLatitude < -90) |
1170 | 0 | { |
1171 | 0 | CPLError(CE_Failure, CPLE_OpenFailed, |
1172 | 0 | "Invalid value (%d) for LATITUDE.", nLatitude); |
1173 | 0 | return nullptr; |
1174 | 0 | } |
1175 | 0 | } |
1176 | | |
1177 | | // If no latitude is supplied, attempt to extract the central latitude |
1178 | | // from the image. It must be set either manually or here, otherwise |
1179 | | // we fail. |
1180 | 0 | double adfSrcGeoTransform[6] = {0.0}; |
1181 | 0 | poSrcDS->GetGeoTransform(adfSrcGeoTransform); |
1182 | 0 | const OGRSpatialReference *poSrcSRS = poSrcDS->GetSpatialRef(); |
1183 | 0 | double dfLongitude = 0.0; |
1184 | 0 | double dfLatitude = 0.0; |
1185 | |
|
1186 | 0 | const int nYSize = poSrcDS->GetRasterYSize(); |
1187 | |
|
1188 | 0 | if (!bCalculateLatitude) |
1189 | 0 | { |
1190 | 0 | dfLatitude = nLatitude; |
1191 | 0 | } |
1192 | 0 | else if (poSrcSRS) |
1193 | 0 | { |
1194 | 0 | OGRSpatialReference oDstSRS; |
1195 | 0 | oDstSRS.importFromEPSG(4269); |
1196 | 0 | oDstSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
1197 | 0 | OGRCoordinateTransformation *poCT = |
1198 | 0 | reinterpret_cast<OGRCoordinateTransformation *>( |
1199 | 0 | OGRCreateCoordinateTransformation(poSrcSRS, &oDstSRS)); |
1200 | 0 | if (poCT != nullptr) |
1201 | 0 | { |
1202 | 0 | dfLatitude = |
1203 | 0 | adfSrcGeoTransform[3] + adfSrcGeoTransform[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 = adfSrcGeoTransform[0] + adfSrcGeoTransform[1] * nXSize; |
1396 | 0 | CPL_LSBPTR64(&dfLongitude); |
1397 | 0 | CPL_IGNORE_RET_VAL(VSIFWriteL(&dfLongitude, 8, 1, fp)); |
1398 | 0 | dfLongitude = adfSrcGeoTransform[0]; |
1399 | 0 | CPL_LSBPTR64(&dfLongitude); |
1400 | 0 | CPL_IGNORE_RET_VAL(VSIFWriteL(&dfLongitude, 8, 1, fp)); |
1401 | 0 | dfLatitude = adfSrcGeoTransform[3]; |
1402 | 0 | CPL_LSBPTR64(&dfLatitude); |
1403 | 0 | CPL_IGNORE_RET_VAL(VSIFWriteL(&dfLatitude, 8, 1, fp)); |
1404 | 0 | dfLatitude = adfSrcGeoTransform[3] + adfSrcGeoTransform[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 = adfSrcGeoTransform[0] + adfSrcGeoTransform[1] * nXSize; |
1477 | 0 | CPL_LSBPTR64(&dfTemp); |
1478 | 0 | CPL_IGNORE_RET_VAL(VSIFWriteL(&dfTemp, 8, 1, fp)); |
1479 | | // Min x. |
1480 | 0 | dfTemp = adfSrcGeoTransform[0]; |
1481 | 0 | CPL_LSBPTR64(&dfTemp); |
1482 | 0 | CPL_IGNORE_RET_VAL(VSIFWriteL(&dfTemp, 8, 1, fp)); |
1483 | | // Max y. |
1484 | 0 | dfTemp = adfSrcGeoTransform[3]; |
1485 | 0 | CPL_LSBPTR64(&dfTemp); |
1486 | 0 | CPL_IGNORE_RET_VAL(VSIFWriteL(&dfTemp, 8, 1, fp)); |
1487 | | // Min y. |
1488 | 0 | dfTemp = adfSrcGeoTransform[3] + adfSrcGeoTransform[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 = adfSrcGeoTransform[1]; |
1499 | 0 | CPL_LSBPTR64(&dfTemp); |
1500 | 0 | CPL_IGNORE_RET_VAL(VSIFWriteL(&dfTemp, 8, 1, fp)); |
1501 | | // Y resolution. |
1502 | 0 | dfTemp = fabs(adfSrcGeoTransform[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 | 0 | return static_cast<GDALDataset *>(GDALOpen(pszFilename, GA_ReadOnly)); |
1631 | 0 | } |
1632 | | |
1633 | | /************************************************************************/ |
1634 | | /* GDALRegister_LCP() */ |
1635 | | /************************************************************************/ |
1636 | | |
1637 | | void GDALRegister_LCP() |
1638 | | |
1639 | 2 | { |
1640 | 2 | if (GDALGetDriverByName("LCP") != nullptr) |
1641 | 0 | return; |
1642 | | |
1643 | 2 | GDALDriver *poDriver = new GDALDriver(); |
1644 | | |
1645 | 2 | poDriver->SetDescription("LCP"); |
1646 | 2 | poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES"); |
1647 | 2 | poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, |
1648 | 2 | "FARSITE v.4 Landscape File (.lcp)"); |
1649 | 2 | poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "lcp"); |
1650 | 2 | poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/lcp.html"); |
1651 | | |
1652 | 2 | poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES"); |
1653 | | |
1654 | 2 | poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES, "Int16"); |
1655 | 2 | poDriver->SetMetadataItem( |
1656 | 2 | GDAL_DMD_CREATIONOPTIONLIST, |
1657 | 2 | "<CreationOptionList>" |
1658 | 2 | " <Option name='ELEVATION_UNIT' type='string-select' " |
1659 | 2 | "default='METERS' description='Elevation units'>" |
1660 | 2 | " <Value>METERS</Value>" |
1661 | 2 | " <Value>FEET</Value>" |
1662 | 2 | " </Option>" |
1663 | 2 | " <Option name='SLOPE_UNIT' type='string-select' default='DEGREES' " |
1664 | 2 | "description='Slope units'>" |
1665 | 2 | " <Value>DEGREES</Value>" |
1666 | 2 | " <Value>PERCENT</Value>" |
1667 | 2 | " </Option>" |
1668 | 2 | " <Option name='ASPECT_UNIT' type='string-select' " |
1669 | 2 | "default='AZIMUTH_DEGREES'>" |
1670 | 2 | " <Value>GRASS_CATEGORIES</Value>" |
1671 | 2 | " <Value>AZIMUTH_DEGREES</Value>" |
1672 | 2 | " <Value>GRASS_DEGREES</Value>" |
1673 | 2 | " </Option>" |
1674 | 2 | " <Option name='FUEL_MODEL_OPTION' type='string-select' " |
1675 | 2 | "default='NO_CUSTOM_AND_NO_FILE'>" |
1676 | 2 | " <Value>NO_CUSTOM_AND_NO_FILE</Value>" |
1677 | 2 | " <Value>CUSTOM_AND_NO_FILE</Value>" |
1678 | 2 | " <Value>NO_CUSTOM_AND_FILE</Value>" |
1679 | 2 | " <Value>CUSTOM_AND_FILE</Value>" |
1680 | 2 | " </Option>" |
1681 | 2 | " <Option name='CANOPY_COV_UNIT' type='string-select' " |
1682 | 2 | "default='PERCENT'>" |
1683 | 2 | " <Value>CATEGORIES</Value>" |
1684 | 2 | " <Value>PERCENT</Value>" |
1685 | 2 | " </Option>" |
1686 | 2 | " <Option name='CANOPY_HT_UNIT' type='string-select' " |
1687 | 2 | "default='METERS_X_10'>" |
1688 | 2 | " <Value>METERS</Value>" |
1689 | 2 | " <Value>FEET</Value>" |
1690 | 2 | " <Value>METERS_X_10</Value>" |
1691 | 2 | " <Value>FEET_X_10</Value>" |
1692 | 2 | " </Option>" |
1693 | 2 | " <Option name='CBH_UNIT' type='string-select' default='METERS_X_10'>" |
1694 | 2 | " <Value>METERS</Value>" |
1695 | 2 | " <Value>FEET</Value>" |
1696 | 2 | " <Value>METERS_X_10</Value>" |
1697 | 2 | " <Value>FEET_X_10</Value>" |
1698 | 2 | " </Option>" |
1699 | 2 | " <Option name='CBD_UNIT' type='string-select' " |
1700 | 2 | "default='KG_PER_CUBIC_METER_X_100'>" |
1701 | 2 | " <Value>KG_PER_CUBIC_METER</Value>" |
1702 | 2 | " <Value>POUND_PER_CUBIC_FOOT</Value>" |
1703 | 2 | " <Value>KG_PER_CUBIC_METER_X_100</Value>" |
1704 | 2 | " <Value>POUND_PER_CUBIC_FOOT_X_1000</Value>" |
1705 | 2 | " </Option>" |
1706 | 2 | " <Option name='DUFF_UNIT' type='string-select' " |
1707 | 2 | "default='MG_PER_HECTARE_X_10'>" |
1708 | 2 | " <Value>MG_PER_HECTARE_X_10</Value>" |
1709 | 2 | " <Value>TONS_PER_ACRE_X_10</Value>" |
1710 | 2 | " </Option>" |
1711 | | // Kyle does not think we need to override this, but maybe? |
1712 | | // " <Option name='CWD_OPTION' type='boolean' default='FALSE' |
1713 | | // description='Override logic for setting the coarse woody presence'/>" |
1714 | | // */ |
1715 | 2 | " <Option name='CALCULATE_STATS' type='boolean' default='YES' " |
1716 | 2 | "description='Write the stats to the lcp'/>" |
1717 | 2 | " <Option name='CLASSIFY_DATA' type='boolean' default='YES' " |
1718 | 2 | "description='Write the stats to the lcp'/>" |
1719 | 2 | " <Option name='LINEAR_UNIT' type='string-select' " |
1720 | 2 | "default='SET_FROM_SRS' description='Set the linear units in the lcp'>" |
1721 | 2 | " <Value>SET_FROM_SRS</Value>" |
1722 | 2 | " <Value>METER</Value>" |
1723 | 2 | " <Value>FOOT</Value>" |
1724 | 2 | " <Value>KILOMETER</Value>" |
1725 | 2 | " </Option>" |
1726 | 2 | " <Option name='LATITUDE' type='int' default='' description='Set the " |
1727 | 2 | "latitude for the dataset, this overrides the driver trying to set it " |
1728 | 2 | "programmatically in EPSG:4269'/>" |
1729 | 2 | " <Option name='DESCRIPTION' type='string' default='LCP file created " |
1730 | 2 | "by GDAL' description='A short description of the lcp file'/>" |
1731 | 2 | "</CreationOptionList>"); |
1732 | | |
1733 | 2 | poDriver->pfnOpen = LCPDataset::Open; |
1734 | 2 | poDriver->pfnCreateCopy = LCPDataset::CreateCopy; |
1735 | 2 | poDriver->pfnIdentify = LCPDataset::Identify; |
1736 | | |
1737 | 2 | GetGDALDriverManager()->RegisterDriver(poDriver); |
1738 | 2 | } |