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 30 : const int nX = static_cast<int>(
262 30 : (dfX - 0.5 * poGDS->m_gt.xscale - poGDS->m_gt.xorig) /
263 30 : poGDS->m_gt.xscale +
264 : 0.5);
265 30 : const int nY = static_cast<int>(
266 30 : (dfY - 0.5 * poGDS->m_gt.yscale - poGDS->m_gt.yorig) /
267 30 : poGDS->m_gt.yscale +
268 : 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 326 : const double dfExpectedY =
420 326 : poGDS->m_gt.yorig + (0.5 + nBlockYOff) * poGDS->m_gt.yscale;
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.yscale) >
508 : RELATIVE_ERROR)
509 : {
510 5 : if (idx < 0)
511 : {
512 3 : const double dfYDeltaOrigin =
513 3 : dfY + 0.5 * poGDS->m_gt.yscale -
514 3 : poGDS->m_gt.yorig;
515 6 : if (!(fabs(dfYDeltaOrigin) >
516 3 : fabs(poGDS->m_gt.yscale) &&
517 3 : fabs(std::round(dfYDeltaOrigin /
518 3 : poGDS->m_gt.yscale) -
519 3 : (dfYDeltaOrigin / poGDS->m_gt.yscale)) <=
520 : RELATIVE_ERROR))
521 : {
522 0 : CPLError(CE_Failure, CPLE_AppDefined,
523 : "At line " CPL_FRMT_GIB
524 : ", found Y=%f instead of %f "
525 : "for nBlockYOff = %d",
526 : poGDS->nLineNum, dfY, dfExpectedY,
527 : nBlockYOff);
528 0 : return CE_Failure;
529 : }
530 : }
531 5 : VSIFSeekL(poGDS->fp, nOffsetBefore, SEEK_SET);
532 5 : nLastYOff = nBlockYOff;
533 5 : poGDS->nLineNum--;
534 5 : return CE_None;
535 : }
536 :
537 31 : idx = static_cast<int>(
538 31 : (dfX - 0.5 * poGDS->m_gt.xscale - poGDS->m_gt.xorig) /
539 31 : poGDS->m_gt.xscale +
540 : 0.5);
541 : }
542 42089 : CPLAssert(idx >= 0 && idx < nRasterXSize);
543 :
544 42089 : if (pImage)
545 : {
546 42089 : if (eDataType == GDT_Float32)
547 : {
548 40416 : reinterpret_cast<float *>(pImage)[idx] =
549 40416 : static_cast<float>(dfZ);
550 : }
551 1673 : else if (eDataType == GDT_Int32)
552 : {
553 400 : reinterpret_cast<GInt32 *>(pImage)[idx] =
554 400 : static_cast<GInt32>(dfZ);
555 : }
556 1273 : else if (eDataType == GDT_Int16)
557 : {
558 13 : reinterpret_cast<GInt16 *>(pImage)[idx] =
559 13 : static_cast<GInt16>(dfZ);
560 : }
561 : else
562 : {
563 1260 : reinterpret_cast<GByte *>(pImage)[idx] =
564 1260 : static_cast<GByte>(dfZ);
565 : }
566 : }
567 : }
568 : /* Skip empty line */
569 42096 : } while (nCol == 1 && bLastWasSep);
570 :
571 42089 : poGDS->nDataLineNum++;
572 42089 : if (nCol < poGDS->nMinTokens)
573 0 : return CE_Failure;
574 :
575 42089 : if (idx + 1 == nRasterXSize)
576 320 : break;
577 41769 : }
578 :
579 320 : if (poGDS->bSameNumberOfValuesPerLine)
580 : {
581 304 : if (poGDS->nDataLineNum !=
582 304 : static_cast<GIntBig>(nBlockYOff + 1) * nBlockXSize)
583 : {
584 0 : CPLError(CE_Failure, CPLE_AssertionFailed,
585 : "The file does not have the same number of values per "
586 : "line as initially thought. It must be somehow corrupted");
587 0 : return CE_Failure;
588 : }
589 : }
590 :
591 320 : nLastYOff = nBlockYOff;
592 :
593 320 : return CE_None;
594 : }
595 :
596 : /************************************************************************/
597 : /* GetMinimum() */
598 : /************************************************************************/
599 :
600 1 : double XYZRasterBand::GetMinimum(int *pbSuccess)
601 : {
602 1 : XYZDataset *poGDS = cpl::down_cast<XYZDataset *>(poDS);
603 1 : if (pbSuccess)
604 1 : *pbSuccess = TRUE;
605 1 : return poGDS->dfMinZ;
606 : }
607 :
608 : /************************************************************************/
609 : /* GetMaximum() */
610 : /************************************************************************/
611 :
612 1 : double XYZRasterBand::GetMaximum(int *pbSuccess)
613 : {
614 1 : XYZDataset *poGDS = cpl::down_cast<XYZDataset *>(poDS);
615 1 : if (pbSuccess)
616 1 : *pbSuccess = TRUE;
617 1 : return poGDS->dfMaxZ;
618 : }
619 :
620 : /************************************************************************/
621 : /* GetNoDataValue() */
622 : /************************************************************************/
623 :
624 339 : double XYZRasterBand::GetNoDataValue(int *pbSuccess)
625 : {
626 339 : XYZDataset *poGDS = cpl::down_cast<XYZDataset *>(poDS);
627 339 : if (!poGDS->bSameNumberOfValuesPerLine && poGDS->dfMinZ > -32768 &&
628 23 : eDataType != GDT_UInt8)
629 : {
630 0 : if (pbSuccess)
631 0 : *pbSuccess = TRUE;
632 0 : return (poGDS->dfMinZ > 0) ? 0 : -32768;
633 : }
634 339 : else if (!poGDS->bSameNumberOfValuesPerLine && poGDS->dfMinZ > 0 &&
635 23 : eDataType == GDT_UInt8)
636 : {
637 23 : if (pbSuccess)
638 23 : *pbSuccess = TRUE;
639 23 : return 0;
640 : }
641 :
642 316 : return GDALPamRasterBand::GetNoDataValue(pbSuccess);
643 : }
644 :
645 : /************************************************************************/
646 : /* ~XYZDataset() */
647 : /************************************************************************/
648 :
649 42 : XYZDataset::XYZDataset()
650 : : fp(nullptr), bHasHeaderLine(FALSE), nCommentLineCount(0),
651 : chDecimalSep('.'), nXIndex(-1), nYIndex(-1), nZIndex(-1), nMinTokens(0),
652 : nLineNum(0), nDataLineNum(GINTBIG_MAX), bSameNumberOfValuesPerLine(TRUE),
653 42 : dfMinZ(0), dfMaxZ(0), bEOF(false)
654 : {
655 42 : }
656 :
657 : /************************************************************************/
658 : /* ~XYZDataset() */
659 : /************************************************************************/
660 :
661 84 : XYZDataset::~XYZDataset()
662 :
663 : {
664 42 : FlushCache(true);
665 42 : if (fp)
666 40 : VSIFCloseL(fp);
667 :
668 : {
669 84 : std::lock_guard<std::mutex> guard(gMutex);
670 42 : if (gpoActiveDS == this)
671 : {
672 4 : gpoActiveDS = nullptr;
673 4 : gasValues.clear();
674 4 : gafValues.clear();
675 : }
676 : }
677 84 : }
678 :
679 : /************************************************************************/
680 : /* Identify() */
681 : /************************************************************************/
682 :
683 61637 : int XYZDataset::Identify(GDALOpenInfo *poOpenInfo)
684 : {
685 : int bHasHeaderLine, nCommentLineCount;
686 : int nXIndex;
687 : int nYIndex;
688 : int nZIndex;
689 61637 : return IdentifyEx(poOpenInfo, bHasHeaderLine, nCommentLineCount, nXIndex,
690 123274 : nYIndex, nZIndex);
691 : }
692 :
693 : /************************************************************************/
694 : /* IdentifyEx() */
695 : /************************************************************************/
696 :
697 61665 : int XYZDataset::IdentifyEx(GDALOpenInfo *poOpenInfo, int &bHasHeaderLine,
698 : int &nCommentLineCount, int &nXIndex, int &nYIndex,
699 : int &nZIndex)
700 :
701 : {
702 61665 : bHasHeaderLine = FALSE;
703 61665 : nCommentLineCount = 0;
704 :
705 123330 : CPLString osFilename(poOpenInfo->pszFilename);
706 61665 : if (EQUAL(CPLGetExtensionSafe(osFilename).c_str(), "GRA") &&
707 0 : !poOpenInfo->IsSingleAllowedDriver("XYZ"))
708 : {
709 : // IGNFHeightASCIIGRID .GRA
710 0 : return FALSE;
711 : }
712 :
713 61665 : std::unique_ptr<GDALOpenInfo> poOpenInfoToDelete; // keep in this scope
714 : /* GZipped .xyz files are common, so automagically open them */
715 : /* if the /vsigzip/ has not been explicitly passed */
716 61665 : if (strlen(poOpenInfo->pszFilename) > 6 &&
717 59910 : EQUAL(poOpenInfo->pszFilename + strlen(poOpenInfo->pszFilename) - 6,
718 0 : "xyz.gz") &&
719 0 : !STARTS_WITH_CI(poOpenInfo->pszFilename, "/vsigzip/"))
720 : {
721 0 : osFilename = "/vsigzip/";
722 0 : osFilename += poOpenInfo->pszFilename;
723 0 : poOpenInfoToDelete = std::make_unique<GDALOpenInfo>(
724 0 : osFilename.c_str(), GA_ReadOnly, poOpenInfo->GetSiblingFiles());
725 0 : poOpenInfo = poOpenInfoToDelete.get();
726 : }
727 :
728 61665 : if (poOpenInfo->nHeaderBytes == 0)
729 : {
730 57107 : return FALSE;
731 : }
732 :
733 : /* -------------------------------------------------------------------- */
734 : /* Check that it looks roughly as an XYZ dataset */
735 : /* -------------------------------------------------------------------- */
736 4558 : const char *pszData =
737 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
738 :
739 4559 : if (poOpenInfo->nHeaderBytes >= 4 && STARTS_WITH(pszData, "DSAA") &&
740 1 : !poOpenInfo->IsSingleAllowedDriver("XYZ"))
741 : {
742 : // Do not match GSAG datasets
743 1 : return FALSE;
744 : }
745 :
746 : /* Skip comments line at the beginning such as in */
747 : /* http://pubs.usgs.gov/of/2003/ofr-03-230/DATA/NSLCU.XYZ */
748 4557 : int i = 0;
749 4557 : if (pszData[i] == '/')
750 : {
751 0 : nCommentLineCount++;
752 :
753 0 : i++;
754 0 : for (; i < poOpenInfo->nHeaderBytes; i++)
755 : {
756 0 : const char ch = pszData[i];
757 0 : if (ch == 13 || ch == 10)
758 : {
759 0 : if (ch == 13 && pszData[i + 1] == 10)
760 0 : i++;
761 0 : if (pszData[i + 1] == '/')
762 : {
763 0 : nCommentLineCount++;
764 0 : i++;
765 : }
766 : else
767 0 : break;
768 : }
769 : }
770 : }
771 :
772 4557 : int iStartLine = i;
773 38437 : for (; i < poOpenInfo->nHeaderBytes; i++)
774 : {
775 38379 : const char ch = pszData[i];
776 38379 : if (ch == 13 || ch == 10)
777 : {
778 : break;
779 : }
780 38193 : else if (ch == ' ' || ch == ',' || ch == '\t' || ch == ';')
781 : ;
782 27162 : else if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '+' ||
783 18403 : ch == '-' || ch == 'e' || ch == 'E')
784 : ;
785 16685 : else if (ch == '"' || (ch >= 'a' && ch <= 'z') ||
786 8297 : (ch >= 'A' && ch <= 'Z'))
787 12372 : bHasHeaderLine = TRUE;
788 : else
789 : {
790 4313 : return FALSE;
791 : }
792 : }
793 :
794 244 : nXIndex = -1;
795 244 : nYIndex = -1;
796 244 : nZIndex = -1;
797 488 : const char *pszColumnOrder = CSLFetchNameValueDef(
798 244 : poOpenInfo->papszOpenOptions, "COLUMN_ORDER", "AUTO");
799 244 : if (EQUAL(pszColumnOrder, "XYZ"))
800 : {
801 2 : nXIndex = 0;
802 2 : nYIndex = 1;
803 2 : nZIndex = 2;
804 2 : return TRUE;
805 : }
806 242 : else if (EQUAL(pszColumnOrder, "YXZ"))
807 : {
808 4 : nXIndex = 1;
809 4 : nYIndex = 0;
810 4 : nZIndex = 2;
811 4 : return TRUE;
812 : }
813 238 : else if (!EQUAL(pszColumnOrder, "AUTO"))
814 : {
815 1 : CPLError(CE_Failure, CPLE_IllegalArg,
816 : "Option COLUMN_ORDER can only be XYZ, YXZ and AUTO."
817 : "%s is not valid",
818 : pszColumnOrder);
819 1 : return FALSE;
820 : }
821 :
822 237 : if (bHasHeaderLine)
823 : {
824 134 : CPLString osHeaderLine;
825 134 : osHeaderLine.assign(pszData + iStartLine, i - iStartLine);
826 : char **papszTokens =
827 134 : CSLTokenizeString2(osHeaderLine, " ,\t;", CSLT_HONOURSTRINGS);
828 134 : int nTokens = CSLCount(papszTokens);
829 901 : for (int iToken = 0; iToken < nTokens; iToken++)
830 : {
831 767 : const char *pszToken = papszTokens[iToken];
832 767 : if (EQUAL(pszToken, "x") || STARTS_WITH_CI(pszToken, "lon") ||
833 737 : STARTS_WITH_CI(pszToken, "east"))
834 30 : nXIndex = iToken;
835 737 : else if (EQUAL(pszToken, "y") || STARTS_WITH_CI(pszToken, "lat") ||
836 709 : STARTS_WITH_CI(pszToken, "north"))
837 28 : nYIndex = iToken;
838 709 : else if (EQUAL(pszToken, "z") || STARTS_WITH_CI(pszToken, "alt") ||
839 681 : EQUAL(pszToken, "height"))
840 28 : nZIndex = iToken;
841 : }
842 134 : CSLDestroy(papszTokens);
843 134 : if (nXIndex >= 0 && nYIndex >= 0 && nZIndex >= 0)
844 : {
845 28 : return TRUE;
846 : }
847 : }
848 :
849 209 : bool bHasFoundNewLine = false;
850 209 : bool bPrevWasSep = true;
851 209 : int nCols = 0;
852 209 : int nMaxCols = 0;
853 20973 : for (; i < poOpenInfo->nHeaderBytes; i++)
854 : {
855 20868 : char ch = pszData[i];
856 20868 : if (ch == 13 || ch == 10)
857 : {
858 1398 : bHasFoundNewLine = true;
859 1398 : if (!bPrevWasSep)
860 : {
861 1223 : nCols++;
862 1223 : if (nCols > nMaxCols)
863 54 : nMaxCols = nCols;
864 : }
865 1398 : bPrevWasSep = true;
866 1398 : nCols = 0;
867 : }
868 19470 : else if (ch == ' ' || ch == ',' || ch == '\t' || ch == ';')
869 : {
870 2996 : if (!bPrevWasSep)
871 : {
872 2935 : nCols++;
873 2935 : if (nCols > nMaxCols)
874 134 : nMaxCols = nCols;
875 : }
876 2996 : bPrevWasSep = true;
877 : }
878 16474 : else if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '+' ||
879 104 : ch == '-' || ch == 'e' || ch == 'E')
880 : {
881 16370 : bPrevWasSep = false;
882 : }
883 : else
884 : {
885 104 : return FALSE;
886 : }
887 : }
888 :
889 105 : return bHasFoundNewLine && nMaxCols >= 3;
890 : }
891 :
892 : /************************************************************************/
893 : /* Open() */
894 : /************************************************************************/
895 :
896 28 : GDALDataset *XYZDataset::Open(GDALOpenInfo *poOpenInfo)
897 :
898 : {
899 : int bHasHeaderLine;
900 28 : int nCommentLineCount = 0;
901 :
902 28 : int nXIndex = -1;
903 28 : int nYIndex = -1;
904 28 : int nZIndex = -1;
905 28 : if (!IdentifyEx(poOpenInfo, bHasHeaderLine, nCommentLineCount, nXIndex,
906 : nYIndex, nZIndex))
907 0 : return nullptr;
908 :
909 56 : CPLString osFilename(poOpenInfo->pszFilename);
910 :
911 : /* GZipped .xyz files are common, so automagically open them */
912 : /* if the /vsigzip/ has not been explicitly passed */
913 28 : if (strlen(poOpenInfo->pszFilename) > 6 &&
914 28 : EQUAL(poOpenInfo->pszFilename + strlen(poOpenInfo->pszFilename) - 6,
915 0 : "xyz.gz") &&
916 0 : !STARTS_WITH_CI(poOpenInfo->pszFilename, "/vsigzip/"))
917 : {
918 0 : osFilename = "/vsigzip/";
919 0 : osFilename += poOpenInfo->pszFilename;
920 : }
921 :
922 : /* -------------------------------------------------------------------- */
923 : /* Find dataset characteristics */
924 : /* -------------------------------------------------------------------- */
925 28 : VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "rb");
926 28 : if (fp == nullptr)
927 0 : return nullptr;
928 :
929 : /* For better performance of CPLReadLine2L() we create a buffered reader */
930 : /* (except for /vsigzip/ since it has one internally) */
931 28 : if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "/vsigzip/"))
932 28 : fp = VSICreateBufferedReaderHandle(fp);
933 :
934 28 : int nMinTokens = 0;
935 :
936 28 : for (int i = 0; i < nCommentLineCount; i++)
937 : {
938 0 : if (CPLReadLine2L(fp, 100, nullptr) == nullptr)
939 : {
940 0 : VSIFCloseL(fp);
941 0 : return nullptr;
942 : }
943 : }
944 :
945 : /* -------------------------------------------------------------------- */
946 : /* Parse header line */
947 : /* -------------------------------------------------------------------- */
948 28 : if (bHasHeaderLine)
949 : {
950 18 : const char *pszLine = CPLReadLine2L(fp, 100, nullptr);
951 18 : if (pszLine == nullptr)
952 : {
953 0 : VSIFCloseL(fp);
954 0 : return nullptr;
955 : }
956 18 : if (nXIndex < 0 || nYIndex < 0 || nZIndex < 0)
957 : {
958 2 : CPLError(CE_Warning, CPLE_AppDefined,
959 : "Could not find one of the X, Y or Z column names in "
960 : "header line. Defaulting to the first 3 columns");
961 2 : nXIndex = 0;
962 2 : nYIndex = 1;
963 2 : nZIndex = 2;
964 : }
965 : }
966 10 : else if (nXIndex < 0 || nYIndex < 0 || nZIndex < 0)
967 : {
968 9 : nXIndex = 0;
969 9 : nYIndex = 1;
970 9 : nZIndex = 2;
971 : }
972 28 : nMinTokens = 1 + std::max(std::max(nXIndex, nYIndex), nZIndex);
973 :
974 : /* -------------------------------------------------------------------- */
975 : /* Parse data lines */
976 : /* -------------------------------------------------------------------- */
977 :
978 28 : GIntBig nLineNum = 0;
979 28 : GIntBig nDataLineNum = 0;
980 28 : double dfX = 0.0;
981 28 : double dfY = 0.0;
982 28 : double dfZ = 0.0;
983 28 : double dfMinX = 0.0;
984 28 : double dfMinY = 0.0;
985 28 : double dfMaxX = 0.0;
986 28 : double dfMaxY = 0.0;
987 28 : double dfMinZ = 0.0;
988 28 : double dfMaxZ = 0.0;
989 28 : double dfLastX = 0.0;
990 28 : double dfLastY = 0.0;
991 56 : std::vector<double> adfStepX;
992 56 : std::vector<double> adfStepY;
993 28 : GDALDataType eDT = GDT_UInt8;
994 28 : bool bSameNumberOfValuesPerLine = true;
995 28 : char chDecimalSep = '\0';
996 28 : int nStepYSign = 0;
997 28 : bool bColOrganization = false;
998 :
999 : const char *pszLine;
1000 28 : GIntBig nCountStepX = 0;
1001 28 : GIntBig nCountStepY = 0;
1002 42589 : while ((pszLine = CPLReadLine2L(fp, 100, nullptr)) != nullptr)
1003 : {
1004 42561 : nLineNum++;
1005 :
1006 42561 : const char *pszPtr = pszLine;
1007 : char ch;
1008 42561 : int nCol = 0;
1009 42561 : bool bLastWasSep = true;
1010 42561 : if (chDecimalSep == '\0')
1011 : {
1012 1673 : int nCountComma = 0;
1013 1673 : int nCountFieldSep = 0;
1014 30709 : while ((ch = *pszPtr) != '\0')
1015 : {
1016 29048 : if (ch == '.')
1017 : {
1018 12 : chDecimalSep = '.';
1019 12 : break;
1020 : }
1021 29036 : else if (ch == ',')
1022 : {
1023 5 : nCountComma++;
1024 5 : bLastWasSep = false;
1025 : }
1026 29031 : else if (ch == ' ')
1027 : {
1028 3301 : if (!bLastWasSep)
1029 3298 : nCountFieldSep++;
1030 3301 : bLastWasSep = true;
1031 : }
1032 25730 : else if (ch == '\t' || ch == ';')
1033 : {
1034 4 : nCountFieldSep++;
1035 4 : bLastWasSep = true;
1036 : }
1037 : else
1038 25726 : bLastWasSep = false;
1039 29036 : pszPtr++;
1040 : }
1041 1673 : if (chDecimalSep == '\0')
1042 : {
1043 : /* 1,2,3 */
1044 1661 : if (nCountComma >= 2 && nCountFieldSep == 0)
1045 2 : chDecimalSep = '.';
1046 : /* 23,5;33;45 */
1047 1659 : else if (nCountComma > 0 && nCountFieldSep > 0)
1048 1 : chDecimalSep = ',';
1049 : }
1050 1673 : pszPtr = pszLine;
1051 1673 : bLastWasSep = true;
1052 : }
1053 :
1054 42561 : char chLocalDecimalSep = chDecimalSep ? chDecimalSep : '.';
1055 42561 : int nUsefulColsFound = 0;
1056 1369300 : while ((ch = *pszPtr) != '\0')
1057 : {
1058 1326730 : if (ch == ' ')
1059 : {
1060 4384 : if (!bLastWasSep)
1061 4264 : nCol++;
1062 4384 : bLastWasSep = true;
1063 : }
1064 1322350 : else if ((ch == ',' && chLocalDecimalSep != ',') || ch == '\t' ||
1065 : ch == ';')
1066 : {
1067 80828 : nCol++;
1068 80828 : bLastWasSep = true;
1069 : }
1070 : else
1071 : {
1072 1241520 : if (bLastWasSep)
1073 : {
1074 127638 : if (nCol == nXIndex)
1075 : {
1076 42546 : nUsefulColsFound++;
1077 42546 : dfX = CPLAtofDelim(pszPtr, chLocalDecimalSep);
1078 42546 : if (std::isnan(dfX))
1079 : {
1080 0 : CPLError(CE_Failure, CPLE_AppDefined,
1081 : "At line " CPL_FRMT_GIB
1082 : ", NaN value found",
1083 : nLineNum);
1084 0 : VSIFCloseL(fp);
1085 0 : return nullptr;
1086 : }
1087 : }
1088 85092 : else if (nCol == nYIndex)
1089 : {
1090 42546 : nUsefulColsFound++;
1091 42546 : dfY = CPLAtofDelim(pszPtr, chLocalDecimalSep);
1092 42546 : if (std::isnan(dfY))
1093 : {
1094 0 : CPLError(CE_Failure, CPLE_AppDefined,
1095 : "At line " CPL_FRMT_GIB
1096 : ", NaN value found",
1097 : nLineNum);
1098 0 : VSIFCloseL(fp);
1099 0 : return nullptr;
1100 : }
1101 : }
1102 42546 : else if (nCol == nZIndex)
1103 : {
1104 42546 : nUsefulColsFound++;
1105 42546 : dfZ = CPLAtofDelim(pszPtr, chLocalDecimalSep);
1106 42546 : if (nDataLineNum == 0)
1107 : {
1108 28 : dfMinZ = dfZ;
1109 28 : dfMaxZ = dfZ;
1110 : }
1111 42518 : else if (dfZ < dfMinZ)
1112 : {
1113 28 : dfMinZ = dfZ;
1114 : }
1115 42490 : else if (dfZ > dfMaxZ)
1116 : {
1117 324 : dfMaxZ = dfZ;
1118 : }
1119 :
1120 42546 : if (!(dfZ >= INT_MIN && dfZ <= INT_MAX))
1121 : {
1122 0 : eDT = GDT_Float32;
1123 : }
1124 : else
1125 : {
1126 42546 : int nZ = static_cast<int>(dfZ);
1127 42546 : if (static_cast<double>(nZ) != dfZ)
1128 : {
1129 28327 : eDT = GDT_Float32;
1130 : }
1131 14219 : else if (
1132 7839 : (eDT == GDT_UInt8 || eDT == GDT_Int16)
1133 : // cppcheck-suppress knownConditionTrueFalse
1134 6389 : && (nZ < 0 || nZ > 255))
1135 : {
1136 10 : if (nZ < -32768 || nZ > 32767)
1137 0 : eDT = GDT_Int32;
1138 : else
1139 10 : eDT = GDT_Int16;
1140 : }
1141 : }
1142 : }
1143 : }
1144 1241520 : bLastWasSep = false;
1145 : }
1146 1326730 : pszPtr++;
1147 : }
1148 : /* skip empty lines */
1149 42561 : if (bLastWasSep && nCol == 0)
1150 : {
1151 15 : continue;
1152 : }
1153 42546 : nDataLineNum++;
1154 42546 : nCol++;
1155 42546 : if (nCol < nMinTokens)
1156 : {
1157 0 : CPLError(CE_Failure, CPLE_AppDefined,
1158 : "At line " CPL_FRMT_GIB
1159 : ", found %d tokens. Expected %d at least",
1160 : nLineNum, nCol, nMinTokens);
1161 0 : VSIFCloseL(fp);
1162 0 : return nullptr;
1163 : }
1164 42546 : if (nUsefulColsFound != 3)
1165 : {
1166 0 : CPLError(CE_Failure, CPLE_AppDefined,
1167 : "At line " CPL_FRMT_GIB
1168 : ", did not find X, Y and/or Z values",
1169 : nLineNum);
1170 0 : VSIFCloseL(fp);
1171 0 : return nullptr;
1172 : }
1173 :
1174 42546 : if (nDataLineNum == 1)
1175 : {
1176 28 : dfMinX = dfX;
1177 28 : dfMaxX = dfX;
1178 28 : dfMinY = dfY;
1179 28 : dfMaxY = dfY;
1180 : }
1181 42518 : else if (nDataLineNum == 2 && dfX == dfLastX)
1182 : {
1183 : // Detect datasets organized by columns
1184 7 : if (dfY == dfLastY)
1185 : {
1186 0 : CPLError(CE_Failure, CPLE_AppDefined,
1187 : "Ungridded dataset: At line " CPL_FRMT_GIB
1188 : ", Failed to detect grid layout.",
1189 : nLineNum);
1190 0 : VSIFCloseL(fp);
1191 0 : return nullptr;
1192 : }
1193 :
1194 7 : bColOrganization = true;
1195 7 : const double dfStepY = dfY - dfLastY;
1196 7 : adfStepY.push_back(fabs(dfStepY));
1197 7 : nStepYSign = dfStepY > 0 ? 1 : -1;
1198 : }
1199 42511 : else if (bColOrganization)
1200 : {
1201 29 : const double dfStepX = dfX - dfLastX;
1202 29 : if (dfStepX == 0)
1203 : {
1204 19 : const double dfStepY = dfY - dfLastY;
1205 19 : const double dfExpectedStepY = adfStepY.back() * nStepYSign;
1206 19 : if (fabs((dfStepY - dfExpectedStepY) / dfExpectedStepY) >
1207 : RELATIVE_ERROR)
1208 : {
1209 0 : CPLError(CE_Failure, CPLE_AppDefined,
1210 : "Ungridded dataset: At line " CPL_FRMT_GIB
1211 : ", Y spacing was %f. Expected %f",
1212 : nLineNum, dfStepY, dfExpectedStepY);
1213 0 : VSIFCloseL(fp);
1214 0 : return nullptr;
1215 : }
1216 : }
1217 10 : else if (dfStepX > 0)
1218 : {
1219 8 : if (adfStepX.empty())
1220 : {
1221 5 : adfStepX.push_back(dfStepX);
1222 : }
1223 3 : else if (fabs((dfStepX - adfStepX.back()) / adfStepX.back()) >
1224 : RELATIVE_ERROR)
1225 : {
1226 0 : CPLError(CE_Failure, CPLE_AppDefined,
1227 : "Ungridded dataset: At line " CPL_FRMT_GIB
1228 : ", X spacing was %f. Expected %f",
1229 0 : nLineNum, dfStepX, adfStepX.back());
1230 0 : VSIFCloseL(fp);
1231 0 : return nullptr;
1232 : }
1233 : }
1234 2 : else if (nDataLineNum == 3)
1235 : {
1236 1 : const double dfStepY = dfY - dfLastY;
1237 1 : const double dfLastSignedStepY = nStepYSign * adfStepY.back();
1238 1 : if (dfStepY * dfLastSignedStepY > 0 &&
1239 1 : (fabs(dfStepY - dfLastSignedStepY) <=
1240 1 : RELATIVE_ERROR * fabs(dfLastSignedStepY)
1241 : #ifdef multiple_of_step_y_not_yet_supported
1242 : ||
1243 : (fabs(dfStepY) > fabs(dfLastSignedStepY) &&
1244 : fabs(std::round(dfStepY / dfLastSignedStepY) -
1245 : (dfStepY / dfLastSignedStepY)) <= RELATIVE_ERROR) ||
1246 : (fabs(dfLastSignedStepY) > fabs(dfStepY) &&
1247 : fabs(std::round(dfLastSignedStepY / dfStepY) -
1248 : (dfLastSignedStepY / dfStepY)) <= RELATIVE_ERROR)
1249 : #endif
1250 : ))
1251 : {
1252 : // Assume it is a file starting with something like:
1253 : // 371999.50 5806917.50 41.21
1254 : // 371999.50 5806918.50 51.99
1255 : // 371998.50 5806919.50 53.50
1256 : // 371999.50 5806919.50 53.68
1257 1 : adfStepX.push_back(dfLastX - dfX);
1258 1 : bColOrganization = false;
1259 : }
1260 : else
1261 : {
1262 0 : CPLError(CE_Failure, CPLE_AppDefined,
1263 : "Ungridded dataset: At line " CPL_FRMT_GIB
1264 : ", X spacing was %f. Expected >0 value",
1265 : nLineNum, dfX - dfLastX);
1266 0 : VSIFCloseL(fp);
1267 0 : return nullptr;
1268 : }
1269 : }
1270 1 : else if (!adfStepX.empty() &&
1271 0 : fabs(std::round(-dfStepX / adfStepX[0]) -
1272 0 : (-dfStepX / adfStepX[0])) <= RELATIVE_ERROR)
1273 : {
1274 0 : bColOrganization = false;
1275 : }
1276 1 : else if (adfStepX.empty())
1277 : {
1278 1 : adfStepX.push_back(fabs(dfStepX));
1279 1 : bColOrganization = false;
1280 : }
1281 : else
1282 : {
1283 0 : CPLError(CE_Failure, CPLE_AppDefined,
1284 : "Ungridded dataset: At line " CPL_FRMT_GIB
1285 : ", X spacing was %f. Expected a multiple of %f",
1286 0 : nLineNum, dfStepX, adfStepX[0]);
1287 0 : VSIFCloseL(fp);
1288 0 : return nullptr;
1289 : }
1290 : }
1291 : else
1292 : {
1293 42482 : double dfStepY = dfY - dfLastY;
1294 42482 : if (dfStepY == 0.0)
1295 : {
1296 42158 : const double dfStepX = dfX - dfLastX;
1297 42158 : if (dfStepX <= 0)
1298 : {
1299 0 : CPLError(CE_Failure, CPLE_AppDefined,
1300 : "Ungridded dataset: At line " CPL_FRMT_GIB
1301 : ", X spacing was %f. Expected >0 value",
1302 : nLineNum, dfStepX);
1303 0 : VSIFCloseL(fp);
1304 0 : return nullptr;
1305 : }
1306 42158 : if (std::find(adfStepX.begin(), adfStepX.end(), dfStepX) ==
1307 84316 : adfStepX.end())
1308 : {
1309 42 : bool bAddNewValue = true;
1310 42 : std::vector<double>::iterator oIter = adfStepX.begin();
1311 42 : std::vector<double> adfStepXNew;
1312 43 : while (oIter != adfStepX.end())
1313 : {
1314 22 : if (fabs((dfStepX - *oIter) / dfStepX) < RELATIVE_ERROR)
1315 : {
1316 20 : double dfNewVal = *oIter;
1317 20 : if (nCountStepX > 0)
1318 : {
1319 : // Update mean step
1320 : /* n * mean(n) = (n-1) * mean(n-1) + val(n)
1321 : mean(n) = mean(n-1) + (val(n) - mean(n-1)) / n
1322 : */
1323 20 : nCountStepX++;
1324 20 : dfNewVal += (dfStepX - *oIter) / nCountStepX;
1325 : }
1326 :
1327 20 : adfStepXNew.push_back(dfNewVal);
1328 20 : bAddNewValue = false;
1329 20 : break;
1330 : }
1331 3 : else if (dfStepX < *oIter &&
1332 1 : fabs(*oIter -
1333 1 : static_cast<int>(*oIter / dfStepX + 0.5) *
1334 1 : dfStepX) /
1335 1 : dfStepX <
1336 : RELATIVE_ERROR)
1337 : {
1338 1 : nCountStepX = -1; // disable update of mean
1339 1 : ++oIter;
1340 : }
1341 2 : else if (dfStepX > *oIter &&
1342 2 : fabs(dfStepX -
1343 1 : static_cast<int>(dfStepX / *oIter + 0.5) *
1344 1 : (*oIter)) /
1345 1 : dfStepX <
1346 : RELATIVE_ERROR)
1347 : {
1348 1 : nCountStepX = -1; // disable update of mean
1349 1 : bAddNewValue = false;
1350 1 : adfStepXNew.push_back(*oIter);
1351 1 : break;
1352 : }
1353 : else
1354 : {
1355 0 : adfStepXNew.push_back(*oIter);
1356 0 : ++oIter;
1357 : }
1358 : }
1359 42 : adfStepX = std::move(adfStepXNew);
1360 42 : if (bAddNewValue)
1361 : {
1362 21 : CPLDebug("XYZ", "New stepX=%.15f", dfStepX);
1363 21 : adfStepX.push_back(dfStepX);
1364 21 : if (adfStepX.size() == 1 && nCountStepX == 0)
1365 : {
1366 20 : nCountStepX++;
1367 : }
1368 1 : else if (adfStepX.size() == 2)
1369 : {
1370 0 : nCountStepX = -1; // disable update of mean
1371 : }
1372 1 : else if (adfStepX.size() == 10)
1373 : {
1374 0 : CPLError(
1375 : CE_Failure, CPLE_AppDefined,
1376 : "Ungridded dataset: too many stepX values");
1377 0 : VSIFCloseL(fp);
1378 0 : return nullptr;
1379 : }
1380 : }
1381 : }
1382 : }
1383 : else
1384 : {
1385 324 : int bNewStepYSign = (dfStepY < 0.0) ? -1 : 1;
1386 324 : if (nStepYSign == 0)
1387 20 : nStepYSign = bNewStepYSign;
1388 304 : else if (nStepYSign != bNewStepYSign)
1389 : {
1390 0 : CPLError(CE_Failure, CPLE_AppDefined,
1391 : "Ungridded dataset: At line " CPL_FRMT_GIB
1392 : ", change of Y direction",
1393 : nLineNum);
1394 0 : VSIFCloseL(fp);
1395 0 : return nullptr;
1396 : }
1397 324 : if (bNewStepYSign < 0)
1398 301 : dfStepY = -dfStepY;
1399 324 : nCountStepY++;
1400 324 : if (adfStepY.empty())
1401 : {
1402 20 : adfStepY.push_back(dfStepY);
1403 : }
1404 304 : else if (fabs((adfStepY[0] - dfStepY) / dfStepY) >
1405 : RELATIVE_ERROR)
1406 : {
1407 4 : if (dfStepY > adfStepY[0] &&
1408 2 : fabs(std::round(dfStepY / adfStepY[0]) -
1409 2 : (dfStepY / adfStepY[0])) <= RELATIVE_ERROR)
1410 : {
1411 : // The new step is a multiple of the previous one,
1412 : // which means we have a missing line: OK
1413 : }
1414 : else
1415 : {
1416 0 : CPLDebug("XYZ", "New stepY=%.15f prev stepY=%.15f",
1417 0 : dfStepY, adfStepY[0]);
1418 0 : CPLError(CE_Failure, CPLE_AppDefined,
1419 : "Ungridded dataset: At line " CPL_FRMT_GIB
1420 : ", too many stepY values",
1421 : nLineNum);
1422 0 : VSIFCloseL(fp);
1423 0 : return nullptr;
1424 : }
1425 : }
1426 : else
1427 : {
1428 : // Update mean step
1429 302 : adfStepY[0] += (dfStepY - adfStepY[0]) / nCountStepY;
1430 : }
1431 : }
1432 : }
1433 :
1434 42546 : if (dfX < dfMinX)
1435 4 : dfMinX = dfX;
1436 42546 : if (dfX > dfMaxX)
1437 323 : dfMaxX = dfX;
1438 42546 : if (dfY < dfMinY)
1439 305 : dfMinY = dfY;
1440 42546 : if (dfY > dfMaxY)
1441 33 : dfMaxY = dfY;
1442 :
1443 42546 : dfLastX = dfX;
1444 42546 : dfLastY = dfY;
1445 : }
1446 :
1447 28 : if (adfStepX.size() != 1 || adfStepX[0] == 0)
1448 : {
1449 2 : if (!((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
1450 1 : poOpenInfo->IsExtensionEqualToCI("CSV")))
1451 : {
1452 0 : CPLError(CE_Failure, CPLE_AppDefined,
1453 : "Couldn't determine X spacing");
1454 : }
1455 1 : VSIFCloseL(fp);
1456 1 : return nullptr;
1457 : }
1458 :
1459 27 : if (adfStepY.size() != 1 || adfStepY[0] == 0)
1460 : {
1461 0 : CPLError(CE_Failure, CPLE_AppDefined, "Couldn't determine Y spacing");
1462 0 : VSIFCloseL(fp);
1463 0 : return nullptr;
1464 : }
1465 :
1466 : // Decide for a north-up organization
1467 27 : if (bColOrganization)
1468 5 : nStepYSign = -1;
1469 :
1470 27 : const double dfXSize = 1 + ((dfMaxX - dfMinX) / adfStepX[0] + 0.5);
1471 27 : const double dfYSize = 1 + ((dfMaxY - dfMinY) / adfStepY[0] + 0.5);
1472 : // Test written such as to detect NaN values
1473 27 : if (!(dfXSize > 0 && dfXSize < INT_MAX) ||
1474 27 : !(dfYSize > 0 && dfYSize < INT_MAX))
1475 : {
1476 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid dimensions");
1477 0 : VSIFCloseL(fp);
1478 0 : return nullptr;
1479 : }
1480 27 : const int nXSize = static_cast<int>(dfXSize);
1481 27 : const int nYSize = static_cast<int>(dfYSize);
1482 27 : const double dfStepX = (dfMaxX - dfMinX) / (nXSize - 1);
1483 27 : const double dfStepY = (dfMaxY - dfMinY) / (nYSize - 1) * nStepYSign;
1484 :
1485 : #ifdef DEBUG_VERBOSE
1486 : CPLDebug("XYZ", "minx=%f maxx=%f stepx=%f", dfMinX, dfMaxX, dfStepX);
1487 : CPLDebug("XYZ", "miny=%f maxy=%f stepy=%f", dfMinY, dfMaxY, dfStepY);
1488 : #endif
1489 :
1490 27 : if (nDataLineNum != static_cast<GIntBig>(nXSize) * nYSize)
1491 : {
1492 5 : if (bColOrganization)
1493 : {
1494 0 : CPLError(CE_Failure, CPLE_NotSupported,
1495 : "The XYZ driver does not support datasets organized by "
1496 : "columns with missing values");
1497 0 : VSIFCloseL(fp);
1498 0 : return nullptr;
1499 : }
1500 5 : bSameNumberOfValuesPerLine = false;
1501 : }
1502 22 : else if (bColOrganization && nDataLineNum > 100 * 1000 * 1000)
1503 : {
1504 0 : CPLError(CE_Failure, CPLE_NotSupported,
1505 : "The XYZ driver cannot load datasets organized by "
1506 : "columns with more than 100 million points");
1507 0 : VSIFCloseL(fp);
1508 0 : return nullptr;
1509 : }
1510 :
1511 27 : const bool bIngestAll = bColOrganization;
1512 27 : if (bIngestAll)
1513 : {
1514 5 : if (eDT == GDT_Int32)
1515 0 : eDT = GDT_Float32;
1516 5 : else if (eDT == GDT_UInt8)
1517 1 : eDT = GDT_Int16;
1518 5 : CPLAssert(eDT == GDT_Int16 || eDT == GDT_Float32);
1519 : }
1520 :
1521 27 : if (poOpenInfo->eAccess == GA_Update)
1522 : {
1523 0 : ReportUpdateNotSupportedByDriver("XYZ");
1524 0 : VSIFCloseL(fp);
1525 0 : return nullptr;
1526 : }
1527 :
1528 : /* -------------------------------------------------------------------- */
1529 : /* Create a corresponding GDALDataset. */
1530 : /* -------------------------------------------------------------------- */
1531 27 : XYZDataset *poDS = new XYZDataset();
1532 27 : poDS->fp = fp;
1533 27 : poDS->bHasHeaderLine = bHasHeaderLine;
1534 27 : poDS->nCommentLineCount = nCommentLineCount;
1535 27 : poDS->chDecimalSep = chDecimalSep ? chDecimalSep : '.';
1536 27 : poDS->nXIndex = nXIndex;
1537 27 : poDS->nYIndex = nYIndex;
1538 27 : poDS->nZIndex = nZIndex;
1539 27 : poDS->nMinTokens = nMinTokens;
1540 27 : poDS->nRasterXSize = nXSize;
1541 27 : poDS->nRasterYSize = nYSize;
1542 27 : poDS->m_gt.xorig = dfMinX - dfStepX / 2;
1543 27 : poDS->m_gt.xscale = dfStepX;
1544 27 : poDS->m_gt.yorig =
1545 27 : (dfStepY < 0) ? dfMaxY - dfStepY / 2 : dfMinY - dfStepY / 2;
1546 27 : poDS->m_gt.yscale = dfStepY;
1547 27 : poDS->bSameNumberOfValuesPerLine = bSameNumberOfValuesPerLine;
1548 27 : poDS->dfMinZ = dfMinZ;
1549 27 : poDS->dfMaxZ = dfMaxZ;
1550 27 : poDS->bIngestAll = bIngestAll;
1551 : #ifdef DEBUG_VERBOSE
1552 : CPLDebug("XYZ", "bSameNumberOfValuesPerLine = %d",
1553 : bSameNumberOfValuesPerLine);
1554 : #endif
1555 :
1556 27 : if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize))
1557 : {
1558 0 : delete poDS;
1559 0 : return nullptr;
1560 : }
1561 :
1562 : /* -------------------------------------------------------------------- */
1563 : /* Create band information objects. */
1564 : /* -------------------------------------------------------------------- */
1565 27 : poDS->nBands = 1;
1566 54 : for (int i = 0; i < poDS->nBands; i++)
1567 27 : poDS->SetBand(i + 1, new XYZRasterBand(poDS, i + 1, eDT));
1568 :
1569 : /* -------------------------------------------------------------------- */
1570 : /* Initialize any PAM information. */
1571 : /* -------------------------------------------------------------------- */
1572 27 : poDS->SetDescription(poOpenInfo->pszFilename);
1573 27 : poDS->TryLoadXML();
1574 :
1575 : /* -------------------------------------------------------------------- */
1576 : /* Support overviews. */
1577 : /* -------------------------------------------------------------------- */
1578 27 : poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename);
1579 27 : return poDS;
1580 : }
1581 :
1582 : /************************************************************************/
1583 : /* CreateCopy() */
1584 : /************************************************************************/
1585 :
1586 33 : GDALDataset *XYZDataset::CreateCopy(const char *pszFilename,
1587 : GDALDataset *poSrcDS, int bStrict,
1588 : CSLConstList papszOptions,
1589 : GDALProgressFunc pfnProgress,
1590 : void *pProgressData)
1591 : {
1592 : /* -------------------------------------------------------------------- */
1593 : /* Some some rudimentary checks */
1594 : /* -------------------------------------------------------------------- */
1595 33 : int nBands = poSrcDS->GetRasterCount();
1596 33 : if (nBands == 0)
1597 : {
1598 1 : CPLError(
1599 : CE_Failure, CPLE_NotSupported,
1600 : "XYZ driver does not support source dataset with zero band.\n");
1601 1 : return nullptr;
1602 : }
1603 :
1604 32 : if (nBands != 1)
1605 : {
1606 4 : CPLError((bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
1607 : "XYZ driver only uses the first band of the dataset.\n");
1608 4 : if (bStrict)
1609 4 : return nullptr;
1610 : }
1611 :
1612 28 : if (pfnProgress && !pfnProgress(0.0, nullptr, pProgressData))
1613 0 : return nullptr;
1614 :
1615 : /* -------------------------------------------------------------------- */
1616 : /* Get source dataset info */
1617 : /* -------------------------------------------------------------------- */
1618 :
1619 28 : int nXSize = poSrcDS->GetRasterXSize();
1620 28 : int nYSize = poSrcDS->GetRasterYSize();
1621 28 : GDALGeoTransform gt;
1622 28 : poSrcDS->GetGeoTransform(gt);
1623 28 : if (gt.xrot != 0 || gt.yrot != 0)
1624 : {
1625 0 : CPLError(CE_Failure, CPLE_NotSupported,
1626 : "XYZ driver does not support CreateCopy() from skewed or "
1627 : "rotated dataset.\n");
1628 0 : return nullptr;
1629 : }
1630 :
1631 28 : const GDALDataType eSrcDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
1632 : GDALDataType eReqDT;
1633 28 : if (eSrcDT == GDT_UInt8 || eSrcDT == GDT_Int16 || eSrcDT == GDT_UInt16 ||
1634 : eSrcDT == GDT_Int32)
1635 20 : eReqDT = GDT_Int32;
1636 : else
1637 8 : eReqDT = GDT_Float32;
1638 :
1639 : /* -------------------------------------------------------------------- */
1640 : /* Create target file */
1641 : /* -------------------------------------------------------------------- */
1642 :
1643 28 : VSILFILE *fp = VSIFOpenL(pszFilename, "wb");
1644 28 : if (fp == nullptr)
1645 : {
1646 3 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot create %s", pszFilename);
1647 3 : return nullptr;
1648 : }
1649 :
1650 : /* -------------------------------------------------------------------- */
1651 : /* Read creation options */
1652 : /* -------------------------------------------------------------------- */
1653 25 : const char *pszColSep = CSLFetchNameValue(papszOptions, "COLUMN_SEPARATOR");
1654 25 : if (pszColSep == nullptr)
1655 24 : pszColSep = " ";
1656 1 : else if (EQUAL(pszColSep, "COMMA"))
1657 0 : pszColSep = ",";
1658 1 : else if (EQUAL(pszColSep, "SPACE"))
1659 0 : pszColSep = " ";
1660 1 : else if (EQUAL(pszColSep, "SEMICOLON"))
1661 0 : pszColSep = ";";
1662 1 : else if (EQUAL(pszColSep, "\\t") || EQUAL(pszColSep, "TAB"))
1663 0 : pszColSep = "\t";
1664 : #ifdef DEBUG_VERBOSE
1665 : else
1666 : CPLDebug("XYZ", "Using raw column separator: '%s' ", pszColSep);
1667 : #endif
1668 :
1669 : const char *pszAddHeaderLine =
1670 25 : CSLFetchNameValue(papszOptions, "ADD_HEADER_LINE");
1671 25 : if (pszAddHeaderLine != nullptr && CPLTestBool(pszAddHeaderLine))
1672 : {
1673 1 : VSIFPrintfL(fp, "X%sY%sZ\n", pszColSep, pszColSep);
1674 : }
1675 :
1676 : /* -------------------------------------------------------------------- */
1677 : /* Copy imagery */
1678 : /* -------------------------------------------------------------------- */
1679 25 : char szFormat[50] = {'\0'};
1680 25 : if (eReqDT == GDT_Int32)
1681 17 : strcpy(szFormat, "%.17g%c%.17g%c%d\n");
1682 : else
1683 8 : strcpy(szFormat, "%.17g%c%.17g%c%.17g\n");
1684 : const char *pszDecimalPrecision =
1685 25 : CSLFetchNameValue(papszOptions, "DECIMAL_PRECISION");
1686 : const char *pszSignificantDigits =
1687 25 : CSLFetchNameValue(papszOptions, "SIGNIFICANT_DIGITS");
1688 25 : bool bIgnoreSigDigits = false;
1689 25 : if (pszDecimalPrecision && pszSignificantDigits)
1690 : {
1691 0 : CPLError(CE_Warning, CPLE_AppDefined,
1692 : "Conflicting precision arguments, using DECIMAL_PRECISION");
1693 0 : bIgnoreSigDigits = true;
1694 : }
1695 : int nPrecision;
1696 25 : if (pszSignificantDigits && !bIgnoreSigDigits)
1697 : {
1698 0 : nPrecision = atoi(pszSignificantDigits);
1699 0 : if (nPrecision >= 0)
1700 : {
1701 0 : if (eReqDT == GDT_Int32)
1702 0 : snprintf(szFormat, sizeof(szFormat), "%%.%dg%%c%%.%dg%%c%%d\n",
1703 : nPrecision, nPrecision);
1704 : else
1705 0 : snprintf(szFormat, sizeof(szFormat),
1706 : "%%.%dg%%c%%.%dg%%c%%.%dg\n", nPrecision, nPrecision,
1707 : nPrecision);
1708 : }
1709 0 : CPLDebug("XYZ", "Setting precision format: %s", szFormat);
1710 : }
1711 25 : else if (pszDecimalPrecision)
1712 : {
1713 0 : nPrecision = atoi(pszDecimalPrecision);
1714 0 : if (nPrecision >= 0)
1715 : {
1716 0 : if (eReqDT == GDT_Int32)
1717 0 : snprintf(szFormat, sizeof(szFormat), "%%.%df%%c%%.%df%%c%%d\n",
1718 : nPrecision, nPrecision);
1719 : else
1720 0 : snprintf(szFormat, sizeof(szFormat),
1721 : "%%.%df%%c%%.%df%%c%%.%df\n", nPrecision, nPrecision,
1722 : nPrecision);
1723 : }
1724 0 : CPLDebug("XYZ", "Setting precision format: %s", szFormat);
1725 : }
1726 25 : void *pLineBuffer = static_cast<void *>(CPLMalloc(nXSize * sizeof(int)));
1727 25 : CPLErr eErr = CE_None;
1728 447 : for (int j = 0; j < nYSize && eErr == CE_None; j++)
1729 : {
1730 422 : eErr = poSrcDS->GetRasterBand(1)->RasterIO(GF_Read, 0, j, nXSize, 1,
1731 : pLineBuffer, nXSize, 1,
1732 : eReqDT, 0, 0, nullptr);
1733 422 : if (eErr != CE_None)
1734 0 : break;
1735 422 : const double dfY = gt.yorig + (j + 0.5) * gt.yscale;
1736 422 : CPLString osBuf;
1737 43587 : for (int i = 0; i < nXSize; i++)
1738 : {
1739 43175 : const double dfX = gt.xorig + (i + 0.5) * gt.xscale;
1740 : char szBuf[256];
1741 43175 : if (eReqDT == GDT_Int32)
1742 2074 : CPLsnprintf(szBuf, sizeof(szBuf), szFormat, dfX, pszColSep[0],
1743 2074 : dfY, pszColSep[0],
1744 2074 : reinterpret_cast<int *>(pLineBuffer)[i]);
1745 : else
1746 41101 : CPLsnprintf(szBuf, sizeof(szBuf), szFormat, dfX, pszColSep[0],
1747 41101 : dfY, pszColSep[0],
1748 41101 : reinterpret_cast<float *>(pLineBuffer)[i]);
1749 43175 : osBuf += szBuf;
1750 43175 : if ((i & 1023) == 0 || i == nXSize - 1)
1751 : {
1752 840 : if (VSIFWriteL(osBuf.c_str(), osBuf.size(), 1, fp) != 1)
1753 : {
1754 10 : eErr = CE_Failure;
1755 10 : CPLError(CE_Failure, CPLE_AppDefined,
1756 : "Write failed, disk full?\n");
1757 10 : break;
1758 : }
1759 830 : osBuf = "";
1760 : }
1761 : }
1762 844 : if (pfnProgress &&
1763 422 : !pfnProgress((j + 1) * 1.0 / nYSize, nullptr, pProgressData))
1764 : {
1765 0 : eErr = CE_Failure;
1766 0 : break;
1767 : }
1768 : }
1769 25 : CPLFree(pLineBuffer);
1770 25 : VSIFCloseL(fp);
1771 :
1772 25 : if (eErr != CE_None)
1773 10 : return nullptr;
1774 :
1775 : /* -------------------------------------------------------------------- */
1776 : /* We don't want to call GDALOpen() since it will be expensive, */
1777 : /* so we "hand prepare" an XYZ dataset referencing our file. */
1778 : /* -------------------------------------------------------------------- */
1779 15 : XYZDataset *poXYZ_DS = new XYZDataset();
1780 15 : poXYZ_DS->nRasterXSize = nXSize;
1781 15 : poXYZ_DS->nRasterYSize = nYSize;
1782 15 : poXYZ_DS->nBands = 1;
1783 15 : poXYZ_DS->SetBand(1, new XYZRasterBand(poXYZ_DS, 1, eReqDT));
1784 : /* If writing to stdout, we can't reopen it --> silence warning */
1785 15 : CPLPushErrorHandler(CPLQuietErrorHandler);
1786 15 : poXYZ_DS->fp = VSIFOpenL(pszFilename, "rb");
1787 15 : CPLPopErrorHandler();
1788 15 : poXYZ_DS->m_gt = gt;
1789 15 : poXYZ_DS->nXIndex = 0;
1790 15 : poXYZ_DS->nYIndex = 1;
1791 15 : poXYZ_DS->nZIndex = 2;
1792 15 : if (pszAddHeaderLine)
1793 : {
1794 1 : poXYZ_DS->nDataLineNum = 1;
1795 1 : poXYZ_DS->bHasHeaderLine = TRUE;
1796 : }
1797 :
1798 15 : return poXYZ_DS;
1799 : }
1800 :
1801 : /************************************************************************/
1802 : /* GetGeoTransform() */
1803 : /************************************************************************/
1804 :
1805 19 : CPLErr XYZDataset::GetGeoTransform(GDALGeoTransform >) const
1806 :
1807 : {
1808 19 : gt = m_gt;
1809 :
1810 19 : return CE_None;
1811 : }
1812 :
1813 : /************************************************************************/
1814 : /* GDALRegister_XYZ() */
1815 : /************************************************************************/
1816 :
1817 2063 : void GDALRegister_XYZ()
1818 :
1819 : {
1820 2063 : if (GDALGetDriverByName("XYZ") != nullptr)
1821 283 : return;
1822 :
1823 1780 : GDALDriver *poDriver = new GDALDriver();
1824 :
1825 1780 : poDriver->SetDescription("XYZ");
1826 1780 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1827 1780 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "ASCII Gridded XYZ");
1828 1780 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/xyz.html");
1829 1780 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "xyz");
1830 1780 : poDriver->SetMetadataItem(
1831 : GDAL_DMD_CREATIONOPTIONLIST,
1832 : "<CreationOptionList>"
1833 : " <Option name='COLUMN_SEPARATOR' type='string' default=' ' "
1834 : "description='Separator between fields.'/>"
1835 : " <Option name='ADD_HEADER_LINE' type='boolean' default='false' "
1836 : "description='Add an header line with column names.'/>"
1837 : " <Option name='SIGNIFICANT_DIGITS' type='int' description='Number "
1838 : "of significant digits when writing floating-point numbers (%g format; "
1839 : "default with 18).'/>\n"
1840 : " <Option name='DECIMAL_PRECISION' type='int' description='Number of "
1841 : "decimal places when writing floating-point numbers (%f format).'/>\n"
1842 1780 : "</CreationOptionList>");
1843 1780 : poDriver->SetMetadataItem(
1844 : GDAL_DMD_OPENOPTIONLIST,
1845 : "<OpenOptionList>"
1846 : " <Option name='COLUMN_ORDER' type='string-select' default='AUTO' "
1847 : "description='Specifies the order of the columns. It overrides the "
1848 : "header.'>"
1849 : " <Value>AUTO</Value>"
1850 : " <Value>XYZ</Value>"
1851 : " <Value>YXZ</Value>"
1852 : " </Option>"
1853 1780 : "</OpenOptionList>");
1854 :
1855 1780 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1856 :
1857 1780 : poDriver->pfnOpen = XYZDataset::Open;
1858 1780 : poDriver->pfnIdentify = XYZDataset::Identify;
1859 1780 : poDriver->pfnCreateCopy = XYZDataset::CreateCopy;
1860 :
1861 1780 : GetGDALDriverManager()->RegisterDriver(poDriver);
1862 : }
|