Coverage Report

Created: 2025-06-09 07:43

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