Coverage Report

Created: 2025-12-31 08:30

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/pcraster/pcrasterdataset.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  PCRaster Integration
4
 * Purpose:  PCRaster CSF 2.0 raster file driver
5
 * Author:   Kor de Jong, Oliver Schmitz
6
 *
7
 ******************************************************************************
8
 * Copyright (c) PCRaster owners
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "cpl_string.h"
14
#include "gdal_pam.h"
15
16
#include "pcrasterrasterband.h"
17
#include "pcrasterdataset.h"
18
#include "pcrasterutil.h"
19
#include "pcrasterdrivercore.h"
20
21
/*!
22
  \file
23
  This file contains the implementation of the PCRasterDataset class.
24
*/
25
26
//------------------------------------------------------------------------------
27
// DEFINITION OF STATIC PCRDATASET MEMBERS
28
//------------------------------------------------------------------------------
29
30
//! Tries to open the file described by \a info.
31
/*!
32
  \param     info Object with information about the dataset to open.
33
  \return    Pointer to newly allocated GDALDataset or 0.
34
35
  Returns a nullptr if the file could not be opened.
36
*/
37
GDALDataset *PCRasterDataset::open(GDALOpenInfo *info)
38
2
{
39
2
    PCRasterDataset *dataset = nullptr;
40
41
2
    if (PCRasterDriverIdentify(info))
42
2
    {
43
2
        MOPEN_PERM mode = info->eAccess == GA_Update ? M_READ_WRITE : M_READ;
44
45
2
        MAP *map = mapOpen(info->pszFilename, mode);
46
47
2
        if (map)
48
1
        {
49
1
            CPLErrorReset();
50
1
            dataset = new PCRasterDataset(map, info->eAccess);
51
1
            if (CPLGetLastErrorType() != CE_None)
52
0
            {
53
0
                delete dataset;
54
0
                return nullptr;
55
0
            }
56
1
        }
57
2
    }
58
59
    /* -------------------------------------------------------------------- */
60
    /*      Initialize any PAM information and overviews.                   */
61
    /* -------------------------------------------------------------------- */
62
2
    if (dataset)
63
1
    {
64
1
        dataset->SetDescription(info->pszFilename);
65
1
        dataset->TryLoadXML();
66
67
1
        dataset->oOvManager.Initialize(dataset, info->pszFilename);
68
1
    }
69
70
2
    return dataset;
71
2
}
72
73
//! Writes a raster to \a filename as a PCRaster raster file.
74
/*!
75
  \warning   The source raster must have only 1 band. Currently, the values in
76
             the source raster must be stored in one of the supported cell
77
             representations (CR_UINT1, CR_INT4, CR_REAL4, CR_REAL8).
78
79
  The meta data item PCRASTER_VALUESCALE will be checked to see what value
80
  scale to use. Otherwise a value scale is determined using
81
  GDALType2ValueScale(GDALDataType).
82
83
  This function always writes rasters using CR_UINT1, CR_INT4 or CR_REAL4
84
  cell representations.
85
*/
86
GDALDataset *
87
PCRasterDataset::createCopy(char const *filename, GDALDataset *source,
88
                            CPL_UNUSED int strict, CPL_UNUSED char **options,
89
                            GDALProgressFunc progress, void *progressData)
