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