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