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