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 169 : AAIGRasterBand::AAIGRasterBand(AAIGDataset *poDSIn, int nDataStart)
89 169 : : panLineOffset(nullptr)
90 : {
91 169 : poDS = poDSIn;
92 :
93 169 : nBand = 1;
94 169 : eDataType = poDSIn->eDataType;
95 :
96 169 : nBlockXSize = poDSIn->nRasterXSize;
97 169 : nBlockYSize = 1;
98 :
99 169 : panLineOffset = static_cast<GUIntBig *>(
100 169 : VSI_CALLOC_VERBOSE(poDSIn->nRasterYSize, sizeof(GUIntBig)));
101 169 : if (panLineOffset == nullptr)
102 : {
103 0 : return;
104 : }
105 169 : panLineOffset[0] = nDataStart;
106 : }
107 :
108 : /************************************************************************/
109 : /* ~AAIGRasterBand() */
110 : /************************************************************************/
111 :
112 338 : AAIGRasterBand::~AAIGRasterBand()
113 : {
114 169 : CPLFree(panLineOffset);
115 338 : }
116 :
117 : /************************************************************************/
118 : /* IReadBlock() */
119 : /************************************************************************/
120 :
121 1009 : CPLErr AAIGRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage)
122 :
123 : {
124 1009 : AAIGDataset *poODS = static_cast<AAIGDataset *>(poDS);
125 :
126 1009 : if (nBlockYOff < 0 || nBlockYOff > poODS->nRasterYSize - 1 ||
127 1009 : nBlockXOff != 0 || panLineOffset == nullptr || poODS->fp == nullptr)
128 0 : return CE_Failure;
129 :
130 1009 : 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 1009 : if (panLineOffset[nBlockYOff] == 0)
138 0 : return CE_Failure;
139 :
140 1009 : 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 33852 : for (int iPixel = 0; iPixel < poODS->nRasterXSize;)
149 : {
150 : // Suck up any pre-white space.
151 32843 : char chNext = '\0';
152 11026 : do
153 : {
154 43869 : chNext = poODS->Getc();
155 43869 : } while (isspace(static_cast<unsigned char>(chNext)));
156 :
157 32843 : char szToken[500] = {'\0'};
158 32843 : int iTokenChar = 0;
159 133158 : while (chNext != '\0' && !isspace((unsigned char)chNext))
160 : {
161 100315 : 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 100315 : szToken[iTokenChar++] = chNext;
169 100315 : chNext = poODS->Getc();
170 : }
171 :
172 32843 : if (chNext == '\0' && (iPixel != poODS->nRasterXSize - 1 ||
173 33 : 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 32843 : szToken[iTokenChar] = '\0';
181 :
182 32843 : if (pImage != nullptr)
183 : {
184 : // "null" seems to be specific of D12 software
185 : // See https://github.com/OSGeo/gdal/issues/5095
186 32645 : 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 32541 : 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 29896 : reinterpret_cast<GInt32 *>(pImage)[iPixel] =
206 29896 : static_cast<GInt32>(atoi(szToken));
207 : }
208 :
209 32843 : iPixel++;
210 : }
211 :
212 1009 : if (nBlockYOff < poODS->nRasterYSize - 1)
213 909 : panLineOffset[nBlockYOff + 1] = poODS->Tell();
214 :
215 1009 : return CE_None;
216 : }
217 :
218 : /************************************************************************/
219 : /* GetNoDataValue() */
220 : /************************************************************************/
221 :
222 184 : double AAIGRasterBand::GetNoDataValue(int *pbSuccess)
223 :
224 : {
225 184 : AAIGDataset *poODS = static_cast<AAIGDataset *>(poDS);
226 :
227 184 : if (pbSuccess)
228 166 : *pbSuccess = poODS->bNoDataSet;
229 :
230 184 : 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 170 : AAIGDataset::AAIGDataset()
259 : : fp(nullptr), papszPrj(nullptr), nBufferOffset(0), nOffsetInBuffer(256),
260 170 : eDataType(GDT_Int32), bNoDataSet(false), dfNoDataValue(-9999.0)
261 : {
262 170 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
263 170 : adfGeoTransform[0] = 0.0;
264 170 : adfGeoTransform[1] = 1.0;
265 170 : adfGeoTransform[2] = 0.0;
266 170 : adfGeoTransform[3] = 0.0;
267 170 : adfGeoTransform[4] = 0.0;
268 170 : adfGeoTransform[5] = 1.0;
269 170 : memset(achReadBuf, 0, sizeof(achReadBuf));
270 170 : }
271 :
272 : /************************************************************************/
273 : /* ~AAIGDataset() */
274 : /************************************************************************/
275 :
276 330 : AAIGDataset::~AAIGDataset()
277 :
278 : {
279 170 : FlushCache(true);
280 :
281 170 : if (fp != nullptr)
282 : {
283 169 : if (VSIFCloseL(fp) != 0)
284 : {
285 0 : ReportError(CE_Failure, CPLE_FileIO, "I/O error");
286 : }
287 : }
288 :
289 170 : CSLDestroy(papszPrj);
290 330 : }
291 :
292 : /************************************************************************/
293 : /* Tell() */
294 : /************************************************************************/
295 :
296 909 : GUIntBig AAIGDataset::Tell() const
297 : {
298 909 : return nBufferOffset + nOffsetInBuffer;
299 : }
300 :
301 : /************************************************************************/
302 : /* Seek() */
303 : /************************************************************************/
304 :
305 1009 : int AAIGDataset::Seek(GUIntBig nNewOffset)
306 :
307 : {
308 1009 : nOffsetInBuffer = sizeof(achReadBuf);
309 1009 : 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 144184 : char AAIGDataset::Getc()
320 :
321 : {
322 144184 : if (nOffsetInBuffer < static_cast<int>(sizeof(achReadBuf)))
323 142975 : return achReadBuf[nOffsetInBuffer++];
324 :
325 1209 : nBufferOffset = VSIFTellL(fp);
326 : const int nRead =
327 1209 : static_cast<int>(VSIFReadL(achReadBuf, 1, sizeof(achReadBuf), fp));
328 59222 : for (unsigned int i = nRead; i < sizeof(achReadBuf); i++)
329 58013 : achReadBuf[i] = '\0';
330 :
331 1209 : nOffsetInBuffer = 0;
332 :
333 1209 : 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 57736 : int AAIGDataset::Identify(GDALOpenInfo *poOpenInfo)
356 :
357 : {
358 : // Does this look like an AI grid file?
359 57736 : if (poOpenInfo->nHeaderBytes < 40 ||
360 7880 : !(STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader, "ncols") ||
361 7556 : STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader, "nrows") ||
362 7556 : STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader, "xllcorner") ||
363 7556 : STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader, "yllcorner") ||
364 7556 : STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader, "xllcenter") ||
365 7556 : STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader, "yllcenter") ||
366 7556 : STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader, "dx") ||
367 7556 : STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader, "dy") ||
368 7556 : STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader, "cellsize")))
369 57412 : return FALSE;
370 :
371 324 : return TRUE;
372 : }
373 :
374 : /************************************************************************/
375 : /* Identify() */
376 : /************************************************************************/
377 :
378 57367 : int GRASSASCIIDataset::Identify(GDALOpenInfo *poOpenInfo)
379 :
380 : {
381 : // Does this look like a GRASS ASCII grid file?
382 57367 : if (poOpenInfo->nHeaderBytes < 40 ||
383 7559 : !(STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader, "north:") ||
384 7555 : STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader, "south:") ||
385 7555 : STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader, "east:") ||
386 7555 : STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader, "west:") ||
387 7555 : STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader, "rows:") ||
388 7555 : STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader, "cols:")))
389 57363 : return FALSE;
390 :
391 4 : return TRUE;
392 : }
393 :
394 : /************************************************************************/
395 : /* Identify() */
396 : /************************************************************************/
397 :
398 57371 : int ISGDataset::Identify(GDALOpenInfo *poOpenInfo)
399 :
400 : {
401 : // Does this look like a ISG grid file?
402 57371 : if (poOpenInfo->nHeaderBytes < 40 ||
403 7563 : !strstr((const char *)poOpenInfo->pabyHeader, "model name"))
404 : {
405 57355 : 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 160 : 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 160 : if (!Identify(poOpenInfo))
444 0 : return nullptr;
445 : #endif
446 :
447 160 : return CommonOpen(poOpenInfo, FORMAT_AAIG);
448 : }
449 :
450 : /************************************************************************/
451 : /* ParseHeader() */
452 : /************************************************************************/
453 :
454 160 : int AAIGDataset::ParseHeader(const char *pszHeader, const char *pszDataType)
455 : {
456 160 : char **papszTokens = CSLTokenizeString2(pszHeader, " \n\r\t", 0);
457 160 : const int nTokens = CSLCount(papszTokens);
458 :
459 160 : int i = 0;
460 160 : if ((i = CSLFindString(papszTokens, "ncols")) < 0 || i + 1 >= nTokens)
461 : {
462 0 : CSLDestroy(papszTokens);
463 0 : return FALSE;
464 : }
465 160 : nRasterXSize = atoi(papszTokens[i + 1]);
466 160 : if ((i = CSLFindString(papszTokens, "nrows")) < 0 || i + 1 >= nTokens)
467 : {
468 0 : CSLDestroy(papszTokens);
469 0 : return FALSE;
470 : }
471 160 : nRasterYSize = atoi(papszTokens[i + 1]);
472 :
473 160 : 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 160 : constexpr int kMaxDimSize = 10000000; // 1e7 cells.
483 160 : if (nRasterXSize > kMaxDimSize || nRasterYSize > kMaxDimSize)
484 : {
485 0 : CSLDestroy(papszTokens);
486 0 : return FALSE;
487 : }
488 :
489 160 : double dfCellDX = 0.0;
490 160 : double dfCellDY = 0.0;
491 160 : 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 156 : if (i + 1 >= nTokens)
508 : {
509 0 : CSLDestroy(papszTokens);
510 0 : return FALSE;
511 : }
512 156 : dfCellDY = CPLAtofM(papszTokens[i + 1]);
513 156 : dfCellDX = dfCellDY;
514 : }
515 :
516 160 : int j = 0;
517 160 : if ((i = CSLFindString(papszTokens, "xllcorner")) >= 0 &&
518 320 : (j = CSLFindString(papszTokens, "yllcorner")) >= 0 && i + 1 < nTokens &&
519 160 : j + 1 < nTokens)
520 : {
521 160 : 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 160 : 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 160 : adfGeoTransform[1] = dfCellDX;
536 160 : adfGeoTransform[2] = 0.0;
537 160 : adfGeoTransform[3] =
538 160 : CPLAtofM(papszTokens[j + 1]) + nRasterYSize * dfCellDY;
539 160 : adfGeoTransform[4] = 0.0;
540 160 : 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 227 : 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 160 : CSLDestroy(papszTokens);
613 :
614 160 : 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 170 : GDALDataset *AAIGDataset::CommonOpen(GDALOpenInfo *poOpenInfo,
1031 : GridFormat eFormat)
1032 : {
1033 170 : if (poOpenInfo->fpL == nullptr)
1034 0 : return nullptr;
1035 :
1036 : // Create a corresponding GDALDataset.
1037 170 : AAIGDataset *poDS = nullptr;
1038 :
1039 170 : if (eFormat == FORMAT_AAIG)
1040 160 : 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 180 : const char *pszDataTypeOption = eFormat == FORMAT_AAIG ? "AAIGRID_DATATYPE"
1050 : : eFormat == FORMAT_GRASSASCII
1051 10 : ? "GRASSASCIIGRID_DATATYPE"
1052 : : nullptr;
1053 :
1054 : const char *pszDataType =
1055 170 : pszDataTypeOption ? CPLGetConfigOption(pszDataTypeOption, nullptr)
1056 170 : : nullptr;
1057 170 : if (pszDataType == nullptr)
1058 : {
1059 : pszDataType =
1060 168 : CSLFetchNameValue(poOpenInfo->papszOpenOptions, "DATATYPE");
1061 : }
1062 170 : 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 170 : if (!poDS->ParseHeader((const char *)poOpenInfo->pabyHeader, pszDataType))
1078 : {
1079 1 : delete poDS;
1080 1 : return nullptr;
1081 : }
1082 :
1083 169 : poDS->fp = poOpenInfo->fpL;
1084 169 : poOpenInfo->fpL = nullptr;
1085 :
1086 : // Find the start of real data.
1087 169 : int nStartOfData = 0;
1088 :
1089 169 : 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 18948 : for (int i = 2; true; i++)
1126 : {
1127 18948 : 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 18948 : if (poOpenInfo->pabyHeader[i - 1] == '\n' ||
1137 18065 : poOpenInfo->pabyHeader[i - 2] == '\n' ||
1138 17344 : poOpenInfo->pabyHeader[i - 1] == '\r' ||
1139 17260 : poOpenInfo->pabyHeader[i - 2] == '\r')
1140 : {
1141 1688 : if ((!isalpha(static_cast<unsigned char>(
1142 1688 : poOpenInfo->pabyHeader[i])) ||
1143 : // null seems to be specific of D12 software
1144 : // See https://github.com/OSGeo/gdal/issues/5095
1145 1447 : (i + 5 < poOpenInfo->nHeaderBytes &&
1146 1447 : memcmp(poOpenInfo->pabyHeader + i, "null ", 5) == 0) ||
1147 1445 : (i + 4 < poOpenInfo->nHeaderBytes &&
1148 1445 : EQUALN(reinterpret_cast<const char *>(
1149 : poOpenInfo->pabyHeader + i),
1150 246 : "nan ", 4))) &&
1151 246 : poOpenInfo->pabyHeader[i] != '\n' &&
1152 162 : poOpenInfo->pabyHeader[i] != '\r')
1153 : {
1154 162 : nStartOfData = i;
1155 :
1156 : // Beginning of real data found.
1157 162 : break;
1158 : }
1159 : }
1160 : }
1161 : }
1162 :
1163 : // Recognize the type of data.
1164 169 : CPLAssert(nullptr != poDS->fp);
1165 :
1166 169 : if (pszDataType == nullptr && poDS->eDataType != GDT_Float32 &&
1167 145 : poDS->eDataType != GDT_Float64)
1168 : {
1169 : // Allocate 100K chunk + 1 extra byte for NULL character.
1170 144 : constexpr size_t nChunkSize = 1024 * 100;
1171 : GByte *pabyChunk = static_cast<GByte *>(
1172 144 : VSI_CALLOC_VERBOSE(nChunkSize + 1, sizeof(GByte)));
1173 144 : if (pabyChunk == nullptr)
1174 : {
1175 0 : delete poDS;
1176 0 : return nullptr;
1177 : }
1178 144 : pabyChunk[nChunkSize] = '\0';
1179 :
1180 144 : 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 288 : while (!VSIFEofL(poDS->fp))
1189 : {
1190 144 : const size_t nLen = VSIFReadL(pabyChunk, 1, nChunkSize, poDS->fp);
1191 :
1192 173634 : for (size_t i = 0; i < nLen; i++)
1193 : {
1194 173535 : const GByte ch = pabyChunk[i];
1195 173535 : 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 144 : VSIFree(pabyChunk);
1205 : }
1206 :
1207 : // Create band information objects.
1208 169 : AAIGRasterBand *band = new AAIGRasterBand(poDS, nStartOfData);
1209 169 : poDS->SetBand(1, band);
1210 169 : if (band->panLineOffset == nullptr)
1211 : {
1212 0 : delete poDS;
1213 0 : return nullptr;
1214 : }
1215 169 : if (!poDS->osUnits.empty())
1216 : {
1217 7 : poDS->GetRasterBand(1)->SetUnitType(poDS->osUnits);
1218 : }
1219 :
1220 : // Try to read projection file.
1221 : char *const pszDirname =
1222 169 : CPLStrdup(CPLGetPathSafe(poOpenInfo->pszFilename).c_str());
1223 : char *const pszBasename =
1224 169 : CPLStrdup(CPLGetBasenameSafe(poOpenInfo->pszFilename).c_str());
1225 :
1226 169 : poDS->osPrjFilename = CPLFormFilenameSafe(pszDirname, pszBasename, "prj");
1227 169 : int nRet = 0;
1228 : {
1229 : VSIStatBufL sStatBuf;
1230 169 : nRet = VSIStatL(poDS->osPrjFilename, &sStatBuf);
1231 : }
1232 169 : if (nRet != 0 && VSIIsCaseSensitiveFS(poDS->osPrjFilename))
1233 : {
1234 : poDS->osPrjFilename =
1235 121 : CPLFormFilenameSafe(pszDirname, pszBasename, "PRJ");
1236 :
1237 : VSIStatBufL sStatBuf;
1238 121 : nRet = VSIStatL(poDS->osPrjFilename, &sStatBuf);
1239 : }
1240 :
1241 169 : if (nRet == 0)
1242 : {
1243 50 : poDS->papszPrj = CSLLoad(poDS->osPrjFilename);
1244 :
1245 50 : CPLDebug("AAIGrid", "Loaded SRS from %s", poDS->osPrjFilename.c_str());
1246 :
1247 100 : OGRSpatialReference oSRS;
1248 50 : oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1249 50 : if (oSRS.importFromESRI(poDS->papszPrj) == OGRERR_NONE)
1250 : {
1251 : // If geographic values are in seconds, we must transform.
1252 : // Is there a code for minutes too?
1253 61 : if (oSRS.IsGeographic() &&
1254 61 : EQUAL(OSR_GDS(poDS->papszPrj, "Units", ""), "DS"))
1255 : {
1256 0 : poDS->adfGeoTransform[0] /= 3600.0;
1257 0 : poDS->adfGeoTransform[1] /= 3600.0;
1258 0 : poDS->adfGeoTransform[2] /= 3600.0;
1259 0 : poDS->adfGeoTransform[3] /= 3600.0;
1260 0 : poDS->adfGeoTransform[4] /= 3600.0;
1261 0 : poDS->adfGeoTransform[5] /= 3600.0;
1262 : }
1263 :
1264 50 : poDS->m_oSRS = std::move(oSRS);
1265 : }
1266 : }
1267 :
1268 169 : CPLFree(pszDirname);
1269 169 : CPLFree(pszBasename);
1270 :
1271 : // Initialize any PAM information.
1272 169 : poDS->SetDescription(poOpenInfo->pszFilename);
1273 169 : poDS->TryLoadXML();
1274 :
1275 : // Check for external overviews.
1276 338 : poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename,
1277 169 : poOpenInfo->GetSiblingFiles());
1278 :
1279 169 : return poDS;
1280 : }
1281 :
1282 : /************************************************************************/
1283 : /* GetGeoTransform() */
1284 : /************************************************************************/
1285 :
1286 114 : CPLErr AAIGDataset::GetGeoTransform(double *padfTransform)
1287 :
1288 : {
1289 114 : memcpy(padfTransform, adfGeoTransform, sizeof(double) * 6);
1290 114 : return CE_None;
1291 : }
1292 :
1293 : /************************************************************************/
1294 : /* GetSpatialRef() */
1295 : /************************************************************************/
1296 :
1297 73 : const OGRSpatialReference *AAIGDataset::GetSpatialRef() const
1298 : {
1299 73 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
1300 : }
1301 :
1302 : /************************************************************************/
1303 : /* CreateCopy() */
1304 : /************************************************************************/
1305 :
1306 49 : GDALDataset *AAIGDataset::CreateCopy(const char *pszFilename,
1307 : GDALDataset *poSrcDS, int /* bStrict */,
1308 : char **papszOptions,
1309 : GDALProgressFunc pfnProgress,
1310 : void *pProgressData)
1311 : {
1312 49 : const int nBands = poSrcDS->GetRasterCount();
1313 49 : const int nXSize = poSrcDS->GetRasterXSize();
1314 49 : const int nYSize = poSrcDS->GetRasterYSize();
1315 :
1316 : // Some rudimentary checks.
1317 49 : if (nBands != 1)
1318 : {
1319 5 : ReportError(pszFilename, CE_Failure, CPLE_NotSupported,
1320 : "AAIG driver doesn't support %d bands. Must be 1 band.",
1321 : nBands);
1322 :
1323 5 : return nullptr;
1324 : }
1325 :
1326 44 : if (!pfnProgress(0.0, nullptr, pProgressData))
1327 0 : return nullptr;
1328 :
1329 : // Create the dataset.
1330 44 : VSILFILE *fpImage = VSIFOpenL(pszFilename, "wt");
1331 44 : if (fpImage == nullptr)
1332 : {
1333 3 : ReportError(pszFilename, CE_Failure, CPLE_OpenFailed,
1334 : "Unable to create file.");
1335 3 : return nullptr;
1336 : }
1337 :
1338 : // Write ASCII Grid file header.
1339 41 : double adfGeoTransform[6] = {};
1340 41 : char szHeader[2000] = {};
1341 : const char *pszForceCellsize =
1342 41 : CSLFetchNameValue(papszOptions, "FORCE_CELLSIZE");
1343 :
1344 41 : poSrcDS->GetGeoTransform(adfGeoTransform);
1345 :
1346 41 : const double dfYLLCorner =
1347 41 : adfGeoTransform[5] < 0
1348 41 : ? adfGeoTransform[3] + nYSize * adfGeoTransform[5]
1349 : : adfGeoTransform[3];
1350 43 : if (std::abs(adfGeoTransform[1] + adfGeoTransform[5]) < 0.0000001 ||
1351 43 : std::abs(adfGeoTransform[1] - adfGeoTransform[5]) < 0.0000001 ||
1352 0 : (pszForceCellsize && CPLTestBool(pszForceCellsize)))
1353 : {
1354 40 : CPLsnprintf(szHeader, sizeof(szHeader),
1355 : "ncols %d\n"
1356 : "nrows %d\n"
1357 : "xllcorner %.12f\n"
1358 : "yllcorner %.12f\n"
1359 : "cellsize %.12f\n",
1360 : nXSize, nYSize, adfGeoTransform[0], dfYLLCorner,
1361 : adfGeoTransform[1]);
1362 : }
1363 : else
1364 : {
1365 1 : if (pszForceCellsize == nullptr)
1366 1 : ReportError(pszFilename, CE_Warning, CPLE_AppDefined,
1367 : "Producing a Golden Surfer style file with DX and DY "
1368 : "instead of CELLSIZE since the input pixels are "
1369 : "non-square. Use the FORCE_CELLSIZE=TRUE creation "
1370 : "option to force use of DX for even though this will "
1371 : "be distorted. Most ASCII Grid readers (ArcGIS "
1372 : "included) do not support the DX and DY parameters.");
1373 1 : CPLsnprintf(szHeader, sizeof(szHeader),
1374 : "ncols %d\n"
1375 : "nrows %d\n"
1376 : "xllcorner %.12f\n"
1377 : "yllcorner %.12f\n"
1378 : "dx %.12f\n"
1379 : "dy %.12f\n",
1380 : nXSize, nYSize, adfGeoTransform[0], dfYLLCorner,
1381 1 : adfGeoTransform[1], fabs(adfGeoTransform[5]));
1382 : }
1383 :
1384 : // Builds the format string used for printing float values.
1385 41 : char szFormatFloat[32] = {'\0'};
1386 41 : strcpy(szFormatFloat, "%.20g");
1387 : const char *pszDecimalPrecision =
1388 41 : CSLFetchNameValue(papszOptions, "DECIMAL_PRECISION");
1389 : const char *pszSignificantDigits =
1390 41 : CSLFetchNameValue(papszOptions, "SIGNIFICANT_DIGITS");
1391 41 : bool bIgnoreSigDigits = false;
1392 41 : if (pszDecimalPrecision && pszSignificantDigits)
1393 : {
1394 0 : ReportError(pszFilename, CE_Warning, CPLE_AppDefined,
1395 : "Conflicting precision arguments, using DECIMAL_PRECISION");
1396 0 : bIgnoreSigDigits = true;
1397 : }
1398 : int nPrecision;
1399 41 : if (pszSignificantDigits && !bIgnoreSigDigits)
1400 : {
1401 2 : nPrecision = atoi(pszSignificantDigits);
1402 2 : if (nPrecision >= 0)
1403 2 : snprintf(szFormatFloat, sizeof(szFormatFloat), "%%.%dg",
1404 : nPrecision);
1405 2 : CPLDebug("AAIGrid", "Setting precision format: %s", szFormatFloat);
1406 : }
1407 39 : else if (pszDecimalPrecision)
1408 : {
1409 2 : nPrecision = atoi(pszDecimalPrecision);
1410 2 : if (nPrecision >= 0)
1411 2 : snprintf(szFormatFloat, sizeof(szFormatFloat), "%%.%df",
1412 : nPrecision);
1413 2 : CPLDebug("AAIGrid", "Setting precision format: %s", szFormatFloat);
1414 : }
1415 :
1416 : // Handle nodata (optionally).
1417 41 : GDALRasterBand *poBand = poSrcDS->GetRasterBand(1);
1418 41 : const bool bReadAsInt = poBand->GetRasterDataType() == GDT_Byte ||
1419 24 : poBand->GetRasterDataType() == GDT_Int16 ||
1420 85 : poBand->GetRasterDataType() == GDT_UInt16 ||
1421 20 : poBand->GetRasterDataType() == GDT_Int32;
1422 :
1423 : // Write `nodata' value to header if it is exists in source dataset
1424 41 : int bSuccess = FALSE;
1425 41 : const double dfNoData = poBand->GetNoDataValue(&bSuccess);
1426 41 : if (bSuccess)
1427 : {
1428 2 : snprintf(szHeader + strlen(szHeader),
1429 2 : sizeof(szHeader) - strlen(szHeader), "%s", "NODATA_value ");
1430 2 : if (bReadAsInt)
1431 0 : snprintf(szHeader + strlen(szHeader),
1432 0 : sizeof(szHeader) - strlen(szHeader), "%d",
1433 : static_cast<int>(dfNoData));
1434 : else
1435 2 : CPLsnprintf(szHeader + strlen(szHeader),
1436 2 : sizeof(szHeader) - strlen(szHeader), szFormatFloat,
1437 : dfNoData);
1438 2 : snprintf(szHeader + strlen(szHeader),
1439 2 : sizeof(szHeader) - strlen(szHeader), "%s", "\n");
1440 : }
1441 :
1442 41 : if (VSIFWriteL(szHeader, strlen(szHeader), 1, fpImage) != 1)
1443 : {
1444 3 : CPL_IGNORE_RET_VAL(VSIFCloseL(fpImage));
1445 3 : return nullptr;
1446 : }
1447 :
1448 : // Loop over image, copying image data.
1449 :
1450 : // Write scanlines to output file
1451 : int *panScanline = bReadAsInt
1452 38 : ? static_cast<int *>(CPLMalloc(sizeof(int) * nXSize))
1453 38 : : nullptr;
1454 :
1455 : double *padfScanline =
1456 38 : bReadAsInt ? nullptr
1457 14 : : static_cast<double *>(CPLMalloc(sizeof(double) * nXSize));
1458 :
1459 38 : CPLErr eErr = CE_None;
1460 :
1461 38 : bool bHasOutputDecimalDot = false;
1462 548 : for (int iLine = 0; eErr == CE_None && iLine < nYSize; iLine++)
1463 : {
1464 1020 : CPLString osBuf;
1465 510 : const int iSrcLine =
1466 510 : adfGeoTransform[5] < 0 ? iLine : nYSize - 1 - iLine;
1467 510 : eErr = poBand->RasterIO(GF_Read, 0, iSrcLine, nXSize, 1,
1468 : bReadAsInt ? static_cast<void *>(panScanline)
1469 : : static_cast<void *>(padfScanline),
1470 : nXSize, 1, bReadAsInt ? GDT_Int32 : GDT_Float64,
1471 : 0, 0, nullptr);
1472 :
1473 510 : if (bReadAsInt)
1474 : {
1475 14657 : for (int iPixel = 0; iPixel < nXSize; iPixel++)
1476 : {
1477 14316 : snprintf(szHeader, sizeof(szHeader), "%d", panScanline[iPixel]);
1478 14316 : osBuf += szHeader;
1479 14316 : osBuf += ' ';
1480 14316 : if ((iPixel > 0 && (iPixel % 1024) == 0) ||
1481 14316 : iPixel == nXSize - 1)
1482 : {
1483 696 : if (VSIFWriteL(osBuf, static_cast<int>(osBuf.size()), 1,
1484 696 : fpImage) != 1)
1485 : {
1486 7 : eErr = CE_Failure;
1487 7 : ReportError(pszFilename, CE_Failure, CPLE_AppDefined,
1488 : "Write failed, disk full?");
1489 7 : break;
1490 : }
1491 341 : osBuf = "";
1492 : }
1493 : }
1494 : }
1495 : else
1496 : {
1497 162 : assert(padfScanline);
1498 :
1499 2514 : for (int iPixel = 0; iPixel < nXSize; iPixel++)
1500 : {
1501 2352 : CPLsnprintf(szHeader, sizeof(szHeader), szFormatFloat,
1502 2352 : padfScanline[iPixel]);
1503 :
1504 : // Make sure that as least one value has a decimal point (#6060)
1505 2352 : if (!bHasOutputDecimalDot)
1506 : {
1507 14 : if (strchr(szHeader, '.') || strchr(szHeader, 'e') ||
1508 11 : strchr(szHeader, 'E'))
1509 : {
1510 3 : bHasOutputDecimalDot = true;
1511 : }
1512 22 : else if (!std::isinf(padfScanline[iPixel]) &&
1513 11 : !std::isnan(padfScanline[iPixel]))
1514 : {
1515 11 : strcat(szHeader, ".0");
1516 11 : bHasOutputDecimalDot = true;
1517 : }
1518 : }
1519 :
1520 2352 : osBuf += szHeader;
1521 2352 : osBuf += ' ';
1522 2352 : if ((iPixel > 0 && (iPixel % 1024) == 0) ||
1523 2352 : iPixel == nXSize - 1)
1524 : {
1525 324 : if (VSIFWriteL(osBuf, static_cast<int>(osBuf.size()), 1,
1526 324 : fpImage) != 1)
1527 : {
1528 0 : eErr = CE_Failure;
1529 0 : ReportError(pszFilename, CE_Failure, CPLE_AppDefined,
1530 : "Write failed, disk full?");
1531 0 : break;
1532 : }
1533 162 : osBuf = "";
1534 : }
1535 : }
1536 : }
1537 510 : if (VSIFWriteL("\n", 1, 1, fpImage) != 1)
1538 1 : eErr = CE_Failure;
1539 :
1540 1013 : if (eErr == CE_None &&
1541 503 : !pfnProgress((iLine + 1) / static_cast<double>(nYSize), nullptr,
1542 : pProgressData))
1543 : {
1544 0 : eErr = CE_Failure;
1545 0 : ReportError(pszFilename, CE_Failure, CPLE_UserInterrupt,
1546 : "User terminated CreateCopy()");
1547 : }
1548 : }
1549 :
1550 38 : CPLFree(panScanline);
1551 38 : CPLFree(padfScanline);
1552 38 : if (VSIFCloseL(fpImage) != 0)
1553 0 : eErr = CE_Failure;
1554 :
1555 38 : if (eErr != CE_None)
1556 7 : return nullptr;
1557 :
1558 : // Try to write projection file.
1559 31 : const char *pszOriginalProjection = poSrcDS->GetProjectionRef();
1560 31 : if (!EQUAL(pszOriginalProjection, ""))
1561 : {
1562 21 : char *pszDirname = CPLStrdup(CPLGetPathSafe(pszFilename).c_str());
1563 21 : char *pszBasename = CPLStrdup(CPLGetBasenameSafe(pszFilename).c_str());
1564 21 : char *pszPrjFilename = CPLStrdup(
1565 42 : CPLFormFilenameSafe(pszDirname, pszBasename, "prj").c_str());
1566 21 : VSILFILE *fp = VSIFOpenL(pszPrjFilename, "wt");
1567 21 : if (fp != nullptr)
1568 : {
1569 42 : OGRSpatialReference oSRS;
1570 21 : oSRS.importFromWkt(pszOriginalProjection);
1571 21 : oSRS.morphToESRI();
1572 21 : char *pszESRIProjection = nullptr;
1573 21 : oSRS.exportToWkt(&pszESRIProjection);
1574 21 : CPL_IGNORE_RET_VAL(VSIFWriteL(pszESRIProjection, 1,
1575 : strlen(pszESRIProjection), fp));
1576 :
1577 21 : CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
1578 21 : CPLFree(pszESRIProjection);
1579 : }
1580 : else
1581 : {
1582 0 : ReportError(pszFilename, CE_Failure, CPLE_FileIO,
1583 : "Unable to create file %s.", pszPrjFilename);
1584 : }
1585 21 : CPLFree(pszDirname);
1586 21 : CPLFree(pszBasename);
1587 21 : CPLFree(pszPrjFilename);
1588 : }
1589 :
1590 : // Re-open dataset, and copy any auxiliary pam information.
1591 :
1592 : // If writing to stdout, we can't reopen it, so return
1593 : // a fake dataset to make the caller happy.
1594 31 : CPLPushErrorHandler(CPLQuietErrorHandler);
1595 : GDALPamDataset *poDS =
1596 31 : reinterpret_cast<GDALPamDataset *>(GDALOpen(pszFilename, GA_ReadOnly));
1597 31 : CPLPopErrorHandler();
1598 31 : if (poDS)
1599 : {
1600 31 : poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
1601 31 : return poDS;
1602 : }
1603 :
1604 0 : CPLErrorReset();
1605 :
1606 0 : AAIGDataset *poAAIG_DS = new AAIGDataset();
1607 0 : poAAIG_DS->nRasterXSize = nXSize;
1608 0 : poAAIG_DS->nRasterYSize = nYSize;
1609 0 : poAAIG_DS->nBands = 1;
1610 0 : poAAIG_DS->SetBand(1, new AAIGRasterBand(poAAIG_DS, 1));
1611 0 : return poAAIG_DS;
1612 : }
1613 :
1614 : /************************************************************************/
1615 : /* OSR_GDS() */
1616 : /************************************************************************/
1617 :
1618 11 : static CPLString OSR_GDS(char **papszNV, const char *pszField,
1619 : const char *pszDefaultValue)
1620 :
1621 : {
1622 11 : if (papszNV == nullptr || papszNV[0] == nullptr)
1623 0 : return pszDefaultValue;
1624 :
1625 11 : int iLine = 0; // Used after for.
1626 22 : for (; papszNV[iLine] != nullptr &&
1627 11 : !EQUALN(papszNV[iLine], pszField, strlen(pszField));
1628 : iLine++)
1629 : {
1630 : }
1631 :
1632 11 : if (papszNV[iLine] == nullptr)
1633 11 : return pszDefaultValue;
1634 : else
1635 : {
1636 0 : char **papszTokens = CSLTokenizeString(papszNV[iLine]);
1637 :
1638 0 : CPLString osResult;
1639 0 : if (CSLCount(papszTokens) > 1)
1640 0 : osResult = papszTokens[1];
1641 : else
1642 0 : osResult = pszDefaultValue;
1643 :
1644 0 : CSLDestroy(papszTokens);
1645 0 : return osResult;
1646 : }
1647 : }
1648 :
1649 : /************************************************************************/
1650 : /* GDALRegister_AAIGrid() */
1651 : /************************************************************************/
1652 :
1653 1682 : void GDALRegister_AAIGrid()
1654 :
1655 : {
1656 1682 : if (GDALGetDriverByName("AAIGrid") != nullptr)
1657 301 : return;
1658 :
1659 1381 : GDALDriver *poDriver = new GDALDriver();
1660 :
1661 1381 : poDriver->SetDescription("AAIGrid");
1662 1381 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1663 1381 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Arc/Info ASCII Grid");
1664 1381 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
1665 1381 : "drivers/raster/aaigrid.html");
1666 1381 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "asc");
1667 1381 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES,
1668 1381 : "Byte UInt16 Int16 Int32 Float32");
1669 :
1670 1381 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1671 1381 : poDriver->SetMetadataItem(
1672 : GDAL_DMD_CREATIONOPTIONLIST,
1673 : "<CreationOptionList>\n"
1674 : " <Option name='FORCE_CELLSIZE' type='boolean' description='Force "
1675 : "use of CELLSIZE, default is FALSE.'/>\n"
1676 : " <Option name='DECIMAL_PRECISION' type='int' description='Number of "
1677 : "decimal when writing floating-point numbers(%f).'/>\n"
1678 : " <Option name='SIGNIFICANT_DIGITS' type='int' description='Number "
1679 : "of significant digits when writing floating-point numbers(%g).'/>\n"
1680 1381 : "</CreationOptionList>\n");
1681 1381 : poDriver->SetMetadataItem(GDAL_DMD_OPENOPTIONLIST,
1682 : "<OpenOptionList>\n"
1683 : " <Option name='DATATYPE' type='string-select' "
1684 : "description='Data type to be used.'>\n"
1685 : " <Value>Int32</Value>\n"
1686 : " <Value>Float32</Value>\n"
1687 : " <Value>Float64</Value>\n"
1688 : " </Option>\n"
1689 1381 : "</OpenOptionList>\n");
1690 :
1691 1381 : poDriver->pfnOpen = AAIGDataset::Open;
1692 1381 : poDriver->pfnIdentify = AAIGDataset::Identify;
1693 1381 : poDriver->pfnCreateCopy = AAIGDataset::CreateCopy;
1694 :
1695 1381 : GetGDALDriverManager()->RegisterDriver(poDriver);
1696 : }
1697 :
1698 : /************************************************************************/
1699 : /* GDALRegister_GRASSASCIIGrid() */
1700 : /************************************************************************/
1701 :
1702 1682 : void GDALRegister_GRASSASCIIGrid()
1703 :
1704 : {
1705 1682 : if (GDALGetDriverByName("GRASSASCIIGrid") != nullptr)
1706 301 : return;
1707 :
1708 1381 : GDALDriver *poDriver = new GDALDriver();
1709 :
1710 1381 : poDriver->SetDescription("GRASSASCIIGrid");
1711 1381 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1712 1381 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "GRASS ASCII Grid");
1713 1381 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
1714 1381 : "drivers/raster/grassasciigrid.html");
1715 :
1716 1381 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1717 :
1718 1381 : poDriver->pfnOpen = GRASSASCIIDataset::Open;
1719 1381 : poDriver->pfnIdentify = GRASSASCIIDataset::Identify;
1720 :
1721 1381 : GetGDALDriverManager()->RegisterDriver(poDriver);
1722 : }
1723 :
1724 : /************************************************************************/
1725 : /* GDALRegister_ISG() */
1726 : /************************************************************************/
1727 :
1728 1682 : void GDALRegister_ISG()
1729 :
1730 : {
1731 1682 : if (GDALGetDriverByName("ISG") != nullptr)
1732 301 : return;
1733 :
1734 1381 : GDALDriver *poDriver = new GDALDriver();
1735 :
1736 1381 : poDriver->SetDescription("ISG");
1737 1381 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1738 1381 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
1739 1381 : "International Service for the Geoid");
1740 1381 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/isg.html");
1741 1381 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "isg");
1742 :
1743 1381 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1744 :
1745 1381 : poDriver->pfnOpen = ISGDataset::Open;
1746 1381 : poDriver->pfnIdentify = ISGDataset::Identify;
1747 :
1748 1381 : GetGDALDriverManager()->RegisterDriver(poDriver);
1749 : }
|