/src/gdal/frmts/usgsdem/usgsdemdataset.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: USGS DEM Driver |
4 | | * Purpose: All reader for USGS DEM Reader |
5 | | * Author: Frank Warmerdam, warmerdam@pobox.com |
6 | | * |
7 | | * Portions of this module derived from the VTP USGS DEM driver by Ben |
8 | | * Discoe, see http://www.vterrain.org |
9 | | * |
10 | | ****************************************************************************** |
11 | | * Copyright (c) 2001, Frank Warmerdam <warmerdam@pobox.com> |
12 | | * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com> |
13 | | * |
14 | | * SPDX-License-Identifier: MIT |
15 | | ****************************************************************************/ |
16 | | |
17 | | #include "gdal_frmts.h" |
18 | | #include "gdal_pam.h" |
19 | | #include "ogr_spatialref.h" |
20 | | |
21 | | #include <algorithm> |
22 | | #include <cmath> |
23 | | |
24 | | typedef struct |
25 | | { |
26 | | double x; |
27 | | double y; |
28 | | } DPoint2; |
29 | | |
30 | | constexpr int USGSDEM_NODATA = -32767; |
31 | | |
32 | | GDALDataset *USGSDEMCreateCopy(const char *, GDALDataset *, int, char **, |
33 | | GDALProgressFunc pfnProgress, |
34 | | void *pProgressData); |
35 | | |
36 | | /************************************************************************/ |
37 | | /* ReadInt() */ |
38 | | /************************************************************************/ |
39 | | |
40 | | static int ReadInt(VSILFILE *fp) |
41 | 6.66k | { |
42 | 6.66k | char c; |
43 | 6.66k | int nRead = 0; |
44 | 6.66k | char szBuffer[12]; |
45 | 6.66k | bool bInProlog = true; |
46 | | |
47 | 54.9k | while (true) |
48 | 54.9k | { |
49 | 54.9k | if (VSIFReadL(&c, 1, 1, fp) != 1) |
50 | 416 | { |
51 | 416 | return 0; |
52 | 416 | } |
53 | 54.4k | if (bInProlog) |
54 | 45.4k | { |
55 | 45.4k | if (!isspace(static_cast<unsigned char>(c))) |
56 | 6.26k | { |
57 | 6.26k | bInProlog = false; |
58 | 6.26k | } |
59 | 45.4k | } |
60 | 54.4k | if (!bInProlog) |
61 | 15.2k | { |
62 | 15.2k | if (c != '-' && c != '+' && !(c >= '0' && c <= '9')) |
63 | 6.25k | { |
64 | 6.25k | CPL_IGNORE_RET_VAL(VSIFSeekL(fp, VSIFTellL(fp) - 1, SEEK_SET)); |
65 | 6.25k | break; |
66 | 6.25k | } |
67 | 9.04k | if (nRead < 11) |
68 | 6.77k | szBuffer[nRead] = c; |
69 | 9.04k | nRead++; |
70 | 9.04k | } |
71 | 54.4k | } |
72 | 6.25k | szBuffer[std::min(nRead, 11)] = 0; |
73 | 6.25k | return atoi(szBuffer); |
74 | 6.66k | } |
75 | | |
76 | | typedef struct |
77 | | { |
78 | | VSILFILE *fp; |
79 | | int max_size; |
80 | | char *buffer; |
81 | | int buffer_size; |
82 | | int cur_index; |
83 | | } Buffer; |
84 | | |
85 | | /************************************************************************/ |
86 | | /* USGSDEMRefillBuffer() */ |
87 | | /************************************************************************/ |
88 | | |
89 | | static void USGSDEMRefillBuffer(Buffer *psBuffer) |
90 | 582 | { |
91 | 582 | memmove(psBuffer->buffer, psBuffer->buffer + psBuffer->cur_index, |
92 | 582 | psBuffer->buffer_size - psBuffer->cur_index); |
93 | | |
94 | 582 | psBuffer->buffer_size -= psBuffer->cur_index; |
95 | 582 | psBuffer->buffer_size += static_cast<int>( |
96 | 582 | VSIFReadL(psBuffer->buffer + psBuffer->buffer_size, 1, |
97 | 582 | psBuffer->max_size - psBuffer->buffer_size, psBuffer->fp)); |
98 | 582 | psBuffer->cur_index = 0; |
99 | 582 | } |
100 | | |
101 | | /************************************************************************/ |
102 | | /* USGSDEMGetCurrentFilePos() */ |
103 | | /************************************************************************/ |
104 | | |
105 | | static vsi_l_offset USGSDEMGetCurrentFilePos(const Buffer *psBuffer) |
106 | 1.46k | { |
107 | 1.46k | return VSIFTellL(psBuffer->fp) - psBuffer->buffer_size + |
108 | 1.46k | psBuffer->cur_index; |
109 | 1.46k | } |
110 | | |
111 | | /************************************************************************/ |
112 | | /* USGSDEMSetCurrentFilePos() */ |
113 | | /************************************************************************/ |
114 | | |
115 | | static void USGSDEMSetCurrentFilePos(Buffer *psBuffer, vsi_l_offset nNewPos) |
116 | 1.46k | { |
117 | 1.46k | vsi_l_offset nCurPosFP = VSIFTellL(psBuffer->fp); |
118 | 1.46k | if (nNewPos >= nCurPosFP - psBuffer->buffer_size && nNewPos < nCurPosFP) |
119 | 1.41k | { |
120 | 1.41k | psBuffer->cur_index = |
121 | 1.41k | static_cast<int>(nNewPos - (nCurPosFP - psBuffer->buffer_size)); |
122 | 1.41k | } |
123 | 52 | else |
124 | 52 | { |
125 | 52 | CPL_IGNORE_RET_VAL(VSIFSeekL(psBuffer->fp, nNewPos, SEEK_SET)); |
126 | 52 | psBuffer->buffer_size = 0; |
127 | 52 | psBuffer->cur_index = 0; |
128 | 52 | } |
129 | 1.46k | } |
130 | | |
131 | | /************************************************************************/ |
132 | | /* USGSDEMReadIntFromBuffer() */ |
133 | | /************************************************************************/ |
134 | | |
135 | | static int USGSDEMReadIntFromBuffer(Buffer *psBuffer, int *pbSuccess = nullptr) |
136 | 179k | { |
137 | 179k | char c; |
138 | | |
139 | 5.29M | while (true) |
140 | 5.29M | { |
141 | 5.29M | if (psBuffer->cur_index >= psBuffer->buffer_size) |
142 | 436 | { |
143 | 436 | USGSDEMRefillBuffer(psBuffer); |
144 | 436 | if (psBuffer->cur_index >= psBuffer->buffer_size) |
145 | 27 | { |
146 | 27 | if (pbSuccess) |
147 | 27 | *pbSuccess = FALSE; |
148 | 27 | return 0; |
149 | 27 | } |
150 | 436 | } |
151 | | |
152 | 5.29M | c = psBuffer->buffer[psBuffer->cur_index]; |
153 | 5.29M | psBuffer->cur_index++; |
154 | 5.29M | if (!isspace(static_cast<unsigned char>(c))) |
155 | 179k | break; |
156 | 5.29M | } |
157 | | |
158 | 179k | GIntBig nVal = 0; |
159 | 179k | int nSign = 1; |
160 | 179k | if (c == '-') |
161 | 11.0k | nSign = -1; |
162 | 168k | else if (c == '+') |
163 | 413 | nSign = 1; |
164 | 167k | else if (c >= '0' && c <= '9') |
165 | 167k | nVal = c - '0'; |
166 | 129 | else |
167 | 129 | { |
168 | 129 | if (pbSuccess) |
169 | 129 | *pbSuccess = FALSE; |
170 | 129 | return 0; |
171 | 129 | } |
172 | | |
173 | 1.13M | while (true) |
174 | 1.13M | { |
175 | 1.13M | if (psBuffer->cur_index >= psBuffer->buffer_size) |
176 | 25 | { |
177 | 25 | USGSDEMRefillBuffer(psBuffer); |
178 | 25 | if (psBuffer->cur_index >= psBuffer->buffer_size) |
179 | 5 | { |
180 | 5 | if (pbSuccess) |
181 | 5 | *pbSuccess = TRUE; |
182 | 5 | return static_cast<int>(nSign * nVal); |
183 | 5 | } |
184 | 25 | } |
185 | | |
186 | 1.13M | c = psBuffer->buffer[psBuffer->cur_index]; |
187 | 1.13M | if (c >= '0' && c <= '9') |
188 | 955k | { |
189 | 955k | psBuffer->cur_index++; |
190 | 955k | if (nVal * nSign < INT_MAX && nVal * nSign > INT_MIN) |
191 | 142k | { |
192 | 142k | nVal = nVal * 10 + (c - '0'); |
193 | 142k | if (nVal * nSign > INT_MAX) |
194 | 217 | { |
195 | 217 | nVal = INT_MAX; |
196 | 217 | nSign = 1; |
197 | 217 | } |
198 | 142k | else if (nVal * nSign < INT_MIN) |
199 | 32 | { |
200 | 32 | nVal = INT_MIN; |
201 | 32 | nSign = 1; |
202 | 32 | } |
203 | 142k | } |
204 | 955k | } |
205 | 179k | else |
206 | 179k | { |
207 | 179k | if (pbSuccess) |
208 | 179k | *pbSuccess = TRUE; |
209 | 179k | return static_cast<int>(nSign * nVal); |
210 | 179k | } |
211 | 1.13M | } |
212 | 179k | } |
213 | | |
214 | | /************************************************************************/ |
215 | | /* USGSDEMReadDoubleFromBuffer() */ |
216 | | /************************************************************************/ |
217 | | |
218 | | static double USGSDEMReadDoubleFromBuffer(Buffer *psBuffer, int nCharCount, |
219 | | int *pbSuccess = nullptr) |
220 | | |
221 | 136k | { |
222 | 136k | if (psBuffer->cur_index + nCharCount > psBuffer->buffer_size) |
223 | 121 | { |
224 | 121 | USGSDEMRefillBuffer(psBuffer); |
225 | 121 | if (psBuffer->cur_index + nCharCount > psBuffer->buffer_size) |
226 | 45 | { |
227 | 45 | if (pbSuccess) |
228 | 45 | *pbSuccess = FALSE; |
229 | 45 | return 0; |
230 | 45 | } |
231 | 121 | } |
232 | | |
233 | 136k | char *szPtr = psBuffer->buffer + psBuffer->cur_index; |
234 | 136k | char backupC = szPtr[nCharCount]; |
235 | 136k | szPtr[nCharCount] = 0; |
236 | 3.42M | for (int i = 0; i < nCharCount; i++) |
237 | 3.28M | { |
238 | 3.28M | if (szPtr[i] == 'D') |
239 | 29.4k | szPtr[i] = 'E'; |
240 | 3.28M | } |
241 | | |
242 | 136k | double dfVal = CPLAtof(szPtr); |
243 | 136k | szPtr[nCharCount] = backupC; |
244 | 136k | psBuffer->cur_index += nCharCount; |
245 | | |
246 | 136k | if (pbSuccess) |
247 | 136k | *pbSuccess = TRUE; |
248 | 136k | return dfVal; |
249 | 136k | } |
250 | | |
251 | | /************************************************************************/ |
252 | | /* DConvert() */ |
253 | | /************************************************************************/ |
254 | | |
255 | | static double DConvert(VSILFILE *fp, int nCharCount) |
256 | | |
257 | 5.79k | { |
258 | 5.79k | char szBuffer[100]; |
259 | | |
260 | 5.79k | CPL_IGNORE_RET_VAL(VSIFReadL(szBuffer, nCharCount, 1, fp)); |
261 | 5.79k | szBuffer[nCharCount] = '\0'; |
262 | | |
263 | 147k | for (int i = 0; i < nCharCount; i++) |
264 | 142k | { |
265 | 142k | if (szBuffer[i] == 'D') |
266 | 2.21k | szBuffer[i] = 'E'; |
267 | 142k | } |
268 | | |
269 | 5.79k | return CPLAtof(szBuffer); |
270 | 5.79k | } |
271 | | |
272 | | /************************************************************************/ |
273 | | /* ==================================================================== */ |
274 | | /* USGSDEMDataset */ |
275 | | /* ==================================================================== */ |
276 | | /************************************************************************/ |
277 | | |
278 | | class USGSDEMRasterBand; |
279 | | |
280 | | class USGSDEMDataset final : public GDALPamDataset |
281 | | { |
282 | | friend class USGSDEMRasterBand; |
283 | | |
284 | | int nDataStartOffset; |
285 | | GDALDataType eNaturalDataFormat; |
286 | | |
287 | | GDALGeoTransform m_gt{}; |
288 | | OGRSpatialReference m_oSRS{}; |
289 | | |
290 | | double fVRes; |
291 | | |
292 | | const char *pszUnits; |
293 | | |
294 | | int LoadFromFile(VSILFILE *); |
295 | | |
296 | | VSILFILE *fp; |
297 | | |
298 | | public: |
299 | | USGSDEMDataset(); |
300 | | ~USGSDEMDataset(); |
301 | | |
302 | | static int Identify(GDALOpenInfo *); |
303 | | static GDALDataset *Open(GDALOpenInfo *); |
304 | | CPLErr GetGeoTransform(GDALGeoTransform >) const override; |
305 | | const OGRSpatialReference *GetSpatialRef() const override; |
306 | | }; |
307 | | |
308 | | /************************************************************************/ |
309 | | /* ==================================================================== */ |
310 | | /* USGSDEMRasterBand */ |
311 | | /* ==================================================================== */ |
312 | | /************************************************************************/ |
313 | | |
314 | | class USGSDEMRasterBand final : public GDALPamRasterBand |
315 | | { |
316 | | friend class USGSDEMDataset; |
317 | | |
318 | | public: |
319 | | explicit USGSDEMRasterBand(USGSDEMDataset *); |
320 | | |
321 | | virtual const char *GetUnitType() override; |
322 | | virtual double GetNoDataValue(int *pbSuccess = nullptr) override; |
323 | | virtual CPLErr IReadBlock(int, int, void *) override; |
324 | | }; |
325 | | |
326 | | /************************************************************************/ |
327 | | /* USGSDEMRasterBand() */ |
328 | | /************************************************************************/ |
329 | | |
330 | | USGSDEMRasterBand::USGSDEMRasterBand(USGSDEMDataset *poDSIn) |
331 | | |
332 | 392 | { |
333 | 392 | this->poDS = poDSIn; |
334 | 392 | this->nBand = 1; |
335 | | |
336 | 392 | eDataType = poDSIn->eNaturalDataFormat; |
337 | | |
338 | 392 | nBlockXSize = poDSIn->GetRasterXSize(); |
339 | 392 | nBlockYSize = poDSIn->GetRasterYSize(); |
340 | 392 | } |
341 | | |
342 | | /************************************************************************/ |
343 | | /* IReadBlock() */ |
344 | | /************************************************************************/ |
345 | | |
346 | | CPLErr USGSDEMRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff, |
347 | | CPL_UNUSED int nBlockYOff, void *pImage) |
348 | | |
349 | 228 | { |
350 | | /* int bad = FALSE; */ |
351 | 228 | USGSDEMDataset *poGDS = cpl::down_cast<USGSDEMDataset *>(poDS); |
352 | | |
353 | | /* -------------------------------------------------------------------- */ |
354 | | /* Initialize image buffer to nodata value. */ |
355 | | /* -------------------------------------------------------------------- */ |
356 | 228 | GDALCopyWords(&USGSDEM_NODATA, GDT_Int32, 0, pImage, GetRasterDataType(), |
357 | 228 | GDALGetDataTypeSizeBytes(GetRasterDataType()), |
358 | 228 | GetXSize() * GetYSize()); |
359 | | |
360 | | /* -------------------------------------------------------------------- */ |
361 | | /* Seek to data. */ |
362 | | /* -------------------------------------------------------------------- */ |
363 | 228 | CPL_IGNORE_RET_VAL(VSIFSeekL(poGDS->fp, poGDS->nDataStartOffset, 0)); |
364 | | |
365 | 228 | double dfYMin = poGDS->m_gt[3] + (GetYSize() - 0.5) * poGDS->m_gt[5]; |
366 | | |
367 | | /* -------------------------------------------------------------------- */ |
368 | | /* Read all the profiles into the image buffer. */ |
369 | | /* -------------------------------------------------------------------- */ |
370 | | |
371 | 228 | Buffer sBuffer; |
372 | 228 | sBuffer.max_size = 32768; |
373 | 228 | sBuffer.buffer = static_cast<char *>(CPLMalloc(sBuffer.max_size + 1)); |
374 | 228 | sBuffer.fp = poGDS->fp; |
375 | 228 | sBuffer.buffer_size = 0; |
376 | 228 | sBuffer.cur_index = 0; |
377 | | |
378 | 27.4k | for (int i = 0; i < GetXSize(); i++) |
379 | 27.4k | { |
380 | 27.4k | int bSuccess; |
381 | 27.4k | const int nRowNumber = USGSDEMReadIntFromBuffer(&sBuffer, &bSuccess); |
382 | 27.4k | if (nRowNumber != 1) |
383 | 2.78k | CPLDebug("USGSDEM", "i = %d, nRowNumber = %d", i, nRowNumber); |
384 | 27.4k | if (bSuccess) |
385 | 27.4k | { |
386 | 27.4k | const int nColNumber = |
387 | 27.4k | USGSDEMReadIntFromBuffer(&sBuffer, &bSuccess); |
388 | 27.4k | if (nColNumber != i + 1) |
389 | 22.8k | { |
390 | 22.8k | CPLDebug("USGSDEM", "i = %d, nColNumber = %d", i, nColNumber); |
391 | 22.8k | } |
392 | 27.4k | } |
393 | 27.4k | const int nCPoints = |
394 | 27.4k | (bSuccess) ? USGSDEMReadIntFromBuffer(&sBuffer, &bSuccess) : 0; |
395 | | #ifdef DEBUG_VERBOSE |
396 | | CPLDebug("USGSDEM", "i = %d, nCPoints = %d", i, nCPoints); |
397 | | #endif |
398 | | |
399 | 27.4k | if (bSuccess) |
400 | 27.4k | { |
401 | 27.4k | const int nNumberOfCols = |
402 | 27.4k | USGSDEMReadIntFromBuffer(&sBuffer, &bSuccess); |
403 | 27.4k | if (nNumberOfCols != 1) |
404 | 1.68k | { |
405 | 1.68k | CPLDebug("USGSDEM", "i = %d, nNumberOfCols = %d", i, |
406 | 1.68k | nNumberOfCols); |
407 | 1.68k | } |
408 | 27.4k | } |
409 | | |
410 | | // x-start |
411 | 27.4k | if (bSuccess) |
412 | 27.3k | /* dxStart = */ USGSDEMReadDoubleFromBuffer(&sBuffer, 24, |
413 | 27.3k | &bSuccess); |
414 | | |
415 | 27.4k | double dyStart = |
416 | 27.4k | (bSuccess) ? USGSDEMReadDoubleFromBuffer(&sBuffer, 24, &bSuccess) |
417 | 27.4k | : 0; |
418 | 27.4k | const double dfElevOffset = |
419 | 27.4k | (bSuccess) ? USGSDEMReadDoubleFromBuffer(&sBuffer, 24, &bSuccess) |
420 | 27.4k | : 0; |
421 | | |
422 | | // min z value |
423 | 27.4k | if (bSuccess) |
424 | 27.3k | /* djunk = */ USGSDEMReadDoubleFromBuffer(&sBuffer, 24, &bSuccess); |
425 | | |
426 | | // max z value |
427 | 27.4k | if (bSuccess) |
428 | 27.3k | /* djunk = */ USGSDEMReadDoubleFromBuffer(&sBuffer, 24, &bSuccess); |
429 | 27.4k | if (!bSuccess) |
430 | 109 | { |
431 | 109 | CPLFree(sBuffer.buffer); |
432 | 109 | return CE_Failure; |
433 | 109 | } |
434 | | |
435 | 27.3k | if (poGDS->m_oSRS.IsGeographic()) |
436 | 21.5k | dyStart = dyStart / 3600.0; |
437 | | |
438 | 27.3k | double dygap = (dfYMin - dyStart) / poGDS->m_gt[5] + 0.5; |
439 | 27.3k | if (dygap <= INT_MIN || dygap >= INT_MAX || !std::isfinite(dygap)) |
440 | 5 | { |
441 | 5 | CPLFree(sBuffer.buffer); |
442 | 5 | return CE_Failure; |
443 | 5 | } |
444 | 27.3k | int lygap = static_cast<int>(dygap); |
445 | 27.3k | if (nCPoints <= 0) |
446 | 518 | continue; |
447 | 26.8k | if (lygap > INT_MAX - nCPoints) |
448 | 1 | lygap = INT_MAX - nCPoints; |
449 | 26.8k | if (lygap < 0 && GetYSize() > INT_MAX + lygap) |
450 | 0 | { |
451 | 0 | CPLFree(sBuffer.buffer); |
452 | 0 | return CE_Failure; |
453 | 0 | } |
454 | | |
455 | 96.2k | for (int j = lygap; j < (nCPoints + lygap); j++) |
456 | 69.5k | { |
457 | 69.5k | const int iY = GetYSize() - j - 1; |
458 | | |
459 | 69.5k | const int nElev = USGSDEMReadIntFromBuffer(&sBuffer, &bSuccess); |
460 | | #ifdef DEBUG_VERBOSE |
461 | | CPLDebug("USGSDEM", " j - lygap = %d, nElev = %d", j - lygap, |
462 | | nElev); |
463 | | #endif |
464 | | |
465 | 69.5k | if (!bSuccess) |
466 | 92 | { |
467 | 92 | CPLFree(sBuffer.buffer); |
468 | 92 | return CE_Failure; |
469 | 92 | } |
470 | | |
471 | 69.4k | if (iY < 0 || iY >= GetYSize()) |
472 | 27.6k | { |
473 | | /* bad = TRUE; */ |
474 | 27.6k | } |
475 | 41.7k | else if (nElev == USGSDEM_NODATA) |
476 | 7.97k | /* leave in output buffer as nodata */; |
477 | 33.7k | else |
478 | 33.7k | { |
479 | 33.7k | const float fComputedElev = |
480 | 33.7k | static_cast<float>(nElev * poGDS->fVRes + dfElevOffset); |
481 | | |
482 | 33.7k | if (GetRasterDataType() == GDT_Int16) |
483 | 32.2k | { |
484 | 32.2k | GUInt16 nVal = (fComputedElev < -32768) ? -32768 |
485 | 32.2k | : (fComputedElev > 32767) |
486 | 31.9k | ? 32767 |
487 | 31.9k | : static_cast<GInt16>(fComputedElev); |
488 | 32.2k | reinterpret_cast<GInt16 *>(pImage)[i + iY * GetXSize()] = |
489 | 32.2k | nVal; |
490 | 32.2k | } |
491 | 1.46k | else |
492 | 1.46k | { |
493 | 1.46k | reinterpret_cast<float *>(pImage)[i + iY * GetXSize()] = |
494 | 1.46k | fComputedElev; |
495 | 1.46k | } |
496 | 33.7k | } |
497 | 69.4k | } |
498 | | |
499 | 26.7k | if (poGDS->nDataStartOffset == 1024) |
500 | 1.46k | { |
501 | | // Seek to the next 1024 byte boundary. |
502 | | // Some files have 'junk' profile values after the valid/declared |
503 | | // ones |
504 | 1.46k | vsi_l_offset nCurPos = USGSDEMGetCurrentFilePos(&sBuffer); |
505 | 1.46k | vsi_l_offset nNewPos = (nCurPos + 1023) / 1024 * 1024; |
506 | 1.46k | if (nNewPos > nCurPos) |
507 | 1.46k | { |
508 | 1.46k | USGSDEMSetCurrentFilePos(&sBuffer, nNewPos); |
509 | 1.46k | } |
510 | 1.46k | } |
511 | 26.7k | } |
512 | 22 | CPLFree(sBuffer.buffer); |
513 | | |
514 | 22 | return CE_None; |
515 | 228 | } |
516 | | |
517 | | /************************************************************************/ |
518 | | /* GetNoDataValue() */ |
519 | | /************************************************************************/ |
520 | | |
521 | | double USGSDEMRasterBand::GetNoDataValue(int *pbSuccess) |
522 | | |
523 | 942 | { |
524 | 942 | if (pbSuccess != nullptr) |
525 | 719 | *pbSuccess = TRUE; |
526 | | |
527 | 942 | return USGSDEM_NODATA; |
528 | 942 | } |
529 | | |
530 | | /************************************************************************/ |
531 | | /* GetUnitType() */ |
532 | | /************************************************************************/ |
533 | | const char *USGSDEMRasterBand::GetUnitType() |
534 | 288 | { |
535 | 288 | USGSDEMDataset *poGDS = cpl::down_cast<USGSDEMDataset *>(poDS); |
536 | | |
537 | 288 | return poGDS->pszUnits; |
538 | 288 | } |
539 | | |
540 | | /************************************************************************/ |
541 | | /* ==================================================================== */ |
542 | | /* USGSDEMDataset */ |
543 | | /* ==================================================================== */ |
544 | | /************************************************************************/ |
545 | | |
546 | | /************************************************************************/ |
547 | | /* USGSDEMDataset() */ |
548 | | /************************************************************************/ |
549 | | |
550 | | USGSDEMDataset::USGSDEMDataset() |
551 | 665 | : nDataStartOffset(0), eNaturalDataFormat(GDT_Unknown), fVRes(0.0), |
552 | 665 | pszUnits(nullptr), fp(nullptr) |
553 | 665 | { |
554 | 665 | m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
555 | 665 | } |
556 | | |
557 | | /************************************************************************/ |
558 | | /* ~USGSDEMDataset() */ |
559 | | /************************************************************************/ |
560 | | |
561 | | USGSDEMDataset::~USGSDEMDataset() |
562 | | |
563 | 665 | { |
564 | 665 | FlushCache(true); |
565 | | |
566 | 665 | if (fp != nullptr) |
567 | 665 | CPL_IGNORE_RET_VAL(VSIFCloseL(fp)); |
568 | 665 | } |
569 | | |
570 | | /************************************************************************/ |
571 | | /* LoadFromFile() */ |
572 | | /* */ |
573 | | /* If the data from DEM is in meters, then values are stored as */ |
574 | | /* shorts. If DEM data is in feet, then height data will be */ |
575 | | /* stored in float, to preserve the precision of the original */ |
576 | | /* data. returns true if the file was successfully opened and */ |
577 | | /* read. */ |
578 | | /************************************************************************/ |
579 | | |
580 | | int USGSDEMDataset::LoadFromFile(VSILFILE *InDem) |
581 | 665 | { |
582 | | // check for version of DEM format |
583 | 665 | CPL_IGNORE_RET_VAL(VSIFSeekL(InDem, 864, 0)); |
584 | | |
585 | | // Read DEM into matrix |
586 | 665 | const int nRow = ReadInt(InDem); |
587 | 665 | const int nColumn = ReadInt(InDem); |
588 | 665 | const bool bNewFormat = |
589 | 665 | VSIFTellL(InDem) >= 1024 || nRow != 1 || nColumn != 1; |
590 | 665 | if (bNewFormat) |
591 | 449 | { |
592 | 449 | CPL_IGNORE_RET_VAL(VSIFSeekL(InDem, 1024, 0)); // New Format |
593 | 449 | int i = ReadInt(InDem); |
594 | 449 | int j = ReadInt(InDem); |
595 | 449 | if (i != 1 || (j != 1 && j != 0)) // File OK? |
596 | 302 | { |
597 | 302 | CPL_IGNORE_RET_VAL( |
598 | 302 | VSIFSeekL(InDem, 893, 0)); // Undocumented Format (39109h1.dem) |
599 | 302 | i = ReadInt(InDem); |
600 | 302 | j = ReadInt(InDem); |
601 | 302 | if (i != 1 || j != 1) // File OK? |
602 | 216 | { |
603 | 216 | CPL_IGNORE_RET_VAL(VSIFSeekL( |
604 | 216 | InDem, 918, 0)); // Latest iteration of the A record, such |
605 | | // as in fema06-140cm_2995441b.dem |
606 | 216 | i = ReadInt(InDem); |
607 | 216 | j = ReadInt(InDem); |
608 | 216 | if (i != 1 || j != 1) // File OK? |
609 | 175 | { |
610 | 175 | CPLError(CE_Failure, CPLE_AppDefined, |
611 | 175 | "Does not appear to be a USGS DEM file."); |
612 | 175 | return FALSE; |
613 | 175 | } |
614 | 41 | else |
615 | 41 | nDataStartOffset = 918; |
616 | 216 | } |
617 | 86 | else |
618 | 86 | nDataStartOffset = 893; |
619 | 302 | } |
620 | 147 | else |
621 | 147 | { |
622 | 147 | nDataStartOffset = 1024; |
623 | | |
624 | | // Some files use 1025 byte records ending with a newline character. |
625 | | // See https://github.com/OSGeo/gdal/issues/5007 |
626 | 147 | CPL_IGNORE_RET_VAL(VSIFSeekL(InDem, 1024, 0)); |
627 | 147 | char c; |
628 | 147 | if (VSIFReadL(&c, 1, 1, InDem) == 1 && c == '\n' && |
629 | 147 | VSIFSeekL(InDem, 1024 + 1024 + 1, 0) == 0 && |
630 | 147 | VSIFReadL(&c, 1, 1, InDem) == 1 && c == '\n') |
631 | 26 | { |
632 | 26 | nDataStartOffset = 1025; |
633 | 26 | } |
634 | 147 | } |
635 | 449 | } |
636 | 216 | else |
637 | 216 | nDataStartOffset = 864; |
638 | | |
639 | 490 | CPL_IGNORE_RET_VAL(VSIFSeekL(InDem, 156, 0)); |
640 | 490 | const int nCoordSystem = ReadInt(InDem); |
641 | 490 | const int iUTMZone = ReadInt(InDem); |
642 | | |
643 | 490 | CPL_IGNORE_RET_VAL(VSIFSeekL(InDem, 528, 0)); |
644 | 490 | const int nGUnit = ReadInt(InDem); |
645 | 490 | const int nVUnit = ReadInt(InDem); |
646 | | |
647 | | // Vertical Units in meters |
648 | 490 | if (nVUnit == 1) |
649 | 171 | pszUnits = "ft"; |
650 | 319 | else |
651 | 319 | pszUnits = "m"; |
652 | | |
653 | 490 | CPL_IGNORE_RET_VAL(VSIFSeekL(InDem, 816, 0)); |
654 | 490 | const double dxdelta = DConvert(InDem, 12); |
655 | 490 | const double dydelta = DConvert(InDem, 12); |
656 | 490 | if (dydelta == 0) |
657 | 76 | return FALSE; |
658 | 414 | fVRes = DConvert(InDem, 12); |
659 | | |
660 | | /* -------------------------------------------------------------------- */ |
661 | | /* Should we treat this as floating point, or GInt16. */ |
662 | | /* -------------------------------------------------------------------- */ |
663 | 414 | if (nVUnit == 1 || fVRes < 1.0) |
664 | 195 | eNaturalDataFormat = GDT_Float32; |
665 | 219 | else |
666 | 219 | eNaturalDataFormat = GDT_Int16; |
667 | | |
668 | | /* -------------------------------------------------------------------- */ |
669 | | /* Read four corner coordinates. */ |
670 | | /* -------------------------------------------------------------------- */ |
671 | 414 | CPL_IGNORE_RET_VAL(VSIFSeekL(InDem, 546, 0)); |
672 | 414 | DPoint2 corners[4]; // SW, NW, NE, SE |
673 | 2.07k | for (int i = 0; i < 4; i++) |
674 | 1.65k | { |
675 | 1.65k | corners[i].x = DConvert(InDem, 24); |
676 | 1.65k | corners[i].y = DConvert(InDem, 24); |
677 | 1.65k | } |
678 | | |
679 | | // find absolute extents of raw vales |
680 | 414 | DPoint2 extent_min, extent_max; |
681 | 414 | extent_min.x = std::min(corners[0].x, corners[1].x); |
682 | 414 | extent_max.x = std::max(corners[2].x, corners[3].x); |
683 | 414 | extent_min.y = std::min(corners[0].y, corners[3].y); |
684 | 414 | extent_max.y = std::max(corners[1].y, corners[2].y); |
685 | | |
686 | | /* dElevMin = */ DConvert(InDem, 48); |
687 | 414 | /* dElevMax = */ DConvert(InDem, 48); |
688 | | |
689 | 414 | CPL_IGNORE_RET_VAL(VSIFSeekL(InDem, 858, 0)); |
690 | 414 | const int nProfiles = ReadInt(InDem); |
691 | | |
692 | | /* -------------------------------------------------------------------- */ |
693 | | /* Collect the spatial reference system. */ |
694 | | /* -------------------------------------------------------------------- */ |
695 | 414 | OGRSpatialReference sr; |
696 | 414 | sr.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
697 | 414 | bool bNAD83 = true; |
698 | | |
699 | | // OLD format header ends at byte 864 |
700 | 414 | if (bNewFormat) |
701 | 200 | { |
702 | | // year of data compilation |
703 | 200 | CPL_IGNORE_RET_VAL(VSIFSeekL(InDem, 876, 0)); |
704 | 200 | char szDateBuffer[5]; |
705 | 200 | CPL_IGNORE_RET_VAL(VSIFReadL(szDateBuffer, 4, 1, InDem)); |
706 | | /* szDateBuffer[4] = 0; */ |
707 | | |
708 | | // Horizontal datum |
709 | | // 1=North American Datum 1927 (NAD 27) |
710 | | // 2=World Geodetic System 1972 (WGS 72) |
711 | | // 3=WGS 84 |
712 | | // 4=NAD 83 |
713 | | // 5=Old Hawaii Datum |
714 | | // 6=Puerto Rico Datum |
715 | 200 | CPL_IGNORE_RET_VAL(VSIFSeekL(InDem, 890, 0)); |
716 | | |
717 | 200 | char szHorzDatum[3]; |
718 | 200 | CPL_IGNORE_RET_VAL(VSIFReadL(szHorzDatum, 1, 2, InDem)); |
719 | 200 | szHorzDatum[2] = '\0'; |
720 | 200 | const int datum = atoi(szHorzDatum); |
721 | 200 | switch (datum) |
722 | 200 | { |
723 | 100 | case 1: |
724 | 100 | sr.SetWellKnownGeogCS("NAD27"); |
725 | 100 | bNAD83 = false; |
726 | 100 | break; |
727 | | |
728 | 9 | case 2: |
729 | 9 | sr.SetWellKnownGeogCS("WGS72"); |
730 | 9 | break; |
731 | | |
732 | 3 | case 3: |
733 | 3 | sr.SetWellKnownGeogCS("WGS84"); |
734 | 3 | break; |
735 | | |
736 | 3 | case 4: |
737 | 3 | sr.SetWellKnownGeogCS("NAD83"); |
738 | 3 | break; |
739 | | |
740 | 0 | case -9: |
741 | 0 | break; |
742 | | |
743 | 85 | default: |
744 | 85 | sr.SetWellKnownGeogCS("NAD27"); |
745 | 85 | break; |
746 | 200 | } |
747 | 200 | } |
748 | 214 | else |
749 | 214 | { |
750 | 214 | sr.SetWellKnownGeogCS("NAD27"); |
751 | 214 | bNAD83 = false; |
752 | 214 | } |
753 | | |
754 | 414 | if (nCoordSystem == 1) // UTM |
755 | 202 | { |
756 | 202 | if (iUTMZone >= -60 && iUTMZone <= 60) |
757 | 202 | { |
758 | 202 | sr.SetUTM(abs(iUTMZone), iUTMZone >= 0); |
759 | 202 | if (nGUnit == 1) |
760 | 99 | { |
761 | 99 | sr.SetLinearUnitsAndUpdateParameters( |
762 | 99 | SRS_UL_US_FOOT, CPLAtof(SRS_UL_US_FOOT_CONV)); |
763 | 99 | char szUTMName[128]; |
764 | 99 | snprintf(szUTMName, sizeof(szUTMName), |
765 | 99 | "UTM Zone %d, Northern Hemisphere, us-ft", iUTMZone); |
766 | 99 | sr.SetNode("PROJCS", szUTMName); |
767 | 99 | } |
768 | 202 | } |
769 | 202 | } |
770 | 212 | else if (nCoordSystem == 2) // state plane |
771 | 54 | { |
772 | 54 | if (nGUnit == 1) |
773 | 24 | sr.SetStatePlane(iUTMZone, bNAD83, "Foot", |
774 | 24 | CPLAtof(SRS_UL_US_FOOT_CONV)); |
775 | 30 | else |
776 | 30 | sr.SetStatePlane(iUTMZone, bNAD83); |
777 | 54 | } |
778 | | |
779 | 414 | m_oSRS = std::move(sr); |
780 | | |
781 | | /* -------------------------------------------------------------------- */ |
782 | | /* For UTM we use the extents (really the UTM coordinates of */ |
783 | | /* the lat/long corners of the quad) to determine the size in */ |
784 | | /* pixels and lines, but we have to make the anchors be modulus */ |
785 | | /* the pixel size which what really gets used. */ |
786 | | /* -------------------------------------------------------------------- */ |
787 | 414 | if (nCoordSystem == 1 // UTM |
788 | 414 | || nCoordSystem == 2 // State Plane |
789 | 414 | || nCoordSystem == -9999) // unknown |
790 | 257 | { |
791 | | // expand extents modulus the pixel size. |
792 | 257 | extent_min.y = floor(extent_min.y / dydelta) * dydelta; |
793 | 257 | extent_max.y = ceil(extent_max.y / dydelta) * dydelta; |
794 | | |
795 | | // Forcibly compute X extents based on first profile and pixelsize. |
796 | 257 | CPL_IGNORE_RET_VAL(VSIFSeekL(InDem, nDataStartOffset, 0)); |
797 | 257 | /* njunk = */ ReadInt(InDem); |
798 | 257 | /* njunk = */ ReadInt(InDem); |
799 | 257 | /* njunk = */ ReadInt(InDem); |
800 | 257 | /* njunk = */ ReadInt(InDem); |
801 | 257 | const double dxStart = DConvert(InDem, 24); |
802 | | |
803 | 257 | nRasterYSize = |
804 | 257 | static_cast<int>((extent_max.y - extent_min.y) / dydelta + 1.5); |
805 | 257 | nRasterXSize = nProfiles; |
806 | | |
807 | 257 | m_gt[0] = dxStart - dxdelta / 2.0; |
808 | 257 | m_gt[1] = dxdelta; |
809 | 257 | m_gt[2] = 0.0; |
810 | 257 | m_gt[3] = extent_max.y + dydelta / 2.0; |
811 | 257 | m_gt[4] = 0.0; |
812 | 257 | m_gt[5] = -dydelta; |
813 | 257 | } |
814 | | /* -------------------------------------------------------------------- */ |
815 | | /* Geographic -- use corners directly. */ |
816 | | /* -------------------------------------------------------------------- */ |
817 | 157 | else |
818 | 157 | { |
819 | 157 | nRasterYSize = |
820 | 157 | static_cast<int>((extent_max.y - extent_min.y) / dydelta + 1.5); |
821 | 157 | nRasterXSize = nProfiles; |
822 | | |
823 | | // Translate extents from arc-seconds to decimal degrees. |
824 | 157 | m_gt[0] = (extent_min.x - dxdelta / 2.0) / 3600.0; |
825 | 157 | m_gt[1] = dxdelta / 3600.0; |
826 | 157 | m_gt[2] = 0.0; |
827 | 157 | m_gt[3] = (extent_max.y + dydelta / 2.0) / 3600.0; |
828 | 157 | m_gt[4] = 0.0; |
829 | 157 | m_gt[5] = (-dydelta) / 3600.0; |
830 | 157 | } |
831 | | |
832 | | // IReadBlock() not ready for more than INT_MAX pixels, and that |
833 | | // would behave badly |
834 | 414 | if (!GDALCheckDatasetDimensions(nRasterXSize, nRasterYSize) || |
835 | 414 | nRasterXSize > INT_MAX / nRasterYSize) |
836 | 22 | { |
837 | 22 | return FALSE; |
838 | 22 | } |
839 | | |
840 | 392 | return TRUE; |
841 | 414 | } |
842 | | |
843 | | /************************************************************************/ |
844 | | /* GetGeoTransform() */ |
845 | | /************************************************************************/ |
846 | | |
847 | | CPLErr USGSDEMDataset::GetGeoTransform(GDALGeoTransform >) const |
848 | | |
849 | 331 | { |
850 | 331 | gt = m_gt; |
851 | 331 | return CE_None; |
852 | 331 | } |
853 | | |
854 | | /************************************************************************/ |
855 | | /* GetSpatialRef() */ |
856 | | /************************************************************************/ |
857 | | |
858 | | const OGRSpatialReference *USGSDEMDataset::GetSpatialRef() const |
859 | | |
860 | 395 | { |
861 | 395 | return m_oSRS.IsEmpty() ? nullptr : &m_oSRS; |
862 | 395 | } |
863 | | |
864 | | /************************************************************************/ |
865 | | /* Identify() */ |
866 | | /************************************************************************/ |
867 | | |
868 | | int USGSDEMDataset::Identify(GDALOpenInfo *poOpenInfo) |
869 | | |
870 | 549k | { |
871 | 549k | if (poOpenInfo->nHeaderBytes < 200) |
872 | 494k | return FALSE; |
873 | | |
874 | 55.2k | if (!STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader + 156, " 0") && |
875 | 55.2k | !STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader + 156, " 1") && |
876 | 55.2k | !STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader + 156, " 2") && |
877 | 55.2k | !STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader + 156, " 3") && |
878 | 55.2k | !STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader + 156, " -9999")) |
879 | 53.7k | return FALSE; |
880 | | |
881 | 1.43k | if (!STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader + 150, " 1") && |
882 | 1.43k | !STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader + 150, " 4")) |
883 | 109 | return FALSE; |
884 | | |
885 | 1.33k | return TRUE; |
886 | 1.43k | } |
887 | | |
888 | | /************************************************************************/ |
889 | | /* Open() */ |
890 | | /************************************************************************/ |
891 | | |
892 | | GDALDataset *USGSDEMDataset::Open(GDALOpenInfo *poOpenInfo) |
893 | | |
894 | 665 | { |
895 | 665 | if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr) |
896 | 0 | return nullptr; |
897 | | |
898 | | /* -------------------------------------------------------------------- */ |
899 | | /* Create a corresponding GDALDataset. */ |
900 | | /* -------------------------------------------------------------------- */ |
901 | 665 | USGSDEMDataset *poDS = new USGSDEMDataset(); |
902 | | |
903 | 665 | poDS->fp = poOpenInfo->fpL; |
904 | 665 | poOpenInfo->fpL = nullptr; |
905 | | |
906 | | /* -------------------------------------------------------------------- */ |
907 | | /* Read the file. */ |
908 | | /* -------------------------------------------------------------------- */ |
909 | 665 | if (!poDS->LoadFromFile(poDS->fp)) |
910 | 273 | { |
911 | 273 | delete poDS; |
912 | 273 | return nullptr; |
913 | 273 | } |
914 | | |
915 | | /* -------------------------------------------------------------------- */ |
916 | | /* Confirm the requested access is supported. */ |
917 | | /* -------------------------------------------------------------------- */ |
918 | 392 | if (poOpenInfo->eAccess == GA_Update) |
919 | 0 | { |
920 | 0 | delete poDS; |
921 | 0 | ReportUpdateNotSupportedByDriver("USGSDEM"); |
922 | 0 | return nullptr; |
923 | 0 | } |
924 | | |
925 | | /* -------------------------------------------------------------------- */ |
926 | | /* Create band information objects. */ |
927 | | /* -------------------------------------------------------------------- */ |
928 | 392 | poDS->SetBand(1, new USGSDEMRasterBand(poDS)); |
929 | | |
930 | 392 | poDS->SetMetadataItem(GDALMD_AREA_OR_POINT, GDALMD_AOP_POINT); |
931 | | |
932 | | /* -------------------------------------------------------------------- */ |
933 | | /* Initialize any PAM information. */ |
934 | | /* -------------------------------------------------------------------- */ |
935 | 392 | poDS->SetDescription(poOpenInfo->pszFilename); |
936 | 392 | poDS->TryLoadXML(); |
937 | | |
938 | | /* -------------------------------------------------------------------- */ |
939 | | /* Open overviews. */ |
940 | | /* -------------------------------------------------------------------- */ |
941 | 392 | poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename); |
942 | | |
943 | 392 | return poDS; |
944 | 392 | } |
945 | | |
946 | | /************************************************************************/ |
947 | | /* GDALRegister_USGSDEM() */ |
948 | | /************************************************************************/ |
949 | | |
950 | | void GDALRegister_USGSDEM() |
951 | | |
952 | 22 | { |
953 | 22 | if (GDALGetDriverByName("USGSDEM") != nullptr) |
954 | 0 | return; |
955 | | |
956 | 22 | GDALDriver *poDriver = new GDALDriver(); |
957 | | |
958 | 22 | poDriver->SetDescription("USGSDEM"); |
959 | 22 | poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES"); |
960 | 22 | poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "dem"); |
961 | 22 | poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, |
962 | 22 | "USGS Optional ASCII DEM (and CDED)"); |
963 | 22 | poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, |
964 | 22 | "drivers/raster/usgsdem.html"); |
965 | | |
966 | 22 | poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES"); |
967 | | |
968 | 22 | poDriver->pfnOpen = USGSDEMDataset::Open; |
969 | 22 | poDriver->pfnIdentify = USGSDEMDataset::Identify; |
970 | | |
971 | 22 | GetGDALDriverManager()->RegisterDriver(poDriver); |
972 | 22 | } |