Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: XYZ driver
4 : * Purpose: GDALDataset driver for XYZ dataset.
5 : * Author: Even Rouault, <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2010-2013, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_string.h"
14 : #include "cpl_vsi_virtual.h"
15 : #include "gdal_frmts.h"
16 : #include "gdal_pam.h"
17 :
18 : #include <algorithm>
19 : #include <cmath>
20 : #include <mutex>
21 : #include <vector>
22 :
23 : constexpr double RELATIVE_ERROR = 1e-3;
24 :
25 : class XYZDataset;
26 :
27 : // Global cache when we must ingest all grid points
28 : static std::mutex gMutex;
29 : static XYZDataset *gpoActiveDS = nullptr;
30 : static std::vector<short> gasValues;
31 : static std::vector<float> gafValues;
32 :
33 : /************************************************************************/
34 : /* ==================================================================== */
35 : /* XYZDataset */
36 : /* ==================================================================== */
37 : /************************************************************************/
38 :
39 : class XYZRasterBand;
40 :
41 : class XYZDataset final : public GDALPamDataset
42 : {
43 : friend class XYZRasterBand;
44 :
45 : VSILFILE *fp;
46 : int bHasHeaderLine;
47 : int nCommentLineCount;
48 : char chDecimalSep;
49 : int nXIndex;
50 : int nYIndex;
51 : int nZIndex;
52 : int nMinTokens;
53 : GIntBig nLineNum; /* any line */
54 : GIntBig nDataLineNum; /* line with values (header line and empty lines
55 : ignored) */
56 : double adfGeoTransform[6];
57 : int bSameNumberOfValuesPerLine;
58 : double dfMinZ;
59 : double dfMaxZ;
60 : bool bEOF;
61 : bool bIngestAll = false;
62 :
63 : static int IdentifyEx(GDALOpenInfo *, int &, int &nCommentLineCount,
64 : int &nXIndex, int &nYIndex, int &nZIndex);
65 :
66 : public:
67 : XYZDataset();
68 : virtual ~XYZDataset();
69 :
70 : virtual CPLErr GetGeoTransform(double *) override;
71 :
72 : static GDALDataset *Open(GDALOpenInfo *);
73 : static int Identify(GDALOpenInfo *);
74 : static GDALDataset *CreateCopy(const char *pszFilename,
75 : GDALDataset *poSrcDS, int bStrict,
76 : char **papszOptions,
77 : GDALProgressFunc pfnProgress,
78 : void *pProgressData);
79 : };
80 :
81 : /************************************************************************/
82 : /* ==================================================================== */
83 : /* XYZRasterBand */
84 : /* ==================================================================== */
85 : /************************************************************************/
86 :
87 : class XYZRasterBand final : public GDALPamRasterBand
88 : {
89 : friend class XYZDataset;
90 :
91 : int nLastYOff;
92 :
93 : public:
94 : XYZRasterBand(XYZDataset *, int, GDALDataType);
95 :
96 : virtual CPLErr IReadBlock(int, int, void *) override;
97 : virtual double GetMinimum(int *pbSuccess = nullptr) override;
98 : virtual double GetMaximum(int *pbSuccess = nullptr) override;
99 : virtual double GetNoDataValue(int *pbSuccess = nullptr) override;
100 : };
101 :
102 : /************************************************************************/
103 : /* XYZRasterBand() */
104 : /************************************************************************/
105 :
106 37 : XYZRasterBand::XYZRasterBand(XYZDataset *poDSIn, int nBandIn, GDALDataType eDT)
107 37 : : nLastYOff(-1)
108 : {
109 37 : poDS = poDSIn;
110 37 : nBand = nBandIn;
111 :
112 37 : eDataType = eDT;
113 :
114 37 : nBlockXSize = poDSIn->GetRasterXSize();
115 37 : nBlockYSize = 1;
116 37 : }
117 :
118 : /************************************************************************/
119 : /* IReadBlock() */
120 : /************************************************************************/
121 :
122 298 : CPLErr XYZRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
123 : void *pImage)
124 : {
125 298 : XYZDataset *poGDS = reinterpret_cast<XYZDataset *>(poDS);
126 :
127 298 : if (poGDS->fp == nullptr)
128 0 : return CE_Failure;
129 :
130 298 : if (poGDS->bIngestAll)
131 : {
132 12 : CPLAssert(eDataType == GDT_Int16 || eDataType == GDT_Float32);
133 :
134 24 : std::lock_guard<std::mutex> guard(gMutex);
135 :
136 12 : if (gpoActiveDS != poGDS || (gasValues.empty() && gafValues.empty()))
137 : {
138 4 : gpoActiveDS = poGDS;
139 :
140 4 : const int nGridSize = nRasterXSize * nRasterYSize;
141 : try
142 : {
143 4 : if (eDataType == GDT_Int16)
144 2 : gasValues.resize(nGridSize);
145 : else
146 2 : gafValues.resize(nGridSize);
147 : }
148 0 : catch (const std::exception &)
149 : {
150 0 : CPLError(CE_Failure, CPLE_OutOfMemory, "Cannot allocate grid");
151 0 : return CE_Failure;
152 : }
153 :
154 4 : poGDS->nDataLineNum = 0;
155 4 : poGDS->nLineNum = 0;
156 4 : poGDS->bEOF = false;
157 4 : VSIFSeekL(poGDS->fp, 0, SEEK_SET);
158 :
159 4 : for (int i = 0; i < poGDS->nCommentLineCount; i++)
160 : {
161 0 : if (CPLReadLine2L(poGDS->fp, 100, nullptr) == nullptr)
162 : {
163 0 : poGDS->bEOF = true;
164 0 : return CE_Failure;
165 : }
166 0 : poGDS->nLineNum++;
167 : }
168 :
169 4 : if (poGDS->bHasHeaderLine)
170 : {
171 4 : const char *pszLine = CPLReadLine2L(poGDS->fp, 100, nullptr);
172 4 : if (pszLine == nullptr)
173 : {
174 0 : poGDS->bEOF = true;
175 0 : return CE_Failure;
176 : }
177 4 : poGDS->nLineNum++;
178 : }
179 :
180 34 : for (int i = 0; i < nGridSize; i++)
181 : {
182 30 : const char *pszLine = CPLReadLine2L(poGDS->fp, 100, nullptr);
183 30 : if (pszLine == nullptr)
184 : {
185 0 : poGDS->bEOF = true;
186 0 : CPLError(CE_Failure, CPLE_AppDefined,
187 : "Cannot read line " CPL_FRMT_GIB,
188 0 : poGDS->nLineNum + 1);
189 0 : return CE_Failure;
190 : }
191 30 : poGDS->nLineNum++;
192 :
193 30 : const char *pszPtr = pszLine;
194 : char ch;
195 30 : int nCol = 0;
196 30 : bool bLastWasSep = true;
197 30 : double dfX = 0.0;
198 30 : double dfY = 0.0;
199 30 : double dfZ = 0.0;
200 30 : int nUsefulColsFound = 0;
201 326 : while ((ch = *pszPtr) != '\0')
202 : {
203 296 : if (ch == ' ')
204 : {
205 60 : if (!bLastWasSep)
206 60 : nCol++;
207 60 : bLastWasSep = true;
208 : }
209 236 : else if ((ch == ',' && poGDS->chDecimalSep != ',') ||
210 236 : ch == '\t' || ch == ';')
211 : {
212 0 : nCol++;
213 0 : bLastWasSep = true;
214 : }
215 : else
216 : {
217 236 : if (bLastWasSep)
218 : {
219 90 : if (nCol == poGDS->nXIndex)
220 : {
221 30 : nUsefulColsFound++;
222 30 : dfX = CPLAtofDelim(pszPtr, poGDS->chDecimalSep);
223 : }
224 60 : else if (nCol == poGDS->nYIndex)
225 : {
226 30 : nUsefulColsFound++;
227 30 : dfY = CPLAtofDelim(pszPtr, poGDS->chDecimalSep);
228 : }
229 30 : else if (nCol == poGDS->nZIndex)
230 : {
231 30 : nUsefulColsFound++;
232 30 : dfZ = CPLAtofDelim(pszPtr, poGDS->chDecimalSep);
233 : }
234 : }
235 236 : bLastWasSep = false;
236 : }
237 296 : pszPtr++;
238 : }
239 :
240 : /* Skip empty line */
241 30 : if (nCol == 0 && bLastWasSep)
242 0 : continue;
243 :
244 30 : if (nUsefulColsFound != 3)
245 : {
246 0 : CPLError(
247 : CE_Failure, CPLE_AppDefined,
248 : "Unexpected number of values at line " CPL_FRMT_GIB,
249 : poGDS->nLineNum);
250 0 : return CE_Failure;
251 : }
252 :
253 30 : poGDS->nDataLineNum++;
254 :
255 30 : const int nX =
256 30 : static_cast<int>((dfX - 0.5 * poGDS->adfGeoTransform[1] -
257 30 : poGDS->adfGeoTransform[0]) /
258 30 : poGDS->adfGeoTransform[1] +
259 : 0.5);
260 30 : const int nY =
261 30 : static_cast<int>((dfY - 0.5 * poGDS->adfGeoTransform[5] -
262 30 : poGDS->adfGeoTransform[3]) /
263 30 : poGDS->adfGeoTransform[5] +
264 : 0.5);
265 30 : if (nX < 0 || nX >= nRasterXSize)
266 : {
267 0 : CPLError(CE_Failure, CPLE_AppDefined,
268 : "Unexpected X value at line " CPL_FRMT_GIB,
269 : poGDS->nLineNum);
270 0 : return CE_Failure;
271 : }
272 30 : if (nY < 0 || nY >= nRasterYSize)
273 : {
274 0 : CPLError(CE_Failure, CPLE_AppDefined,
275 : "Unexpected Y value at line " CPL_FRMT_GIB,
276 : poGDS->nLineNum);
277 0 : return CE_Failure;
278 : }
279 30 : const int nIdx = nX + nY * nRasterXSize;
280 30 : if (eDataType == GDT_Int16)
281 15 : gasValues[nIdx] = static_cast<short>(0.5 + dfZ);
282 : else
283 15 : gafValues[nIdx] = static_cast<float>(dfZ);
284 : }
285 : }
286 :
287 12 : if (eDataType == GDT_Int16)
288 6 : memcpy(pImage,
289 6 : &gasValues[static_cast<size_t>(nBlockYOff) * nBlockXSize],
290 6 : sizeof(short) * nBlockXSize);
291 : else
292 6 : memcpy(pImage,
293 6 : &gafValues[static_cast<size_t>(nBlockYOff) * nBlockXSize],
294 6 : sizeof(float) * nBlockXSize);
295 12 : return CE_None;
296 : }
297 :
298 286 : if (pImage)
299 : {
300 286 : int bSuccess = FALSE;
301 286 : double dfNoDataValue = GetNoDataValue(&bSuccess);
302 286 : if (!bSuccess)
303 264 : dfNoDataValue = 0.0;
304 572 : GDALCopyWords(&dfNoDataValue, GDT_Float64, 0, pImage, eDataType,
305 286 : GDALGetDataTypeSize(eDataType) / 8, nRasterXSize);
306 : }
307 :
308 : // Only valid if bSameNumberOfValuesPerLine.
309 286 : const GIntBig nLineInFile = static_cast<GIntBig>(nBlockYOff) * nBlockXSize;
310 286 : if ((poGDS->bSameNumberOfValuesPerLine &&
311 264 : poGDS->nDataLineNum > nLineInFile) ||
312 270 : (!poGDS->bSameNumberOfValuesPerLine &&
313 22 : (nLastYOff == -1 || nBlockYOff == 0)))
314 : {
315 21 : poGDS->nDataLineNum = 0;
316 21 : poGDS->nLineNum = 0;
317 21 : poGDS->bEOF = false;
318 21 : VSIFSeekL(poGDS->fp, 0, SEEK_SET);
319 :
320 21 : for (int i = 0; i < poGDS->nCommentLineCount; i++)
321 : {
322 0 : if (CPLReadLine2L(poGDS->fp, 100, nullptr) == nullptr)
323 : {
324 0 : poGDS->bEOF = true;
325 0 : return CE_Failure;
326 : }
327 0 : poGDS->nLineNum++;
328 : }
329 :
330 21 : if (poGDS->bHasHeaderLine)
331 : {
332 14 : const char *pszLine = CPLReadLine2L(poGDS->fp, 100, nullptr);
333 14 : if (pszLine == nullptr)
334 : {
335 0 : poGDS->bEOF = true;
336 0 : return CE_Failure;
337 : }
338 14 : poGDS->nLineNum++;
339 : }
340 : }
341 :
342 286 : if (!poGDS->bSameNumberOfValuesPerLine)
343 : {
344 22 : if (nBlockYOff < nLastYOff)
345 : {
346 0 : nLastYOff = -1;
347 0 : for (int iY = 0; iY < nBlockYOff; iY++)
348 : {
349 0 : if (IReadBlock(0, iY, nullptr) != CE_None)
350 0 : return CE_Failure;
351 : }
352 : }
353 : else
354 : {
355 22 : if (poGDS->bEOF)
356 : {
357 0 : return CE_Failure;
358 : }
359 22 : for (int iY = nLastYOff + 1; iY < nBlockYOff; iY++)
360 : {
361 0 : if (IReadBlock(0, iY, nullptr) != CE_None)
362 0 : return CE_Failure;
363 : }
364 : }
365 : }
366 : else
367 : {
368 264 : if (poGDS->bEOF)
369 : {
370 0 : return CE_Failure;
371 : }
372 290 : while (poGDS->nDataLineNum < nLineInFile)
373 : {
374 26 : const char *pszLine = CPLReadLine2L(poGDS->fp, 100, nullptr);
375 26 : if (pszLine == nullptr)
376 : {
377 0 : poGDS->bEOF = true;
378 0 : return CE_Failure;
379 : }
380 26 : poGDS->nLineNum++;
381 :
382 26 : const char *pszPtr = pszLine;
383 : char ch;
384 26 : int nCol = 0;
385 26 : bool bLastWasSep = true;
386 146 : while ((ch = *pszPtr) != '\0')
387 : {
388 120 : if (ch == ' ')
389 : {
390 40 : if (!bLastWasSep)
391 40 : nCol++;
392 40 : bLastWasSep = true;
393 : }
394 80 : else if ((ch == ',' && poGDS->chDecimalSep != ',') ||
395 80 : ch == '\t' || ch == ';')
396 : {
397 0 : nCol++;
398 0 : bLastWasSep = true;
399 : }
400 : else
401 : {
402 80 : bLastWasSep = false;
403 : }
404 120 : pszPtr++;
405 : }
406 :
407 : /* Skip empty line */
408 26 : if (nCol == 0 && bLastWasSep)
409 6 : continue;
410 :
411 20 : poGDS->nDataLineNum++;
412 : }
413 : }
414 :
415 286 : const double dfExpectedY = poGDS->adfGeoTransform[3] +
416 286 : (0.5 + nBlockYOff) * poGDS->adfGeoTransform[5];
417 :
418 286 : int idx = -1;
419 : while (true)
420 : {
421 : int nCol;
422 : bool bLastWasSep;
423 7 : do
424 : {
425 41302 : const vsi_l_offset nOffsetBefore = VSIFTellL(poGDS->fp);
426 41302 : const char *pszLine = CPLReadLine2L(poGDS->fp, 100, nullptr);
427 41302 : if (pszLine == nullptr)
428 : {
429 1 : poGDS->bEOF = true;
430 1 : if (poGDS->bSameNumberOfValuesPerLine)
431 : {
432 0 : CPLError(CE_Failure, CPLE_AppDefined,
433 : "Cannot read line " CPL_FRMT_GIB,
434 0 : poGDS->nLineNum + 1);
435 0 : return CE_Failure;
436 : }
437 : else
438 : {
439 1 : nLastYOff = nBlockYOff;
440 : }
441 1 : return CE_None;
442 : }
443 41301 : poGDS->nLineNum++;
444 :
445 41301 : const char *pszPtr = pszLine;
446 : char ch;
447 41301 : nCol = 0;
448 41301 : bLastWasSep = true;
449 41301 : double dfX = 0.0;
450 41301 : double dfY = 0.0;
451 41301 : double dfZ = 0.0;
452 41301 : int nUsefulColsFound = 0;
453 1342950 : while ((ch = *pszPtr) != '\0')
454 : {
455 1301650 : if (ch == ' ')
456 : {
457 1906 : if (!bLastWasSep)
458 1786 : nCol++;
459 1906 : bLastWasSep = true;
460 : }
461 1299750 : else if ((ch == ',' && poGDS->chDecimalSep != ',') ||
462 1218940 : ch == '\t' || ch == ';')
463 : {
464 80802 : nCol++;
465 80802 : bLastWasSep = true;
466 : }
467 : else
468 : {
469 1218940 : if (bLastWasSep)
470 : {
471 123882 : if (nCol == poGDS->nXIndex)
472 : {
473 41294 : nUsefulColsFound++;
474 41294 : if (!poGDS->bSameNumberOfValuesPerLine)
475 36 : dfX = CPLAtofDelim(pszPtr, poGDS->chDecimalSep);
476 : }
477 82588 : else if (nCol == poGDS->nYIndex)
478 : {
479 41294 : nUsefulColsFound++;
480 41294 : if (!poGDS->bSameNumberOfValuesPerLine)
481 36 : dfY = CPLAtofDelim(pszPtr, poGDS->chDecimalSep);
482 : }
483 41294 : else if (nCol == poGDS->nZIndex)
484 : {
485 41294 : nUsefulColsFound++;
486 41294 : dfZ = CPLAtofDelim(pszPtr, poGDS->chDecimalSep);
487 : }
488 : }
489 1218940 : bLastWasSep = false;
490 : }
491 1301650 : pszPtr++;
492 : }
493 41301 : nCol++;
494 :
495 41301 : if (nUsefulColsFound == 3)
496 : {
497 41294 : if (poGDS->bSameNumberOfValuesPerLine)
498 : {
499 41258 : idx++;
500 : }
501 : else
502 : {
503 36 : if (fabs((dfY - dfExpectedY) / poGDS->adfGeoTransform[5]) >
504 : RELATIVE_ERROR)
505 : {
506 5 : if (idx < 0)
507 : {
508 3 : const double dfYDeltaOrigin =
509 3 : dfY + 0.5 * poGDS->adfGeoTransform[5] -
510 3 : poGDS->adfGeoTransform[3];
511 6 : if (!(fabs(dfYDeltaOrigin) >
512 3 : fabs(poGDS->adfGeoTransform[5]) &&
513 3 : fabs(std::round(dfYDeltaOrigin /
514 3 : poGDS->adfGeoTransform[5]) -
515 3 : (dfYDeltaOrigin /
516 3 : poGDS->adfGeoTransform[5])) <=
517 : RELATIVE_ERROR))
518 : {
519 0 : CPLError(CE_Failure, CPLE_AppDefined,
520 : "At line " CPL_FRMT_GIB
521 : ", found Y=%f instead of %f "
522 : "for nBlockYOff = %d",
523 : poGDS->nLineNum, dfY, dfExpectedY,
524 : nBlockYOff);
525 0 : return CE_Failure;
526 : }
527 : }
528 5 : VSIFSeekL(poGDS->fp, nOffsetBefore, SEEK_SET);
529 5 : nLastYOff = nBlockYOff;
530 5 : poGDS->nLineNum--;
531 5 : return CE_None;
532 : }
533 :
534 31 : idx = static_cast<int>((dfX -
535 31 : 0.5 * poGDS->adfGeoTransform[1] -
536 31 : poGDS->adfGeoTransform[0]) /
537 31 : poGDS->adfGeoTransform[1] +
538 : 0.5);
539 : }
540 41289 : CPLAssert(idx >= 0 && idx < nRasterXSize);
541 :
542 41289 : if (pImage)
543 : {
544 41289 : if (eDataType == GDT_Float32)
545 : {
546 40416 : reinterpret_cast<float *>(pImage)[idx] =
547 40416 : static_cast<float>(dfZ);
548 : }
549 873 : else if (eDataType == GDT_Int32)
550 : {
551 400 : reinterpret_cast<GInt32 *>(pImage)[idx] =
552 400 : static_cast<GInt32>(dfZ);
553 : }
554 473 : else if (eDataType == GDT_Int16)
555 : {
556 13 : reinterpret_cast<GInt16 *>(pImage)[idx] =
557 13 : static_cast<GInt16>(dfZ);
558 : }
559 : else
560 : {
561 460 : reinterpret_cast<GByte *>(pImage)[idx] =
562 460 : static_cast<GByte>(dfZ);
563 : }
564 : }
565 : }
566 : /* Skip empty line */
567 41296 : } while (nCol == 1 && bLastWasSep);
568 :
569 41289 : poGDS->nDataLineNum++;
570 41289 : if (nCol < poGDS->nMinTokens)
571 0 : return CE_Failure;
572 :
573 41289 : if (idx + 1 == nRasterXSize)
574 280 : break;
575 41009 : }
576 :
577 280 : if (poGDS->bSameNumberOfValuesPerLine)
578 : {
579 264 : if (poGDS->nDataLineNum !=
580 264 : static_cast<GIntBig>(nBlockYOff + 1) * nBlockXSize)
581 : {
582 0 : CPLError(CE_Failure, CPLE_AssertionFailed,
583 : "The file does not have the same number of values per "
584 : "line as initially thought. It must be somehow corrupted");
585 0 : return CE_Failure;
586 : }
587 : }
588 :
589 280 : nLastYOff = nBlockYOff;
590 :
591 280 : return CE_None;
592 : }
593 :
594 : /************************************************************************/
595 : /* GetMinimum() */
596 : /************************************************************************/
597 :
598 1 : double XYZRasterBand::GetMinimum(int *pbSuccess)
599 : {
600 1 : XYZDataset *poGDS = reinterpret_cast<XYZDataset *>(poDS);
601 1 : if (pbSuccess)
602 1 : *pbSuccess = TRUE;
603 1 : return poGDS->dfMinZ;
604 : }
605 :
606 : /************************************************************************/
607 : /* GetMaximum() */
608 : /************************************************************************/
609 :
610 1 : double XYZRasterBand::GetMaximum(int *pbSuccess)
611 : {
612 1 : XYZDataset *poGDS = reinterpret_cast<XYZDataset *>(poDS);
613 1 : if (pbSuccess)
614 1 : *pbSuccess = TRUE;
615 1 : return poGDS->dfMaxZ;
616 : }
617 :
618 : /************************************************************************/
619 : /* GetNoDataValue() */
620 : /************************************************************************/
621 :
622 298 : double XYZRasterBand::GetNoDataValue(int *pbSuccess)
623 : {
624 298 : XYZDataset *poGDS = reinterpret_cast<XYZDataset *>(poDS);
625 298 : if (!poGDS->bSameNumberOfValuesPerLine && poGDS->dfMinZ > -32768 &&
626 23 : eDataType != GDT_Byte)
627 : {
628 0 : if (pbSuccess)
629 0 : *pbSuccess = TRUE;
630 0 : return (poGDS->dfMinZ > 0) ? 0 : -32768;
631 : }
632 298 : else if (!poGDS->bSameNumberOfValuesPerLine && poGDS->dfMinZ > 0 &&
633 23 : eDataType == GDT_Byte)
634 : {
635 23 : if (pbSuccess)
636 23 : *pbSuccess = TRUE;
637 23 : return 0;
638 : }
639 :
640 275 : return GDALPamRasterBand::GetNoDataValue(pbSuccess);
641 : }
642 :
643 : /************************************************************************/
644 : /* ~XYZDataset() */
645 : /************************************************************************/
646 :
647 37 : XYZDataset::XYZDataset()
648 : : fp(nullptr), bHasHeaderLine(FALSE), nCommentLineCount(0),
649 : chDecimalSep('.'), nXIndex(-1), nYIndex(-1), nZIndex(-1), nMinTokens(0),
650 : nLineNum(0), nDataLineNum(GINTBIG_MAX), bSameNumberOfValuesPerLine(TRUE),
651 37 : dfMinZ(0), dfMaxZ(0), bEOF(false)
652 : {
653 37 : adfGeoTransform[0] = 0;
654 37 : adfGeoTransform[1] = 1;
655 37 : adfGeoTransform[2] = 0;
656 37 : adfGeoTransform[3] = 0;
657 37 : adfGeoTransform[4] = 0;
658 37 : adfGeoTransform[5] = 1;
659 37 : }
660 :
661 : /************************************************************************/
662 : /* ~XYZDataset() */
663 : /************************************************************************/
664 :
665 74 : XYZDataset::~XYZDataset()
666 :
667 : {
668 37 : FlushCache(true);
669 37 : if (fp)
670 37 : VSIFCloseL(fp);
671 :
672 : {
673 74 : std::lock_guard<std::mutex> guard(gMutex);
674 37 : if (gpoActiveDS == this)
675 : {
676 4 : gpoActiveDS = nullptr;
677 4 : gasValues.clear();
678 4 : gafValues.clear();
679 : }
680 : }
681 74 : }
682 :
683 : /************************************************************************/
684 : /* Identify() */
685 : /************************************************************************/
686 :
687 50724 : int XYZDataset::Identify(GDALOpenInfo *poOpenInfo)
688 : {
689 : int bHasHeaderLine, nCommentLineCount;
690 : int nXIndex;
691 : int nYIndex;
692 : int nZIndex;
693 50724 : return IdentifyEx(poOpenInfo, bHasHeaderLine, nCommentLineCount, nXIndex,
694 101442 : nYIndex, nZIndex);
695 : }
696 :
697 : /************************************************************************/
698 : /* IdentifyEx() */
699 : /************************************************************************/
700 :
701 50746 : int XYZDataset::IdentifyEx(GDALOpenInfo *poOpenInfo, int &bHasHeaderLine,
702 : int &nCommentLineCount, int &nXIndex, int &nYIndex,
703 : int &nZIndex)
704 :
705 : {
706 50746 : bHasHeaderLine = FALSE;
707 50746 : nCommentLineCount = 0;
708 :
709 101485 : CPLString osFilename(poOpenInfo->pszFilename);
710 50749 : if (EQUAL(CPLGetExtension(osFilename), "GRA") &&
711 0 : !poOpenInfo->IsSingleAllowedDriver("XYZ"))
712 : {
713 : // IGNFHeightASCIIGRID .GRA
714 0 : return FALSE;
715 : }
716 :
717 50743 : std::unique_ptr<GDALOpenInfo> poOpenInfoToDelete; // keep in this scope
718 : /* GZipped .xyz files are common, so automagically open them */
719 : /* if the /vsigzip/ has not been explicitly passed */
720 50742 : if (strlen(poOpenInfo->pszFilename) > 6 &&
721 49882 : EQUAL(poOpenInfo->pszFilename + strlen(poOpenInfo->pszFilename) - 6,
722 0 : "xyz.gz") &&
723 0 : !STARTS_WITH_CI(poOpenInfo->pszFilename, "/vsigzip/"))
724 : {
725 0 : osFilename = "/vsigzip/";
726 0 : osFilename += poOpenInfo->pszFilename;
727 0 : poOpenInfoToDelete = std::make_unique<GDALOpenInfo>(
728 0 : osFilename.c_str(), GA_ReadOnly, poOpenInfo->GetSiblingFiles());
729 0 : poOpenInfo = poOpenInfoToDelete.get();
730 : }
731 :
732 50743 : if (poOpenInfo->nHeaderBytes == 0)
733 : {
734 47710 : return FALSE;
735 : }
736 :
737 : /* -------------------------------------------------------------------- */
738 : /* Check that it looks roughly as an XYZ dataset */
739 : /* -------------------------------------------------------------------- */
740 3033 : const char *pszData =
741 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
742 :
743 3034 : if (poOpenInfo->nHeaderBytes >= 4 && STARTS_WITH(pszData, "DSAA") &&
744 1 : !poOpenInfo->IsSingleAllowedDriver("XYZ"))
745 : {
746 : // Do not match GSAG datasets
747 1 : return FALSE;
748 : }
749 :
750 : /* Skip comments line at the beginning such as in */
751 : /* http://pubs.usgs.gov/of/2003/ofr-03-230/DATA/NSLCU.XYZ */
752 3032 : int i = 0;
753 3032 : if (pszData[i] == '/')
754 : {
755 9 : nCommentLineCount++;
756 :
757 9 : i++;
758 81 : for (; i < poOpenInfo->nHeaderBytes; i++)
759 : {
760 81 : const char ch = pszData[i];
761 81 : if (ch == 13 || ch == 10)
762 : {
763 9 : if (ch == 13 && pszData[i + 1] == 10)
764 0 : i++;
765 9 : if (pszData[i + 1] == '/')
766 : {
767 0 : nCommentLineCount++;
768 0 : i++;
769 : }
770 : else
771 9 : break;
772 : }
773 : }
774 : }
775 :
776 3032 : int iStartLine = i;
777 27310 : for (; i < poOpenInfo->nHeaderBytes; i++)
778 : {
779 27264 : const char ch = pszData[i];
780 27264 : if (ch == 13 || ch == 10)
781 : {
782 : break;
783 : }
784 27101 : else if (ch == ' ' || ch == ',' || ch == '\t' || ch == ';')
785 : ;
786 16289 : else if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '+' ||
787 10879 : ch == '-' || ch == 'e' || ch == 'E')
788 : ;
789 10260 : else if (ch == '"' || (ch >= 'a' && ch <= 'z') ||
790 3818 : (ch >= 'A' && ch <= 'Z'))
791 7437 : bHasHeaderLine = TRUE;
792 : else
793 : {
794 2823 : return FALSE;
795 : }
796 : }
797 :
798 209 : nXIndex = -1;
799 209 : nYIndex = -1;
800 209 : nZIndex = -1;
801 418 : const char *pszColumnOrder = CSLFetchNameValueDef(
802 209 : poOpenInfo->papszOpenOptions, "COLUMN_ORDER", "AUTO");
803 209 : if (EQUAL(pszColumnOrder, "XYZ"))
804 : {
805 2 : nXIndex = 0;
806 2 : nYIndex = 1;
807 2 : nZIndex = 2;
808 2 : return TRUE;
809 : }
810 207 : else if (EQUAL(pszColumnOrder, "YXZ"))
811 : {
812 4 : nXIndex = 1;
813 4 : nYIndex = 0;
814 4 : nZIndex = 2;
815 4 : return TRUE;
816 : }
817 203 : else if (!EQUAL(pszColumnOrder, "AUTO"))
818 : {
819 1 : CPLError(CE_Failure, CPLE_IllegalArg,
820 : "Option COLUMN_ORDER can only be XYZ, YXZ and AUTO."
821 : "%s is not valid",
822 : pszColumnOrder);
823 1 : return FALSE;
824 : }
825 :
826 202 : if (bHasHeaderLine)
827 : {
828 124 : CPLString osHeaderLine;
829 124 : osHeaderLine.assign(pszData + iStartLine, i - iStartLine);
830 : char **papszTokens =
831 124 : CSLTokenizeString2(osHeaderLine, " ,\t;", CSLT_HONOURSTRINGS);
832 124 : int nTokens = CSLCount(papszTokens);
833 855 : for (int iToken = 0; iToken < nTokens; iToken++)
834 : {
835 731 : const char *pszToken = papszTokens[iToken];
836 731 : if (EQUAL(pszToken, "x") || STARTS_WITH_CI(pszToken, "lon") ||
837 701 : STARTS_WITH_CI(pszToken, "east"))
838 30 : nXIndex = iToken;
839 701 : else if (EQUAL(pszToken, "y") || STARTS_WITH_CI(pszToken, "lat") ||
840 673 : STARTS_WITH_CI(pszToken, "north"))
841 28 : nYIndex = iToken;
842 673 : else if (EQUAL(pszToken, "z") || STARTS_WITH_CI(pszToken, "alt") ||
843 645 : EQUAL(pszToken, "height"))
844 28 : nZIndex = iToken;
845 : }
846 124 : CSLDestroy(papszTokens);
847 124 : if (nXIndex >= 0 && nYIndex >= 0 && nZIndex >= 0)
848 : {
849 28 : return TRUE;
850 : }
851 : }
852 :
853 174 : bool bHasFoundNewLine = false;
854 174 : bool bPrevWasSep = true;
855 174 : int nCols = 0;
856 174 : int nMaxCols = 0;
857 11566 : for (; i < poOpenInfo->nHeaderBytes; i++)
858 : {
859 11488 : char ch = pszData[i];
860 11488 : if (ch == 13 || ch == 10)
861 : {
862 832 : bHasFoundNewLine = true;
863 832 : if (!bPrevWasSep)
864 : {
865 685 : nCols++;
866 685 : if (nCols > nMaxCols)
867 37 : nMaxCols = nCols;
868 : }
869 832 : bPrevWasSep = true;
870 832 : nCols = 0;
871 : }
872 10656 : else if (ch == ' ' || ch == ',' || ch == '\t' || ch == ';')
873 : {
874 1738 : if (!bPrevWasSep)
875 : {
876 1692 : nCols++;
877 1692 : if (nCols > nMaxCols)
878 93 : nMaxCols = nCols;
879 : }
880 1738 : bPrevWasSep = true;
881 : }
882 8918 : else if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '+' ||
883 96 : ch == '-' || ch == 'e' || ch == 'E')
884 : {
885 8822 : bPrevWasSep = false;
886 : }
887 : else
888 : {
889 96 : return FALSE;
890 : }
891 : }
892 :
893 78 : return bHasFoundNewLine && nMaxCols >= 3;
894 : }
895 :
896 : /************************************************************************/
897 : /* Open() */
898 : /************************************************************************/
899 :
900 24 : GDALDataset *XYZDataset::Open(GDALOpenInfo *poOpenInfo)
901 :
902 : {
903 : int bHasHeaderLine;
904 24 : int nCommentLineCount = 0;
905 :
906 24 : int nXIndex = -1;
907 24 : int nYIndex = -1;
908 24 : int nZIndex = -1;
909 24 : if (!IdentifyEx(poOpenInfo, bHasHeaderLine, nCommentLineCount, nXIndex,
910 : nYIndex, nZIndex))
911 0 : return nullptr;
912 :
913 48 : CPLString osFilename(poOpenInfo->pszFilename);
914 :
915 : /* GZipped .xyz files are common, so automagically open them */
916 : /* if the /vsigzip/ has not been explicitly passed */
917 24 : if (strlen(poOpenInfo->pszFilename) > 6 &&
918 24 : EQUAL(poOpenInfo->pszFilename + strlen(poOpenInfo->pszFilename) - 6,
919 0 : "xyz.gz") &&
920 0 : !STARTS_WITH_CI(poOpenInfo->pszFilename, "/vsigzip/"))
921 : {
922 0 : osFilename = "/vsigzip/";
923 0 : osFilename += poOpenInfo->pszFilename;
924 : }
925 :
926 : /* -------------------------------------------------------------------- */
927 : /* Find dataset characteristics */
928 : /* -------------------------------------------------------------------- */
929 24 : VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "rb");
930 24 : if (fp == nullptr)
931 0 : return nullptr;
932 :
933 : /* For better performance of CPLReadLine2L() we create a buffered reader */
934 : /* (except for /vsigzip/ since it has one internally) */
935 24 : if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "/vsigzip/"))
936 24 : fp = VSICreateBufferedReaderHandle(fp);
937 :
938 24 : int nMinTokens = 0;
939 :
940 24 : for (int i = 0; i < nCommentLineCount; i++)
941 : {
942 0 : if (CPLReadLine2L(fp, 100, nullptr) == nullptr)
943 : {
944 0 : VSIFCloseL(fp);
945 0 : return nullptr;
946 : }
947 : }
948 :
949 : /* -------------------------------------------------------------------- */
950 : /* Parse header line */
951 : /* -------------------------------------------------------------------- */
952 24 : if (bHasHeaderLine)
953 : {
954 17 : const char *pszLine = CPLReadLine2L(fp, 100, nullptr);
955 17 : if (pszLine == nullptr)
956 : {
957 0 : VSIFCloseL(fp);
958 0 : return nullptr;
959 : }
960 17 : if (nXIndex < 0 || nYIndex < 0 || nZIndex < 0)
961 : {
962 1 : CPLError(CE_Warning, CPLE_AppDefined,
963 : "Could not find one of the X, Y or Z column names in "
964 : "header line. Defaulting to the first 3 columns");
965 1 : nXIndex = 0;
966 1 : nYIndex = 1;
967 1 : nZIndex = 2;
968 : }
969 : }
970 7 : else if (nXIndex < 0 || nYIndex < 0 || nZIndex < 0)
971 : {
972 6 : nXIndex = 0;
973 6 : nYIndex = 1;
974 6 : nZIndex = 2;
975 : }
976 24 : nMinTokens = 1 + std::max(std::max(nXIndex, nYIndex), nZIndex);
977 :
978 : /* -------------------------------------------------------------------- */
979 : /* Parse data lines */
980 : /* -------------------------------------------------------------------- */
981 :
982 24 : GIntBig nLineNum = 0;
983 24 : GIntBig nDataLineNum = 0;
984 24 : double dfX = 0.0;
985 24 : double dfY = 0.0;
986 24 : double dfZ = 0.0;
987 24 : double dfMinX = 0.0;
988 24 : double dfMinY = 0.0;
989 24 : double dfMaxX = 0.0;
990 24 : double dfMaxY = 0.0;
991 24 : double dfMinZ = 0.0;
992 24 : double dfMaxZ = 0.0;
993 24 : double dfLastX = 0.0;
994 24 : double dfLastY = 0.0;
995 48 : std::vector<double> adfStepX;
996 48 : std::vector<double> adfStepY;
997 24 : GDALDataType eDT = GDT_Byte;
998 24 : bool bSameNumberOfValuesPerLine = true;
999 24 : char chDecimalSep = '\0';
1000 24 : int nStepYSign = 0;
1001 24 : bool bColOrganization = false;
1002 :
1003 : const char *pszLine;
1004 24 : GIntBig nCountStepX = 0;
1005 24 : GIntBig nCountStepY = 0;
1006 41384 : while ((pszLine = CPLReadLine2L(fp, 100, nullptr)) != nullptr)
1007 : {
1008 41360 : nLineNum++;
1009 :
1010 41360 : const char *pszPtr = pszLine;
1011 : char ch;
1012 41360 : int nCol = 0;
1013 41360 : bool bLastWasSep = true;
1014 41360 : if (chDecimalSep == '\0')
1015 : {
1016 871 : int nCountComma = 0;
1017 871 : int nCountFieldSep = 0;
1018 15575 : while ((ch = *pszPtr) != '\0')
1019 : {
1020 14715 : if (ch == '.')
1021 : {
1022 11 : chDecimalSep = '.';
1023 11 : break;
1024 : }
1025 14704 : else if (ch == ',')
1026 : {
1027 3 : nCountComma++;
1028 3 : bLastWasSep = false;
1029 : }
1030 14701 : else if (ch == ' ')
1031 : {
1032 1701 : if (!bLastWasSep)
1033 1698 : nCountFieldSep++;
1034 1701 : bLastWasSep = true;
1035 : }
1036 13000 : else if (ch == '\t' || ch == ';')
1037 : {
1038 4 : nCountFieldSep++;
1039 4 : bLastWasSep = true;
1040 : }
1041 : else
1042 12996 : bLastWasSep = false;
1043 14704 : pszPtr++;
1044 : }
1045 871 : if (chDecimalSep == '\0')
1046 : {
1047 : /* 1,2,3 */
1048 860 : if (nCountComma >= 2 && nCountFieldSep == 0)
1049 1 : chDecimalSep = '.';
1050 : /* 23,5;33;45 */
1051 859 : else if (nCountComma > 0 && nCountFieldSep > 0)
1052 1 : chDecimalSep = ',';
1053 : }
1054 871 : pszPtr = pszLine;
1055 871 : bLastWasSep = true;
1056 : }
1057 :
1058 41360 : char chLocalDecimalSep = chDecimalSep ? chDecimalSep : '.';
1059 41360 : int nUsefulColsFound = 0;
1060 1343410 : while ((ch = *pszPtr) != '\0')
1061 : {
1062 1302050 : if (ch == ' ')
1063 : {
1064 1984 : if (!bLastWasSep)
1065 1864 : nCol++;
1066 1984 : bLastWasSep = true;
1067 : }
1068 1300070 : else if ((ch == ',' && chLocalDecimalSep != ',') || ch == '\t' ||
1069 : ch == ';')
1070 : {
1071 80826 : nCol++;
1072 80826 : bLastWasSep = true;
1073 : }
1074 : else
1075 : {
1076 1219240 : if (bLastWasSep)
1077 : {
1078 124035 : if (nCol == nXIndex)
1079 : {
1080 41345 : nUsefulColsFound++;
1081 41345 : dfX = CPLAtofDelim(pszPtr, chLocalDecimalSep);
1082 : }
1083 82690 : else if (nCol == nYIndex)
1084 : {
1085 41345 : nUsefulColsFound++;
1086 41345 : dfY = CPLAtofDelim(pszPtr, chLocalDecimalSep);
1087 : }
1088 41345 : else if (nCol == nZIndex)
1089 : {
1090 41345 : nUsefulColsFound++;
1091 41345 : dfZ = CPLAtofDelim(pszPtr, chLocalDecimalSep);
1092 41345 : if (nDataLineNum == 0)
1093 : {
1094 24 : dfMinZ = dfZ;
1095 24 : dfMaxZ = dfZ;
1096 : }
1097 41321 : else if (dfZ < dfMinZ)
1098 : {
1099 19 : dfMinZ = dfZ;
1100 : }
1101 41302 : else if (dfZ > dfMaxZ)
1102 : {
1103 297 : dfMaxZ = dfZ;
1104 : }
1105 :
1106 41345 : if (dfZ < INT_MIN || dfZ > INT_MAX)
1107 : {
1108 0 : eDT = GDT_Float32;
1109 : }
1110 : else
1111 : {
1112 41345 : int nZ = static_cast<int>(dfZ);
1113 41345 : if (static_cast<double>(nZ) != dfZ)
1114 : {
1115 28327 : eDT = GDT_Float32;
1116 : }
1117 13018 : else if ((eDT == GDT_Byte || eDT == GDT_Int16)
1118 : // cppcheck-suppress
1119 : // knownConditionTrueFalse
1120 5188 : && (nZ < 0 || nZ > 255))
1121 : {
1122 10 : if (nZ < -32768 || nZ > 32767)
1123 0 : eDT = GDT_Int32;
1124 : else
1125 10 : eDT = GDT_Int16;
1126 : }
1127 : }
1128 : }
1129 : }
1130 1219240 : bLastWasSep = false;
1131 : }
1132 1302050 : pszPtr++;
1133 : }
1134 : /* skip empty lines */
1135 41360 : if (bLastWasSep && nCol == 0)
1136 : {
1137 15 : continue;
1138 : }
1139 41345 : nDataLineNum++;
1140 41345 : nCol++;
1141 41345 : if (nCol < nMinTokens)
1142 : {
1143 0 : CPLError(CE_Failure, CPLE_AppDefined,
1144 : "At line " CPL_FRMT_GIB
1145 : ", found %d tokens. Expected %d at least",
1146 : nLineNum, nCol, nMinTokens);
1147 0 : VSIFCloseL(fp);
1148 0 : return nullptr;
1149 : }
1150 41345 : if (nUsefulColsFound != 3)
1151 : {
1152 0 : CPLError(CE_Failure, CPLE_AppDefined,
1153 : "At line " CPL_FRMT_GIB
1154 : ", did not find X, Y and/or Z values",
1155 : nLineNum);
1156 0 : VSIFCloseL(fp);
1157 0 : return nullptr;
1158 : }
1159 :
1160 41345 : if (nDataLineNum == 1)
1161 : {
1162 24 : dfMinX = dfX;
1163 24 : dfMaxX = dfX;
1164 24 : dfMinY = dfY;
1165 24 : dfMaxY = dfY;
1166 : }
1167 41321 : else if (nDataLineNum == 2 && dfX == dfLastX)
1168 : {
1169 : // Detect datasets organized by columns
1170 7 : if (dfY == dfLastY)
1171 : {
1172 0 : CPLError(CE_Failure, CPLE_AppDefined,
1173 : "Ungridded dataset: At line " CPL_FRMT_GIB
1174 : ", Failed to detect grid layout.",
1175 : nLineNum);
1176 0 : VSIFCloseL(fp);
1177 0 : return nullptr;
1178 : }
1179 :
1180 7 : bColOrganization = true;
1181 7 : const double dfStepY = dfY - dfLastY;
1182 7 : adfStepY.push_back(fabs(dfStepY));
1183 7 : nStepYSign = dfStepY > 0 ? 1 : -1;
1184 : }
1185 41314 : else if (bColOrganization)
1186 : {
1187 29 : const double dfStepX = dfX - dfLastX;
1188 29 : if (dfStepX == 0)
1189 : {
1190 19 : const double dfStepY = dfY - dfLastY;
1191 19 : const double dfExpectedStepY = adfStepY.back() * nStepYSign;
1192 19 : if (fabs((dfStepY - dfExpectedStepY) / dfExpectedStepY) >
1193 : RELATIVE_ERROR)
1194 : {
1195 0 : CPLError(CE_Failure, CPLE_AppDefined,
1196 : "Ungridded dataset: At line " CPL_FRMT_GIB
1197 : ", Y spacing was %f. Expected %f",
1198 : nLineNum, dfStepY, dfExpectedStepY);
1199 0 : VSIFCloseL(fp);
1200 0 : return nullptr;
1201 : }
1202 : }
1203 10 : else if (dfStepX > 0)
1204 : {
1205 8 : if (adfStepX.empty())
1206 : {
1207 5 : adfStepX.push_back(dfStepX);
1208 : }
1209 3 : else if (fabs((dfStepX - adfStepX.back()) / adfStepX.back()) >
1210 : RELATIVE_ERROR)
1211 : {
1212 0 : CPLError(CE_Failure, CPLE_AppDefined,
1213 : "Ungridded dataset: At line " CPL_FRMT_GIB
1214 : ", X spacing was %f. Expected %f",
1215 0 : nLineNum, dfStepX, adfStepX.back());
1216 0 : VSIFCloseL(fp);
1217 0 : return nullptr;
1218 : }
1219 : }
1220 2 : else if (nDataLineNum == 3)
1221 : {
1222 1 : const double dfStepY = dfY - dfLastY;
1223 1 : const double dfLastSignedStepY = nStepYSign * adfStepY.back();
1224 1 : if (dfStepY * dfLastSignedStepY > 0 &&
1225 1 : (fabs(dfStepY - dfLastSignedStepY) <=
1226 1 : RELATIVE_ERROR * fabs(dfLastSignedStepY)
1227 : #ifdef multiple_of_step_y_not_yet_supported
1228 : ||
1229 : (fabs(dfStepY) > fabs(dfLastSignedStepY) &&
1230 : fabs(std::round(dfStepY / dfLastSignedStepY) -
1231 : (dfStepY / dfLastSignedStepY)) <= RELATIVE_ERROR) ||
1232 : (fabs(dfLastSignedStepY) > fabs(dfStepY) &&
1233 : fabs(std::round(dfLastSignedStepY / dfStepY) -
1234 : (dfLastSignedStepY / dfStepY)) <= RELATIVE_ERROR)
1235 : #endif
1236 : ))
1237 : {
1238 : // Assume it is a file starting with something like:
1239 : // 371999.50 5806917.50 41.21
1240 : // 371999.50 5806918.50 51.99
1241 : // 371998.50 5806919.50 53.50
1242 : // 371999.50 5806919.50 53.68
1243 1 : adfStepX.push_back(dfLastX - dfX);
1244 1 : bColOrganization = false;
1245 : }
1246 : else
1247 : {
1248 0 : CPLError(CE_Failure, CPLE_AppDefined,
1249 : "Ungridded dataset: At line " CPL_FRMT_GIB
1250 : ", X spacing was %f. Expected >0 value",
1251 : nLineNum, dfX - dfLastX);
1252 0 : VSIFCloseL(fp);
1253 0 : return nullptr;
1254 : }
1255 : }
1256 1 : else if (!adfStepX.empty() &&
1257 0 : fabs(std::round(-dfStepX / adfStepX[0]) -
1258 0 : (-dfStepX / adfStepX[0])) <= RELATIVE_ERROR)
1259 : {
1260 0 : bColOrganization = false;
1261 : }
1262 1 : else if (adfStepX.empty())
1263 : {
1264 1 : adfStepX.push_back(fabs(dfStepX));
1265 1 : bColOrganization = false;
1266 : }
1267 : else
1268 : {
1269 0 : CPLError(CE_Failure, CPLE_AppDefined,
1270 : "Ungridded dataset: At line " CPL_FRMT_GIB
1271 : ", X spacing was %f. Expected a multiple of %f",
1272 0 : nLineNum, dfStepX, adfStepX[0]);
1273 0 : VSIFCloseL(fp);
1274 0 : return nullptr;
1275 : }
1276 : }
1277 : else
1278 : {
1279 41285 : double dfStepY = dfY - dfLastY;
1280 41285 : if (dfStepY == 0.0)
1281 : {
1282 41018 : const double dfStepX = dfX - dfLastX;
1283 41018 : if (dfStepX <= 0)
1284 : {
1285 0 : CPLError(CE_Failure, CPLE_AppDefined,
1286 : "Ungridded dataset: At line " CPL_FRMT_GIB
1287 : ", X spacing was %f. Expected >0 value",
1288 : nLineNum, dfStepX);
1289 0 : VSIFCloseL(fp);
1290 0 : return nullptr;
1291 : }
1292 41018 : if (std::find(adfStepX.begin(), adfStepX.end(), dfStepX) ==
1293 82036 : adfStepX.end())
1294 : {
1295 39 : bool bAddNewValue = true;
1296 39 : std::vector<double>::iterator oIter = adfStepX.begin();
1297 39 : std::vector<double> adfStepXNew;
1298 40 : while (oIter != adfStepX.end())
1299 : {
1300 22 : if (fabs((dfStepX - *oIter) / dfStepX) < RELATIVE_ERROR)
1301 : {
1302 20 : double dfNewVal = *oIter;
1303 20 : if (nCountStepX > 0)
1304 : {
1305 : // Update mean step
1306 : /* n * mean(n) = (n-1) * mean(n-1) + val(n)
1307 : mean(n) = mean(n-1) + (val(n) - mean(n-1)) / n
1308 : */
1309 20 : nCountStepX++;
1310 20 : dfNewVal += (dfStepX - *oIter) / nCountStepX;
1311 : }
1312 :
1313 20 : adfStepXNew.push_back(dfNewVal);
1314 20 : bAddNewValue = false;
1315 20 : break;
1316 : }
1317 3 : else if (dfStepX < *oIter &&
1318 1 : fabs(*oIter -
1319 1 : static_cast<int>(*oIter / dfStepX + 0.5) *
1320 1 : dfStepX) /
1321 1 : dfStepX <
1322 : RELATIVE_ERROR)
1323 : {
1324 1 : nCountStepX = -1; // disable update of mean
1325 1 : ++oIter;
1326 : }
1327 2 : else if (dfStepX > *oIter &&
1328 2 : fabs(dfStepX -
1329 1 : static_cast<int>(dfStepX / *oIter + 0.5) *
1330 1 : (*oIter)) /
1331 1 : dfStepX <
1332 : RELATIVE_ERROR)
1333 : {
1334 1 : nCountStepX = -1; // disable update of mean
1335 1 : bAddNewValue = false;
1336 1 : adfStepXNew.push_back(*oIter);
1337 1 : break;
1338 : }
1339 : else
1340 : {
1341 0 : adfStepXNew.push_back(*oIter);
1342 0 : ++oIter;
1343 : }
1344 : }
1345 39 : adfStepX = std::move(adfStepXNew);
1346 39 : if (bAddNewValue)
1347 : {
1348 18 : CPLDebug("XYZ", "New stepX=%.15f", dfStepX);
1349 18 : adfStepX.push_back(dfStepX);
1350 18 : if (adfStepX.size() == 1 && nCountStepX == 0)
1351 : {
1352 17 : nCountStepX++;
1353 : }
1354 1 : else if (adfStepX.size() == 2)
1355 : {
1356 0 : nCountStepX = -1; // disable update of mean
1357 : }
1358 1 : else if (adfStepX.size() == 10)
1359 : {
1360 0 : CPLError(
1361 : CE_Failure, CPLE_AppDefined,
1362 : "Ungridded dataset: too many stepX values");
1363 0 : VSIFCloseL(fp);
1364 0 : return nullptr;
1365 : }
1366 : }
1367 : }
1368 : }
1369 : else
1370 : {
1371 267 : int bNewStepYSign = (dfStepY < 0.0) ? -1 : 1;
1372 267 : if (nStepYSign == 0)
1373 17 : nStepYSign = bNewStepYSign;
1374 250 : else if (nStepYSign != bNewStepYSign)
1375 : {
1376 0 : CPLError(CE_Failure, CPLE_AppDefined,
1377 : "Ungridded dataset: At line " CPL_FRMT_GIB
1378 : ", change of Y direction",
1379 : nLineNum);
1380 0 : VSIFCloseL(fp);
1381 0 : return nullptr;
1382 : }
1383 267 : if (bNewStepYSign < 0)
1384 244 : dfStepY = -dfStepY;
1385 267 : nCountStepY++;
1386 267 : if (adfStepY.empty())
1387 : {
1388 17 : adfStepY.push_back(dfStepY);
1389 : }
1390 250 : else if (fabs((adfStepY[0] - dfStepY) / dfStepY) >
1391 : RELATIVE_ERROR)
1392 : {
1393 4 : if (dfStepY > adfStepY[0] &&
1394 2 : fabs(std::round(dfStepY / adfStepY[0]) -
1395 2 : (dfStepY / adfStepY[0])) <= RELATIVE_ERROR)
1396 : {
1397 : // The new step is a multiple of the previous one,
1398 : // which means we have a missing line: OK
1399 : }
1400 : else
1401 : {
1402 0 : CPLDebug("XYZ", "New stepY=%.15f prev stepY=%.15f",
1403 0 : dfStepY, adfStepY[0]);
1404 0 : CPLError(CE_Failure, CPLE_AppDefined,
1405 : "Ungridded dataset: At line " CPL_FRMT_GIB
1406 : ", too many stepY values",
1407 : nLineNum);
1408 0 : VSIFCloseL(fp);
1409 0 : return nullptr;
1410 : }
1411 : }
1412 : else
1413 : {
1414 : // Update mean step
1415 248 : adfStepY[0] += (dfStepY - adfStepY[0]) / nCountStepY;
1416 : }
1417 : }
1418 : }
1419 :
1420 41345 : if (dfX < dfMinX)
1421 4 : dfMinX = dfX;
1422 41345 : if (dfX > dfMaxX)
1423 266 : dfMaxX = dfX;
1424 41345 : if (dfY < dfMinY)
1425 248 : dfMinY = dfY;
1426 41345 : if (dfY > dfMaxY)
1427 33 : dfMaxY = dfY;
1428 :
1429 41345 : dfLastX = dfX;
1430 41345 : dfLastY = dfY;
1431 : }
1432 :
1433 24 : if (adfStepX.size() != 1 || adfStepX[0] == 0)
1434 : {
1435 0 : CPLError(CE_Failure, CPLE_AppDefined, "Couldn't determine X spacing");
1436 0 : VSIFCloseL(fp);
1437 0 : return nullptr;
1438 : }
1439 :
1440 24 : if (adfStepY.size() != 1 || adfStepY[0] == 0)
1441 : {
1442 0 : CPLError(CE_Failure, CPLE_AppDefined, "Couldn't determine Y spacing");
1443 0 : VSIFCloseL(fp);
1444 0 : return nullptr;
1445 : }
1446 :
1447 : // Decide for a north-up organization
1448 24 : if (bColOrganization)
1449 5 : nStepYSign = -1;
1450 :
1451 24 : const double dfXSize = 1 + ((dfMaxX - dfMinX) / adfStepX[0] + 0.5);
1452 24 : const double dfYSize = 1 + ((dfMaxY - dfMinY) / adfStepY[0] + 0.5);
1453 : // Test written such as to detect NaN values
1454 24 : if (!(dfXSize > 0 && dfXSize < INT_MAX) ||
1455 24 : !(dfYSize > 0 && dfYSize < INT_MAX))
1456 : {
1457 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid dimensions");
1458 0 : VSIFCloseL(fp);
1459 0 : return nullptr;
1460 : }
1461 24 : const int nXSize = static_cast<int>(dfXSize);
1462 24 : const int nYSize = static_cast<int>(dfYSize);
1463 24 : const double dfStepX = (dfMaxX - dfMinX) / (nXSize - 1);
1464 24 : const double dfStepY = (dfMaxY - dfMinY) / (nYSize - 1) * nStepYSign;
1465 :
1466 : #ifdef DEBUG_VERBOSE
1467 : CPLDebug("XYZ", "minx=%f maxx=%f stepx=%f", dfMinX, dfMaxX, dfStepX);
1468 : CPLDebug("XYZ", "miny=%f maxy=%f stepy=%f", dfMinY, dfMaxY, dfStepY);
1469 : #endif
1470 :
1471 24 : if (nDataLineNum != static_cast<GIntBig>(nXSize) * nYSize)
1472 : {
1473 5 : if (bColOrganization)
1474 : {
1475 0 : CPLError(CE_Failure, CPLE_NotSupported,
1476 : "The XYZ driver does not support datasets organized by "
1477 : "columns with missing values");
1478 0 : VSIFCloseL(fp);
1479 0 : return nullptr;
1480 : }
1481 5 : bSameNumberOfValuesPerLine = false;
1482 : }
1483 19 : else if (bColOrganization && nDataLineNum > 100 * 1000 * 1000)
1484 : {
1485 0 : CPLError(CE_Failure, CPLE_NotSupported,
1486 : "The XYZ driver cannot load datasets organized by "
1487 : "columns with more than 100 million points");
1488 0 : VSIFCloseL(fp);
1489 0 : return nullptr;
1490 : }
1491 :
1492 24 : const bool bIngestAll = bColOrganization;
1493 24 : if (bIngestAll)
1494 : {
1495 5 : if (eDT == GDT_Int32)
1496 0 : eDT = GDT_Float32;
1497 5 : else if (eDT == GDT_Byte)
1498 1 : eDT = GDT_Int16;
1499 5 : CPLAssert(eDT == GDT_Int16 || eDT == GDT_Float32);
1500 : }
1501 :
1502 24 : if (poOpenInfo->eAccess == GA_Update)
1503 : {
1504 0 : CPLError(CE_Failure, CPLE_NotSupported,
1505 : "The XYZ driver does not support update access to existing"
1506 : " datasets.\n");
1507 0 : VSIFCloseL(fp);
1508 0 : return nullptr;
1509 : }
1510 :
1511 : /* -------------------------------------------------------------------- */
1512 : /* Create a corresponding GDALDataset. */
1513 : /* -------------------------------------------------------------------- */
1514 24 : XYZDataset *poDS = new XYZDataset();
1515 24 : poDS->fp = fp;
1516 24 : poDS->bHasHeaderLine = bHasHeaderLine;
1517 24 : poDS->nCommentLineCount = nCommentLineCount;
1518 24 : poDS->chDecimalSep = chDecimalSep ? chDecimalSep : '.';
1519 24 : poDS->nXIndex = nXIndex;
1520 24 : poDS->nYIndex = nYIndex;
1521 24 : poDS->nZIndex = nZIndex;
1522 24 : poDS->nMinTokens = nMinTokens;
1523 24 : poDS->nRasterXSize = nXSize;
1524 24 : poDS->nRasterYSize = nYSize;
1525 24 : poDS->adfGeoTransform[0] = dfMinX - dfStepX / 2;
1526 24 : poDS->adfGeoTransform[1] = dfStepX;
1527 24 : poDS->adfGeoTransform[3] =
1528 24 : (dfStepY < 0) ? dfMaxY - dfStepY / 2 : dfMinY - dfStepY / 2;
1529 24 : poDS->adfGeoTransform[5] = dfStepY;
1530 24 : poDS->bSameNumberOfValuesPerLine = bSameNumberOfValuesPerLine;
1531 24 : poDS->dfMinZ = dfMinZ;
1532 24 : poDS->dfMaxZ = dfMaxZ;
1533 24 : poDS->bIngestAll = bIngestAll;
1534 : #ifdef DEBUG_VERBOSE
1535 : CPLDebug("XYZ", "bSameNumberOfValuesPerLine = %d",
1536 : bSameNumberOfValuesPerLine);
1537 : #endif
1538 :
1539 24 : if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize))
1540 : {
1541 0 : delete poDS;
1542 0 : return nullptr;
1543 : }
1544 :
1545 : /* -------------------------------------------------------------------- */
1546 : /* Create band information objects. */
1547 : /* -------------------------------------------------------------------- */
1548 24 : poDS->nBands = 1;
1549 48 : for (int i = 0; i < poDS->nBands; i++)
1550 24 : poDS->SetBand(i + 1, new XYZRasterBand(poDS, i + 1, eDT));
1551 :
1552 : /* -------------------------------------------------------------------- */
1553 : /* Initialize any PAM information. */
1554 : /* -------------------------------------------------------------------- */
1555 24 : poDS->SetDescription(poOpenInfo->pszFilename);
1556 24 : poDS->TryLoadXML();
1557 :
1558 : /* -------------------------------------------------------------------- */
1559 : /* Support overviews. */
1560 : /* -------------------------------------------------------------------- */
1561 24 : poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename);
1562 24 : return poDS;
1563 : }
1564 :
1565 : /************************************************************************/
1566 : /* CreateCopy() */
1567 : /************************************************************************/
1568 :
1569 31 : GDALDataset *XYZDataset::CreateCopy(const char *pszFilename,
1570 : GDALDataset *poSrcDS, int bStrict,
1571 : char **papszOptions,
1572 : GDALProgressFunc pfnProgress,
1573 : void *pProgressData)
1574 : {
1575 : /* -------------------------------------------------------------------- */
1576 : /* Some some rudimentary checks */
1577 : /* -------------------------------------------------------------------- */
1578 31 : int nBands = poSrcDS->GetRasterCount();
1579 31 : if (nBands == 0)
1580 : {
1581 1 : CPLError(
1582 : CE_Failure, CPLE_NotSupported,
1583 : "XYZ driver does not support source dataset with zero band.\n");
1584 1 : return nullptr;
1585 : }
1586 :
1587 30 : if (nBands != 1)
1588 : {
1589 4 : CPLError((bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
1590 : "XYZ driver only uses the first band of the dataset.\n");
1591 4 : if (bStrict)
1592 4 : return nullptr;
1593 : }
1594 :
1595 26 : if (pfnProgress && !pfnProgress(0.0, nullptr, pProgressData))
1596 0 : return nullptr;
1597 :
1598 : /* -------------------------------------------------------------------- */
1599 : /* Get source dataset info */
1600 : /* -------------------------------------------------------------------- */
1601 :
1602 26 : int nXSize = poSrcDS->GetRasterXSize();
1603 26 : int nYSize = poSrcDS->GetRasterYSize();
1604 : double adfGeoTransform[6];
1605 26 : poSrcDS->GetGeoTransform(adfGeoTransform);
1606 26 : if (adfGeoTransform[2] != 0 || adfGeoTransform[4] != 0)
1607 : {
1608 0 : CPLError(CE_Failure, CPLE_NotSupported,
1609 : "XYZ driver does not support CreateCopy() from skewed or "
1610 : "rotated dataset.\n");
1611 0 : return nullptr;
1612 : }
1613 :
1614 26 : const GDALDataType eSrcDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
1615 : GDALDataType eReqDT;
1616 26 : if (eSrcDT == GDT_Byte || eSrcDT == GDT_Int16 || eSrcDT == GDT_UInt16 ||
1617 : eSrcDT == GDT_Int32)
1618 18 : eReqDT = GDT_Int32;
1619 : else
1620 8 : eReqDT = GDT_Float32;
1621 :
1622 : /* -------------------------------------------------------------------- */
1623 : /* Create target file */
1624 : /* -------------------------------------------------------------------- */
1625 :
1626 26 : VSILFILE *fp = VSIFOpenL(pszFilename, "wb");
1627 26 : if (fp == nullptr)
1628 : {
1629 3 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot create %s", pszFilename);
1630 3 : return nullptr;
1631 : }
1632 :
1633 : /* -------------------------------------------------------------------- */
1634 : /* Read creation options */
1635 : /* -------------------------------------------------------------------- */
1636 23 : const char *pszColSep = CSLFetchNameValue(papszOptions, "COLUMN_SEPARATOR");
1637 23 : if (pszColSep == nullptr)
1638 22 : pszColSep = " ";
1639 1 : else if (EQUAL(pszColSep, "COMMA"))
1640 0 : pszColSep = ",";
1641 1 : else if (EQUAL(pszColSep, "SPACE"))
1642 0 : pszColSep = " ";
1643 1 : else if (EQUAL(pszColSep, "SEMICOLON"))
1644 0 : pszColSep = ";";
1645 1 : else if (EQUAL(pszColSep, "\\t") || EQUAL(pszColSep, "TAB"))
1646 0 : pszColSep = "\t";
1647 : #ifdef DEBUG_VERBOSE
1648 : else
1649 : CPLDebug("XYZ", "Using raw column separator: '%s' ", pszColSep);
1650 : #endif
1651 :
1652 : const char *pszAddHeaderLine =
1653 23 : CSLFetchNameValue(papszOptions, "ADD_HEADER_LINE");
1654 23 : if (pszAddHeaderLine != nullptr && CPLTestBool(pszAddHeaderLine))
1655 : {
1656 1 : VSIFPrintfL(fp, "X%sY%sZ\n", pszColSep, pszColSep);
1657 : }
1658 :
1659 : /* -------------------------------------------------------------------- */
1660 : /* Copy imagery */
1661 : /* -------------------------------------------------------------------- */
1662 23 : char szFormat[50] = {'\0'};
1663 23 : if (eReqDT == GDT_Int32)
1664 15 : strcpy(szFormat, "%.17g%c%.17g%c%d\n");
1665 : else
1666 8 : strcpy(szFormat, "%.17g%c%.17g%c%.17g\n");
1667 : const char *pszDecimalPrecision =
1668 23 : CSLFetchNameValue(papszOptions, "DECIMAL_PRECISION");
1669 : const char *pszSignificantDigits =
1670 23 : CSLFetchNameValue(papszOptions, "SIGNIFICANT_DIGITS");
1671 23 : bool bIgnoreSigDigits = false;
1672 23 : if (pszDecimalPrecision && pszSignificantDigits)
1673 : {
1674 0 : CPLError(CE_Warning, CPLE_AppDefined,
1675 : "Conflicting precision arguments, using DECIMAL_PRECISION");
1676 0 : bIgnoreSigDigits = true;
1677 : }
1678 : int nPrecision;
1679 23 : if (pszSignificantDigits && !bIgnoreSigDigits)
1680 : {
1681 0 : nPrecision = atoi(pszSignificantDigits);
1682 0 : if (nPrecision >= 0)
1683 : {
1684 0 : if (eReqDT == GDT_Int32)
1685 0 : snprintf(szFormat, sizeof(szFormat), "%%.%dg%%c%%.%dg%%c%%d\n",
1686 : nPrecision, nPrecision);
1687 : else
1688 0 : snprintf(szFormat, sizeof(szFormat),
1689 : "%%.%dg%%c%%.%dg%%c%%.%dg\n", nPrecision, nPrecision,
1690 : nPrecision);
1691 : }
1692 0 : CPLDebug("XYZ", "Setting precision format: %s", szFormat);
1693 : }
1694 23 : else if (pszDecimalPrecision)
1695 : {
1696 0 : nPrecision = atoi(pszDecimalPrecision);
1697 0 : if (nPrecision >= 0)
1698 : {
1699 0 : if (eReqDT == GDT_Int32)
1700 0 : snprintf(szFormat, sizeof(szFormat), "%%.%df%%c%%.%df%%c%%d\n",
1701 : nPrecision, nPrecision);
1702 : else
1703 0 : snprintf(szFormat, sizeof(szFormat),
1704 : "%%.%df%%c%%.%df%%c%%.%df\n", nPrecision, nPrecision,
1705 : nPrecision);
1706 : }
1707 0 : CPLDebug("XYZ", "Setting precision format: %s", szFormat);
1708 : }
1709 : void *pLineBuffer =
1710 23 : reinterpret_cast<void *>(CPLMalloc(nXSize * sizeof(int)));
1711 23 : CPLErr eErr = CE_None;
1712 405 : for (int j = 0; j < nYSize && eErr == CE_None; j++)
1713 : {
1714 382 : eErr = poSrcDS->GetRasterBand(1)->RasterIO(GF_Read, 0, j, nXSize, 1,
1715 : pLineBuffer, nXSize, 1,
1716 : eReqDT, 0, 0, nullptr);
1717 382 : if (eErr != CE_None)
1718 0 : break;
1719 382 : const double dfY = adfGeoTransform[3] + (j + 0.5) * adfGeoTransform[5];
1720 382 : CPLString osBuf;
1721 42747 : for (int i = 0; i < nXSize; i++)
1722 : {
1723 42375 : const double dfX =
1724 42375 : adfGeoTransform[0] + (i + 0.5) * adfGeoTransform[1];
1725 : char szBuf[256];
1726 42375 : if (eReqDT == GDT_Int32)
1727 1274 : CPLsnprintf(szBuf, sizeof(szBuf), szFormat, dfX, pszColSep[0],
1728 1274 : dfY, pszColSep[0],
1729 1274 : reinterpret_cast<int *>(pLineBuffer)[i]);
1730 : else
1731 41101 : CPLsnprintf(szBuf, sizeof(szBuf), szFormat, dfX, pszColSep[0],
1732 41101 : dfY, pszColSep[0],
1733 41101 : reinterpret_cast<float *>(pLineBuffer)[i]);
1734 42375 : osBuf += szBuf;
1735 42375 : if ((i & 1023) == 0 || i == nXSize - 1)
1736 : {
1737 760 : if (VSIFWriteL(osBuf, static_cast<int>(osBuf.size()), 1, fp) !=
1738 : 1)
1739 : {
1740 10 : eErr = CE_Failure;
1741 10 : CPLError(CE_Failure, CPLE_AppDefined,
1742 : "Write failed, disk full?\n");
1743 10 : break;
1744 : }
1745 750 : osBuf = "";
1746 : }
1747 : }
1748 764 : if (pfnProgress &&
1749 382 : !pfnProgress((j + 1) * 1.0 / nYSize, nullptr, pProgressData))
1750 : {
1751 0 : eErr = CE_Failure;
1752 0 : break;
1753 : }
1754 : }
1755 23 : CPLFree(pLineBuffer);
1756 23 : VSIFCloseL(fp);
1757 :
1758 23 : if (eErr != CE_None)
1759 10 : return nullptr;
1760 :
1761 : /* -------------------------------------------------------------------- */
1762 : /* We don't want to call GDALOpen() since it will be expensive, */
1763 : /* so we "hand prepare" an XYZ dataset referencing our file. */
1764 : /* -------------------------------------------------------------------- */
1765 13 : XYZDataset *poXYZ_DS = new XYZDataset();
1766 13 : poXYZ_DS->nRasterXSize = nXSize;
1767 13 : poXYZ_DS->nRasterYSize = nYSize;
1768 13 : poXYZ_DS->nBands = 1;
1769 13 : poXYZ_DS->SetBand(1, new XYZRasterBand(poXYZ_DS, 1, eReqDT));
1770 : /* If writing to stdout, we can't reopen it --> silence warning */
1771 13 : CPLPushErrorHandler(CPLQuietErrorHandler);
1772 13 : poXYZ_DS->fp = VSIFOpenL(pszFilename, "rb");
1773 13 : CPLPopErrorHandler();
1774 13 : memcpy(&(poXYZ_DS->adfGeoTransform), adfGeoTransform, sizeof(double) * 6);
1775 13 : poXYZ_DS->nXIndex = 0;
1776 13 : poXYZ_DS->nYIndex = 1;
1777 13 : poXYZ_DS->nZIndex = 2;
1778 13 : if (pszAddHeaderLine)
1779 : {
1780 1 : poXYZ_DS->nDataLineNum = 1;
1781 1 : poXYZ_DS->bHasHeaderLine = TRUE;
1782 : }
1783 :
1784 13 : return poXYZ_DS;
1785 : }
1786 :
1787 : /************************************************************************/
1788 : /* GetGeoTransform() */
1789 : /************************************************************************/
1790 :
1791 18 : CPLErr XYZDataset::GetGeoTransform(double *padfTransform)
1792 :
1793 : {
1794 18 : memcpy(padfTransform, adfGeoTransform, 6 * sizeof(double));
1795 :
1796 18 : return CE_None;
1797 : }
1798 :
1799 : /************************************************************************/
1800 : /* GDALRegister_XYZ() */
1801 : /************************************************************************/
1802 :
1803 1595 : void GDALRegister_XYZ()
1804 :
1805 : {
1806 1595 : if (GDALGetDriverByName("XYZ") != nullptr)
1807 302 : return;
1808 :
1809 1293 : GDALDriver *poDriver = new GDALDriver();
1810 :
1811 1293 : poDriver->SetDescription("XYZ");
1812 1293 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1813 1293 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "ASCII Gridded XYZ");
1814 1293 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/xyz.html");
1815 1293 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "xyz");
1816 1293 : poDriver->SetMetadataItem(
1817 : GDAL_DMD_CREATIONOPTIONLIST,
1818 : "<CreationOptionList>"
1819 : " <Option name='COLUMN_SEPARATOR' type='string' default=' ' "
1820 : "description='Separator between fields.'/>"
1821 : " <Option name='ADD_HEADER_LINE' type='boolean' default='false' "
1822 : "description='Add an header line with column names.'/>"
1823 : " <Option name='SIGNIFICANT_DIGITS' type='int' description='Number "
1824 : "of significant digits when writing floating-point numbers (%g format; "
1825 : "default with 18).'/>\n"
1826 : " <Option name='DECIMAL_PRECISION' type='int' description='Number of "
1827 : "decimal places when writing floating-point numbers (%f format).'/>\n"
1828 1293 : "</CreationOptionList>");
1829 1293 : poDriver->SetMetadataItem(
1830 : GDAL_DMD_OPENOPTIONLIST,
1831 : "<OpenOptionList>"
1832 : " <Option name='COLUMN_ORDER' type='string-select' default='AUTO' "
1833 : "description='Specifies the order of the columns. It overrides the "
1834 : "header.'>"
1835 : " <Value>AUTO</Value>"
1836 : " <Value>XYZ</Value>"
1837 : " <Value>YXZ</Value>"
1838 : " </Option>"
1839 1293 : "</OpenOptionList>");
1840 :
1841 1293 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1842 :
1843 1293 : poDriver->pfnOpen = XYZDataset::Open;
1844 1293 : poDriver->pfnIdentify = XYZDataset::Identify;
1845 1293 : poDriver->pfnCreateCopy = XYZDataset::CreateCopy;
1846 :
1847 1293 : GetGDALDriverManager()->RegisterDriver(poDriver);
1848 : }
|