90
0
{
91
    // Checks.
92
0
    const int nrBands = source->GetRasterCount();
93
0
    if (nrBands != 1)
94
0
    {
95
0
        CPLError(CE_Failure, CPLE_NotSupported,
96
0
                 "PCRaster driver: Too many bands ('%d'): must be 1 band",
97
0
                 nrBands);
98
0
        return nullptr;
99
0
    }
100
101
0
    GDALRasterBand *raster = source->GetRasterBand(1);
102
103
    // Create PCRaster raster. Determine properties of raster to create.
104
105
    // The in-file type of the cells.
106
0
    CSF_CR fileCellRepresentation =
107
0
        GDALType2CellRepresentation(raster->GetRasterDataType(), false);
108
109
0
    if (fileCellRepresentation == CR_UNDEFINED)
110
0
    {
111
0
        CPLError(
112
0
            CE_Failure, CPLE_NotSupported,
113
0
            "PCRaster driver: Cannot determine a valid cell representation");
114
0
        return nullptr;
115
0
    }
116
117
    // The value scale of the values.
118
0
    CSF_VS valueScale = VS_UNDEFINED;
119
0
    std::string osString;
120
0
    if (source->GetMetadataItem("PCRASTER_VALUESCALE"))
121
0
    {
122
0
        osString = source->GetMetadataItem("PCRASTER_VALUESCALE");
123
0
    }
124
125
0
    valueScale = !osString.empty()
126
0
                     ? string2ValueScale(osString)
127
0
                     : GDALType2ValueScale(raster->GetRasterDataType());
128
129
0
    if (valueScale == VS_UNDEFINED)
130
0
    {
131
0
        CPLError(CE_Failure, CPLE_NotSupported,
132
0
                 "PCRaster driver: Cannot determine a valid value scale");
133
0
        return nullptr;
134
0
    }
135
136
0
    CSF_PT const projection = PT_YDECT2B;
137
0
    const REAL8 angle = 0.0;
138
0
    REAL8 west = 0.0;
139
0
    REAL8 north = 0.0;
140
0
    REAL8 cellSize = 1.0;
141
142
0
    GDALGeoTransform gt;
143
0
    if (source->GetGeoTransform(gt) == CE_None)
144
0
    {
145
0
        if (gt[2] == 0.0 && gt[4] == 0.0)
146
0
        {
147
0
            west = static_cast<REAL8>(gt[0]);
148
0
            north = static_cast<REAL8>(gt[3]);
149
0
            cellSize = static_cast<REAL8>(gt[1]);
150
0
        }
151
0
    }
152
153
    // The in-memory type of the cells.
154
0
    CSF_CR appCellRepresentation =
155
0
        GDALType2CellRepresentation(raster->GetRasterDataType(), true);
156
157
0
    if (appCellRepresentation == CR_UNDEFINED)
158
0
    {
159
0
        CPLError(
160
0
            CE_Failure, CPLE_NotSupported,
161
0
            "PCRaster driver: Cannot determine a valid cell representation");
162
0
        return nullptr;
163
0
    }
164
165
    // Check whether value scale fits the cell representation. Adjust when
166
    // needed.
167
0
    valueScale = fitValueScale(valueScale, appCellRepresentation);
168
169
    // Create a raster with the in file cell representation.
170
0
    const size_t nrRows = raster->GetYSize();
171
0
    const size_t nrCols = raster->GetXSize();
172
0
    MAP *map = Rcreate(filename, nrRows, nrCols, fileCellRepresentation,
173
0
                       valueScale, projection, west, north, angle, cellSize);
174
175
0
    if (!map)
176
0
    {
177
0
        CPLError(CE_Failure, CPLE_OpenFailed,
178
0
                 "PCRaster driver: Unable to create raster %s", filename);
179
0
        return nullptr;
180
0
    }
181
182
    // Try to convert in app cell representation to the cell representation
183
    // of the file.
184
0
    if (RuseAs(map, appCellRepresentation))
185
0
    {
186
0
        CPLError(CE_Failure, CPLE_NotSupported,
187
0
                 "PCRaster driver: Cannot convert cells: %s", MstrError());
188
0
        Mclose(map);
189
0
        return nullptr;
190
0
    }
191
192
0
    int hasMissingValue;
193
0
    double missingValue = raster->GetNoDataValue(&hasMissingValue);
194
195
    // This is needed to get my (KDJ) unit tests running.
196
    // I am still uncertain why this is needed. If the input raster has float32
197
    // values and the output int32, than the missing value in the dataset object
198
    // is not updated like the values are.
199
0
    if (missingValue == ::missingValue(CR_REAL4) &&
200
0
        fileCellRepresentation == CR_INT4)
201
0
    {
202
0
        missingValue = ::missingValue(fileCellRepresentation);
203
0
    }
204
205
    // TODO: Proper translation of TODO.
206
    // TODO: Support conversion to INT2 (?) INT4. ruseas.c see line 503.
207
    // Conversion r 159.
208
209
    // Create buffer for one row of values.
210
0
    void *buffer = Rmalloc(map, nrCols);
211
212
    // Copy values from source to target.
213
0
    CPLErr errorCode = CE_None;
214
0
    for (size_t row = 0; errorCode == CE_None && row < nrRows; ++row)
215
0
    {
216
217
        // Get row from source.
218
0
        if (raster->RasterIO(
219
0
                GF_Read, 0, static_cast<int>(row), static_cast<int>(nrCols), 1,
220
0
                buffer, static_cast<int>(nrCols), 1,
221
0
                raster->GetRasterDataType(), 0, 0, nullptr) != CE_None)
222
0
        {
223
0
            CPLError(CE_Failure, CPLE_FileIO,
224
0
                     "PCRaster driver: Error reading from source raster");
225
0
            errorCode = CE_Failure;
226
0
            break;
227
0
        }
228
229
        // Upon reading values are converted to the
230
        // right data type. This includes the missing value. If the source
231
        // value cannot be represented in the target data type it is set to a
232
        // missing value.
233
234
0
        if (hasMissingValue)
235
0
        {
236
0
            alterToStdMV(buffer, nrCols, appCellRepresentation, missingValue);
237
0
        }
238
239
0
        if (valueScale == VS_BOOLEAN)
240
0
        {
241
0
            castValuesToBooleanRange(buffer, nrCols, appCellRepresentation);
242
0
        }
243
244
        // Write row in target.
245
0
        RputRow(map, row, buffer);
246
247
0
        if (!progress((row + 1) / (static_cast<double>(nrRows)), nullptr,
248
0
                      progressData))
249
0
        {
250
0
            CPLError(CE_Failure, CPLE_UserInterrupt,
251
0
                     "PCRaster driver: User terminated CreateCopy()");
252
0
            errorCode = CE_Failure;
253
0
            break;
254
0
        }
255
0
    }
256
257
0
    Mclose(map);
258
0
    map = nullptr;
259
260
0
    free(buffer);
261
0
    buffer = nullptr;
262
263
0
    if (errorCode != CE_None)
264
0
        return nullptr;
265
266
    /* -------------------------------------------------------------------- */
267
    /*      Re-open dataset, and copy any auxiliary pam information.        */
268
    /* -------------------------------------------------------------------- */
269
0
    GDALPamDataset *poDS = cpl::down_cast<GDALPamDataset *>(
270
0
        GDALDataset::Open(filename, GDAL_OF_RASTER | GDAL_OF_UPDATE));
271
272
0
    if (poDS)
273
0
        poDS->CloneInfo(source, GCIF_PAM_DEFAULT);
274
275
0
    return poDS;
276
0
}
277
278
//------------------------------------------------------------------------------
279
// DEFINITION OF PCRDATASET MEMBERS
280
//------------------------------------------------------------------------------
281
282
//! Constructor.
283
/*!
284
  \param     mapIn PCRaster map handle. It is ours to close.
285
*/
286
PCRasterDataset::PCRasterDataset(MAP *mapIn, GDALAccess eAccessIn)
287
1
    : GDALPamDataset(), d_map(mapIn), d_west(0.0), d_north(0.0),
