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