Line data Source code
1 : /******************************************************************************
2 : * Project: SAGA GIS Binary Driver
3 : * Purpose: Implements the SAGA GIS Binary Grid Format.
4 : * Author: Volker Wichmann, wichmann@laserdata.at
5 : * (Based on gsbgdataset.cpp by Kevin Locke and Frank Warmerdam)
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2009, Volker Wichmann <wichmann@laserdata.at>
9 : * Copyright (c) 2009-2011, Even Rouault <even dot rouault at spatialys.com>
10 : *
11 : * SPDX-License-Identifier: MIT
12 : ****************************************************************************/
13 :
14 : #include "cpl_conv.h"
15 :
16 : #include <cassert>
17 : #include <cfloat>
18 : #include <climits>
19 :
20 : #include "gdal_frmts.h"
21 : #include "gdal_pam.h"
22 : #include "ogr_spatialref.h"
23 :
24 : #ifndef INT_MAX
25 : #define INT_MAX 2147483647
26 : #endif /* INT_MAX */
27 :
28 : /* NODATA Values */
29 : // #define SG_NODATA_GDT_Bit 0.0
30 : constexpr GByte SG_NODATA_GDT_Byte = 255;
31 : #define SG_NODATA_GDT_UInt16 65535
32 : #define SG_NODATA_GDT_Int16 -32767
33 : #define SG_NODATA_GDT_UInt32 4294967295U
34 : #define SG_NODATA_GDT_Int32 -2147483647
35 : #define SG_NODATA_GDT_Float32 -99999.0
36 : #define SG_NODATA_GDT_Float64 -99999.0
37 :
38 : /************************************************************************/
39 : /* ==================================================================== */
40 : /* SAGADataset */
41 : /* ==================================================================== */
42 : /************************************************************************/
43 :
44 : class SAGARasterBand;
45 :
46 : class SAGADataset final : public GDALPamDataset
47 : {
48 : friend class SAGARasterBand;
49 :
50 : static CPLErr WriteHeader(CPLString osHDRFilename, GDALDataType eType,
51 : int nXSize, int nYSize, double dfMinX,
52 : double dfMinY, double dfCellsize, double dfNoData,
53 : double dfZFactor, bool bTopToBottom);
54 : VSILFILE *fp;
55 : OGRSpatialReference m_oSRS{};
56 : bool headerDirty = false;
57 :
58 : public:
59 : SAGADataset();
60 : virtual ~SAGADataset();
61 :
62 : static GDALDataset *Open(GDALOpenInfo *);
63 : static GDALDataset *Create(const char *pszFilename, int nXSize, int nYSize,
64 : int nBandsIn, GDALDataType eType,
65 : char **papszParamList);
66 : static GDALDataset *CreateCopy(const char *pszFilename,
67 : GDALDataset *poSrcDS, int bStrict,
68 : char **papszOptions,
69 : GDALProgressFunc pfnProgress,
70 : void *pProgressData);
71 :
72 : const OGRSpatialReference *GetSpatialRef() const override;
73 : CPLErr SetSpatialRef(const OGRSpatialReference *poSRS) override;
74 :
75 : virtual char **GetFileList() override;
76 :
77 : CPLErr GetGeoTransform(double *padfGeoTransform) override;
78 : CPLErr SetGeoTransform(double *padfGeoTransform) override;
79 : };
80 :
81 : /************************************************************************/
82 : /* ==================================================================== */
83 : /* SAGARasterBand */
84 : /* ==================================================================== */
85 : /************************************************************************/
86 :
87 : class SAGARasterBand final : public GDALPamRasterBand
88 : {
89 : friend class SAGADataset;
90 :
91 : double m_Xmin;
92 : double m_Ymin;
93 : double m_Cellsize;
94 : double m_NoData;
95 : int m_ByteOrder;
96 : int m_nBits;
97 :
98 : void SetDataType(GDALDataType eType);
99 : void SwapBuffer(void *pImage) const;
100 :
101 : public:
102 : SAGARasterBand(SAGADataset *, int);
103 :
104 : CPLErr IReadBlock(int, int, void *) override;
105 : CPLErr IWriteBlock(int, int, void *) override;
106 :
107 : double GetNoDataValue(int *pbSuccess = nullptr) override;
108 : CPLErr SetNoDataValue(double dfNoData) override;
109 : };
110 :
111 : /************************************************************************/
112 : /* SAGARasterBand() */
113 : /************************************************************************/
114 :
115 108 : SAGARasterBand::SAGARasterBand(SAGADataset *poDS_, int nBand_)
116 : : m_Xmin(0.0), m_Ymin(0.0), m_Cellsize(0.0), m_NoData(0.0), m_ByteOrder(0),
117 108 : m_nBits(0)
118 : {
119 108 : poDS = poDS_;
120 108 : nBand = nBand_;
121 :
122 108 : eDataType = GDT_Float32;
123 :
124 108 : nBlockXSize = poDS->GetRasterXSize();
125 108 : nBlockYSize = 1;
126 108 : }
127 :
128 : /************************************************************************/
129 : /* SetDataType() */
130 : /************************************************************************/
131 :
132 108 : void SAGARasterBand::SetDataType(GDALDataType eType)
133 :
134 : {
135 108 : eDataType = eType;
136 108 : return;
137 : }
138 :
139 : /************************************************************************/
140 : /* SwapBuffer() */
141 : /************************************************************************/
142 :
143 1127 : void SAGARasterBand::SwapBuffer(void *pImage) const
144 : {
145 :
146 : #ifdef CPL_LSB
147 1127 : const bool bSwap = (m_ByteOrder == 1);
148 : #else
149 : const bool bSwap = (m_ByteOrder == 0);
150 : #endif
151 :
152 1127 : if (bSwap)
153 : {
154 0 : if (m_nBits == 16)
155 : {
156 0 : short *pImage16 = reinterpret_cast<short *>(pImage);
157 0 : for (int iPixel = 0; iPixel < nBlockXSize; iPixel++)
158 : {
159 0 : CPL_SWAP16PTR(pImage16 + iPixel);
160 : }
161 : }
162 0 : else if (m_nBits == 32)
163 : {
164 0 : int *pImage32 = reinterpret_cast<int *>(pImage);
165 0 : for (int iPixel = 0; iPixel < nBlockXSize; iPixel++)
166 : {
167 0 : CPL_SWAP32PTR(pImage32 + iPixel);
168 : }
169 : }
170 0 : else if (m_nBits == 64)
171 : {
172 0 : double *pImage64 = reinterpret_cast<double *>(pImage);
173 0 : for (int iPixel = 0; iPixel < nBlockXSize; iPixel++)
174 : {
175 0 : CPL_SWAP64PTR(pImage64 + iPixel);
176 : }
177 : }
178 : }
179 1127 : }
180 :
181 : /************************************************************************/
182 : /* IReadBlock() */
183 : /************************************************************************/
184 :
185 367 : CPLErr SAGARasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage)
186 :
187 : {
188 367 : if (nBlockYOff < 0 || nBlockYOff > nRasterYSize - 1 || nBlockXOff != 0)
189 0 : return CE_Failure;
190 :
191 367 : SAGADataset *poGDS = static_cast<SAGADataset *>(poDS);
192 367 : vsi_l_offset offset = static_cast<vsi_l_offset>(m_nBits / 8) *
193 367 : nRasterXSize * (nRasterYSize - nBlockYOff - 1);
194 :
195 367 : if (VSIFSeekL(poGDS->fp, offset, SEEK_SET) != 0)
196 : {
197 0 : CPLError(CE_Failure, CPLE_FileIO,
198 : "Unable to seek to beginning of grid row.\n");
199 0 : return CE_Failure;
200 : }
201 367 : if (VSIFReadL(pImage, m_nBits / 8, nBlockXSize, poGDS->fp) !=
202 367 : static_cast<unsigned>(nBlockXSize))
203 : {
204 0 : CPLError(CE_Failure, CPLE_FileIO,
205 : "Unable to read block from grid file.\n");
206 0 : return CE_Failure;
207 : }
208 :
209 367 : SwapBuffer(pImage);
210 :
211 367 : return CE_None;
212 : }
213 :
214 : /************************************************************************/
215 : /* IWriteBlock() */
216 : /************************************************************************/
217 :
218 380 : CPLErr SAGARasterBand::IWriteBlock(int nBlockXOff, int nBlockYOff, void *pImage)
219 :
220 : {
221 380 : if (eAccess == GA_ReadOnly)
222 : {
223 0 : CPLError(CE_Failure, CPLE_NoWriteAccess,
224 : "Unable to write block, dataset opened read only.\n");
225 0 : return CE_Failure;
226 : }
227 :
228 380 : if (nBlockYOff < 0 || nBlockYOff > nRasterYSize - 1 || nBlockXOff != 0)
229 0 : return CE_Failure;
230 :
231 380 : const vsi_l_offset offset = static_cast<vsi_l_offset>(m_nBits / 8) *
232 380 : nRasterXSize * (nRasterYSize - nBlockYOff - 1);
233 380 : SAGADataset *poGDS = static_cast<SAGADataset *>(poDS);
234 380 : assert(poGDS != nullptr);
235 :
236 380 : if (VSIFSeekL(poGDS->fp, offset, SEEK_SET) != 0)
237 : {
238 0 : CPLError(CE_Failure, CPLE_FileIO,
239 : "Unable to seek to beginning of grid row.\n");
240 0 : return CE_Failure;
241 : }
242 :
243 380 : SwapBuffer(pImage);
244 :
245 : const bool bSuccess =
246 380 : (VSIFWriteL(pImage, m_nBits / 8, nBlockXSize, poGDS->fp) ==
247 380 : static_cast<unsigned>(nBlockXSize));
248 :
249 380 : SwapBuffer(pImage);
250 :
251 380 : if (!bSuccess)
252 : {
253 0 : CPLError(CE_Failure, CPLE_FileIO,
254 : "Unable to write block to grid file.\n");
255 0 : return CE_Failure;
256 : }
257 :
258 380 : return CE_None;
259 : }
260 :
261 : /************************************************************************/
262 : /* GetNoDataValue() */
263 : /************************************************************************/
264 :
265 72 : double SAGARasterBand::GetNoDataValue(int *pbSuccess)
266 : {
267 72 : if (pbSuccess)
268 59 : *pbSuccess = TRUE;
269 :
270 72 : return m_NoData;
271 : }
272 :
273 : /************************************************************************/
274 : /* SetNoDataValue() */
275 : /************************************************************************/
276 :
277 2 : CPLErr SAGARasterBand::SetNoDataValue(double dfNoData)
278 : {
279 2 : if (eAccess == GA_ReadOnly)
280 : {
281 1 : CPLError(CE_Failure, CPLE_NoWriteAccess,
282 : "Unable to set no data value, dataset opened read only.\n");
283 1 : return CE_Failure;
284 : }
285 :
286 1 : m_NoData = dfNoData;
287 1 : SAGADataset *poSAGADS = static_cast<SAGADataset *>(poDS);
288 1 : poSAGADS->headerDirty = true;
289 1 : return CE_None;
290 : }
291 :
292 : /************************************************************************/
293 : /* ==================================================================== */
294 : /* SAGADataset */
295 : /* ==================================================================== */
296 : /************************************************************************/
297 :
298 108 : SAGADataset::SAGADataset() : fp(nullptr)
299 : {
300 108 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
301 108 : }
302 :
303 216 : SAGADataset::~SAGADataset()
304 : {
305 108 : if (headerDirty)
306 : {
307 24 : SAGARasterBand *poGRB = static_cast<SAGARasterBand *>(GetRasterBand(1));
308 48 : const CPLString osPath = CPLGetPath(GetDescription());
309 48 : const CPLString osName = CPLGetBasename(GetDescription());
310 24 : const CPLString osFilename = CPLFormCIFilename(osPath, osName, ".sgrd");
311 24 : WriteHeader(osFilename, poGRB->GetRasterDataType(), poGRB->nRasterXSize,
312 : poGRB->nRasterYSize, poGRB->m_Xmin, poGRB->m_Ymin,
313 : poGRB->m_Cellsize, poGRB->m_NoData, 1.0, false);
314 : }
315 108 : FlushCache(true);
316 108 : if (fp != nullptr)
317 108 : VSIFCloseL(fp);
318 216 : }
319 :
320 : /************************************************************************/
321 : /* GetFileList() */
322 : /************************************************************************/
323 :
324 25 : char **SAGADataset::GetFileList()
325 : {
326 50 : const CPLString osPath = CPLGetPath(GetDescription());
327 25 : const CPLString osName = CPLGetBasename(GetDescription());
328 :
329 : // Main data file, etc.
330 25 : char **papszFileList = GDALPamDataset::GetFileList();
331 :
332 25 : if (!EQUAL(CPLGetExtension(GetDescription()), "sg-grd-z"))
333 : {
334 : // Header file.
335 48 : CPLString osFilename = CPLFormCIFilename(osPath, osName, ".sgrd");
336 24 : papszFileList = CSLAddString(papszFileList, osFilename);
337 :
338 : // projections file.
339 24 : osFilename = CPLFormCIFilename(osPath, osName, "prj");
340 : VSIStatBufL sStatBuf;
341 24 : if (VSIStatExL(osFilename, &sStatBuf, VSI_STAT_EXISTS_FLAG) == 0)
342 10 : papszFileList = CSLAddString(papszFileList, osFilename);
343 : }
344 :
345 50 : return papszFileList;
346 : }
347 :
348 : /************************************************************************/
349 : /* GetSpatialRef() */
350 : /************************************************************************/
351 :
352 6 : const OGRSpatialReference *SAGADataset::GetSpatialRef() const
353 :
354 : {
355 6 : if (!m_oSRS.IsEmpty())
356 6 : return &m_oSRS;
357 :
358 0 : return GDALPamDataset::GetSpatialRef();
359 : }
360 :
361 : /************************************************************************/
362 : /* SetSpatialRef() */
363 : /************************************************************************/
364 :
365 23 : CPLErr SAGADataset::SetSpatialRef(const OGRSpatialReference *poSRS)
366 :
367 : {
368 : /* -------------------------------------------------------------------- */
369 : /* Reset coordinate system on the dataset. */
370 : /* -------------------------------------------------------------------- */
371 23 : m_oSRS.Clear();
372 23 : if (poSRS == nullptr)
373 0 : return CE_None;
374 23 : m_oSRS = *poSRS;
375 :
376 : /* -------------------------------------------------------------------- */
377 : /* Convert to ESRI WKT. */
378 : /* -------------------------------------------------------------------- */
379 23 : char *pszESRI_SRS = nullptr;
380 23 : const char *const apszOptions[] = {"FORMAT=WKT1_ESRI", nullptr};
381 23 : m_oSRS.exportToWkt(&pszESRI_SRS, apszOptions);
382 :
383 : /* -------------------------------------------------------------------- */
384 : /* Write to .prj file. */
385 : /* -------------------------------------------------------------------- */
386 23 : const CPLString osPrjFilename = CPLResetExtension(GetDescription(), "prj");
387 23 : VSILFILE *l_fp = VSIFOpenL(osPrjFilename.c_str(), "wt");
388 23 : if (l_fp != nullptr)
389 : {
390 23 : VSIFWriteL(pszESRI_SRS, 1, strlen(pszESRI_SRS), l_fp);
391 23 : VSIFWriteL(reinterpret_cast<void *>(const_cast<char *>("\n")), 1, 1,
392 : l_fp);
393 23 : VSIFCloseL(l_fp);
394 : }
395 :
396 23 : CPLFree(pszESRI_SRS);
397 :
398 23 : return CE_None;
399 : }
400 :
401 : /************************************************************************/
402 : /* Open() */
403 : /************************************************************************/
404 :
405 29856 : GDALDataset *SAGADataset::Open(GDALOpenInfo *poOpenInfo)
406 :
407 : {
408 : /* -------------------------------------------------------------------- */
409 : /* We assume the user is pointing to the binary (i.e. .sdat) file or a */
410 : /* compressed raster (.sg-grd-z) file. */
411 : /* -------------------------------------------------------------------- */
412 59694 : CPLString osExtension(CPLGetExtension(poOpenInfo->pszFilename));
413 :
414 29840 : if (!EQUAL(osExtension, "sdat") && !EQUAL(osExtension, "sg-grd-z"))
415 : {
416 29577 : return nullptr;
417 : }
418 :
419 531 : CPLString osPath, osFullname, osName, osHDRFilename;
420 :
421 263 : if (EQUAL(osExtension, "sg-grd-z") &&
422 2 : !STARTS_WITH(poOpenInfo->pszFilename, "/vsizip"))
423 : {
424 2 : osPath = "/vsizip/{";
425 2 : osPath += poOpenInfo->pszFilename;
426 2 : osPath += "}/";
427 :
428 2 : char **filesinzip = VSIReadDir(osPath);
429 2 : if (filesinzip == nullptr)
430 0 : return nullptr; // empty zip file
431 :
432 2 : CPLString file;
433 4 : for (int iFile = 0; filesinzip[iFile] != nullptr; iFile++)
434 : {
435 4 : if (EQUAL(CPLGetExtension(filesinzip[iFile]), "sdat"))
436 : {
437 2 : file = filesinzip[iFile];
438 2 : break;
439 : }
440 : }
441 :
442 2 : CSLDestroy(filesinzip);
443 :
444 2 : osFullname = CPLFormFilename(osPath, file, nullptr);
445 2 : osName = CPLGetBasename(file);
446 2 : osHDRFilename = CPLFormFilename(osPath, CPLGetBasename(file), "sgrd");
447 : }
448 : else
449 : {
450 259 : osFullname = poOpenInfo->pszFilename;
451 259 : osPath = CPLGetPath(poOpenInfo->pszFilename);
452 259 : osName = CPLGetBasename(poOpenInfo->pszFilename);
453 : osHDRFilename = CPLFormCIFilename(
454 259 : osPath, CPLGetBasename(poOpenInfo->pszFilename), "sgrd");
455 : }
456 :
457 261 : VSILFILE *fp = VSIFOpenL(osHDRFilename, "r");
458 261 : if (fp == nullptr)
459 : {
460 143 : return nullptr;
461 : }
462 :
463 : /* -------------------------------------------------------------------- */
464 : /* Is this file a SAGA header file? Read a few lines of text */
465 : /* searching for something starting with nrows or ncols. */
466 : /* -------------------------------------------------------------------- */
467 118 : int nRows = -1;
468 118 : int nCols = -1;
469 118 : double dXmin = 0.0;
470 118 : double dYmin = 0.0;
471 118 : double dCellsize = 0.0;
472 118 : double dNoData = 0.0;
473 118 : double dZFactor = 0.0;
474 118 : int nLineCount = 0;
475 118 : char szDataFormat[20] = "DOUBLE";
476 118 : char szByteOrderBig[10] = "FALSE";
477 118 : char szTopToBottom[10] = "FALSE";
478 :
479 118 : const char *pszLine = nullptr;
480 1656 : while ((pszLine = CPLReadLineL(fp)) != nullptr)
481 : {
482 1538 : nLineCount++;
483 :
484 1538 : if (nLineCount > 50 || strlen(pszLine) > 1000)
485 : break;
486 :
487 : char **papszTokens =
488 1538 : CSLTokenizeStringComplex(pszLine, " =", TRUE, FALSE);
489 1538 : if (CSLCount(papszTokens) < 2)
490 : {
491 229 : CSLDestroy(papszTokens);
492 229 : continue;
493 : }
494 :
495 1309 : char **papszHDR = CSLAddString(nullptr, pszLine);
496 :
497 1309 : if (STARTS_WITH_CI(papszTokens[0], "CELLCOUNT_X"))
498 108 : nCols = atoi(papszTokens[1]);
499 1201 : else if (STARTS_WITH_CI(papszTokens[0], "CELLCOUNT_Y"))
500 108 : nRows = atoi(papszTokens[1]);
501 1093 : else if (STARTS_WITH_CI(papszTokens[0], "POSITION_XMIN"))
502 108 : dXmin = CPLAtofM(papszTokens[1]);
503 985 : else if (STARTS_WITH_CI(papszTokens[0], "POSITION_YMIN"))
504 108 : dYmin = CPLAtofM(papszTokens[1]);
505 877 : else if (STARTS_WITH_CI(papszTokens[0], "CELLSIZE"))
506 108 : dCellsize = CPLAtofM(papszTokens[1]);
507 769 : else if (STARTS_WITH_CI(papszTokens[0], "NODATA_VALUE"))
508 108 : dNoData = CPLAtofM(papszTokens[1]);
509 661 : else if (STARTS_WITH_CI(papszTokens[0], "DATAFORMAT"))
510 108 : strncpy(szDataFormat, papszTokens[1], sizeof(szDataFormat) - 1);
511 553 : else if (STARTS_WITH_CI(papszTokens[0], "BYTEORDER_BIG"))
512 109 : strncpy(szByteOrderBig, papszTokens[1], sizeof(szByteOrderBig) - 1);
513 444 : else if (STARTS_WITH_CI(papszTokens[0], "TOPTOBOTTOM"))
514 108 : strncpy(szTopToBottom, papszTokens[1], sizeof(szTopToBottom) - 1);
515 336 : else if (STARTS_WITH_CI(papszTokens[0], "Z_FACTOR"))
516 108 : dZFactor = CPLAtofM(papszTokens[1]);
517 :
518 1309 : CSLDestroy(papszTokens);
519 1309 : CSLDestroy(papszHDR);
520 : }
521 :
522 118 : VSIFCloseL(fp);
523 :
524 : /* -------------------------------------------------------------------- */
525 : /* Did we get the required keywords? If not we return with */
526 : /* this never having been considered to be a match. This isn't */
527 : /* an error! */
528 : /* -------------------------------------------------------------------- */
529 118 : if (nRows == -1 || nCols == -1)
530 : {
531 10 : return nullptr;
532 : }
533 :
534 108 : if (!GDALCheckDatasetDimensions(nCols, nRows))
535 : {
536 0 : return nullptr;
537 : }
538 :
539 108 : if (STARTS_WITH_CI(szTopToBottom, "TRUE"))
540 : {
541 0 : CPLError(CE_Failure, CPLE_AppDefined,
542 : "Currently the SAGA Binary Grid driver does not support\n"
543 : "SAGA grids written TOPTOBOTTOM.\n");
544 0 : return nullptr;
545 : }
546 108 : if (dZFactor != 1.0)
547 : {
548 0 : CPLError(CE_Warning, CPLE_AppDefined,
549 : "Currently the SAGA Binary Grid driver does not support\n"
550 : "ZFACTORs other than 1.\n");
551 : }
552 :
553 : /* -------------------------------------------------------------------- */
554 : /* Create a corresponding GDALDataset. */
555 : /* -------------------------------------------------------------------- */
556 108 : SAGADataset *poDS = new SAGADataset();
557 :
558 108 : poDS->eAccess = poOpenInfo->eAccess;
559 108 : if (poOpenInfo->eAccess == GA_ReadOnly)
560 68 : poDS->fp = VSIFOpenL(osFullname.c_str(), "rb");
561 : else
562 40 : poDS->fp = VSIFOpenL(osFullname.c_str(), "r+b");
563 :
564 108 : if (poDS->fp == nullptr)
565 : {
566 0 : delete poDS;
567 0 : CPLError(CE_Failure, CPLE_OpenFailed,
568 : "VSIFOpenL(%s) failed unexpectedly.", osFullname.c_str());
569 0 : return nullptr;
570 : }
571 :
572 108 : poDS->nRasterXSize = nCols;
573 108 : poDS->nRasterYSize = nRows;
574 :
575 108 : SAGARasterBand *poBand = new SAGARasterBand(poDS, 1);
576 :
577 : /* -------------------------------------------------------------------- */
578 : /* Figure out the byte order. */
579 : /* -------------------------------------------------------------------- */
580 108 : if (STARTS_WITH_CI(szByteOrderBig, "TRUE"))
581 0 : poBand->m_ByteOrder = 1;
582 108 : else if (STARTS_WITH_CI(szByteOrderBig, "FALSE"))
583 108 : poBand->m_ByteOrder = 0;
584 :
585 : /* -------------------------------------------------------------------- */
586 : /* Figure out the data type. */
587 : /* -------------------------------------------------------------------- */
588 108 : if (EQUAL(szDataFormat, "BIT"))
589 : {
590 0 : poBand->SetDataType(GDT_Byte);
591 0 : poBand->m_nBits = 8;
592 : }
593 108 : else if (EQUAL(szDataFormat, "BYTE_UNSIGNED"))
594 : {
595 13 : poBand->SetDataType(GDT_Byte);
596 13 : poBand->m_nBits = 8;
597 : }
598 95 : else if (EQUAL(szDataFormat, "BYTE"))
599 : {
600 0 : poBand->SetDataType(GDT_Byte);
601 0 : poBand->m_nBits = 8;
602 : }
603 95 : else if (EQUAL(szDataFormat, "SHORTINT_UNSIGNED"))
604 : {
605 13 : poBand->SetDataType(GDT_UInt16);
606 13 : poBand->m_nBits = 16;
607 : }
608 82 : else if (EQUAL(szDataFormat, "SHORTINT"))
609 : {
610 13 : poBand->SetDataType(GDT_Int16);
611 13 : poBand->m_nBits = 16;
612 : }
613 69 : else if (EQUAL(szDataFormat, "INTEGER_UNSIGNED"))
614 : {
615 13 : poBand->SetDataType(GDT_UInt32);
616 13 : poBand->m_nBits = 32;
617 : }
618 56 : else if (EQUAL(szDataFormat, "INTEGER"))
619 : {
620 13 : poBand->SetDataType(GDT_Int32);
621 13 : poBand->m_nBits = 32;
622 : }
623 43 : else if (EQUAL(szDataFormat, "FLOAT"))
624 : {
625 29 : poBand->SetDataType(GDT_Float32);
626 29 : poBand->m_nBits = 32;
627 : }
628 14 : else if (EQUAL(szDataFormat, "DOUBLE"))
629 : {
630 14 : poBand->SetDataType(GDT_Float64);
631 14 : poBand->m_nBits = 64;
632 : }
633 : else
634 : {
635 0 : CPLError(CE_Failure, CPLE_NotSupported,
636 : "SAGA driver does not support the dataformat %s.",
637 : szDataFormat);
638 0 : delete poBand;
639 0 : delete poDS;
640 0 : return nullptr;
641 : }
642 :
643 : /* -------------------------------------------------------------------- */
644 : /* Save band information */
645 : /* -------------------------------------------------------------------- */
646 108 : poBand->m_Xmin = dXmin;
647 108 : poBand->m_Ymin = dYmin;
648 108 : poBand->m_NoData = dNoData;
649 108 : poBand->m_Cellsize = dCellsize;
650 :
651 108 : poDS->SetBand(1, poBand);
652 :
653 : /* -------------------------------------------------------------------- */
654 : /* Initialize any PAM information. */
655 : /* -------------------------------------------------------------------- */
656 108 : poDS->SetDescription(poOpenInfo->pszFilename);
657 108 : poDS->TryLoadXML();
658 :
659 : /* -------------------------------------------------------------------- */
660 : /* Check for a .prj file. */
661 : /* -------------------------------------------------------------------- */
662 108 : const char *pszPrjFilename = CPLFormCIFilename(osPath, osName, "prj");
663 :
664 108 : fp = VSIFOpenL(pszPrjFilename, "r");
665 :
666 108 : if (fp != nullptr)
667 : {
668 32 : VSIFCloseL(fp);
669 :
670 32 : char **papszLines = CSLLoad(pszPrjFilename);
671 :
672 32 : poDS->m_oSRS.importFromESRI(papszLines);
673 :
674 32 : CSLDestroy(papszLines);
675 : }
676 :
677 : /* -------------------------------------------------------------------- */
678 : /* Check for external overviews. */
679 : /* -------------------------------------------------------------------- */
680 216 : poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename,
681 108 : poOpenInfo->GetSiblingFiles());
682 :
683 108 : return poDS;
684 : }
685 :
686 : /************************************************************************/
687 : /* GetGeoTransform() */
688 : /************************************************************************/
689 :
690 11 : CPLErr SAGADataset::GetGeoTransform(double *padfGeoTransform)
691 : {
692 11 : if (padfGeoTransform == nullptr)
693 0 : return CE_Failure;
694 :
695 11 : SAGARasterBand *poGRB = static_cast<SAGARasterBand *>(GetRasterBand(1));
696 :
697 11 : if (poGRB == nullptr)
698 : {
699 0 : padfGeoTransform[0] = 0;
700 0 : padfGeoTransform[1] = 1;
701 0 : padfGeoTransform[2] = 0;
702 0 : padfGeoTransform[3] = 0;
703 0 : padfGeoTransform[4] = 0;
704 0 : padfGeoTransform[5] = 1;
705 0 : return CE_Failure;
706 : }
707 :
708 : /* check if we have a PAM GeoTransform stored */
709 11 : CPLPushErrorHandler(CPLQuietErrorHandler);
710 11 : CPLErr eErr = GDALPamDataset::GetGeoTransform(padfGeoTransform);
711 11 : CPLPopErrorHandler();
712 :
713 11 : if (eErr == CE_None)
714 0 : return CE_None;
715 :
716 11 : padfGeoTransform[1] = poGRB->m_Cellsize;
717 11 : padfGeoTransform[5] = poGRB->m_Cellsize * -1.0;
718 11 : padfGeoTransform[0] = poGRB->m_Xmin - poGRB->m_Cellsize / 2;
719 11 : padfGeoTransform[3] = poGRB->m_Ymin +
720 11 : (nRasterYSize - 1) * poGRB->m_Cellsize +
721 11 : poGRB->m_Cellsize / 2;
722 :
723 : /* tilt/rotation is not supported by SAGA grids */
724 11 : padfGeoTransform[4] = 0.0;
725 11 : padfGeoTransform[2] = 0.0;
726 :
727 11 : return CE_None;
728 : }
729 :
730 : /************************************************************************/
731 : /* SetGeoTransform() */
732 : /************************************************************************/
733 :
734 23 : CPLErr SAGADataset::SetGeoTransform(double *padfGeoTransform)
735 : {
736 :
737 23 : if (eAccess == GA_ReadOnly)
738 : {
739 0 : CPLError(CE_Failure, CPLE_NoWriteAccess,
740 : "Unable to set GeoTransform, dataset opened read only.\n");
741 0 : return CE_Failure;
742 : }
743 :
744 23 : SAGARasterBand *poGRB = static_cast<SAGARasterBand *>(GetRasterBand(1));
745 :
746 23 : if (poGRB == nullptr || padfGeoTransform == nullptr)
747 0 : return CE_Failure;
748 :
749 23 : if (padfGeoTransform[1] != padfGeoTransform[5] * -1.0)
750 : {
751 0 : CPLError(CE_Failure, CPLE_NotSupported,
752 : "Unable to set GeoTransform, SAGA binary grids only support "
753 : "the same cellsize in x-y.\n");
754 0 : return CE_Failure;
755 : }
756 :
757 23 : const double dfMinX = padfGeoTransform[0] + padfGeoTransform[1] / 2;
758 23 : const double dfMinY =
759 23 : padfGeoTransform[5] * (nRasterYSize - 0.5) + padfGeoTransform[3];
760 :
761 23 : poGRB->m_Xmin = dfMinX;
762 23 : poGRB->m_Ymin = dfMinY;
763 23 : poGRB->m_Cellsize = padfGeoTransform[1];
764 23 : headerDirty = true;
765 :
766 23 : return CE_None;
767 : }
768 :
769 : /************************************************************************/
770 : /* WriteHeader() */
771 : /************************************************************************/
772 :
773 73 : CPLErr SAGADataset::WriteHeader(CPLString osHDRFilename, GDALDataType eType,
774 : int nXSize, int nYSize, double dfMinX,
775 : double dfMinY, double dfCellsize,
776 : double dfNoData, double dfZFactor,
777 : bool bTopToBottom)
778 :
779 : {
780 73 : VSILFILE *fp = VSIFOpenL(osHDRFilename, "wt");
781 :
782 73 : if (fp == nullptr)
783 : {
784 0 : CPLError(CE_Failure, CPLE_OpenFailed, "Failed to write .sgrd file %s.",
785 : osHDRFilename.c_str());
786 0 : return CE_Failure;
787 : }
788 :
789 73 : VSIFPrintfL(fp, "NAME\t= %s\n", CPLGetBasename(osHDRFilename));
790 73 : VSIFPrintfL(fp, "DESCRIPTION\t=\n");
791 73 : VSIFPrintfL(fp, "UNIT\t=\n");
792 73 : VSIFPrintfL(fp, "DATAFILE_OFFSET\t= 0\n");
793 :
794 73 : if (eType == GDT_Int32)
795 8 : VSIFPrintfL(fp, "DATAFORMAT\t= INTEGER\n");
796 65 : else if (eType == GDT_UInt32)
797 8 : VSIFPrintfL(fp, "DATAFORMAT\t= INTEGER_UNSIGNED\n");
798 57 : else if (eType == GDT_Int16)
799 8 : VSIFPrintfL(fp, "DATAFORMAT\t= SHORTINT\n");
800 49 : else if (eType == GDT_UInt16)
801 8 : VSIFPrintfL(fp, "DATAFORMAT\t= SHORTINT_UNSIGNED\n");
802 41 : else if (eType == GDT_Byte)
803 18 : VSIFPrintfL(fp, "DATAFORMAT\t= BYTE_UNSIGNED\n");
804 23 : else if (eType == GDT_Float32)
805 13 : VSIFPrintfL(fp, "DATAFORMAT\t= FLOAT\n");
806 : else // if( eType == GDT_Float64 )
807 10 : VSIFPrintfL(fp, "DATAFORMAT\t= DOUBLE\n");
808 : #ifdef CPL_LSB
809 73 : VSIFPrintfL(fp, "BYTEORDER_BIG\t= FALSE\n");
810 : #else
811 : VSIFPrintfL(fp, "BYTEORDER_BIG\t= TRUE\n");
812 : #endif
813 :
814 73 : VSIFPrintfL(fp, "POSITION_XMIN\t= %.10f\n", dfMinX);
815 73 : VSIFPrintfL(fp, "POSITION_YMIN\t= %.10f\n", dfMinY);
816 73 : VSIFPrintfL(fp, "CELLCOUNT_X\t= %d\n", nXSize);
817 73 : VSIFPrintfL(fp, "CELLCOUNT_Y\t= %d\n", nYSize);
818 73 : VSIFPrintfL(fp, "CELLSIZE\t= %.10f\n", dfCellsize);
819 73 : VSIFPrintfL(fp, "Z_FACTOR\t= %f\n", dfZFactor);
820 73 : VSIFPrintfL(fp, "NODATA_VALUE\t= %f\n", dfNoData);
821 73 : if (bTopToBottom)
822 0 : VSIFPrintfL(fp, "TOPTOBOTTOM\t= TRUE\n");
823 : else
824 73 : VSIFPrintfL(fp, "TOPTOBOTTOM\t= FALSE\n");
825 :
826 73 : VSIFCloseL(fp);
827 :
828 73 : return CE_None;
829 : }
830 :
831 : /************************************************************************/
832 : /* Create() */
833 : /************************************************************************/
834 :
835 81 : GDALDataset *SAGADataset::Create(const char *pszFilename, int nXSize,
836 : int nYSize, int nBandsIn, GDALDataType eType,
837 : char **papszParamList)
838 :
839 : {
840 81 : if (nXSize <= 0 || nYSize <= 0)
841 : {
842 0 : CPLError(CE_Failure, CPLE_IllegalArg,
843 : "Unable to create grid, both X and Y size must be "
844 : "non-negative.\n");
845 :
846 0 : return nullptr;
847 : }
848 :
849 81 : if (nBandsIn != 1)
850 : {
851 18 : CPLError(CE_Failure, CPLE_IllegalArg,
852 : "SAGA Binary Grid only supports 1 band");
853 18 : return nullptr;
854 : }
855 :
856 63 : if (eType != GDT_Byte && eType != GDT_UInt16 && eType != GDT_Int16 &&
857 30 : eType != GDT_UInt32 && eType != GDT_Int32 && eType != GDT_Float32 &&
858 : eType != GDT_Float64)
859 : {
860 11 : CPLError(CE_Failure, CPLE_AppDefined,
861 : "SAGA Binary Grid only supports Byte, UInt16, Int16, "
862 : "UInt32, Int32, Float32 and Float64 datatypes. Unable to "
863 : "create with type %s.\n",
864 : GDALGetDataTypeName(eType));
865 :
866 11 : return nullptr;
867 : }
868 :
869 52 : VSILFILE *fp = VSIFOpenL(pszFilename, "w+b");
870 :
871 52 : if (fp == nullptr)
872 : {
873 3 : CPLError(CE_Failure, CPLE_OpenFailed,
874 : "Attempt to create file '%s' failed.\n", pszFilename);
875 3 : return nullptr;
876 : }
877 :
878 49 : double dfNoDataVal = 0.0;
879 :
880 : const char *pszNoDataValue =
881 49 : CSLFetchNameValue(papszParamList, "NODATA_VALUE");
882 49 : if (pszNoDataValue)
883 : {
884 2 : dfNoDataVal = CPLAtofM(pszNoDataValue);
885 : }
886 : else
887 : {
888 47 : switch (eType) /* GDT_Byte, GDT_UInt16, GDT_Int16, GDT_UInt32 */
889 : { /* GDT_Int32, GDT_Float32, GDT_Float64 */
890 15 : case (GDT_Byte):
891 : {
892 15 : dfNoDataVal = SG_NODATA_GDT_Byte;
893 15 : break;
894 : }
895 5 : case (GDT_UInt16):
896 : {
897 5 : dfNoDataVal = SG_NODATA_GDT_UInt16;
898 5 : break;
899 : }
900 5 : case (GDT_Int16):
901 : {
902 5 : dfNoDataVal = SG_NODATA_GDT_Int16;
903 5 : break;
904 : }
905 5 : case (GDT_UInt32):
906 : {
907 5 : dfNoDataVal = SG_NODATA_GDT_UInt32;
908 5 : break;
909 : }
910 5 : case (GDT_Int32):
911 : {
912 5 : dfNoDataVal = SG_NODATA_GDT_Int32;
913 5 : break;
914 : }
915 6 : default:
916 : case (GDT_Float32):
917 : {
918 6 : dfNoDataVal = SG_NODATA_GDT_Float32;
919 6 : break;
920 : }
921 6 : case (GDT_Float64):
922 : {
923 6 : dfNoDataVal = SG_NODATA_GDT_Float64;
924 6 : break;
925 : }
926 : }
927 : }
928 :
929 : double dfNoDataForAlignment;
930 49 : void *abyNoData = &dfNoDataForAlignment;
931 49 : GDALCopyWords(&dfNoDataVal, GDT_Float64, 0, abyNoData, eType, 0, 1);
932 :
933 98 : const CPLString osHdrFilename = CPLResetExtension(pszFilename, "sgrd");
934 49 : CPLErr eErr = WriteHeader(osHdrFilename, eType, nXSize, nYSize, 0.0, 0.0,
935 : 1.0, dfNoDataVal, 1.0, false);
936 :
937 49 : if (eErr != CE_None)
938 : {
939 0 : VSIFCloseL(fp);
940 0 : return nullptr;
941 : }
942 :
943 49 : if (CPLFetchBool(papszParamList, "FILL_NODATA", true))
944 : {
945 23 : const int nDataTypeSize = GDALGetDataTypeSize(eType) / 8;
946 : GByte *pabyNoDataBuf =
947 23 : reinterpret_cast<GByte *>(VSIMalloc2(nDataTypeSize, nXSize));
948 23 : if (pabyNoDataBuf == nullptr)
949 : {
950 0 : VSIFCloseL(fp);
951 0 : return nullptr;
952 : }
953 :
954 889 : for (int iCol = 0; iCol < nXSize; iCol++)
955 : {
956 866 : memcpy(pabyNoDataBuf + iCol * nDataTypeSize, abyNoData,
957 : nDataTypeSize);
958 : }
959 :
960 889 : for (int iRow = 0; iRow < nYSize; iRow++)
961 : {
962 866 : if (VSIFWriteL(pabyNoDataBuf, nDataTypeSize, nXSize, fp) !=
963 866 : static_cast<unsigned>(nXSize))
964 : {
965 0 : VSIFCloseL(fp);
966 0 : VSIFree(pabyNoDataBuf);
967 0 : CPLError(CE_Failure, CPLE_FileIO,
968 : "Unable to write grid cell. Disk full?\n");
969 0 : return nullptr;
970 : }
971 : }
972 :
973 23 : VSIFree(pabyNoDataBuf);
974 : }
975 :
976 49 : VSIFCloseL(fp);
977 :
978 49 : return GDALDataset::FromHandle(GDALOpen(pszFilename, GA_Update));
979 : }
980 :
981 : /************************************************************************/
982 : /* CreateCopy() */
983 : /************************************************************************/
984 :
985 38 : GDALDataset *SAGADataset::CreateCopy(const char *pszFilename,
986 : GDALDataset *poSrcDS, int bStrict,
987 : CPL_UNUSED char **papszOptions,
988 : GDALProgressFunc pfnProgress,
989 : void *pProgressData)
990 : {
991 38 : if (pfnProgress == nullptr)
992 0 : pfnProgress = GDALDummyProgress;
993 :
994 38 : int nBands = poSrcDS->GetRasterCount();
995 38 : if (nBands == 0)
996 : {
997 1 : CPLError(
998 : CE_Failure, CPLE_NotSupported,
999 : "SAGA driver does not support source dataset with zero band.\n");
1000 1 : return nullptr;
1001 : }
1002 37 : else if (nBands > 1)
1003 : {
1004 4 : if (bStrict)
1005 : {
1006 4 : CPLError(CE_Failure, CPLE_NotSupported,
1007 : "Unable to create copy, SAGA Binary Grid "
1008 : "format only supports one raster band.\n");
1009 4 : return nullptr;
1010 : }
1011 : else
1012 0 : CPLError(CE_Warning, CPLE_NotSupported,
1013 : "SAGA Binary Grid format only supports one "
1014 : "raster band, first band will be copied.\n");
1015 : }
1016 :
1017 33 : GDALRasterBand *poSrcBand = poSrcDS->GetRasterBand(1);
1018 :
1019 33 : char **papszCreateOptions = CSLSetNameValue(nullptr, "FILL_NODATA", "NO");
1020 :
1021 33 : int bHasNoDataValue = FALSE;
1022 33 : const double dfNoDataValue = poSrcBand->GetNoDataValue(&bHasNoDataValue);
1023 33 : if (bHasNoDataValue)
1024 : papszCreateOptions =
1025 2 : CSLSetNameValue(papszCreateOptions, "NODATA_VALUE",
1026 : CPLSPrintf("%.16g", dfNoDataValue));
1027 :
1028 : GDALDataset *poDstDS =
1029 33 : Create(pszFilename, poSrcBand->GetXSize(), poSrcBand->GetYSize(), 1,
1030 : poSrcBand->GetRasterDataType(), papszCreateOptions);
1031 33 : CSLDestroy(papszCreateOptions);
1032 :
1033 33 : if (poDstDS == nullptr)
1034 17 : return nullptr;
1035 :
1036 : /* -------------------------------------------------------------------- */
1037 : /* Copy band data. */
1038 : /* -------------------------------------------------------------------- */
1039 :
1040 : CPLErr eErr =
1041 16 : GDALDatasetCopyWholeRaster((GDALDatasetH)poSrcDS, (GDALDatasetH)poDstDS,
1042 : nullptr, pfnProgress, pProgressData);
1043 :
1044 16 : if (eErr == CE_Failure)
1045 : {
1046 0 : delete poDstDS;
1047 0 : return nullptr;
1048 : }
1049 :
1050 : double adfGeoTransform[6];
1051 :
1052 16 : poSrcDS->GetGeoTransform(adfGeoTransform);
1053 16 : poDstDS->SetGeoTransform(adfGeoTransform);
1054 :
1055 16 : poDstDS->SetProjection(poSrcDS->GetProjectionRef());
1056 :
1057 16 : return poDstDS;
1058 : }
1059 :
1060 : /************************************************************************/
1061 : /* GDALRegister_SAGA() */
1062 : /************************************************************************/
1063 :
1064 1595 : void GDALRegister_SAGA()
1065 :
1066 : {
1067 1595 : if (GDALGetDriverByName("SAGA") != nullptr)
1068 302 : return;
1069 :
1070 1293 : GDALDriver *poDriver = new GDALDriver();
1071 :
1072 1293 : poDriver->SetDescription("SAGA");
1073 1293 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1074 1293 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
1075 1293 : "SAGA GIS Binary Grid (.sdat, .sg-grd-z)");
1076 1293 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/sdat.html");
1077 1293 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "sdat sg-grd-z");
1078 1293 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES,
1079 : "Byte Int16 "
1080 1293 : "UInt16 Int32 UInt32 Float32 Float64");
1081 :
1082 1293 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1083 :
1084 1293 : poDriver->pfnOpen = SAGADataset::Open;
1085 1293 : poDriver->pfnCreate = SAGADataset::Create;
1086 1293 : poDriver->pfnCreateCopy = SAGADataset::CreateCopy;
1087 :
1088 1293 : GetGDALDriverManager()->RegisterDriver(poDriver);
1089 : }
|