288
1
      d_cellSize(0.0), d_cellRepresentation(CR_UNDEFINED),
289
1
      d_valueScale(VS_UNDEFINED), d_defaultNoDataValue(0.0),
290
1
      d_location_changed(false)
291
1
{
292
    // Read header info.
293
1
    eAccess = eAccessIn;
294
1
    nRasterXSize = static_cast<int>(RgetNrCols(d_map));
295
1
    nRasterYSize = static_cast<int>(RgetNrRows(d_map));
296
1
    if (!GDALCheckDatasetDimensions(nRasterXSize, nRasterYSize))
297
0
    {
298
0
        return;
299
0
    }
300
1
    d_west = static_cast<double>(RgetXUL(d_map));
301
1
    d_north = static_cast<double>(RgetYUL(d_map));
302
1
    d_cellSize = static_cast<double>(RgetCellSize(d_map));
303
1
    d_cellRepresentation = RgetUseCellRepr(d_map);
304
1
    if (d_cellRepresentation == CR_UNDEFINED)
305
0
    {
306
0
        CPLError(CE_Failure, CPLE_AssertionFailed,
307
0
                 "d_cellRepresentation != CR_UNDEFINED");
308
0
    }
309
1
    d_valueScale = RgetValueScale(d_map);
310
1
    if (d_valueScale == VS_UNDEFINED)
311
0
    {
312
0
        CPLError(CE_Failure, CPLE_AssertionFailed,
313
0
                 "d_valueScale != VS_UNDEFINED");
314
0
    }
315
1
    d_defaultNoDataValue = ::missingValue(d_cellRepresentation);
316
317
    // Create band information objects.
318
1
    nBands = 1;
319
1
    SetBand(1, new PCRasterRasterBand(this));
320
321
1
    SetMetadataItem("PCRASTER_VALUESCALE",
322
1
                    valueScale2String(d_valueScale).c_str());
323
1
}
324
325
//! Destructor.
326
/*!
327
  \warning   The map given in the constructor is closed.
328
*/
329
PCRasterDataset::~PCRasterDataset()
330
1
{
331
1
    FlushCache(true);
332
1
    Mclose(d_map);
333
1
}
334
335
//! Sets projections info.
336
/*!
337
  \param     gt Array to fill.
338
339
  CSF 2.0 supports the notion of y coordinates which increase from north to
340
  south. Support for this has been dropped and applications reading PCRaster
341
  rasters will treat or already treat y coordinates as increasing from south
342
  to north only.
343
*/
344
CPLErr PCRasterDataset::GetGeoTransform(GDALGeoTransform &gt) const
345
1
{
346
    // x = west + nrCols * cellsize
347
1
    gt[0] = d_west;
348
1
    gt[1] = d_cellSize;
349
1
    gt[2] = 0.0;
350
351
    // y = north + nrRows * -cellsize
352
1
    gt[3] = d_north;
353
1
    gt[4] = 0.0;
354
1
    gt[5] = -1.0 * d_cellSize;
355
356
1
    return CE_None;
357
1
}
358
359
//! Returns the map handle.
360
/*!
361
  \return    Map handle.
362
*/
363
MAP *PCRasterDataset::map() const
364
512
{
365
512
    return d_map;
366
512
}
367
368
//! Returns the in-app cell representation.
369
/*!
370
  \return    cell representation
371
  \warning   This might not be the same representation as use to store the
372
  values in the file. \sa        valueScale()
373
*/
374
CSF_CR PCRasterDataset::cellRepresentation() const
375
513
{
376
513
    return d_cellRepresentation;
377
513
}
378
379
//! Returns the value scale of the data.
380
/*!
381
  \return    Value scale
382
  \sa        cellRepresentation()
383
*/
384
CSF_VS PCRasterDataset::valueScale() const
385
0
{
386
0
    return d_valueScale;
387
0
}
388
389
//! Returns the value of the missing value.
390
/*!
391
  \return    Missing value
392
*/
393
double PCRasterDataset::defaultNoDataValue() const
394
516
{
395
516
    return d_defaultNoDataValue;
396
516
}
397
398
GDALDataset *PCRasterDataset::create(const char *filename, int nr_cols,
399
                                     int nr_rows, int nrBands,
400
                                     GDALDataType gdalType,
401
                                     char **papszParamList)
