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 : CSLConstList 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 42 : XYZRasterBand::XYZRasterBand(XYZDataset *poDSIn, int nBandIn, GDALDataType eDT)
113 42 : : nLastYOff(-1)
114 : {
115 42 : poDS = poDSIn;
116 42 : nBand = nBandIn;
117 :
118 42 : eDataType = eDT;
119 :
120 42 : nBlockXSize = poDSIn->GetRasterXSize();
121 42 : nBlockYSize = 1;
122 42 : }
123 :
124 : /************************************************************************/
125 : /* IReadBlock() */
126 : /************************************************************************/
127 :
128 338 : CPLErr XYZRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
129 : void *pImage)
130 : {
131 338 : XYZDataset *poGDS = cpl::down_cast<XYZDataset *>(poDS);
132 :
133 338 : if (poGDS->fp == nullptr)
134 0 : return CE_Failure;
135 :
136 338 : 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 326 : if (pImage)
303 : {
304 326 : int bSuccess = FALSE;
305 326 : double dfNoDataValue = GetNoDataValue(&bSuccess);
306 326 : if (!bSuccess)
307 304 : dfNoDataValue = 0.0;
308 326 : GDALCopyWords(&dfNoDataValue, GDT_Float64, 0, pImage, eDataType,
309 : GDALGetDataTypeSizeBytes(eDataType), nRasterXSize);
310 : }
311 :
312 : // Only valid if bSameNumberOfValuesPerLine.
313 326 : const GIntBig nLineInFile = static_cast<GIntBig>(nBlockYOff) * nBlockXSize;
314 326 : if ((poGDS->bSameNumberOfValuesPerLine &&
315 304 : poGDS->nDataLineNum > nLineInFile) ||
316 308 : (!poGDS->bSameNumberOfValuesPerLine &&
317 22 : (nLastYOff == -1 || nBlockYOff == 0)))
318 : {
319 23 : poGDS->nDataLineNum = 0;
320 23 : poGDS->nLineNum = 0;
321 23 : poGDS->bEOF = false;
322 23 : VSIFSeekL(poGDS->fp, 0, SEEK_SET);
323 :
324 23 : 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 23 : 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 326 : 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 304 : if (poGDS->bEOF)
373 : {
374 0 : return CE_Failure;
375 : }
376 330 : 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 326 : poGDS->m_gt[3] + (0.5 + nBlockYOff) * poGDS->m_gt[5];
421 :
422 326 : int idx = -1;
423 : while (true)
424 : {
425 : int nCol;
426 : bool bLastWasSep;
427 7 : do
428 : {
429 42102 : const vsi_l_offset nOffsetBefore = VSIFTellL(poGDS->fp);
430 42102 : const char *pszLine = CPLReadLine2L(poGDS->fp, 100, nullptr);
431 42102 : 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 42101 : poGDS->nLineNum++;
448 :
449 42101 : const char *pszPtr = pszLine;
450 : char ch;
451 42101 : nCol = 0;
452 42101 : bLastWasSep = true;
453 42101 : double dfX = 0.0;
454 42101 : double dfY = 0.0;
455 42101 : double dfZ = 0.0;
456 42101 : int nUsefulColsFound = 0;
457 1361260 : while ((ch = *pszPtr) != '\0')
458 : {
459 1319160 : if (ch == ' ')
460 : {
461 3506 : if (!bLastWasSep)
462 3386 : nCol++;
463 3506 : bLastWasSep = true;
464 : }
465 1315660 : else if ((ch == ',' && poGDS->chDecimalSep != ',') ||
466 1234860 : ch == '\t' || ch == ';')
467 : {
468 80802 : nCol++;
469 80802 : bLastWasSep = true;
470 : }
471 : else
472 : {
473 1234860 : if (bLastWasSep)
474 : {
475 126282 : if (nCol == poGDS->nXIndex)
476 : {
477 42094 : nUsefulColsFound++;
478 42094 : if (!poGDS->bSameNumberOfValuesPerLine)
479 36 : dfX = CPLAtofDelim(pszPtr, poGDS->chDecimalSep);
480 : }
481 84188 : else if (nCol == poGDS->nYIndex)
482 : {
483 42094 : nUsefulColsFound++;
484 42094 : if (!poGDS->bSameNumberOfValuesPerLine)
485 36 : dfY = CPLAtofDelim(pszPtr, poGDS->chDecimalSep);
486 : }
487 42094 : else if (nCol == poGDS->nZIndex)
488 : {
489 42094 : nUsefulColsFound++;
490 42094 : dfZ = CPLAtofDelim(pszPtr, poGDS->chDecimalSep);
491 : }
492 : }
493 1234860 : bLastWasSep = false;
494 : }
495 1319160 : pszPtr++;
496 : }
497 42101 : nCol++;
498 :
499 42101 : if (nUsefulColsFound == 3)
500 : {
501 42094 : if (poGDS->bSameNumberOfValuesPerLine)
502 : {
503 42058 : 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 42089 : CPLAssert(idx >= 0 && idx < nRasterXSize);
541 :
542 42089 : if (pImage)
543 : {
544 42089 : if (eDataType == GDT_Float32)
545 : {
546 40416 : reinterpret_cast<float *>(pImage)[idx] =
547 40416 : static_cast<float>(dfZ);
548 : }
549 1673 : else if (eDataType == GDT_Int32)
550 : {
551 400 : reinterpret_cast<GInt32 *>(pImage)[idx] =
552 400 : static_cast<GInt32>(dfZ);
553 : }
554 1273 : else if (eDataType == GDT_Int16)
555 : {
556 13 : reinterpret_cast<GInt16 *>(pImage)[idx] =
557 13 : static_cast<GInt16>(dfZ);
558 : }
559 : else
560 : {
561 1260 : reinterpret_cast<GByte *>(pImage)[idx] =
562 1260 : static_cast<GByte>(dfZ);
563 : }
564 : }
565 : }
566 : /* Skip empty line */
567 42096 : } while (nCol == 1 && bLastWasSep);
568 :
569 42089 : poGDS->nDataLineNum++;
570 42089 : if (nCol < poGDS->nMinTokens)
571 0 : return CE_Failure;
572 :
573 42089 : if (idx + 1 == nRasterXSize)
574 320 : break;
575 41769 : }
576 :
577 320 : if (poGDS->bSameNumberOfValuesPerLine)
578 : {
579 304 : if (poGDS->nDataLineNum !=
580 304 : 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 320 : nLastYOff = nBlockYOff;
590 :
591 320 : 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 339 : double XYZRasterBand::GetNoDataValue(int *pbSuccess)
623 : {
624 339 : XYZDataset *poGDS = cpl::down_cast<XYZDataset *>(poDS);
625 339 : if (!poGDS->bSameNumberOfValuesPerLine && poGDS->dfMinZ > -32768 &&
626 23 : eDataType != GDT_UInt8)
627 : {
628 0 : if (pbSuccess)
629 0 : *pbSuccess = TRUE;
630 0 : return (poGDS->dfMinZ > 0) ? 0 : -32768;
631 : }
632 339 : else if (!poGDS->bSameNumberOfValuesPerLine && poGDS->dfMinZ > 0 &&
633 23 : eDataType == GDT_UInt8)
634 : {
635 23 : if (pbSuccess)
636 23 : *pbSuccess = TRUE;
637 23 : return 0;
638 : }
639 :
640 316 : return GDALPamRasterBand::GetNoDataValue(pbSuccess);
641 : }
642 :
643 : /************************************************************************/
644 : /* ~XYZDataset() */
645 : /************************************************************************/
646 :
647 42 : 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 42 : dfMinZ(0), dfMaxZ(0), bEOF(false)
652 : {
653 42 : }
654 :
655 : /************************************************************************/
656 : /* ~XYZDataset() */
657 : /************************************************************************/
658 :
659 84 : XYZDataset::~XYZDataset()
660 :
661 : {
662 42 : FlushCache(true);
663 42 : if (fp)
664 40 : VSIFCloseL(fp);
665 :
666 : {
667 84 : std::lock_guard<std::mutex> guard(gMutex);
668 42 : if (gpoActiveDS == this)
669 : {
670 4 : gpoActiveDS = nullptr;
671 4 : gasValues.clear();
672 4 : gafValues.clear();
673 : }
674 : }
675 84 : }
676 :
677 : /************************************************************************/
678 : /* Identify() */
679 : /************************************************************************/
680 :
681 60978 : int XYZDataset::Identify(GDALOpenInfo *poOpenInfo)
682 : {
683 : int bHasHeaderLine, nCommentLineCount;
684 : int nXIndex;
685 : int nYIndex;
686 : int nZIndex;
687 60978 : return IdentifyEx(poOpenInfo, bHasHeaderLine, nCommentLineCount, nXIndex,
688 121956 : nYIndex, nZIndex);
689 : }
690 :
691 : /************************************************************************/
692 : /* IdentifyEx() */
693 : /************************************************************************/
694 :
695 61006 : int XYZDataset::IdentifyEx(GDALOpenInfo *poOpenInfo, int &bHasHeaderLine,
696 : int &nCommentLineCount, int &nXIndex, int &nYIndex,
697 : int &nZIndex)
698 :
699 : {
700 61006 : bHasHeaderLine = FALSE;
701 61006 : nCommentLineCount = 0;
702 :
703 122012 : CPLString osFilename(poOpenInfo->pszFilename);
704 61006 : if (EQUAL(CPLGetExtensionSafe(osFilename).c_str(), "GRA") &&
705 0 : !poOpenInfo->IsSingleAllowedDriver("XYZ"))
706 : {
707 : // IGNFHeightASCIIGRID .GRA
708 0 : return FALSE;
709 : }
710 :
711 61006 : 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 61006 : if (strlen(poOpenInfo->pszFilename) > 6 &&
715 59341 : 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 61006 : if (poOpenInfo->nHeaderBytes == 0)
727 : {
728 56576 : return FALSE;
729 : }
730 :
731 : /* -------------------------------------------------------------------- */
732 : /* Check that it looks roughly as an XYZ dataset */
733 : /* -------------------------------------------------------------------- */
734 4430 : const char *pszData =
735 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
736 :
737 4431 : 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 4429 : int i = 0;
747 4429 : 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 4429 : int iStartLine = i;
771 38272 : for (; i < poOpenInfo->nHeaderBytes; i++)
772 : {
773 38217 : const char ch = pszData[i];
774 38217 : if (ch == 13 || ch == 10)
775 : {
776 : break;
777 : }
778 38032 : else if (ch == ' ' || ch == ',' || ch == '\t' || ch == ';')
779 : ;
780 27003 : else if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '+' ||
781 18245 : ch == '-' || ch == 'e' || ch == 'E')
782 : ;
783 16528 : else if (ch == '"' || (ch >= 'a' && ch <= 'z') ||
784 8178 : (ch >= 'A' && ch <= 'Z'))
785 12339 : bHasHeaderLine = TRUE;
786 : else
787 : {
788 4189 : return FALSE;
789 : }
790 : }
791 :
792 240 : nXIndex = -1;
793 240 : nYIndex = -1;
794 240 : nZIndex = -1;
795 480 : const char *pszColumnOrder = CSLFetchNameValueDef(
796 240 : poOpenInfo->papszOpenOptions, "COLUMN_ORDER", "AUTO");
797 240 : if (EQUAL(pszColumnOrder, "XYZ"))
798 : {
799 2 : nXIndex = 0;
800 2 : nYIndex = 1;
801 2 : nZIndex = 2;
802 2 : return TRUE;
803 : }
804 238 : else if (EQUAL(pszColumnOrder, "YXZ"))
805 : {
806 4 : nXIndex = 1;
807 4 : nYIndex = 0;
808 4 : nZIndex = 2;
809 4 : return TRUE;
810 : }
811 234 : 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 233 : if (bHasHeaderLine)
821 : {
822 130 : CPLString osHeaderLine;
823 130 : osHeaderLine.assign(pszData + iStartLine, i - iStartLine);
824 : char **papszTokens =
825 130 : CSLTokenizeString2(osHeaderLine, " ,\t;", CSLT_HONOURSTRINGS);
826 130 : int nTokens = CSLCount(papszTokens);
827 893 : for (int iToken = 0; iToken < nTokens; iToken++)
828 : {
829 763 : const char *pszToken = papszTokens[iToken];
830 763 : if (EQUAL(pszToken, "x") || STARTS_WITH_CI(pszToken, "lon") ||
831 733 : STARTS_WITH_CI(pszToken, "east"))
832 30 : nXIndex = iToken;
833 733 : else if (EQUAL(pszToken, "y") || STARTS_WITH_CI(pszToken, "lat") ||
834 705 : STARTS_WITH_CI(pszToken, "north"))
835 28 : nYIndex = iToken;
836 705 : else if (EQUAL(pszToken, "z") || STARTS_WITH_CI(pszToken, "alt") ||
837 677 : EQUAL(pszToken, "height"))
838 28 : nZIndex = iToken;
839 : }
840 130 : CSLDestroy(papszTokens);
841 130 : if (nXIndex >= 0 && nYIndex >= 0 && nZIndex >= 0)
842 : {
843 28 : return TRUE;
844 : }
845 : }
846 :
847 205 : bool bHasFoundNewLine = false;
848 205 : bool bPrevWasSep = true;
849 205 : int nCols = 0;
850 205 : int nMaxCols = 0;
851 20926 : for (; i < poOpenInfo->nHeaderBytes; i++)
852 : {
853 20825 : char ch = pszData[i];
854 20825 : if (ch == 13 || ch == 10)
855 : {
856 1387 : bHasFoundNewLine = true;
857 1387 : if (!bPrevWasSep)
858 : {
859 1213 : nCols++;
860 1213 : if (nCols > nMaxCols)
861 53 : nMaxCols = nCols;
862 : }
863 1387 : bPrevWasSep = true;
864 1387 : nCols = 0;
865 : }
866 19438 : else if (ch == ' ' || ch == ',' || ch == '\t' || ch == ';')
867 : {
868 2996 : if (!bPrevWasSep)
869 : {
870 2935 : nCols++;
871 2935 : if (nCols > nMaxCols)
872 134 : nMaxCols = nCols;
873 : }
874 2996 : bPrevWasSep = true;
875 : }
876 16442 : else if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '+' ||
877 104 : ch == '-' || ch == 'e' || ch == 'E')
878 : {
879 16338 : bPrevWasSep = false;
880 : }
881 : else
882 : {
883 104 : return FALSE;
884 : }
885 : }
886 :
887 101 : return bHasFoundNewLine && nMaxCols >= 3;
888 : }
889 :
890 : /************************************************************************/
891 : /* Open() */
892 : /************************************************************************/
893 :
894 28 : GDALDataset *XYZDataset::Open(GDALOpenInfo *poOpenInfo)
895 :
896 : {
897 : int bHasHeaderLine;
898 28 : int nCommentLineCount = 0;
899 :
900 28 : int nXIndex = -1;
901 28 : int nYIndex = -1;
902 28 : int nZIndex = -1;
903 28 : if (!IdentifyEx(poOpenInfo, bHasHeaderLine, nCommentLineCount, nXIndex,
904 : nYIndex, nZIndex))
905 0 : return nullptr;
906 :
907 56 : 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 28 : if (strlen(poOpenInfo->pszFilename) > 6 &&
912 28 : 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 28 : VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "rb");
924 28 : 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 28 : if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "/vsigzip/"))
930 28 : fp = VSICreateBufferedReaderHandle(fp);
931 :
932 28 : int nMinTokens = 0;
933 :
934 28 : 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 28 : 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 10 : else if (nXIndex < 0 || nYIndex < 0 || nZIndex < 0)
965 : {
966 9 : nXIndex = 0;
967 9 : nYIndex = 1;
968 9 : nZIndex = 2;
969 : }
970 28 : nMinTokens = 1 + std::max(std::max(nXIndex, nYIndex), nZIndex);
971 :
972 : /* -------------------------------------------------------------------- */
973 : /* Parse data lines */
974 : /* -------------------------------------------------------------------- */
975 :
976 28 : GIntBig nLineNum = 0;
977 28 : GIntBig nDataLineNum = 0;
978 28 : double dfX = 0.0;
979 28 : double dfY = 0.0;
980 28 : double dfZ = 0.0;
981 28 : double dfMinX = 0.0;
982 28 : double dfMinY = 0.0;
983 28 : double dfMaxX = 0.0;
984 28 : double dfMaxY = 0.0;
985 28 : double dfMinZ = 0.0;
986 28 : double dfMaxZ = 0.0;
987 28 : double dfLastX = 0.0;
988 28 : double dfLastY = 0.0;
989 56 : std::vector<double> adfStepX;
990 56 : std::vector<double> adfStepY;
991 28 : GDALDataType eDT = GDT_UInt8;
992 28 : bool bSameNumberOfValuesPerLine = true;
993 28 : char chDecimalSep = '\0';
994 28 : int nStepYSign = 0;
995 28 : bool bColOrganization = false;
996 :
997 : const char *pszLine;
998 28 : GIntBig nCountStepX = 0;
999 28 : GIntBig nCountStepY = 0;
1000 42589 : while ((pszLine = CPLReadLine2L(fp, 100, nullptr)) != nullptr)
1001 : {
1002 42561 : nLineNum++;
1003 :
1004 42561 : const char *pszPtr = pszLine;
1005 : char ch;
1006 42561 : int nCol = 0;
1007 42561 : bool bLastWasSep = true;
1008 42561 : if (chDecimalSep == '\0')
1009 : {
1010 1673 : int nCountComma = 0;
1011 1673 : int nCountFieldSep = 0;
1012 30709 : while ((ch = *pszPtr) != '\0')
1013 : {
1014 29048 : if (ch == '.')
1015 : {
1016 12 : chDecimalSep = '.';
1017 12 : break;
1018 : }
1019 29036 : else if (ch == ',')
1020 : {
1021 5 : nCountComma++;
1022 5 : bLastWasSep = false;
1023 : }
1024 29031 : else if (ch == ' ')
1025 : {
1026 3301 : if (!bLastWasSep)
1027 3298 : nCountFieldSep++;
1028 3301 : bLastWasSep = true;
1029 : }
1030 25730 : else if (ch == '\t' || ch == ';')
1031 : {
1032 4 : nCountFieldSep++;
1033 4 : bLastWasSep = true;
1034 : }
1035 : else
1036 25726 : bLastWasSep = false;
1037 29036 : pszPtr++;
1038 : }
1039 1673 : if (chDecimalSep == '\0')
1040 : {
1041 : /* 1,2,3 */
1042 1661 : if (nCountComma >= 2 && nCountFieldSep == 0)
1043 2 : chDecimalSep = '.';
1044 : /* 23,5;33;45 */
1045 1659 : else if (nCountComma > 0 && nCountFieldSep > 0)
1046 1 : chDecimalSep = ',';
1047 : }
1048 1673 : pszPtr = pszLine;
1049 1673 : bLastWasSep = true;
1050 : }
1051 :
1052 42561 : char chLocalDecimalSep = chDecimalSep ? chDecimalSep : '.';
1053 42561 : int nUsefulColsFound = 0;
1054 1369300 : while ((ch = *pszPtr) != '\0')
1055 : {
1056 1326730 : if (ch == ' ')
1057 : {
1058 4384 : if (!bLastWasSep)
1059 4264 : nCol++;
1060 4384 : bLastWasSep = true;
1061 : }
1062 1322350 : else if ((ch == ',' && chLocalDecimalSep != ',') || ch == '\t' ||
1063 : ch == ';')
1064 : {
1065 80828 : nCol++;
1066 80828 : bLastWasSep = true;
1067 : }
1068 : else
1069 : {
1070 1241520 : if (bLastWasSep)
1071 : {
1072 127638 : if (nCol == nXIndex)
1073 : {
1074 42546 : nUsefulColsFound++;
1075 42546 : dfX = CPLAtofDelim(pszPtr, chLocalDecimalSep);
1076 42546 : if (std::isnan(dfX))
1077 : {
1078 0 : CPLError(CE_Failure, CPLE_AppDefined,
1079 : "At line " CPL_FRMT_GIB
1080 : ", NaN value found",
1081 : nLineNum);
1082 0 : VSIFCloseL(fp);
1083 0 : return nullptr;
1084 : }
1085 : }
1086 85092 : else if (nCol == nYIndex)
1087 : {
1088 42546 : nUsefulColsFound++;
1089 42546 : dfY = CPLAtofDelim(pszPtr, chLocalDecimalSep);
1090 42546 : if (std::isnan(dfY))
1091 : {
1092 0 : CPLError(CE_Failure, CPLE_AppDefined,
1093 : "At line " CPL_FRMT_GIB
1094 : ", NaN value found",
1095 : nLineNum);
1096 0 : VSIFCloseL(fp);
1097 0 : return nullptr;
1098 : }
1099 : }
1100 42546 : else if (nCol == nZIndex)
1101 : {
1102 42546 : nUsefulColsFound++;
1103 42546 : dfZ = CPLAtofDelim(pszPtr, chLocalDecimalSep);
1104 42546 : if (nDataLineNum == 0)
1105 : {
1106 28 : dfMinZ = dfZ;
1107 28 : dfMaxZ = dfZ;
1108 : }
1109 42518 : else if (dfZ < dfMinZ)
1110 : {
1111 28 : dfMinZ = dfZ;
1112 : }
1113 42490 : else if (dfZ > dfMaxZ)
1114 : {
1115 324 : dfMaxZ = dfZ;
1116 : }
1117 :
1118 42546 : if (!(dfZ >= INT_MIN && dfZ <= INT_MAX))
1119 : {
1120 0 : eDT = GDT_Float32;
1121 : }
1122 : else
1123 : {
1124 42546 : int nZ = static_cast<int>(dfZ);
1125 42546 : if (static_cast<double>(nZ) != dfZ)
1126 : {
1127 28327 : eDT = GDT_Float32;
1128 : }
1129 14219 : else if (
1130 7839 : (eDT == GDT_UInt8 || eDT == GDT_Int16)
1131 : // cppcheck-suppress knownConditionTrueFalse
1132 6389 : && (nZ < 0 || nZ > 255))
1133 : {
1134 10 : if (nZ < -32768 || nZ > 32767)
1135 0 : eDT = GDT_Int32;
1136 : else
1137 10 : eDT = GDT_Int16;
1138 : }
1139 : }
1140 : }
1141 : }
1142 1241520 : bLastWasSep = false;
1143 : }
1144 1326730 : pszPtr++;
1145 : }
1146 : /* skip empty lines */
1147 42561 : if (bLastWasSep && nCol == 0)
1148 : {
1149 15 : continue;
1150 : }
1151 42546 : nDataLineNum++;
1152 42546 : nCol++;
1153 42546 : if (nCol < nMinTokens)
1154 : {
1155 0 : CPLError(CE_Failure, CPLE_AppDefined,
1156 : "At line " CPL_FRMT_GIB
1157 : ", found %d tokens. Expected %d at least",
1158 : nLineNum, nCol, nMinTokens);
1159 0 : VSIFCloseL(fp);
1160 0 : return nullptr;
1161 : }
1162 42546 : if (nUsefulColsFound != 3)
1163 : {
1164 0 : CPLError(CE_Failure, CPLE_AppDefined,
1165 : "At line " CPL_FRMT_GIB
1166 : ", did not find X, Y and/or Z values",
1167 : nLineNum);
1168 0 : VSIFCloseL(fp);
1169 0 : return nullptr;
1170 : }
1171 :
1172 42546 : if (nDataLineNum == 1)
1173 : {
1174 28 : dfMinX = dfX;
1175 28 : dfMaxX = dfX;
1176 28 : dfMinY = dfY;
1177 28 : dfMaxY = dfY;
1178 : }
1179 42518 : else if (nDataLineNum == 2 && dfX == dfLastX)
1180 : {
1181 : // Detect datasets organized by columns
1182 7 : if (dfY == dfLastY)
1183 : {
1184 0 : CPLError(CE_Failure, CPLE_AppDefined,
1185 : "Ungridded dataset: At line " CPL_FRMT_GIB
1186 : ", Failed to detect grid layout.",
1187 : nLineNum);
1188 0 : VSIFCloseL(fp);
1189 0 : return nullptr;
1190 : }
1191 :
1192 7 : bColOrganization = true;
1193 7 : const double dfStepY = dfY - dfLastY;
1194 7 : adfStepY.push_back(fabs(dfStepY));
1195 7 : nStepYSign = dfStepY > 0 ? 1 : -1;
1196 : }
1197 42511 : else if (bColOrganization)
1198 : {
1199 29 : const double dfStepX = dfX - dfLastX;
1200 29 : if (dfStepX == 0)
1201 : {
1202 19 : const double dfStepY = dfY - dfLastY;
1203 19 : const double dfExpectedStepY = adfStepY.back() * nStepYSign;
1204 19 : if (fabs((dfStepY - dfExpectedStepY) / dfExpectedStepY) >
1205 : RELATIVE_ERROR)
1206 : {
1207 0 : CPLError(CE_Failure, CPLE_AppDefined,
1208 : "Ungridded dataset: At line " CPL_FRMT_GIB
1209 : ", Y spacing was %f. Expected %f",
1210 : nLineNum, dfStepY, dfExpectedStepY);
1211 0 : VSIFCloseL(fp);
1212 0 : return nullptr;
1213 : }
1214 : }
1215 10 : else if (dfStepX > 0)
1216 : {
1217 8 : if (adfStepX.empty())
1218 : {
1219 5 : adfStepX.push_back(dfStepX);
1220 : }
1221 3 : else if (fabs((dfStepX - adfStepX.back()) / adfStepX.back()) >
1222 : RELATIVE_ERROR)
1223 : {
1224 0 : CPLError(CE_Failure, CPLE_AppDefined,
1225 : "Ungridded dataset: At line " CPL_FRMT_GIB
1226 : ", X spacing was %f. Expected %f",
1227 0 : nLineNum, dfStepX, adfStepX.back());
1228 0 : VSIFCloseL(fp);
1229 0 : return nullptr;
1230 : }
1231 : }
1232 2 : else if (nDataLineNum == 3)
1233 : {
1234 1 : const double dfStepY = dfY - dfLastY;
1235 1 : const double dfLastSignedStepY = nStepYSign * adfStepY.back();
1236 1 : if (dfStepY * dfLastSignedStepY > 0 &&
1237 1 : (fabs(dfStepY - dfLastSignedStepY) <=
1238 1 : RELATIVE_ERROR * fabs(dfLastSignedStepY)
1239 : #ifdef multiple_of_step_y_not_yet_supported
1240 : ||
1241 : (fabs(dfStepY) > fabs(dfLastSignedStepY) &&
1242 : fabs(std::round(dfStepY / dfLastSignedStepY) -
1243 : (dfStepY / dfLastSignedStepY)) <= RELATIVE_ERROR) ||
1244 : (fabs(dfLastSignedStepY) > fabs(dfStepY) &&
1245 : fabs(std::round(dfLastSignedStepY / dfStepY) -
1246 : (dfLastSignedStepY / dfStepY)) <= RELATIVE_ERROR)
1247 : #endif
1248 : ))
1249 : {
1250 : // Assume it is a file starting with something like:
1251 : // 371999.50 5806917.50 41.21
1252 : // 371999.50 5806918.50 51.99
1253 : // 371998.50 5806919.50 53.50
1254 : // 371999.50 5806919.50 53.68
1255 1 : adfStepX.push_back(dfLastX - dfX);
1256 1 : bColOrganization = false;
1257 : }
1258 : else
1259 : {
1260 0 : CPLError(CE_Failure, CPLE_AppDefined,
1261 : "Ungridded dataset: At line " CPL_FRMT_GIB
1262 : ", X spacing was %f. Expected >0 value",
1263 : nLineNum, dfX - dfLastX);
1264 0 : VSIFCloseL(fp);
1265 0 : return nullptr;
1266 : }
1267 : }
1268 1 : else if (!adfStepX.empty() &&
1269 0 : fabs(std::round(-dfStepX / adfStepX[0]) -
1270 0 : (-dfStepX / adfStepX[0])) <= RELATIVE_ERROR)
1271 : {
1272 0 : bColOrganization = false;
1273 : }
1274 1 : else if (adfStepX.empty())
1275 : {
1276 1 : adfStepX.push_back(fabs(dfStepX));
1277 1 : bColOrganization = false;
1278 : }
1279 : else
1280 : {
1281 0 : CPLError(CE_Failure, CPLE_AppDefined,
1282 : "Ungridded dataset: At line " CPL_FRMT_GIB
1283 : ", X spacing was %f. Expected a multiple of %f",
1284 0 : nLineNum, dfStepX, adfStepX[0]);
1285 0 : VSIFCloseL(fp);
1286 0 : return nullptr;
1287 : }
1288 : }
1289 : else
1290 : {
1291 42482 : double dfStepY = dfY - dfLastY;
1292 42482 : if (dfStepY == 0.0)
1293 : {
1294 42158 : const double dfStepX = dfX - dfLastX;
1295 42158 : if (dfStepX <= 0)
1296 : {
1297 0 : CPLError(CE_Failure, CPLE_AppDefined,
1298 : "Ungridded dataset: At line " CPL_FRMT_GIB
1299 : ", X spacing was %f. Expected >0 value",
1300 : nLineNum, dfStepX);
1301 0 : VSIFCloseL(fp);
1302 0 : return nullptr;
1303 : }
1304 42158 : if (std::find(adfStepX.begin(), adfStepX.end(), dfStepX) ==
1305 84316 : adfStepX.end())
1306 : {
1307 42 : bool bAddNewValue = true;
1308 42 : std::vector<double>::iterator oIter = adfStepX.begin();
1309 42 : std::vector<double> adfStepXNew;
1310 43 : while (oIter != adfStepX.end())
1311 : {
1312 22 : if (fabs((dfStepX - *oIter) / dfStepX) < RELATIVE_ERROR)
1313 : {
1314 20 : double dfNewVal = *oIter;
1315 20 : if (nCountStepX > 0)
1316 : {
1317 : // Update mean step
1318 : /* n * mean(n) = (n-1) * mean(n-1) + val(n)
1319 : mean(n) = mean(n-1) + (val(n) - mean(n-1)) / n
1320 : */
1321 20 : nCountStepX++;
1322 20 : dfNewVal += (dfStepX - *oIter) / nCountStepX;
1323 : }
1324 :
1325 20 : adfStepXNew.push_back(dfNewVal);
1326 20 : bAddNewValue = false;
1327 20 : break;
1328 : }
1329 3 : else if (dfStepX < *oIter &&
1330 1 : fabs(*oIter -
1331 1 : static_cast<int>(*oIter / dfStepX + 0.5) *
1332 1 : dfStepX) /
1333 1 : dfStepX <
1334 : RELATIVE_ERROR)
1335 : {
1336 1 : nCountStepX = -1; // disable update of mean
1337 1 : ++oIter;
1338 : }
1339 2 : else if (dfStepX > *oIter &&
1340 2 : fabs(dfStepX -
1341 1 : static_cast<int>(dfStepX / *oIter + 0.5) *
1342 1 : (*oIter)) /
1343 1 : dfStepX <
1344 : RELATIVE_ERROR)
1345 : {
1346 1 : nCountStepX = -1; // disable update of mean
1347 1 : bAddNewValue = false;
1348 1 : adfStepXNew.push_back(*oIter);
1349 1 : break;
1350 : }
1351 : else
1352 : {
1353 0 : adfStepXNew.push_back(*oIter);
1354 0 : ++oIter;
1355 : }
1356 : }
1357 42 : adfStepX = std::move(adfStepXNew);
1358 42 : if (bAddNewValue)
1359 : {
1360 21 : CPLDebug("XYZ", "New stepX=%.15f", dfStepX);
1361 21 : adfStepX.push_back(dfStepX);
1362 21 : if (adfStepX.size() == 1 && nCountStepX == 0)
1363 : {
1364 20 : nCountStepX++;
1365 : }
1366 1 : else if (adfStepX.size() == 2)
1367 : {
1368 0 : nCountStepX = -1; // disable update of mean
1369 : }
1370 1 : else if (adfStepX.size() == 10)
1371 : {
1372 0 : CPLError(
1373 : CE_Failure, CPLE_AppDefined,
1374 : "Ungridded dataset: too many stepX values");
1375 0 : VSIFCloseL(fp);
1376 0 : return nullptr;
1377 : }
1378 : }
1379 : }
1380 : }
1381 : else
1382 : {
1383 324 : int bNewStepYSign = (dfStepY < 0.0) ? -1 : 1;
1384 324 : if (nStepYSign == 0)
1385 20 : nStepYSign = bNewStepYSign;
1386 304 : else if (nStepYSign != bNewStepYSign)
1387 : {
1388 0 : CPLError(CE_Failure, CPLE_AppDefined,
1389 : "Ungridded dataset: At line " CPL_FRMT_GIB
1390 : ", change of Y direction",
1391 : nLineNum);
1392 0 : VSIFCloseL(fp);
1393 0 : return nullptr;
1394 : }
1395 324 : if (bNewStepYSign < 0)
1396 301 : dfStepY = -dfStepY;
1397 324 : nCountStepY++;
1398 324 : if (adfStepY.empty())
1399 : {
1400 20 : adfStepY.push_back(dfStepY);
1401 : }
1402 304 : else if (fabs((adfStepY[0] - dfStepY) / dfStepY) >
1403 : RELATIVE_ERROR)
1404 : {
1405 4 : if (dfStepY > adfStepY[0] &&
1406 2 : fabs(std::round(dfStepY / adfStepY[0]) -
1407 2 : (dfStepY / adfStepY[0])) <= RELATIVE_ERROR)
1408 : {
1409 : // The new step is a multiple of the previous one,
1410 : // which means we have a missing line: OK
1411 : }
1412 : else
1413 : {
1414 0 : CPLDebug("XYZ", "New stepY=%.15f prev stepY=%.15f",
1415 0 : dfStepY, adfStepY[0]);
1416 0 : CPLError(CE_Failure, CPLE_AppDefined,
1417 : "Ungridded dataset: At line " CPL_FRMT_GIB
1418 : ", too many stepY values",
1419 : nLineNum);
1420 0 : VSIFCloseL(fp);
1421 0 : return nullptr;
1422 : }
1423 : }
1424 : else
1425 : {
1426 : // Update mean step
1427 302 : adfStepY[0] += (dfStepY - adfStepY[0]) / nCountStepY;
1428 : }
1429 : }
1430 : }
1431 :
1432 42546 : if (dfX < dfMinX)
1433 4 : dfMinX = dfX;
1434 42546 : if (dfX > dfMaxX)
1435 323 : dfMaxX = dfX;
1436 42546 : if (dfY < dfMinY)
1437 305 : dfMinY = dfY;
1438 42546 : if (dfY > dfMaxY)
1439 33 : dfMaxY = dfY;
1440 :
1441 42546 : dfLastX = dfX;
1442 42546 : dfLastY = dfY;
1443 : }
1444 :
1445 28 : if (adfStepX.size() != 1 || adfStepX[0] == 0)
1446 : {
1447 2 : if (!((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
1448 1 : poOpenInfo->IsExtensionEqualToCI("CSV")))
1449 : {
1450 0 : CPLError(CE_Failure, CPLE_AppDefined,
1451 : "Couldn't determine X spacing");
1452 : }
1453 1 : VSIFCloseL(fp);
1454 1 : return nullptr;
1455 : }
1456 :
1457 27 : if (adfStepY.size() != 1 || adfStepY[0] == 0)
1458 : {
1459 0 : CPLError(CE_Failure, CPLE_AppDefined, "Couldn't determine Y spacing");
1460 0 : VSIFCloseL(fp);
1461 0 : return nullptr;
1462 : }
1463 :
1464 : // Decide for a north-up organization
1465 27 : if (bColOrganization)
1466 5 : nStepYSign = -1;
1467 :
1468 27 : const double dfXSize = 1 + ((dfMaxX - dfMinX) / adfStepX[0] + 0.5);
1469 27 : const double dfYSize = 1 + ((dfMaxY - dfMinY) / adfStepY[0] + 0.5);
1470 : // Test written such as to detect NaN values
1471 27 : if (!(dfXSize > 0 && dfXSize < INT_MAX) ||
1472 27 : !(dfYSize > 0 && dfYSize < INT_MAX))
1473 : {
1474 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid dimensions");
1475 0 : VSIFCloseL(fp);
1476 0 : return nullptr;
1477 : }
1478 27 : const int nXSize = static_cast<int>(dfXSize);
1479 27 : const int nYSize = static_cast<int>(dfYSize);
1480 27 : const double dfStepX = (dfMaxX - dfMinX) / (nXSize - 1);
1481 27 : const double dfStepY = (dfMaxY - dfMinY) / (nYSize - 1) * nStepYSign;
1482 :
1483 : #ifdef DEBUG_VERBOSE
1484 : CPLDebug("XYZ", "minx=%f maxx=%f stepx=%f", dfMinX, dfMaxX, dfStepX);
1485 : CPLDebug("XYZ", "miny=%f maxy=%f stepy=%f", dfMinY, dfMaxY, dfStepY);
1486 : #endif
1487 :
1488 27 : if (nDataLineNum != static_cast<GIntBig>(nXSize) * nYSize)
1489 : {
1490 5 : if (bColOrganization)
1491 : {
1492 0 : CPLError(CE_Failure, CPLE_NotSupported,
1493 : "The XYZ driver does not support datasets organized by "
1494 : "columns with missing values");
1495 0 : VSIFCloseL(fp);
1496 0 : return nullptr;
1497 : }
1498 5 : bSameNumberOfValuesPerLine = false;
1499 : }
1500 22 : else if (bColOrganization && nDataLineNum > 100 * 1000 * 1000)
1501 : {
1502 0 : CPLError(CE_Failure, CPLE_NotSupported,
1503 : "The XYZ driver cannot load datasets organized by "
1504 : "columns with more than 100 million points");
1505 0 : VSIFCloseL(fp);
1506 0 : return nullptr;
1507 : }
1508 :
1509 27 : const bool bIngestAll = bColOrganization;
1510 27 : if (bIngestAll)
1511 : {
1512 5 : if (eDT == GDT_Int32)
1513 0 : eDT = GDT_Float32;
1514 5 : else if (eDT == GDT_UInt8)
1515 1 : eDT = GDT_Int16;
1516 5 : CPLAssert(eDT == GDT_Int16 || eDT == GDT_Float32);
1517 : }
1518 :
1519 27 : if (poOpenInfo->eAccess == GA_Update)
1520 : {
1521 0 : ReportUpdateNotSupportedByDriver("XYZ");
1522 0 : VSIFCloseL(fp);
1523 0 : return nullptr;
1524 : }
1525 :
1526 : /* -------------------------------------------------------------------- */
1527 : /* Create a corresponding GDALDataset. */
1528 : /* -------------------------------------------------------------------- */
1529 27 : XYZDataset *poDS = new XYZDataset();
1530 27 : poDS->fp = fp;
1531 27 : poDS->bHasHeaderLine = bHasHeaderLine;
1532 27 : poDS->nCommentLineCount = nCommentLineCount;
1533 27 : poDS->chDecimalSep = chDecimalSep ? chDecimalSep : '.';
1534 27 : poDS->nXIndex = nXIndex;
1535 27 : poDS->nYIndex = nYIndex;
1536 27 : poDS->nZIndex = nZIndex;
1537 27 : poDS->nMinTokens = nMinTokens;
1538 27 : poDS->nRasterXSize = nXSize;
1539 27 : poDS->nRasterYSize = nYSize;
1540 27 : poDS->m_gt[0] = dfMinX - dfStepX / 2;
1541 27 : poDS->m_gt[1] = dfStepX;
1542 27 : poDS->m_gt[3] = (dfStepY < 0) ? dfMaxY - dfStepY / 2 : dfMinY - dfStepY / 2;
1543 27 : poDS->m_gt[5] = dfStepY;
1544 27 : poDS->bSameNumberOfValuesPerLine = bSameNumberOfValuesPerLine;
1545 27 : poDS->dfMinZ = dfMinZ;
1546 27 : poDS->dfMaxZ = dfMaxZ;
1547 27 : poDS->bIngestAll = bIngestAll;
1548 : #ifdef DEBUG_VERBOSE
1549 : CPLDebug("XYZ", "bSameNumberOfValuesPerLine = %d",
1550 : bSameNumberOfValuesPerLine);
1551 : #endif
1552 :
1553 27 : if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize))
1554 : {
1555 0 : delete poDS;
1556 0 : return nullptr;
1557 : }
1558 :
1559 : /* -------------------------------------------------------------------- */
1560 : /* Create band information objects. */
1561 : /* -------------------------------------------------------------------- */
1562 27 : poDS->nBands = 1;
1563 54 : for (int i = 0; i < poDS->nBands; i++)
1564 27 : poDS->SetBand(i + 1, new XYZRasterBand(poDS, i + 1, eDT));
1565 :
1566 : /* -------------------------------------------------------------------- */
1567 : /* Initialize any PAM information. */
1568 : /* -------------------------------------------------------------------- */
1569 27 : poDS->SetDescription(poOpenInfo->pszFilename);
1570 27 : poDS->TryLoadXML();
1571 :
1572 : /* -------------------------------------------------------------------- */
1573 : /* Support overviews. */
1574 : /* -------------------------------------------------------------------- */
1575 27 : poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename);
1576 27 : return poDS;
1577 : }
1578 :
1579 : /************************************************************************/
1580 : /* CreateCopy() */
1581 : /************************************************************************/
1582 :
1583 33 : GDALDataset *XYZDataset::CreateCopy(const char *pszFilename,
1584 : GDALDataset *poSrcDS, int bStrict,
1585 : CSLConstList papszOptions,
1586 : GDALProgressFunc pfnProgress,
1587 : void *pProgressData)
1588 : {
1589 : /* -------------------------------------------------------------------- */
1590 : /* Some some rudimentary checks */
1591 : /* -------------------------------------------------------------------- */
1592 33 : int nBands = poSrcDS->GetRasterCount();
1593 33 : if (nBands == 0)
1594 : {
1595 1 : CPLError(
1596 : CE_Failure, CPLE_NotSupported,
1597 : "XYZ driver does not support source dataset with zero band.\n");
1598 1 : return nullptr;
1599 : }
1600 :
1601 32 : if (nBands != 1)
1602 : {
1603 4 : CPLError((bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
1604 : "XYZ driver only uses the first band of the dataset.\n");
1605 4 : if (bStrict)
1606 4 : return nullptr;
1607 : }
1608 :
1609 28 : if (pfnProgress && !pfnProgress(0.0, nullptr, pProgressData))
1610 0 : return nullptr;
1611 :
1612 : /* -------------------------------------------------------------------- */
1613 : /* Get source dataset info */
1614 : /* -------------------------------------------------------------------- */
1615 :
1616 28 : int nXSize = poSrcDS->GetRasterXSize();
1617 28 : int nYSize = poSrcDS->GetRasterYSize();
1618 28 : GDALGeoTransform gt;
1619 28 : poSrcDS->GetGeoTransform(gt);
1620 28 : if (gt[2] != 0 || gt[4] != 0)
1621 : {
1622 0 : CPLError(CE_Failure, CPLE_NotSupported,
1623 : "XYZ driver does not support CreateCopy() from skewed or "
1624 : "rotated dataset.\n");
1625 0 : return nullptr;
1626 : }
1627 :
1628 28 : const GDALDataType eSrcDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
1629 : GDALDataType eReqDT;
1630 28 : if (eSrcDT == GDT_UInt8 || eSrcDT == GDT_Int16 || eSrcDT == GDT_UInt16 ||
1631 : eSrcDT == GDT_Int32)
1632 20 : eReqDT = GDT_Int32;
1633 : else
1634 8 : eReqDT = GDT_Float32;
1635 :
1636 : /* -------------------------------------------------------------------- */
1637 : /* Create target file */
1638 : /* -------------------------------------------------------------------- */
1639 :
1640 28 : VSILFILE *fp = VSIFOpenL(pszFilename, "wb");
1641 28 : if (fp == nullptr)
1642 : {
1643 3 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot create %s", pszFilename);
1644 3 : return nullptr;
1645 : }
1646 :
1647 : /* -------------------------------------------------------------------- */
1648 : /* Read creation options */
1649 : /* -------------------------------------------------------------------- */
1650 25 : const char *pszColSep = CSLFetchNameValue(papszOptions, "COLUMN_SEPARATOR");
1651 25 : if (pszColSep == nullptr)
1652 24 : pszColSep = " ";
1653 1 : else if (EQUAL(pszColSep, "COMMA"))
1654 0 : pszColSep = ",";
1655 1 : else if (EQUAL(pszColSep, "SPACE"))
1656 0 : pszColSep = " ";
1657 1 : else if (EQUAL(pszColSep, "SEMICOLON"))
1658 0 : pszColSep = ";";
1659 1 : else if (EQUAL(pszColSep, "\\t") || EQUAL(pszColSep, "TAB"))
1660 0 : pszColSep = "\t";
1661 : #ifdef DEBUG_VERBOSE
1662 : else
1663 : CPLDebug("XYZ", "Using raw column separator: '%s' ", pszColSep);
1664 : #endif
1665 :
1666 : const char *pszAddHeaderLine =
1667 25 : CSLFetchNameValue(papszOptions, "ADD_HEADER_LINE");
1668 25 : if (pszAddHeaderLine != nullptr && CPLTestBool(pszAddHeaderLine))
1669 : {
1670 1 : VSIFPrintfL(fp, "X%sY%sZ\n", pszColSep, pszColSep);
1671 : }
1672 :
1673 : /* -------------------------------------------------------------------- */
1674 : /* Copy imagery */
1675 : /* -------------------------------------------------------------------- */
1676 25 : char szFormat[50] = {'\0'};
1677 25 : if (eReqDT == GDT_Int32)
1678 17 : strcpy(szFormat, "%.17g%c%.17g%c%d\n");
1679 : else
1680 8 : strcpy(szFormat, "%.17g%c%.17g%c%.17g\n");
1681 : const char *pszDecimalPrecision =
1682 25 : CSLFetchNameValue(papszOptions, "DECIMAL_PRECISION");
1683 : const char *pszSignificantDigits =
1684 25 : CSLFetchNameValue(papszOptions, "SIGNIFICANT_DIGITS");
1685 25 : bool bIgnoreSigDigits = false;
1686 25 : if (pszDecimalPrecision && pszSignificantDigits)
1687 : {
1688 0 : CPLError(CE_Warning, CPLE_AppDefined,
1689 : "Conflicting precision arguments, using DECIMAL_PRECISION");
1690 0 : bIgnoreSigDigits = true;
1691 : }
1692 : int nPrecision;
1693 25 : if (pszSignificantDigits && !bIgnoreSigDigits)
1694 : {
1695 0 : nPrecision = atoi(pszSignificantDigits);
1696 0 : if (nPrecision >= 0)
1697 : {
1698 0 : if (eReqDT == GDT_Int32)
1699 0 : snprintf(szFormat, sizeof(szFormat), "%%.%dg%%c%%.%dg%%c%%d\n",
1700 : nPrecision, nPrecision);
1701 : else
1702 0 : snprintf(szFormat, sizeof(szFormat),
1703 : "%%.%dg%%c%%.%dg%%c%%.%dg\n", nPrecision, nPrecision,
1704 : nPrecision);
1705 : }
1706 0 : CPLDebug("XYZ", "Setting precision format: %s", szFormat);
1707 : }
1708 25 : else if (pszDecimalPrecision)
1709 : {
1710 0 : nPrecision = atoi(pszDecimalPrecision);
1711 0 : if (nPrecision >= 0)
1712 : {
1713 0 : if (eReqDT == GDT_Int32)
1714 0 : snprintf(szFormat, sizeof(szFormat), "%%.%df%%c%%.%df%%c%%d\n",
1715 : nPrecision, nPrecision);
1716 : else
1717 0 : snprintf(szFormat, sizeof(szFormat),
1718 : "%%.%df%%c%%.%df%%c%%.%df\n", nPrecision, nPrecision,
1719 : nPrecision);
1720 : }
1721 0 : CPLDebug("XYZ", "Setting precision format: %s", szFormat);
1722 : }
1723 25 : void *pLineBuffer = static_cast<void *>(CPLMalloc(nXSize * sizeof(int)));
1724 25 : CPLErr eErr = CE_None;
1725 447 : for (int j = 0; j < nYSize && eErr == CE_None; j++)
1726 : {
1727 422 : eErr = poSrcDS->GetRasterBand(1)->RasterIO(GF_Read, 0, j, nXSize, 1,
1728 : pLineBuffer, nXSize, 1,
1729 : eReqDT, 0, 0, nullptr);
1730 422 : if (eErr != CE_None)
1731 0 : break;
1732 422 : const double dfY = gt[3] + (j + 0.5) * gt[5];
1733 422 : CPLString osBuf;
1734 43587 : for (int i = 0; i < nXSize; i++)
1735 : {
1736 43175 : const double dfX = gt[0] + (i + 0.5) * gt[1];
1737 : char szBuf[256];
1738 43175 : if (eReqDT == GDT_Int32)
1739 2074 : CPLsnprintf(szBuf, sizeof(szBuf), szFormat, dfX, pszColSep[0],
1740 2074 : dfY, pszColSep[0],
1741 2074 : reinterpret_cast<int *>(pLineBuffer)[i]);
1742 : else
1743 41101 : CPLsnprintf(szBuf, sizeof(szBuf), szFormat, dfX, pszColSep[0],
1744 41101 : dfY, pszColSep[0],
1745 41101 : reinterpret_cast<float *>(pLineBuffer)[i]);
1746 43175 : osBuf += szBuf;
1747 43175 : if ((i & 1023) == 0 || i == nXSize - 1)
1748 : {
1749 840 : if (VSIFWriteL(osBuf.c_str(), osBuf.size(), 1, fp) != 1)
1750 : {
1751 10 : eErr = CE_Failure;
1752 10 : CPLError(CE_Failure, CPLE_AppDefined,
1753 : "Write failed, disk full?\n");
1754 10 : break;
1755 : }
1756 830 : osBuf = "";
1757 : }
1758 : }
1759 844 : if (pfnProgress &&
1760 422 : !pfnProgress((j + 1) * 1.0 / nYSize, nullptr, pProgressData))
1761 : {
1762 0 : eErr = CE_Failure;
1763 0 : break;
1764 : }
1765 : }
1766 25 : CPLFree(pLineBuffer);
1767 25 : VSIFCloseL(fp);
1768 :
1769 25 : if (eErr != CE_None)
1770 10 : return nullptr;
1771 :
1772 : /* -------------------------------------------------------------------- */
1773 : /* We don't want to call GDALOpen() since it will be expensive, */
1774 : /* so we "hand prepare" an XYZ dataset referencing our file. */
1775 : /* -------------------------------------------------------------------- */
1776 15 : XYZDataset *poXYZ_DS = new XYZDataset();
1777 15 : poXYZ_DS->nRasterXSize = nXSize;
1778 15 : poXYZ_DS->nRasterYSize = nYSize;
1779 15 : poXYZ_DS->nBands = 1;
1780 15 : poXYZ_DS->SetBand(1, new XYZRasterBand(poXYZ_DS, 1, eReqDT));
1781 : /* If writing to stdout, we can't reopen it --> silence warning */
1782 15 : CPLPushErrorHandler(CPLQuietErrorHandler);
1783 15 : poXYZ_DS->fp = VSIFOpenL(pszFilename, "rb");
1784 15 : CPLPopErrorHandler();
1785 15 : poXYZ_DS->m_gt = gt;
1786 15 : poXYZ_DS->nXIndex = 0;
1787 15 : poXYZ_DS->nYIndex = 1;
1788 15 : poXYZ_DS->nZIndex = 2;
1789 15 : if (pszAddHeaderLine)
1790 : {
1791 1 : poXYZ_DS->nDataLineNum = 1;
1792 1 : poXYZ_DS->bHasHeaderLine = TRUE;
1793 : }
1794 :
1795 15 : return poXYZ_DS;
1796 : }
1797 :
1798 : /************************************************************************/
1799 : /* GetGeoTransform() */
1800 : /************************************************************************/
1801 :
1802 19 : CPLErr XYZDataset::GetGeoTransform(GDALGeoTransform >) const
1803 :
1804 : {
1805 19 : gt = m_gt;
1806 :
1807 19 : return CE_None;
1808 : }
1809 :
1810 : /************************************************************************/
1811 : /* GDALRegister_XYZ() */
1812 : /************************************************************************/
1813 :
1814 2059 : void GDALRegister_XYZ()
1815 :
1816 : {
1817 2059 : if (GDALGetDriverByName("XYZ") != nullptr)
1818 283 : return;
1819 :
1820 1776 : GDALDriver *poDriver = new GDALDriver();
1821 :
1822 1776 : poDriver->SetDescription("XYZ");
1823 1776 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1824 1776 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "ASCII Gridded XYZ");
1825 1776 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/xyz.html");
1826 1776 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "xyz");
1827 1776 : poDriver->SetMetadataItem(
1828 : GDAL_DMD_CREATIONOPTIONLIST,
1829 : "<CreationOptionList>"
1830 : " <Option name='COLUMN_SEPARATOR' type='string' default=' ' "
1831 : "description='Separator between fields.'/>"
1832 : " <Option name='ADD_HEADER_LINE' type='boolean' default='false' "
1833 : "description='Add an header line with column names.'/>"
1834 : " <Option name='SIGNIFICANT_DIGITS' type='int' description='Number "
1835 : "of significant digits when writing floating-point numbers (%g format; "
1836 : "default with 18).'/>\n"
1837 : " <Option name='DECIMAL_PRECISION' type='int' description='Number of "
1838 : "decimal places when writing floating-point numbers (%f format).'/>\n"
1839 1776 : "</CreationOptionList>");
1840 1776 : poDriver->SetMetadataItem(
1841 : GDAL_DMD_OPENOPTIONLIST,
1842 : "<OpenOptionList>"
1843 : " <Option name='COLUMN_ORDER' type='string-select' default='AUTO' "
1844 : "description='Specifies the order of the columns. It overrides the "
1845 : "header.'>"
1846 : " <Value>AUTO</Value>"
1847 : " <Value>XYZ</Value>"
1848 : " <Value>YXZ</Value>"
1849 : " </Option>"
1850 1776 : "</OpenOptionList>");
1851 :
1852 1776 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1853 :
1854 1776 : poDriver->pfnOpen = XYZDataset::Open;
1855 1776 : poDriver->pfnIdentify = XYZDataset::Identify;
1856 1776 : poDriver->pfnCreateCopy = XYZDataset::CreateCopy;
1857 :
1858 1776 : GetGDALDriverManager()->RegisterDriver(poDriver);
1859 : }
|