Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: Implements Arc/Info ASCII Grid Format.
5 : * Author: Frank Warmerdam, warmerdam@pobox.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2001, Frank Warmerdam (warmerdam@pobox.com)
9 : * Copyright (c) 2007-2012, Even Rouault <even dot rouault at spatialys.com>
10 : * Copyright (c) 2014, Kyle Shannon <kyle at pobox dot com>
11 : *
12 : * SPDX-License-Identifier: MIT
13 : ****************************************************************************/
14 :
15 : // We need cpl_port as first include to avoid VSIStatBufL being not
16 : // defined on i586-mingw32msvc.
17 : #include "cpl_port.h"
18 : #include "aaigriddataset.h"
19 : #include "gdal_frmts.h"
20 :
21 : #include <cassert>
22 : #include <cctype>
23 : #include <climits>
24 : #include <cmath>
25 : #include <cstddef>
26 : #include <cstdio>
27 : #include <cstdlib>
28 : #include <cstring>
29 : #if HAVE_FCNTL_H
30 : #include <fcntl.h>
31 : #endif
32 :
33 : #include <algorithm>
34 : #include <limits>
35 : #include <string>
36 :
37 : #include "cpl_conv.h"
38 : #include "cpl_error.h"
39 : #include "cpl_progress.h"
40 : #include "cpl_string.h"
41 : #include "cpl_vsi.h"
42 : #include "gdal.h"
43 : #include "gdal_pam.h"
44 : #include "gdal_priv.h"
45 : #include "ogr_core.h"
46 : #include "ogr_spatialref.h"
47 :
48 : namespace
49 : {
50 :
51 2643 : float DoubleToFloatClamp(double dfValue)
52 : {
53 2643 : if (dfValue <= std::numeric_limits<float>::lowest())
54 0 : return std::numeric_limits<float>::lowest();
55 2643 : if (dfValue >= std::numeric_limits<float>::max())
56 0 : return std::numeric_limits<float>::max();
57 2643 : return static_cast<float>(dfValue);
58 : }
59 :
60 : // Cast to float and back for make sure the NoData value matches
61 : // that expressed by a float value. Clamps to the range of a float
62 : // if the value is too large. Preserves +/-inf and NaN.
63 : // TODO(schwehr): This should probably be moved to port as it is likely
64 : // to be needed for other formats.
65 17 : double MapNoDataToFloat(double dfNoDataValue)
66 : {
67 17 : if (std::isinf(dfNoDataValue) || std::isnan(dfNoDataValue))
68 3 : return dfNoDataValue;
69 :
70 14 : if (dfNoDataValue >= std::numeric_limits<float>::max())
71 0 : return std::numeric_limits<float>::max();
72 :
73 14 : if (dfNoDataValue <= -std::numeric_limits<float>::max())
74 0 : return -std::numeric_limits<float>::max();
75 :
76 14 : return static_cast<double>(static_cast<float>(dfNoDataValue));
77 : }
78 :
79 : } // namespace
80 :
81 : static CPLString OSR_GDS(char **papszNV, const char *pszField,
82 : const char *pszDefaultValue);
83 :
84 : /************************************************************************/
85 : /* AAIGRasterBand() */
86 : /************************************************************************/
87 :
88 208 : AAIGRasterBand::AAIGRasterBand(AAIGDataset *poDSIn, int nDataStart)
89 208 : : panLineOffset(nullptr)
90 : {
91 208 : poDS = poDSIn;
92 :
93 208 : nBand = 1;
94 208 : eDataType = poDSIn->eDataType;
95 :
96 208 : nBlockXSize = poDSIn->nRasterXSize;
97 208 : nBlockYSize = 1;
98 :
99 208 : panLineOffset = static_cast<GUIntBig *>(
100 208 : VSI_CALLOC_VERBOSE(poDSIn->nRasterYSize, sizeof(GUIntBig)));
101 208 : if (panLineOffset == nullptr)
102 : {
103 0 : return;
104 : }
105 208 : panLineOffset[0] = nDataStart;
106 : }
107 :
108 : /************************************************************************/
109 : /* ~AAIGRasterBand() */
110 : /************************************************************************/
111 :
112 416 : AAIGRasterBand::~AAIGRasterBand()
113 : {
114 208 : CPLFree(panLineOffset);
115 416 : }
116 :
117 : /************************************************************************/
118 : /* IReadBlock() */
119 : /************************************************************************/
120 :
121 1125 : CPLErr AAIGRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage)
122 :
123 : {
124 1125 : AAIGDataset *poODS = static_cast<AAIGDataset *>(poDS);
125 :
126 1125 : if (nBlockYOff < 0 || nBlockYOff > poODS->nRasterYSize - 1 ||
127 1125 : nBlockXOff != 0 || panLineOffset == nullptr || poODS->fp == nullptr)
128 0 : return CE_Failure;
129 :
130 1125 : if (panLineOffset[nBlockYOff] == 0)
131 : {
132 22 : for (int iPrevLine = 1; iPrevLine <= nBlockYOff; iPrevLine++)
133 18 : if (panLineOffset[iPrevLine] == 0)
134 18 : IReadBlock(nBlockXOff, iPrevLine - 1, nullptr);
135 : }
136 :
137 1125 : if (panLineOffset[nBlockYOff] == 0)
138 0 : return CE_Failure;
139 :
140 1125 : if (poODS->Seek(panLineOffset[nBlockYOff]) != 0)
141 : {
142 0 : ReportError(CE_Failure, CPLE_FileIO,
143 : "Can't seek to offset %lu in input file to read data.",
144 0 : static_cast<long unsigned int>(panLineOffset[nBlockYOff]));
145 0 : return CE_Failure;
146 : }
147 :
148 34438 : for (int iPixel = 0; iPixel < poODS->nRasterXSize;)
149 : {
150 : // Suck up any pre-white space.
151 33313 : char chNext = '\0';
152 11096 : do
153 : {
154 44409 : chNext = poODS->Getc();
155 44409 : } while (isspace(static_cast<unsigned char>(chNext)));
156 :
157 33313 : char szToken[500] = {'\0'};
158 33313 : int iTokenChar = 0;
159 134727 : while (chNext != '\0' && !isspace(static_cast<unsigned char>(chNext)))
160 : {
161 101414 : if (iTokenChar == sizeof(szToken) - 2)
162 : {
163 0 : ReportError(CE_Failure, CPLE_FileIO,
164 : "Token too long at scanline %d.", nBlockYOff);
165 0 : return CE_Failure;
166 : }
167 :
168 101414 : szToken[iTokenChar++] = chNext;
169 101414 : chNext = poODS->Getc();
170 : }
171 :
172 33313 : if (chNext == '\0' && (iPixel != poODS->nRasterXSize - 1 ||
173 56 : nBlockYOff != poODS->nRasterYSize - 1))
174 : {
175 0 : ReportError(CE_Failure, CPLE_FileIO,
176 : "File short, can't read line %d.", nBlockYOff);
177 0 : return CE_Failure;
178 : }
179 :
180 33313 : szToken[iTokenChar] = '\0';
181 :
182 33313 : if (pImage != nullptr)
183 : {
184 : // "null" seems to be specific of D12 software
185 : // See https://github.com/OSGeo/gdal/issues/5095
186 33115 : if (eDataType == GDT_Float64)
187 : {
188 104 : if (strcmp(szToken, "null") == 0)
189 2 : reinterpret_cast<double *>(pImage)[iPixel] =
190 2 : -std::numeric_limits<double>::max();
191 : else
192 204 : reinterpret_cast<double *>(pImage)[iPixel] =
193 102 : CPLAtofM(szToken);
194 : }
195 33011 : else if (eDataType == GDT_Float32)
196 : {
197 2645 : if (strcmp(szToken, "null") == 0)
198 2 : reinterpret_cast<float *>(pImage)[iPixel] =
199 2 : -std::numeric_limits<float>::max();
200 : else
201 2643 : reinterpret_cast<float *>(pImage)[iPixel] =
202 2643 : DoubleToFloatClamp(CPLAtofM(szToken));
203 : }
204 : else
205 30366 : reinterpret_cast<GInt32 *>(pImage)[iPixel] =
206 30366 : static_cast<GInt32>(atoi(szToken));
207 : }
208 :
209 33313 : iPixel++;
210 : }
211 :
212 1125 : if (nBlockYOff < poODS->nRasterYSize - 1)
213 992 : panLineOffset[nBlockYOff + 1] = poODS->Tell();
214 :
215 1125 : return CE_None;
216 : }
217 :
218 : /************************************************************************/
219 : /* GetNoDataValue() */
220 : /************************************************************************/
221 :
222 285 : double AAIGRasterBand::GetNoDataValue(int *pbSuccess)
223 :
224 : {
225 285 : AAIGDataset *poODS = static_cast<AAIGDataset *>(poDS);
226 :
227 285 : if (pbSuccess)
228 258 : *pbSuccess = poODS->bNoDataSet;
229 :
230 285 : return poODS->dfNoDataValue;
231 : }
232 :
233 : /************************************************************************/
234 : /* SetNoDataValue() */
235 : /************************************************************************/
236 :
237 0 : CPLErr AAIGRasterBand::SetNoDataValue(double dfNoData)
238 :
239 : {
240 0 : AAIGDataset *poODS = static_cast<AAIGDataset *>(poDS);
241 :
242 0 : poODS->bNoDataSet = true;
243 0 : poODS->dfNoDataValue = dfNoData;
244 :
245 0 : return CE_None;
246 : }
247 :
248 : /************************************************************************/
249 : /* ==================================================================== */
250 : /* AAIGDataset */
251 : /* ==================================================================== */
252 : /************************************************************************/
253 :
254 : /************************************************************************/
255 : /* AAIGDataset() */
256 : /************************************************************************/
257 :
258 209 : AAIGDataset::AAIGDataset()
259 : : fp(nullptr), papszPrj(nullptr), nBufferOffset(0), nOffsetInBuffer(256),
260 209 : eDataType(GDT_Int32), bNoDataSet(false), dfNoDataValue(-9999.0)
261 : {
262 209 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
263 209 : memset(achReadBuf, 0, sizeof(achReadBuf));
264 209 : }
265 :
266 : /************************************************************************/
267 : /* ~AAIGDataset() */
268 : /************************************************************************/
269 :
270 408 : AAIGDataset::~AAIGDataset()
271 :
272 : {
273 209 : FlushCache(true);
274 :
275 209 : if (fp != nullptr)
276 : {
277 208 : if (VSIFCloseL(fp) != 0)
278 : {
279 0 : ReportError(CE_Failure, CPLE_FileIO, "I/O error");
280 : }
281 : }
282 :
283 209 : CSLDestroy(papszPrj);
284 408 : }
285 :
286 : /************************************************************************/
287 : /* Tell() */
288 : /************************************************************************/
289 :
290 992 : GUIntBig AAIGDataset::Tell() const
291 : {
292 992 : return nBufferOffset + nOffsetInBuffer;
293 : }
294 :
295 : /************************************************************************/
296 : /* Seek() */
297 : /************************************************************************/
298 :
299 1125 : int AAIGDataset::Seek(GUIntBig nNewOffset)
300 :
301 : {
302 1125 : nOffsetInBuffer = sizeof(achReadBuf);
303 1125 : return VSIFSeekL(fp, nNewOffset, SEEK_SET);
304 : }
305 :
306 : /************************************************************************/
307 : /* Getc() */
308 : /* */
309 : /* Read a single character from the input file (efficiently we */
310 : /* hope). */
311 : /************************************************************************/
312 :
313 145823 : char AAIGDataset::Getc()
314 :
315 : {
316 145823 : if (nOffsetInBuffer < static_cast<int>(sizeof(achReadBuf)))
317 144498 : return achReadBuf[nOffsetInBuffer++];
318 :
319 1325 : nBufferOffset = VSIFTellL(fp);
320 : const int nRead =
321 1325 : static_cast<int>(VSIFReadL(achReadBuf, 1, sizeof(achReadBuf), fp));
322 83131 : for (unsigned int i = nRead; i < sizeof(achReadBuf); i++)
323 81806 : achReadBuf[i] = '\0';
324 :
325 1325 : nOffsetInBuffer = 0;
326 :
327 1325 : return achReadBuf[nOffsetInBuffer++];
328 : }
329 :
330 : /************************************************************************/
331 : /* GetFileList() */
332 : /************************************************************************/
333 :
334 32 : char **AAIGDataset::GetFileList()
335 :
336 : {
337 32 : char **papszFileList = GDALPamDataset::GetFileList();
338 :
339 32 : if (papszPrj != nullptr)
340 12 : papszFileList = CSLAddString(papszFileList, osPrjFilename);
341 :
342 32 : return papszFileList;
343 : }
344 :
345 : /************************************************************************/
346 : /* Identify() */
347 : /************************************************************************/
348 :
349 62668 : int AAIGDataset::Identify(GDALOpenInfo *poOpenInfo)
350 :
351 : {
352 : // Does this look like an AI grid file?
353 62668 : if (poOpenInfo->nHeaderBytes < 40 ||
354 7832 : !(STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
355 7429 : "ncols") ||
356 7429 : STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
357 7429 : "nrows") ||
358 7429 : STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
359 7429 : "xllcorner") ||
360 7429 : STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
361 7429 : "yllcorner") ||
362 7429 : STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
363 7429 : "xllcenter") ||
364 7429 : STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
365 7429 : "yllcenter") ||
366 7429 : STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
367 7429 : "dx") ||
368 7429 : STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
369 : "dy") ||
370 7429 : STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
371 : "cellsize")))
372 62265 : return FALSE;
373 :
374 403 : return TRUE;
375 : }
376 :
377 : /************************************************************************/
378 : /* Identify() */
379 : /************************************************************************/
380 :
381 62220 : int GRASSASCIIDataset::Identify(GDALOpenInfo *poOpenInfo)
382 :
383 : {
384 : // Does this look like a GRASS ASCII grid file?
385 62220 : if (poOpenInfo->nHeaderBytes < 40 ||
386 7432 : !(STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
387 7428 : "north:") ||
388 7428 : STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
389 7428 : "south:") ||
390 7428 : STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
391 7428 : "east:") ||
392 7428 : STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
393 7428 : "west:") ||
394 7428 : STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
395 : "rows:") ||
396 7428 : STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
397 : "cols:")))
398 62216 : return FALSE;
399 :
400 4 : return TRUE;
401 : }
402 :
403 : /************************************************************************/
404 : /* Identify() */
405 : /************************************************************************/
406 :
407 62224 : int ISGDataset::Identify(GDALOpenInfo *poOpenInfo)
408 :
409 : {
410 : // Does this look like a ISG grid file?
411 62224 : if (poOpenInfo->nHeaderBytes < 40 ||
412 7436 : !strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
413 : "model name"))
414 : {
415 62208 : return FALSE;
416 : }
417 17 : for (int i = 0; i < 2; ++i)
418 : {
419 17 : if (strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
420 17 : "lat min") != nullptr &&
421 17 : strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
422 17 : "lat max") != nullptr &&
423 17 : strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
424 17 : "lon min") != nullptr &&
425 17 : strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
426 17 : "lon max") != nullptr &&
427 17 : strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
428 16 : "nrows") != nullptr &&
429 16 : strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
430 : "ncols") != nullptr)
431 : {
432 16 : return TRUE;
433 : }
434 : // Some files like https://isgeoid.polimi.it/Geoid/Europe/Slovenia/public/Slovenia_2016_SLO_VRP2016_Koper_hybrQ_20221122.isg
435 : // have initial comment lines, so we may need to ingest more bytes
436 1 : if (i == 0)
437 : {
438 1 : if (poOpenInfo->nHeaderBytes >= 8192)
439 0 : break;
440 1 : poOpenInfo->TryToIngest(8192);
441 : }
442 : }
443 :
444 0 : return TRUE;
445 : }
446 :
447 : /************************************************************************/
448 : /* Open() */
449 : /************************************************************************/
450 :
451 199 : GDALDataset *AAIGDataset::Open(GDALOpenInfo *poOpenInfo)
452 : {
453 : #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
454 : // During fuzzing, do not use Identify to reject crazy content.
455 199 : if (!Identify(poOpenInfo))
456 0 : return nullptr;
457 : #endif
458 :
459 199 : return CommonOpen(poOpenInfo, FORMAT_AAIG);
460 : }
461 :
462 : /************************************************************************/
463 : /* ParseHeader() */
464 : /************************************************************************/
465 :
466 199 : int AAIGDataset::ParseHeader(const char *pszHeader, const char *pszDataType)
467 : {
468 199 : char **papszTokens = CSLTokenizeString2(pszHeader, " \n\r\t", 0);
469 199 : const int nTokens = CSLCount(papszTokens);
470 :
471 199 : int i = 0;
472 199 : if ((i = CSLFindString(papszTokens, "ncols")) < 0 || i + 1 >= nTokens)
473 : {
474 0 : CSLDestroy(papszTokens);
475 0 : return FALSE;
476 : }
477 199 : nRasterXSize = atoi(papszTokens[i + 1]);
478 199 : if ((i = CSLFindString(papszTokens, "nrows")) < 0 || i + 1 >= nTokens)
479 : {
480 0 : CSLDestroy(papszTokens);
481 0 : return FALSE;
482 : }
483 199 : nRasterYSize = atoi(papszTokens[i + 1]);
484 :
485 199 : if (!GDALCheckDatasetDimensions(nRasterXSize, nRasterYSize))
486 : {
487 0 : CSLDestroy(papszTokens);
488 0 : return FALSE;
489 : }
490 :
491 : // TODO(schwehr): Would be good to also factor the file size into the max.
492 : // TODO(schwehr): Allow the user to disable this check.
493 : // The driver allocates a panLineOffset array based on nRasterYSize
494 199 : constexpr int kMaxDimSize = 10000000; // 1e7 cells.
495 199 : if (nRasterXSize > kMaxDimSize || nRasterYSize > kMaxDimSize)
496 : {
497 0 : CSLDestroy(papszTokens);
498 0 : return FALSE;
499 : }
500 :
501 199 : double dfCellDX = 0.0;
502 199 : double dfCellDY = 0.0;
503 199 : if ((i = CSLFindString(papszTokens, "cellsize")) < 0)
504 : {
505 : int iDX, iDY;
506 4 : if ((iDX = CSLFindString(papszTokens, "dx")) < 0 ||
507 4 : (iDY = CSLFindString(papszTokens, "dy")) < 0 ||
508 8 : iDX + 1 >= nTokens || iDY + 1 >= nTokens)
509 : {
510 0 : CSLDestroy(papszTokens);
511 0 : return FALSE;
512 : }
513 :
514 4 : dfCellDX = CPLAtofM(papszTokens[iDX + 1]);
515 4 : dfCellDY = CPLAtofM(papszTokens[iDY + 1]);
516 : }
517 : else
518 : {
519 195 : if (i + 1 >= nTokens)
520 : {
521 0 : CSLDestroy(papszTokens);
522 0 : return FALSE;
523 : }
524 195 : dfCellDY = CPLAtofM(papszTokens[i + 1]);
525 195 : dfCellDX = dfCellDY;
526 : }
527 :
528 199 : int j = 0;
529 199 : if ((i = CSLFindString(papszTokens, "xllcorner")) >= 0 &&
530 398 : (j = CSLFindString(papszTokens, "yllcorner")) >= 0 && i + 1 < nTokens &&
531 199 : j + 1 < nTokens)
532 : {
533 199 : m_gt[0] = CPLAtofM(papszTokens[i + 1]);
534 :
535 : // Small hack to compensate from insufficient precision in cellsize
536 : // parameter in datasets of
537 : // http://ccafs-climate.org/data/A2a_2020s/hccpr_hadcm3
538 0 : if ((nRasterXSize % 360) == 0 && fabs(m_gt[0] - (-180.0)) < 1e-12 &&
539 199 : dfCellDX == dfCellDY &&
540 0 : fabs(dfCellDX - (360.0 / nRasterXSize)) < 1e-9)
541 : {
542 0 : dfCellDY = 360.0 / nRasterXSize;
543 0 : dfCellDX = dfCellDY;
544 : }
545 :
546 199 : m_gt[1] = dfCellDX;
547 199 : m_gt[2] = 0.0;
548 199 : m_gt[3] = CPLAtofM(papszTokens[j + 1]) + nRasterYSize * dfCellDY;
549 199 : m_gt[4] = 0.0;
550 199 : m_gt[5] = -dfCellDY;
551 : }
552 0 : else if ((i = CSLFindString(papszTokens, "xllcenter")) >= 0 &&
553 0 : (j = CSLFindString(papszTokens, "yllcenter")) >= 0 &&
554 0 : i + 1 < nTokens && j + 1 < nTokens)
555 : {
556 0 : SetMetadataItem(GDALMD_AREA_OR_POINT, GDALMD_AOP_POINT);
557 :
558 0 : m_gt[0] = CPLAtofM(papszTokens[i + 1]) - 0.5 * dfCellDX;
559 0 : m_gt[1] = dfCellDX;
560 0 : m_gt[2] = 0.0;
561 0 : m_gt[3] = CPLAtofM(papszTokens[j + 1]) - 0.5 * dfCellDY +
562 0 : nRasterYSize * dfCellDY;
563 0 : m_gt[4] = 0.0;
564 0 : m_gt[5] = -dfCellDY;
565 : }
566 : else
567 : {
568 0 : m_gt[0] = 0.0;
569 0 : m_gt[1] = dfCellDX;
570 0 : m_gt[2] = 0.0;
571 0 : m_gt[3] = 0.0;
572 0 : m_gt[4] = 0.0;
573 0 : m_gt[5] = -dfCellDY;
574 : }
575 :
576 278 : if ((i = CSLFindString(papszTokens, "NODATA_value")) >= 0 &&
577 79 : i + 1 < nTokens)
578 : {
579 79 : const char *pszNoData = papszTokens[i + 1];
580 :
581 79 : bNoDataSet = true;
582 79 : if (strcmp(pszNoData, "null") == 0)
583 : {
584 : // "null" seems to be specific of D12 software
585 : // See https://github.com/OSGeo/gdal/issues/5095
586 2 : if (pszDataType == nullptr || eDataType == GDT_Float32)
587 : {
588 1 : dfNoDataValue = -std::numeric_limits<float>::max();
589 1 : eDataType = GDT_Float32;
590 : }
591 : else
592 : {
593 1 : dfNoDataValue = -std::numeric_limits<double>::max();
594 1 : eDataType = GDT_Float64;
595 : }
596 : }
597 : else
598 : {
599 77 : dfNoDataValue = CPLAtofM(pszNoData);
600 149 : if (pszDataType == nullptr &&
601 72 : (strchr(pszNoData, '.') != nullptr ||
602 128 : strchr(pszNoData, ',') != nullptr ||
603 64 : std::isnan(dfNoDataValue) ||
604 61 : std::numeric_limits<int>::min() > dfNoDataValue ||
605 61 : dfNoDataValue > std::numeric_limits<int>::max()))
606 : {
607 11 : eDataType = GDT_Float32;
608 22 : if (!std::isinf(dfNoDataValue) &&
609 11 : (fabs(dfNoDataValue) < std::numeric_limits<float>::min() ||
610 10 : fabs(dfNoDataValue) > std::numeric_limits<float>::max()))
611 : {
612 1 : eDataType = GDT_Float64;
613 : }
614 : }
615 77 : if (eDataType == GDT_Float32)
616 : {
617 10 : dfNoDataValue = MapNoDataToFloat(dfNoDataValue);
618 : }
619 : }
620 : }
621 :
622 199 : CSLDestroy(papszTokens);
623 :
624 199 : return TRUE;
625 : }
626 :
627 : /************************************************************************/
628 : /* Open() */
629 : /************************************************************************/
630 :
631 2 : GDALDataset *GRASSASCIIDataset::Open(GDALOpenInfo *poOpenInfo)
632 : {
633 : #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
634 : // During fuzzing, do not use Identify to reject crazy content.
635 2 : if (!Identify(poOpenInfo))
636 0 : return nullptr;
637 : #endif
638 :
639 2 : return CommonOpen(poOpenInfo, FORMAT_GRASSASCII);
640 : }
641 :
642 : /************************************************************************/
643 : /* ParseHeader() */
644 : /************************************************************************/
645 :
646 2 : int GRASSASCIIDataset::ParseHeader(const char *pszHeader,
647 : const char *pszDataType)
648 : {
649 2 : char **papszTokens = CSLTokenizeString2(pszHeader, " \n\r\t:", 0);
650 2 : const int nTokens = CSLCount(papszTokens);
651 2 : int i = 0;
652 2 : if ((i = CSLFindString(papszTokens, "cols")) < 0 || i + 1 >= nTokens)
653 : {
654 0 : CSLDestroy(papszTokens);
655 0 : return FALSE;
656 : }
657 2 : nRasterXSize = atoi(papszTokens[i + 1]);
658 2 : if ((i = CSLFindString(papszTokens, "rows")) < 0 || i + 1 >= nTokens)
659 : {
660 0 : CSLDestroy(papszTokens);
661 0 : return FALSE;
662 : }
663 2 : nRasterYSize = atoi(papszTokens[i + 1]);
664 :
665 2 : if (!GDALCheckDatasetDimensions(nRasterXSize, nRasterYSize))
666 : {
667 0 : CSLDestroy(papszTokens);
668 0 : return FALSE;
669 : }
670 :
671 : // TODO(schwehr): Would be good to also factor the file size into the max.
672 : // TODO(schwehr): Allow the user to disable this check.
673 : // The driver allocates a panLineOffset array based on nRasterYSize
674 2 : constexpr int kMaxDimSize = 10000000; // 1e7 cells.
675 2 : if (nRasterXSize > kMaxDimSize || nRasterYSize > kMaxDimSize)
676 : {
677 0 : CSLDestroy(papszTokens);
678 0 : return FALSE;
679 : }
680 :
681 2 : const int iNorth = CSLFindString(papszTokens, "north");
682 2 : const int iSouth = CSLFindString(papszTokens, "south");
683 2 : const int iEast = CSLFindString(papszTokens, "east");
684 2 : const int iWest = CSLFindString(papszTokens, "west");
685 :
686 4 : if (iNorth == -1 || iSouth == -1 || iEast == -1 || iWest == -1 ||
687 2 : std::max(std::max(iNorth, iSouth), std::max(iEast, iWest)) + 1 >=
688 : nTokens)
689 : {
690 0 : CSLDestroy(papszTokens);
691 0 : return FALSE;
692 : }
693 :
694 2 : const double dfNorth = CPLAtofM(papszTokens[iNorth + 1]);
695 2 : const double dfSouth = CPLAtofM(papszTokens[iSouth + 1]);
696 2 : const double dfEast = CPLAtofM(papszTokens[iEast + 1]);
697 2 : const double dfWest = CPLAtofM(papszTokens[iWest + 1]);
698 2 : const double dfPixelXSize = (dfEast - dfWest) / nRasterXSize;
699 2 : const double dfPixelYSize = (dfNorth - dfSouth) / nRasterYSize;
700 :
701 2 : m_gt[0] = dfWest;
702 2 : m_gt[1] = dfPixelXSize;
703 2 : m_gt[2] = 0.0;
704 2 : m_gt[3] = dfNorth;
705 2 : m_gt[4] = 0.0;
706 2 : m_gt[5] = -dfPixelYSize;
707 :
708 2 : if ((i = CSLFindString(papszTokens, "null")) >= 0 && i + 1 < nTokens)
709 : {
710 0 : const char *pszNoData = papszTokens[i + 1];
711 :
712 0 : bNoDataSet = true;
713 0 : dfNoDataValue = CPLAtofM(pszNoData);
714 0 : if (pszDataType == nullptr &&
715 0 : (strchr(pszNoData, '.') != nullptr ||
716 0 : strchr(pszNoData, ',') != nullptr || std::isnan(dfNoDataValue) ||
717 0 : std::numeric_limits<int>::min() > dfNoDataValue ||
718 0 : dfNoDataValue > std::numeric_limits<int>::max()))
719 : {
720 0 : eDataType = GDT_Float32;
721 : }
722 0 : if (eDataType == GDT_Float32)
723 : {
724 0 : dfNoDataValue = MapNoDataToFloat(dfNoDataValue);
725 : }
726 : }
727 :
728 2 : if ((i = CSLFindString(papszTokens, "type")) >= 0 && i + 1 < nTokens)
729 : {
730 0 : const char *pszType = papszTokens[i + 1];
731 0 : if (EQUAL(pszType, "int"))
732 0 : eDataType = GDT_Int32;
733 0 : else if (EQUAL(pszType, "float"))
734 0 : eDataType = GDT_Float32;
735 0 : else if (EQUAL(pszType, "double"))
736 0 : eDataType = GDT_Float64;
737 : else
738 : {
739 0 : ReportError(CE_Warning, CPLE_AppDefined,
740 : "Invalid value for type parameter : %s", pszType);
741 : }
742 : }
743 :
744 2 : CSLDestroy(papszTokens);
745 :
746 2 : return TRUE;
747 : }
748 :
749 : /************************************************************************/
750 : /* Open() */
751 : /************************************************************************/
752 :
753 8 : GDALDataset *ISGDataset::Open(GDALOpenInfo *poOpenInfo)
754 : {
755 : #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
756 : // During fuzzing, do not use Identify to reject crazy content.
757 8 : if (!Identify(poOpenInfo))
758 0 : return nullptr;
759 : #endif
760 :
761 8 : return CommonOpen(poOpenInfo, FORMAT_ISG);
762 : }
763 :
764 : /************************************************************************/
765 : /* ParseHeader() */
766 : /************************************************************************/
767 :
768 8 : int ISGDataset::ParseHeader(const char *pszHeader, const char *)
769 : {
770 : // See https://www.isgeoid.polimi.it/Geoid/ISG_format_v10_20160121.pdf
771 : // https://www.isgeoid.polimi.it/Geoid/ISG_format_v101_20180915.pdf
772 : // https://www.isgeoid.polimi.it/Geoid/ISG_format_v20_20200625.pdf
773 :
774 16 : CPLStringList aosLines(CSLTokenizeString2(pszHeader, "\n\r", 0));
775 16 : CPLString osLatMin;
776 16 : CPLString osLatMax;
777 16 : CPLString osLonMin;
778 16 : CPLString osLonMax;
779 16 : CPLString osDeltaLat;
780 16 : CPLString osDeltaLon;
781 16 : CPLString osRows;
782 16 : CPLString osCols;
783 16 : CPLString osNodata;
784 16 : std::string osISGFormat;
785 16 : std::string osDataFormat; // ISG 2.0
786 16 : std::string osDataOrdering; // ISG 2.0
787 16 : std::string osCoordType; // ISG 2.0
788 16 : std::string osCoordUnits; // ISG 2.0
789 180 : for (int iLine = 0; iLine < aosLines.size(); iLine++)
790 : {
791 344 : CPLStringList aosTokens(CSLTokenizeString2(aosLines[iLine], ":=", 0));
792 172 : if (aosTokens.size() == 2)
793 : {
794 280 : const CPLString osLeft(CPLString(aosTokens[0]).Trim());
795 280 : CPLString osRight(CPLString(aosTokens[1]).Trim());
796 140 : if (osLeft == "lat min")
797 8 : osLatMin = std::move(osRight);
798 132 : else if (osLeft == "lat max")
799 8 : osLatMax = std::move(osRight);
800 124 : else if (osLeft == "lon min")
801 8 : osLonMin = std::move(osRight);
802 116 : else if (osLeft == "lon max")
803 8 : osLonMax = std::move(osRight);
804 108 : else if (osLeft == "delta lat")
805 8 : osDeltaLat = std::move(osRight);
806 100 : else if (osLeft == "delta lon")
807 8 : osDeltaLon = std::move(osRight);
808 92 : else if (osLeft == "nrows")
809 8 : osRows = std::move(osRight);
810 84 : else if (osLeft == "ncols")
811 8 : osCols = std::move(osRight);
812 76 : else if (osLeft == "nodata")
813 8 : osNodata = std::move(osRight);
814 68 : else if (osLeft == "model name")
815 8 : SetMetadataItem("MODEL_NAME", osRight);
816 60 : else if (osLeft == "model type")
817 8 : SetMetadataItem("MODEL_TYPE", osRight);
818 52 : else if (osLeft == "units" || osLeft == "data units")
819 8 : osUnits = std::move(osRight);
820 44 : else if (osLeft == "ISG format")
821 8 : osISGFormat = std::move(osRight);
822 36 : else if (osLeft == "data format")
823 2 : osDataFormat = std::move(osRight);
824 34 : else if (osLeft == "data ordering")
825 2 : osDataOrdering = std::move(osRight);
826 32 : else if (osLeft == "coord type")
827 2 : osCoordType = std::move(osRight);
828 30 : else if (osLeft == "coord units")
829 2 : osCoordUnits = std::move(osRight);
830 : }
831 : }
832 : const double dfVersion =
833 8 : osISGFormat.empty() ? 0.0 : CPLAtof(osISGFormat.c_str());
834 24 : if (osLatMin.empty() || osLatMax.empty() || osLonMin.empty() ||
835 24 : osLonMax.empty() || osDeltaLat.empty() || osDeltaLon.empty() ||
836 24 : osRows.empty() || osCols.empty())
837 : {
838 0 : return FALSE;
839 : }
840 8 : if (!osDataFormat.empty() && osDataFormat != "grid")
841 : {
842 0 : CPLError(CE_Failure, CPLE_NotSupported,
843 : "ISG: data format = %s not supported", osDataFormat.c_str());
844 0 : return FALSE;
845 : }
846 8 : if (!osDataOrdering.empty() && osDataOrdering != "N-to-S, W-to-E")
847 : {
848 0 : CPLError(CE_Failure, CPLE_NotSupported,
849 : "ISG: data ordering = %s not supported",
850 : osDataOrdering.c_str());
851 0 : return FALSE;
852 : }
853 8 : if (!osCoordType.empty() && osCoordType != "geodetic")
854 : {
855 0 : CPLError(CE_Failure, CPLE_NotSupported,
856 : "ISG: coord type = %s not supported", osCoordType.c_str());
857 0 : return FALSE;
858 : }
859 :
860 6 : const auto parseDMS = [](CPLString &str)
861 : {
862 12 : const std::string degreeSymbol{"\xc2\xb0"};
863 6 : str.replaceAll(degreeSymbol, "D");
864 12 : return CPLDMSToDec(str);
865 : };
866 :
867 8 : bool useDMS = false;
868 8 : if (!osCoordUnits.empty())
869 : {
870 2 : if (osCoordUnits == "dms")
871 : {
872 : // CPLDMSToDec does not support the non ascii char for degree used in ISG.
873 : // just replace it with "D" to make it compatible.
874 1 : useDMS = true;
875 : }
876 1 : else if (osCoordUnits != "deg")
877 : {
878 0 : CPLError(CE_Failure, CPLE_NotSupported,
879 : "ISG: coord units = %s not supported",
880 : osCoordUnits.c_str());
881 0 : return FALSE;
882 : }
883 : }
884 8 : double dfLatMin = useDMS ? parseDMS(osLatMin) : CPLAtof(osLatMin);
885 8 : double dfLatMax = useDMS ? parseDMS(osLatMax) : CPLAtof(osLatMax);
886 8 : double dfLonMin = useDMS ? parseDMS(osLonMin) : CPLAtof(osLonMin);
887 8 : double dfLonMax = useDMS ? parseDMS(osLonMax) : CPLAtof(osLonMax);
888 8 : double dfDeltaLon = useDMS ? parseDMS(osDeltaLon) : CPLAtof(osDeltaLon);
889 8 : double dfDeltaLat = useDMS ? parseDMS(osDeltaLat) : CPLAtof(osDeltaLat);
890 8 : if (dfVersion >= 2.0)
891 : {
892 2 : dfLatMin -= dfDeltaLat / 2.0;
893 2 : dfLatMax += dfDeltaLat / 2.0;
894 2 : dfLonMin -= dfDeltaLon / 2.0;
895 2 : dfLonMax += dfDeltaLon / 2.0;
896 : }
897 8 : const int nRows = atoi(osRows);
898 8 : const int nCols = atoi(osCols);
899 8 : if (nRows <= 0 || nCols <= 0 ||
900 8 : !(dfDeltaLat > 0 && dfDeltaLon > 0 && dfDeltaLat < 180 &&
901 8 : dfDeltaLon < 360))
902 : {
903 0 : return FALSE;
904 : }
905 :
906 : // Correct rounding errors.
907 :
908 14 : const auto TryRoundTo = [](double &dfDelta, double dfRoundedDelta,
909 : double &dfMin, double &dfMax, int nVals,
910 : double dfRelTol)
911 : {
912 14 : double dfMinTry = dfMin;
913 14 : double dfMaxTry = dfMax;
914 14 : double dfDeltaTry = dfDelta;
915 14 : if (dfRoundedDelta != dfDelta &&
916 6 : fabs(fabs(dfMin / dfRoundedDelta) -
917 6 : (floor(fabs(dfMin / dfRoundedDelta)) + 0.5)) < dfRelTol &&
918 6 : fabs(fabs(dfMax / dfRoundedDelta) -
919 6 : (floor(fabs(dfMax / dfRoundedDelta)) + 0.5)) < dfRelTol)
920 : {
921 : {
922 5 : double dfVal = (floor(fabs(dfMin / dfRoundedDelta)) + 0.5) *
923 : dfRoundedDelta;
924 5 : dfMinTry = (dfMin < 0) ? -dfVal : dfVal;
925 : }
926 : {
927 5 : double dfVal = (floor(fabs(dfMax / dfRoundedDelta)) + 0.5) *
928 : dfRoundedDelta;
929 5 : dfMaxTry = (dfMax < 0) ? -dfVal : dfVal;
930 : }
931 5 : dfDeltaTry = dfRoundedDelta;
932 : }
933 9 : else if (dfRoundedDelta != dfDelta &&
934 1 : fabs(fabs(dfMin / dfRoundedDelta) -
935 1 : (floor(fabs(dfMin / dfRoundedDelta) + 0.5) + 0.)) <
936 0 : dfRelTol &&
937 0 : fabs(fabs(dfMax / dfRoundedDelta) -
938 0 : (floor(fabs(dfMax / dfRoundedDelta) + 0.5) + 0.)) <
939 : dfRelTol)
940 : {
941 : {
942 0 : double dfVal =
943 0 : (floor(fabs(dfMin / dfRoundedDelta) + 0.5) + 0.) *
944 : dfRoundedDelta;
945 0 : dfMinTry = (dfMin < 0) ? -dfVal : dfVal;
946 : }
947 : {
948 0 : double dfVal =
949 0 : (floor(fabs(dfMax / dfRoundedDelta) + 0.5) + 0.) *
950 : dfRoundedDelta;
951 0 : dfMaxTry = (dfMax < 0) ? -dfVal : dfVal;
952 : }
953 0 : dfDeltaTry = dfRoundedDelta;
954 : }
955 14 : if (fabs(dfMinTry + dfDeltaTry * nVals - dfMaxTry) <
956 14 : dfRelTol * dfDeltaTry)
957 : {
958 10 : dfMin = dfMinTry;
959 10 : dfMax = dfMaxTry;
960 10 : dfDelta = dfDeltaTry;
961 10 : return true;
962 : }
963 4 : return false;
964 : };
965 :
966 : const double dfRoundedDeltaLon =
967 8 : (osDeltaLon == "0.0167" ||
968 7 : (dfDeltaLon < 1 &&
969 7 : fabs(1. / dfDeltaLon - floor(1. / dfDeltaLon + 0.5)) < 0.06))
970 15 : ? 1. / floor(1. / dfDeltaLon + 0.5)
971 8 : : dfDeltaLon;
972 :
973 : const double dfRoundedDeltaLat =
974 8 : (osDeltaLat == "0.0167" ||
975 4 : (dfDeltaLat < 1 &&
976 4 : fabs(1. / dfDeltaLat - floor(1. / dfDeltaLat + 0.5)) < 0.06))
977 12 : ? 1. / floor(1. / dfDeltaLat + 0.5)
978 8 : : dfDeltaLat;
979 :
980 8 : bool bOK = TryRoundTo(dfDeltaLon, dfRoundedDeltaLon, dfLonMin, dfLonMax,
981 12 : nCols, 1e-2) &&
982 4 : TryRoundTo(dfDeltaLat, dfRoundedDeltaLat, dfLatMin, dfLatMax,
983 8 : nRows, 1e-2);
984 8 : if (!bOK && osDeltaLon == "0.0167" && osDeltaLat == "0.0167")
985 : {
986 : // For https://www.isgeoid.polimi.it/Geoid/America/Argentina/public/GEOIDEAR16_20160419.isg
987 1 : bOK =
988 2 : TryRoundTo(dfDeltaLon, 0.016667, dfLonMin, dfLonMax, nCols, 1e-1) &&
989 1 : TryRoundTo(dfDeltaLat, 0.016667, dfLatMin, dfLatMax, nRows, 1e-1);
990 : }
991 8 : if (!bOK)
992 : {
993 : // 0.005 is what would be needed for the above GEOIDEAR16_20160419.isg
994 : // file without the specific fine tuning done.
995 6 : if ((fabs((dfLonMax - dfLonMin) / nCols - dfDeltaLon) <
996 4 : 0.005 * dfDeltaLon &&
997 1 : fabs((dfLatMax - dfLatMin) / nRows - dfDeltaLat) <
998 5 : 0.005 * dfDeltaLat) ||
999 2 : CPLTestBool(
1000 : CPLGetConfigOption("ISG_SKIP_GEOREF_CONSISTENCY_CHECK", "NO")))
1001 : {
1002 2 : CPLError(CE_Warning, CPLE_AppDefined,
1003 : "Georeference might be slightly approximate due to "
1004 : "rounding of coordinates and resolution in file header.");
1005 2 : dfDeltaLon = (dfLonMax - dfLonMin) / nCols;
1006 2 : dfDeltaLat = (dfLatMax - dfLatMin) / nRows;
1007 : }
1008 : else
1009 : {
1010 1 : CPLError(CE_Failure, CPLE_AppDefined,
1011 : "Inconsistent extent/resolution/raster dimension, or "
1012 : "rounding of coordinates and resolution in file header "
1013 : "higher than accepted. You may skip this consistency "
1014 : "check by setting the ISG_SKIP_GEOREF_CONSISTENCY_CHECK "
1015 : "configuration option to YES.");
1016 1 : return false;
1017 : }
1018 : }
1019 7 : nRasterXSize = nCols;
1020 7 : nRasterYSize = nRows;
1021 7 : m_gt[0] = dfLonMin;
1022 7 : m_gt[1] = dfDeltaLon;
1023 7 : m_gt[2] = 0.0;
1024 7 : m_gt[3] = dfLatMax;
1025 7 : m_gt[4] = 0.0;
1026 7 : m_gt[5] = -dfDeltaLat;
1027 7 : if (!osNodata.empty())
1028 : {
1029 7 : bNoDataSet = true;
1030 7 : dfNoDataValue = MapNoDataToFloat(CPLAtof(osNodata));
1031 : }
1032 7 : return TRUE;
1033 : }
1034 :
1035 : /************************************************************************/
1036 : /* CommonOpen() */
1037 : /************************************************************************/
1038 :
1039 209 : GDALDataset *AAIGDataset::CommonOpen(GDALOpenInfo *poOpenInfo,
1040 : GridFormat eFormat)
1041 : {
1042 209 : if (poOpenInfo->fpL == nullptr)
1043 0 : return nullptr;
1044 :
1045 : // Create a corresponding GDALDataset.
1046 209 : AAIGDataset *poDS = nullptr;
1047 :
1048 209 : if (eFormat == FORMAT_AAIG)
1049 199 : poDS = new AAIGDataset();
1050 10 : else if (eFormat == FORMAT_GRASSASCII)
1051 2 : poDS = new GRASSASCIIDataset();
1052 : else
1053 : {
1054 8 : poDS = new ISGDataset();
1055 8 : poDS->eDataType = GDT_Float32;
1056 : }
1057 :
1058 219 : const char *pszDataTypeOption = eFormat == FORMAT_AAIG ? "AAIGRID_DATATYPE"
1059 : : eFormat == FORMAT_GRASSASCII
1060 10 : ? "GRASSASCIIGRID_DATATYPE"
1061 : : nullptr;
1062 :
1063 : const char *pszDataType =
1064 209 : pszDataTypeOption ? CPLGetConfigOption(pszDataTypeOption, nullptr)
1065 209 : : nullptr;
1066 209 : if (pszDataType == nullptr)
1067 : {
1068 : pszDataType =
1069 207 : CSLFetchNameValue(poOpenInfo->papszOpenOptions, "DATATYPE");
1070 : }
1071 209 : if (pszDataType != nullptr)
1072 : {
1073 6 : poDS->eDataType = GDALGetDataTypeByName(pszDataType);
1074 6 : if (!(poDS->eDataType == GDT_Int32 || poDS->eDataType == GDT_Float32 ||
1075 6 : poDS->eDataType == GDT_Float64))
1076 : {
1077 0 : ReportError(poOpenInfo->pszFilename, CE_Warning, CPLE_NotSupported,
1078 : "Unsupported value for %s : %s", pszDataTypeOption,
1079 : pszDataType);
1080 0 : poDS->eDataType = GDT_Int32;
1081 0 : pszDataType = nullptr;
1082 : }
1083 : }
1084 :
1085 : // Parse the header.
1086 209 : if (!poDS->ParseHeader(
1087 209 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
1088 209 : pszDataType))
1089 : {
1090 1 : delete poDS;
1091 1 : return nullptr;
1092 : }
1093 :
1094 208 : poDS->fp = poOpenInfo->fpL;
1095 208 : poOpenInfo->fpL = nullptr;
1096 :
1097 : // Find the start of real data.
1098 208 : int nStartOfData = 0;
1099 :
1100 208 : if (eFormat == FORMAT_ISG)
1101 : {
1102 : const char *pszEOH =
1103 7 : strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
1104 : "end_of_head");
1105 7 : if (pszEOH == nullptr)
1106 : {
1107 0 : delete poDS;
1108 0 : return nullptr;
1109 : }
1110 443 : for (int i = 0; pszEOH[i]; i++)
1111 : {
1112 443 : if (pszEOH[i] == '\n' || pszEOH[i] == '\r')
1113 : {
1114 7 : nStartOfData =
1115 7 : static_cast<int>(pszEOH - reinterpret_cast<const char *>(
1116 7 : poOpenInfo->pabyHeader)) +
1117 : i;
1118 7 : break;
1119 : }
1120 : }
1121 7 : if (nStartOfData == 0)
1122 : {
1123 0 : delete poDS;
1124 0 : return nullptr;
1125 : }
1126 7 : if (poOpenInfo->pabyHeader[nStartOfData] == '\n' ||
1127 0 : poOpenInfo->pabyHeader[nStartOfData] == '\r')
1128 : {
1129 7 : nStartOfData++;
1130 : }
1131 :
1132 7 : poDS->m_oSRS.importFromWkt(SRS_WKT_WGS84_LAT_LONG);
1133 : }
1134 : else
1135 : {
1136 22644 : for (int i = 2; true; i++)
1137 : {
1138 22644 : if (poOpenInfo->pabyHeader[i] == '\0')
1139 : {
1140 0 : ReportError(poOpenInfo->pszFilename, CE_Failure,
1141 : CPLE_AppDefined,
1142 : "Couldn't find data values in ASCII Grid file.");
1143 0 : delete poDS;
1144 0 : return nullptr;
1145 : }
1146 :
1147 22644 : if (poOpenInfo->pabyHeader[i - 1] == '\n' ||
1148 21554 : poOpenInfo->pabyHeader[i - 2] == '\n' ||
1149 20665 : poOpenInfo->pabyHeader[i - 1] == '\r' ||
1150 20581 : poOpenInfo->pabyHeader[i - 2] == '\r')
1151 : {
1152 2063 : if ((!isalpha(static_cast<unsigned char>(
1153 2063 : poOpenInfo->pabyHeader[i])) ||
1154 : // null seems to be specific of D12 software
1155 : // See https://github.com/OSGeo/gdal/issues/5095
1156 1783 : (i + 5 < poOpenInfo->nHeaderBytes &&
1157 1783 : memcmp(poOpenInfo->pabyHeader + i, "null ", 5) == 0) ||
1158 1781 : (i + 4 < poOpenInfo->nHeaderBytes &&
1159 1781 : EQUALN(reinterpret_cast<const char *>(
1160 : poOpenInfo->pabyHeader + i),
1161 285 : "nan ", 4))) &&
1162 285 : poOpenInfo->pabyHeader[i] != '\n' &&
1163 201 : poOpenInfo->pabyHeader[i] != '\r')
1164 : {
1165 201 : nStartOfData = i;
1166 :
1167 : // Beginning of real data found.
1168 201 : break;
1169 : }
1170 : }
1171 : }
1172 : }
1173 :
1174 : // Recognize the type of data.
1175 208 : CPLAssert(nullptr != poDS->fp);
1176 :
1177 208 : if (pszDataType == nullptr && poDS->eDataType != GDT_Float32 &&
1178 184 : poDS->eDataType != GDT_Float64)
1179 : {
1180 : // Allocate 100K chunk + 1 extra byte for NULL character.
1181 183 : constexpr size_t nChunkSize = 1024 * 100;
1182 : GByte *pabyChunk = static_cast<GByte *>(
1183 183 : VSI_CALLOC_VERBOSE(nChunkSize + 1, sizeof(GByte)));
1184 183 : if (pabyChunk == nullptr)
1185 : {
1186 0 : delete poDS;
1187 0 : return nullptr;
1188 : }
1189 183 : pabyChunk[nChunkSize] = '\0';
1190 :
1191 183 : if (VSIFSeekL(poDS->fp, nStartOfData, SEEK_SET) < 0)
1192 : {
1193 0 : delete poDS;
1194 0 : VSIFree(pabyChunk);
1195 0 : return nullptr;
1196 : }
1197 :
1198 : // Scan for dot in subsequent chunks of data.
1199 366 : while (!VSIFEofL(poDS->fp))
1200 : {
1201 183 : const size_t nLen = VSIFReadL(pabyChunk, 1, nChunkSize, poDS->fp);
1202 :
1203 175584 : for (size_t i = 0; i < nLen; i++)
1204 : {
1205 175446 : const GByte ch = pabyChunk[i];
1206 175446 : if (ch == '.' || ch == ',' || ch == 'e' || ch == 'E')
1207 : {
1208 45 : poDS->eDataType = GDT_Float32;
1209 45 : break;
1210 : }
1211 : }
1212 : }
1213 :
1214 : // Deallocate chunk.
1215 183 : VSIFree(pabyChunk);
1216 : }
1217 :
1218 : // Create band information objects.
1219 208 : AAIGRasterBand *band = new AAIGRasterBand(poDS, nStartOfData);
1220 208 : poDS->SetBand(1, band);
1221 208 : if (band->panLineOffset == nullptr)
1222 : {
1223 0 : delete poDS;
1224 0 : return nullptr;
1225 : }
1226 208 : if (!poDS->osUnits.empty())
1227 : {
1228 7 : poDS->GetRasterBand(1)->SetUnitType(poDS->osUnits);
1229 : }
1230 :
1231 : // Try to read projection file.
1232 : char *const pszDirname =
1233 208 : CPLStrdup(CPLGetPathSafe(poOpenInfo->pszFilename).c_str());
1234 : char *const pszBasename =
1235 208 : CPLStrdup(CPLGetBasenameSafe(poOpenInfo->pszFilename).c_str());
1236 :
1237 208 : poDS->osPrjFilename = CPLFormFilenameSafe(pszDirname, pszBasename, "prj");
1238 208 : int nRet = 0;
1239 : {
1240 : VSIStatBufL sStatBuf;
1241 208 : nRet = VSIStatL(poDS->osPrjFilename, &sStatBuf);
1242 : }
1243 208 : if (nRet != 0 && VSIIsCaseSensitiveFS(poDS->osPrjFilename))
1244 : {
1245 : poDS->osPrjFilename =
1246 160 : CPLFormFilenameSafe(pszDirname, pszBasename, "PRJ");
1247 :
1248 : VSIStatBufL sStatBuf;
1249 160 : nRet = VSIStatL(poDS->osPrjFilename, &sStatBuf);
1250 : }
1251 :
1252 208 : if (nRet == 0)
1253 : {
1254 50 : poDS->papszPrj = CSLLoad(poDS->osPrjFilename);
1255 :
1256 50 : CPLDebug("AAIGrid", "Loaded SRS from %s", poDS->osPrjFilename.c_str());
1257 :
1258 100 : OGRSpatialReference oSRS;
1259 50 : oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1260 50 : if (oSRS.importFromESRI(poDS->papszPrj) == OGRERR_NONE)
1261 : {
1262 : // If geographic values are in seconds, we must transform.
1263 : // Is there a code for minutes too?
1264 61 : if (oSRS.IsGeographic() &&
1265 61 : EQUAL(OSR_GDS(poDS->papszPrj, "Units", ""), "DS"))
1266 : {
1267 0 : poDS->m_gt[0] /= 3600.0;
1268 0 : poDS->m_gt[1] /= 3600.0;
1269 0 : poDS->m_gt[2] /= 3600.0;
1270 0 : poDS->m_gt[3] /= 3600.0;
1271 0 : poDS->m_gt[4] /= 3600.0;
1272 0 : poDS->m_gt[5] /= 3600.0;
1273 : }
1274 :
1275 50 : poDS->m_oSRS = std::move(oSRS);
1276 : }
1277 : }
1278 :
1279 208 : CPLFree(pszDirname);
1280 208 : CPLFree(pszBasename);
1281 :
1282 : // Initialize any PAM information.
1283 208 : poDS->SetDescription(poOpenInfo->pszFilename);
1284 208 : poDS->TryLoadXML();
1285 :
1286 : // Check for external overviews.
1287 416 : poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename,
1288 208 : poOpenInfo->GetSiblingFiles());
1289 :
1290 208 : return poDS;
1291 : }
1292 :
1293 : /************************************************************************/
1294 : /* GetGeoTransform() */
1295 : /************************************************************************/
1296 :
1297 146 : CPLErr AAIGDataset::GetGeoTransform(GDALGeoTransform >) const
1298 :
1299 : {
1300 146 : gt = m_gt;
1301 146 : return CE_None;
1302 : }
1303 :
1304 : /************************************************************************/
1305 : /* GetSpatialRef() */
1306 : /************************************************************************/
1307 :
1308 108 : const OGRSpatialReference *AAIGDataset::GetSpatialRef() const
1309 : {
1310 108 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
1311 : }
1312 :
1313 : /************************************************************************/
1314 : /* CreateCopy() */
1315 : /************************************************************************/
1316 :
1317 49 : GDALDataset *AAIGDataset::CreateCopy(const char *pszFilename,
1318 : GDALDataset *poSrcDS, int /* bStrict */,
1319 : char **papszOptions,
1320 : GDALProgressFunc pfnProgress,
1321 : void *pProgressData)
1322 : {
1323 49 : const int nBands = poSrcDS->GetRasterCount();
1324 49 : const int nXSize = poSrcDS->GetRasterXSize();
1325 49 : const int nYSize = poSrcDS->GetRasterYSize();
1326 :
1327 : // Some rudimentary checks.
1328 49 : if (nBands != 1)
1329 : {
1330 5 : ReportError(pszFilename, CE_Failure, CPLE_NotSupported,
1331 : "AAIG driver doesn't support %d bands. Must be 1 band.",
1332 : nBands);
1333 :
1334 5 : return nullptr;
1335 : }
1336 :
1337 44 : if (!pfnProgress(0.0, nullptr, pProgressData))
1338 0 : return nullptr;
1339 :
1340 : // Create the dataset.
1341 44 : VSILFILE *fpImage = VSIFOpenL(pszFilename, "wt");
1342 44 : if (fpImage == nullptr)
1343 : {
1344 3 : ReportError(pszFilename, CE_Failure, CPLE_OpenFailed,
1345 : "Unable to create file.");
1346 3 : return nullptr;
1347 : }
1348 :
1349 : // Write ASCII Grid file header.
1350 41 : GDALGeoTransform gt;
1351 41 : char szHeader[2000] = {};
1352 : const char *pszForceCellsize =
1353 41 : CSLFetchNameValue(papszOptions, "FORCE_CELLSIZE");
1354 :
1355 41 : poSrcDS->GetGeoTransform(gt);
1356 :
1357 41 : const double dfYLLCorner = gt[5] < 0 ? gt[3] + nYSize * gt[5] : gt[3];
1358 43 : if (std::abs(gt[1] + gt[5]) < 0.0000001 ||
1359 43 : std::abs(gt[1] - gt[5]) < 0.0000001 ||
1360 0 : (pszForceCellsize && CPLTestBool(pszForceCellsize)))
1361 : {
1362 40 : CPLsnprintf(szHeader, sizeof(szHeader),
1363 : "ncols %d\n"
1364 : "nrows %d\n"
1365 : "xllcorner %.12f\n"
1366 : "yllcorner %.12f\n"
1367 : "cellsize %.12f\n",
1368 40 : nXSize, nYSize, gt[0], dfYLLCorner, gt[1]);
1369 : }
1370 : else
1371 : {
1372 1 : if (pszForceCellsize == nullptr)
1373 1 : ReportError(pszFilename, CE_Warning, CPLE_AppDefined,
1374 : "Producing a Golden Surfer style file with DX and DY "
1375 : "instead of CELLSIZE since the input pixels are "
1376 : "non-square. Use the FORCE_CELLSIZE=TRUE creation "
1377 : "option to force use of DX for even though this will "
1378 : "be distorted. Most ASCII Grid readers (ArcGIS "
1379 : "included) do not support the DX and DY parameters.");
1380 1 : CPLsnprintf(szHeader, sizeof(szHeader),
1381 : "ncols %d\n"
1382 : "nrows %d\n"
1383 : "xllcorner %.12f\n"
1384 : "yllcorner %.12f\n"
1385 : "dx %.12f\n"
1386 : "dy %.12f\n",
1387 1 : nXSize, nYSize, gt[0], dfYLLCorner, gt[1], fabs(gt[5]));
1388 : }
1389 :
1390 : // Builds the format string used for printing float values.
1391 41 : char szFormatFloat[32] = {'\0'};
1392 41 : strcpy(szFormatFloat, "%.20g");
1393 : const char *pszDecimalPrecision =
1394 41 : CSLFetchNameValue(papszOptions, "DECIMAL_PRECISION");
1395 : const char *pszSignificantDigits =
1396 41 : CSLFetchNameValue(papszOptions, "SIGNIFICANT_DIGITS");
1397 41 : bool bIgnoreSigDigits = false;
1398 41 : if (pszDecimalPrecision && pszSignificantDigits)
1399 : {
1400 0 : ReportError(pszFilename, CE_Warning, CPLE_AppDefined,
1401 : "Conflicting precision arguments, using DECIMAL_PRECISION");
1402 0 : bIgnoreSigDigits = true;
1403 : }
1404 : int nPrecision;
1405 41 : if (pszSignificantDigits && !bIgnoreSigDigits)
1406 : {
1407 2 : nPrecision = atoi(pszSignificantDigits);
1408 2 : if (nPrecision >= 0)
1409 2 : snprintf(szFormatFloat, sizeof(szFormatFloat), "%%.%dg",
1410 : nPrecision);
1411 2 : CPLDebug("AAIGrid", "Setting precision format: %s", szFormatFloat);
1412 : }
1413 39 : else if (pszDecimalPrecision)
1414 : {
1415 2 : nPrecision = atoi(pszDecimalPrecision);
1416 2 : if (nPrecision >= 0)
1417 2 : snprintf(szFormatFloat, sizeof(szFormatFloat), "%%.%df",
1418 : nPrecision);
1419 2 : CPLDebug("AAIGrid", "Setting precision format: %s", szFormatFloat);
1420 : }
1421 :
1422 : // Handle nodata (optionally).
1423 41 : GDALRasterBand *poBand = poSrcDS->GetRasterBand(1);
1424 41 : const bool bReadAsInt = poBand->GetRasterDataType() == GDT_Byte ||
1425 24 : poBand->GetRasterDataType() == GDT_Int16 ||
1426 85 : poBand->GetRasterDataType() == GDT_UInt16 ||
1427 20 : poBand->GetRasterDataType() == GDT_Int32;
1428 :
1429 : // Write `nodata' value to header if it is exists in source dataset
1430 41 : int bSuccess = FALSE;
1431 41 : const double dfNoData = poBand->GetNoDataValue(&bSuccess);
1432 41 : if (bSuccess)
1433 : {
1434 2 : snprintf(szHeader + strlen(szHeader),
1435 2 : sizeof(szHeader) - strlen(szHeader), "%s", "NODATA_value ");
1436 2 : if (bReadAsInt)
1437 0 : snprintf(szHeader + strlen(szHeader),
1438 0 : sizeof(szHeader) - strlen(szHeader), "%d",
1439 : static_cast<int>(dfNoData));
1440 : else
1441 2 : CPLsnprintf(szHeader + strlen(szHeader),
1442 2 : sizeof(szHeader) - strlen(szHeader), szFormatFloat,
1443 : dfNoData);
1444 2 : snprintf(szHeader + strlen(szHeader),
1445 2 : sizeof(szHeader) - strlen(szHeader), "%s", "\n");
1446 : }
1447 :
1448 41 : if (VSIFWriteL(szHeader, strlen(szHeader), 1, fpImage) != 1)
1449 : {
1450 3 : CPL_IGNORE_RET_VAL(VSIFCloseL(fpImage));
1451 3 : return nullptr;
1452 : }
1453 :
1454 : // Loop over image, copying image data.
1455 :
1456 : // Write scanlines to output file
1457 : int *panScanline = bReadAsInt
1458 38 : ? static_cast<int *>(CPLMalloc(sizeof(int) * nXSize))
1459 38 : : nullptr;
1460 :
1461 : double *padfScanline =
1462 38 : bReadAsInt ? nullptr
1463 14 : : static_cast<double *>(CPLMalloc(sizeof(double) * nXSize));
1464 :
1465 38 : CPLErr eErr = CE_None;
1466 :
1467 38 : bool bHasOutputDecimalDot = false;
1468 548 : for (int iLine = 0; eErr == CE_None && iLine < nYSize; iLine++)
1469 : {
1470 1020 : CPLString osBuf;
1471 510 : const int iSrcLine = gt[5] < 0 ? iLine : nYSize - 1 - iLine;
1472 510 : eErr = poBand->RasterIO(GF_Read, 0, iSrcLine, nXSize, 1,
1473 : bReadAsInt ? static_cast<void *>(panScanline)
1474 : : static_cast<void *>(padfScanline),
1475 : nXSize, 1, bReadAsInt ? GDT_Int32 : GDT_Float64,
1476 : 0, 0, nullptr);
1477 :
1478 510 : if (bReadAsInt)
1479 : {
1480 14657 : for (int iPixel = 0; iPixel < nXSize; iPixel++)
1481 : {
1482 14316 : snprintf(szHeader, sizeof(szHeader), "%d", panScanline[iPixel]);
1483 14316 : osBuf += szHeader;
1484 14316 : osBuf += ' ';
1485 14316 : if ((iPixel > 0 && (iPixel % 1024) == 0) ||
1486 14316 : iPixel == nXSize - 1)
1487 : {
1488 696 : if (VSIFWriteL(osBuf, static_cast<int>(osBuf.size()), 1,
1489 696 : fpImage) != 1)
1490 : {
1491 7 : eErr = CE_Failure;
1492 7 : ReportError(pszFilename, CE_Failure, CPLE_AppDefined,
1493 : "Write failed, disk full?");
1494 7 : break;
1495 : }
1496 341 : osBuf = "";
1497 : }
1498 : }
1499 : }
1500 : else
1501 : {
1502 162 : assert(padfScanline);
1503 :
1504 2514 : for (int iPixel = 0; iPixel < nXSize; iPixel++)
1505 : {
1506 2352 : CPLsnprintf(szHeader, sizeof(szHeader), szFormatFloat,
1507 2352 : padfScanline[iPixel]);
1508 :
1509 : // Make sure that as least one value has a decimal point (#6060)
1510 2352 : if (!bHasOutputDecimalDot)
1511 : {
1512 14 : if (strchr(szHeader, '.') || strchr(szHeader, 'e') ||
1513 11 : strchr(szHeader, 'E'))
1514 : {
1515 3 : bHasOutputDecimalDot = true;
1516 : }
1517 22 : else if (!std::isinf(padfScanline[iPixel]) &&
1518 11 : !std::isnan(padfScanline[iPixel]))
1519 : {
1520 11 : strcat(szHeader, ".0");
1521 11 : bHasOutputDecimalDot = true;
1522 : }
1523 : }
1524 :
1525 2352 : osBuf += szHeader;
1526 2352 : osBuf += ' ';
1527 2352 : if ((iPixel > 0 && (iPixel % 1024) == 0) ||
1528 2352 : iPixel == nXSize - 1)
1529 : {
1530 324 : if (VSIFWriteL(osBuf, static_cast<int>(osBuf.size()), 1,
1531 324 : fpImage) != 1)
1532 : {
1533 0 : eErr = CE_Failure;
1534 0 : ReportError(pszFilename, CE_Failure, CPLE_AppDefined,
1535 : "Write failed, disk full?");
1536 0 : break;
1537 : }
1538 162 : osBuf = "";
1539 : }
1540 : }
1541 : }
1542 510 : if (VSIFWriteL("\n", 1, 1, fpImage) != 1)
1543 1 : eErr = CE_Failure;
1544 :
1545 1013 : if (eErr == CE_None &&
1546 503 : !pfnProgress((iLine + 1) / static_cast<double>(nYSize), nullptr,
1547 : pProgressData))
1548 : {
1549 0 : eErr = CE_Failure;
1550 0 : ReportError(pszFilename, CE_Failure, CPLE_UserInterrupt,
1551 : "User terminated CreateCopy()");
1552 : }
1553 : }
1554 :
1555 38 : CPLFree(panScanline);
1556 38 : CPLFree(padfScanline);
1557 38 : if (VSIFCloseL(fpImage) != 0)
1558 0 : eErr = CE_Failure;
1559 :
1560 38 : if (eErr != CE_None)
1561 7 : return nullptr;
1562 :
1563 : // Try to write projection file.
1564 31 : const char *pszOriginalProjection = poSrcDS->GetProjectionRef();
1565 31 : if (!EQUAL(pszOriginalProjection, ""))
1566 : {
1567 21 : char *pszDirname = CPLStrdup(CPLGetPathSafe(pszFilename).c_str());
1568 21 : char *pszBasename = CPLStrdup(CPLGetBasenameSafe(pszFilename).c_str());
1569 21 : char *pszPrjFilename = CPLStrdup(
1570 42 : CPLFormFilenameSafe(pszDirname, pszBasename, "prj").c_str());
1571 21 : VSILFILE *fp = VSIFOpenL(pszPrjFilename, "wt");
1572 21 : if (fp != nullptr)
1573 : {
1574 42 : OGRSpatialReference oSRS;
1575 21 : oSRS.importFromWkt(pszOriginalProjection);
1576 21 : oSRS.morphToESRI();
1577 21 : char *pszESRIProjection = nullptr;
1578 21 : oSRS.exportToWkt(&pszESRIProjection);
1579 21 : CPL_IGNORE_RET_VAL(VSIFWriteL(pszESRIProjection, 1,
1580 : strlen(pszESRIProjection), fp));
1581 :
1582 21 : CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
1583 21 : CPLFree(pszESRIProjection);
1584 : }
1585 : else
1586 : {
1587 0 : ReportError(pszFilename, CE_Failure, CPLE_FileIO,
1588 : "Unable to create file %s.", pszPrjFilename);
1589 : }
1590 21 : CPLFree(pszDirname);
1591 21 : CPLFree(pszBasename);
1592 21 : CPLFree(pszPrjFilename);
1593 : }
1594 :
1595 : // Re-open dataset, and copy any auxiliary pam information.
1596 :
1597 : // If writing to stdout, we can't reopen it, so return
1598 : // a fake dataset to make the caller happy.
1599 31 : CPLPushErrorHandler(CPLQuietErrorHandler);
1600 : GDALPamDataset *poDS =
1601 31 : reinterpret_cast<GDALPamDataset *>(GDALOpen(pszFilename, GA_ReadOnly));
1602 31 : CPLPopErrorHandler();
1603 31 : if (poDS)
1604 : {
1605 31 : poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
1606 31 : return poDS;
1607 : }
1608 :
1609 0 : CPLErrorReset();
1610 :
1611 0 : AAIGDataset *poAAIG_DS = new AAIGDataset();
1612 0 : poAAIG_DS->nRasterXSize = nXSize;
1613 0 : poAAIG_DS->nRasterYSize = nYSize;
1614 0 : poAAIG_DS->nBands = 1;
1615 0 : poAAIG_DS->SetBand(1, new AAIGRasterBand(poAAIG_DS, 1));
1616 0 : return poAAIG_DS;
1617 : }
1618 :
1619 : /************************************************************************/
1620 : /* OSR_GDS() */
1621 : /************************************************************************/
1622 :
1623 11 : static CPLString OSR_GDS(char **papszNV, const char *pszField,
1624 : const char *pszDefaultValue)
1625 :
1626 : {
1627 11 : if (papszNV == nullptr || papszNV[0] == nullptr)
1628 0 : return pszDefaultValue;
1629 :
1630 11 : int iLine = 0; // Used after for.
1631 22 : for (; papszNV[iLine] != nullptr &&
1632 11 : !EQUALN(papszNV[iLine], pszField, strlen(pszField));
1633 : iLine++)
1634 : {
1635 : }
1636 :
1637 11 : if (papszNV[iLine] == nullptr)
1638 11 : return pszDefaultValue;
1639 : else
1640 : {
1641 0 : char **papszTokens = CSLTokenizeString(papszNV[iLine]);
1642 :
1643 0 : CPLString osResult;
1644 0 : if (CSLCount(papszTokens) > 1)
1645 0 : osResult = papszTokens[1];
1646 : else
1647 0 : osResult = pszDefaultValue;
1648 :
1649 0 : CSLDestroy(papszTokens);
1650 0 : return osResult;
1651 : }
1652 : }
1653 :
1654 : /************************************************************************/
1655 : /* GDALRegister_AAIGrid() */
1656 : /************************************************************************/
1657 :
1658 1911 : void GDALRegister_AAIGrid()
1659 :
1660 : {
1661 1911 : if (GDALGetDriverByName("AAIGrid") != nullptr)
1662 282 : return;
1663 :
1664 1629 : GDALDriver *poDriver = new GDALDriver();
1665 :
1666 1629 : poDriver->SetDescription("AAIGrid");
1667 1629 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1668 1629 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Arc/Info ASCII Grid");
1669 1629 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
1670 1629 : "drivers/raster/aaigrid.html");
1671 1629 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "asc");
1672 1629 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES,
1673 1629 : "Byte UInt16 Int16 Int32 Float32");
1674 :
1675 1629 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1676 1629 : poDriver->SetMetadataItem(
1677 : GDAL_DMD_CREATIONOPTIONLIST,
1678 : "<CreationOptionList>\n"
1679 : " <Option name='FORCE_CELLSIZE' type='boolean' description='Force "
1680 : "use of CELLSIZE, default is FALSE.'/>\n"
1681 : " <Option name='DECIMAL_PRECISION' type='int' description='Number of "
1682 : "decimal when writing floating-point numbers(%f).'/>\n"
1683 : " <Option name='SIGNIFICANT_DIGITS' type='int' description='Number "
1684 : "of significant digits when writing floating-point numbers(%g).'/>\n"
1685 1629 : "</CreationOptionList>\n");
1686 1629 : poDriver->SetMetadataItem(GDAL_DMD_OPENOPTIONLIST,
1687 : "<OpenOptionList>\n"
1688 : " <Option name='DATATYPE' type='string-select' "
1689 : "description='Data type to be used.'>\n"
1690 : " <Value>Int32</Value>\n"
1691 : " <Value>Float32</Value>\n"
1692 : " <Value>Float64</Value>\n"
1693 : " </Option>\n"
1694 1629 : "</OpenOptionList>\n");
1695 :
1696 1629 : poDriver->pfnOpen = AAIGDataset::Open;
1697 1629 : poDriver->pfnIdentify = AAIGDataset::Identify;
1698 1629 : poDriver->pfnCreateCopy = AAIGDataset::CreateCopy;
1699 :
1700 1629 : GetGDALDriverManager()->RegisterDriver(poDriver);
1701 : }
1702 :
1703 : /************************************************************************/
1704 : /* GDALRegister_GRASSASCIIGrid() */
1705 : /************************************************************************/
1706 :
1707 1911 : void GDALRegister_GRASSASCIIGrid()
1708 :
1709 : {
1710 1911 : if (GDALGetDriverByName("GRASSASCIIGrid") != nullptr)
1711 282 : return;
1712 :
1713 1629 : GDALDriver *poDriver = new GDALDriver();
1714 :
1715 1629 : poDriver->SetDescription("GRASSASCIIGrid");
1716 1629 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1717 1629 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "GRASS ASCII Grid");
1718 1629 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
1719 1629 : "drivers/raster/grassasciigrid.html");
1720 :
1721 1629 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1722 :
1723 1629 : poDriver->pfnOpen = GRASSASCIIDataset::Open;
1724 1629 : poDriver->pfnIdentify = GRASSASCIIDataset::Identify;
1725 :
1726 1629 : GetGDALDriverManager()->RegisterDriver(poDriver);
1727 : }
1728 :
1729 : /************************************************************************/
1730 : /* GDALRegister_ISG() */
1731 : /************************************************************************/
1732 :
1733 1911 : void GDALRegister_ISG()
1734 :
1735 : {
1736 1911 : if (GDALGetDriverByName("ISG") != nullptr)
1737 282 : return;
1738 :
1739 1629 : GDALDriver *poDriver = new GDALDriver();
1740 :
1741 1629 : poDriver->SetDescription("ISG");
1742 1629 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1743 1629 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
1744 1629 : "International Service for the Geoid");
1745 1629 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/isg.html");
1746 1629 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "isg");
1747 :
1748 1629 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1749 :
1750 1629 : poDriver->pfnOpen = ISGDataset::Open;
1751 1629 : poDriver->pfnIdentify = ISGDataset::Identify;
1752 :
1753 1629 : GetGDALDriverManager()->RegisterDriver(poDriver);
1754 : }
|