402
0
{
403
    // Checks
404
0
    if (nrBands != 1)
405
0
    {
406
0
        CPLError(CE_Failure, CPLE_NotSupported,
407
0
                 "PCRaster driver : "
408
0
                 "attempt to create dataset with too many bands (%d); "
409
0
                 "must be 1 band.\n",
410
0
                 nrBands);
411
0
        return nullptr;
412
0
    }
413
414
0
    const int row_col_max = INT4_MAX - 1;
415
0
    if (nr_cols > row_col_max)
416
0
    {
417
0
        CPLError(CE_Failure, CPLE_NotSupported,
418
0
                 "PCRaster driver : "
419
0
                 "attempt to create dataset with too many columns (%d); "
420
0
                 "must be smaller than %d.",
421
0
                 nr_cols, row_col_max);
422
0
        return nullptr;
423
0
    }
424
425
0
    if (nr_rows > row_col_max)
426
0
    {
427
0
        CPLError(CE_Failure, CPLE_NotSupported,
428
0
                 "PCRaster driver : "
429
0
                 "attempt to create dataset with too many rows (%d); "
430
0
                 "must be smaller than %d.",
431
0
                 nr_rows, row_col_max);
432
0
        return nullptr;
433
0
    }
434
435
0
    if (gdalType != GDT_UInt8 && gdalType != GDT_Int32 &&
436
0
        gdalType != GDT_Float32)
437
0
    {
438
0
        CPLError(CE_Failure, CPLE_AppDefined,
439
0
                 "PCRaster driver: "
440
0
                 "attempt to create dataset with an illegal data type (%s); "
441
0
                 "use either Byte, Int32 or Float32.",
442
0
                 GDALGetDataTypeName(gdalType));
443
0
        return nullptr;
444
0
    }
445
446
    // value scale must be specified by the user,
447
    // determines cell representation
448
0
    const char *valueScale =
449
0
        CSLFetchNameValue(papszParamList, "PCRASTER_VALUESCALE");
450
451
0
    if (valueScale == nullptr)
452
0
    {
453
0
        CPLError(CE_Failure, CPLE_AppDefined,
454
0
                 "PCRaster driver: value scale can not be determined; "
455
0
                 "specify PCRASTER_VALUESCALE.");
456
0
        return nullptr;
457
0
    }
458
459
0
    CSF_VS csf_value_scale = string2ValueScale(valueScale);
460
461
0
    if (csf_value_scale == VS_UNDEFINED)
462
0
    {
463
0
        CPLError(CE_Failure, CPLE_AppDefined,
464
0
                 "PCRaster driver: value scale can not be determined (%s); "
465
0
                 "use either VS_BOOLEAN, VS_NOMINAL, VS_ORDINAL, VS_SCALAR, "
466
0
                 "VS_DIRECTION, VS_LDD",
467
0
                 valueScale);
468
0
        return nullptr;
469
0
    }
470
471
0
    CSF_CR csf_cell_representation =
472
0
        GDALType2CellRepresentation(gdalType, false);
473
474
    // default values
475
0
    REAL8 west = 0.0;
476
0
    REAL8 north = 0.0;
477
0
    REAL8 length = 1.0;
478
0
    REAL8 angle = 0.0;
479
0
    CSF_PT projection = PT_YDECT2B;
480
481
    // Create a new raster
482
0
    MAP *map = Rcreate(filename, nr_rows, nr_cols, csf_cell_representation,
483
0
                       csf_value_scale, projection, west, north, angle, length);
484
485
0
    if (!map)
486
0
    {
487
0
        CPLError(CE_Failure, CPLE_OpenFailed,
488
0
                 "PCRaster driver: Unable to create raster %s", filename);
489
0
        return nullptr;
490
0
    }
491
492
0
    Mclose(map);
493
0
    map = nullptr;
494
495
0
    return GDALDataset::Open(filename, GDAL_OF_RASTER | GDAL_OF_UPDATE);
496
0
}
497
498
CPLErr PCRasterDataset::SetGeoTransform(const GDALGeoTransform &gt)
499
0
{
500
0
    if ((gt[2] != 0.0) || (gt[4] != 0.0))
501
0
    {
502
0
        CPLError(CE_Failure, CPLE_NotSupported,
503
0
                 "PCRaster driver: "
504
0
                 "rotated geogtations are not supported.");
505
0
        return CE_Failure;
506
0
    }
507
508
0
    if (gt[1] != gt[5] * -1.0)
509
0
    {
510
0
        CPLError(CE_Failure, CPLE_NotSupported,
511
0
                 "PCRaster driver: "
512
0
                 "only the same width and height for cells is supported.");
513
0
        return CE_Failure;
514
0
    }
515
516
0
    d_west = gt[0];
517
0
    d_north = gt[3];
518
0
    d_cellSize = gt[1];
519
0
    d_location_changed = true;
520
521
0
    return CE_None;
522
0
}
523
524
bool PCRasterDataset::location_changed() const
525
0
{
526
0
    return d_location_changed;
527
0
}