Coverage Report

Created: 2025-12-03 08:24

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