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