Coverage Report

Created: 2025-06-09 07:07

/src/gdal/frmts/eeda/eedaidataset.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  Earth Engine Data API Images driver
4
 * Purpose:  Earth Engine Data API Images driver
5
 * Author:   Even Rouault, even dot rouault at spatialys.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2017-2018, Planet Labs
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "gdal_priv.h"
14
#include "cpl_http.h"
15
#include "cpl_conv.h"
16
#include "ogrlibjsonutils.h"
17
#include "eeda.h"
18
19
#include <algorithm>
20
#include <vector>
21
#include <map>
22
#include <limits>
23
24
extern "C" void GDALRegister_EEDAI();
25
26
static const int DEFAULT_BLOCK_SIZE = 256;
27
28
const GUInt32 RETRY_PER_BAND = 1;
29
const GUInt32 RETRY_SPATIAL_SPLIT = 2;
30
31
// Eart engine server only allows up to 16 MB per request
32
const int SERVER_BYTE_LIMIT = 16 * 1024 * 1024;
33
const int SERVER_SIMUTANEOUS_BAND_LIMIT = 100;
34
const int SERVER_DIMENSION_LIMIT = 10000;
35
36
/************************************************************************/
37
/*                          GDALEEDAIDataset                            */
38
/************************************************************************/
39
40
class GDALEEDAIDataset final : public GDALEEDABaseDataset
41
{
42
    CPL_DISALLOW_COPY_ASSIGN(GDALEEDAIDataset)
43
44
    friend class GDALEEDAIRasterBand;
45
46
    int m_nBlockSize;
47
    CPLString m_osAsset{};
48
    CPLString m_osAssetName{};
49
    GDALEEDAIDataset *m_poParentDS;
50
#ifdef DEBUG_VERBOSE
51
    int m_iOvrLevel;
52
#endif
53
    CPLString m_osPixelEncoding{};
54
    bool m_bQueryMultipleBands;
55
    OGRSpatialReference m_oSRS{};
56
    double m_adfGeoTransform[6];
57
    std::vector<GDALEEDAIDataset *> m_apoOverviewDS{};
58
59
    GDALEEDAIDataset(GDALEEDAIDataset *poParentDS, int iOvrLevel);
60
61
    void
62
    SetMetadataFromProperties(json_object *poProperties,
63
                              const std::map<CPLString, int> &aoMapBandNames);
64
65
  public:
66
    GDALEEDAIDataset();
67
    virtual ~GDALEEDAIDataset();
68
69
    const OGRSpatialReference *GetSpatialRef() const override;
70
    virtual CPLErr GetGeoTransform(double *) override;
71
72
    virtual CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
73
                             int nXSize, int nYSize, void *pData, int nBufXSize,
74
                             int nBufYSize, GDALDataType eBufType,
75
                             int nBandCount, BANDMAP_TYPE panBandMap,
76
                             GSpacing nPixelSpace, GSpacing nLineSpace,
77
                             GSpacing nBandSpace,
78
                             GDALRasterIOExtraArg *psExtraArg) override;
79
80
    bool ComputeQueryStrategy();
81
82
    bool Open(GDALOpenInfo *poOpenInfo);
83
};
84
85
/************************************************************************/
86
/*                        GDALEEDAIRasterBand                           */
87
/************************************************************************/
88
89
class GDALEEDAIRasterBand final : public GDALRasterBand
90
{
91
    CPL_DISALLOW_COPY_ASSIGN(GDALEEDAIRasterBand)
92
93
    friend class GDALEEDAIDataset;
94
95
    GDALColorInterp m_eInterp;
96
97
    bool DecodeNPYArray(const GByte *pabyData, int nDataLen,
98
                        bool bQueryAllBands, void *pDstBuffer, int nBlockXOff,
99
                        int nBlockYOff, int nXBlocks, int nYBlocks,
100
                        int nReqXSize, int nReqYSize) const;
101
    bool DecodeGDALDataset(const GByte *pabyData, int nDataLen,
102
                           bool bQueryAllBands, void *pDstBuffer,
103
                           int nBlockXOff, int nBlockYOff, int nXBlocks,
104
                           int nYBlocks, int nReqXSize, int nReqYSize);
105
106
    CPLErr GetBlocks(int nBlockXOff, int nBlockYOff, int nXBlocks, int nYBlocks,
107
                     bool bQueryAllBands, void *pBuffer);
108
    GUInt32 PrefetchBlocks(int nXOff, int nYOff, int nXSize, int nYSize,
109
                           int nBufXSize, int nBufYSize, bool bQueryAllBands);
110
111
  public:
112
    GDALEEDAIRasterBand(GDALEEDAIDataset *poDSIn, GDALDataType eDT);
113
    virtual ~GDALEEDAIRasterBand();
114
115
    virtual CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
116
                             int nXSize, int nYSize, void *pData, int nBufXSize,
117
                             int nBufYSize, GDALDataType eBufType,
118
                             GSpacing nPixelSpace, GSpacing nLineSpace,
119
                             GDALRasterIOExtraArg *psExtraArg) CPL_OVERRIDE;
120
121
    virtual CPLErr IReadBlock(int, int, void *) CPL_OVERRIDE;
122
    virtual int GetOverviewCount() CPL_OVERRIDE;
123
    virtual GDALRasterBand *GetOverview(int) CPL_OVERRIDE;
124
125
    virtual CPLErr SetColorInterpretation(GDALColorInterp eInterp) CPL_OVERRIDE
126
0
    {
127
0
        m_eInterp = eInterp;
128
0
        return CE_None;
129
0
    }
130
131
    virtual GDALColorInterp GetColorInterpretation() CPL_OVERRIDE
132
0
    {
133
0
        return m_eInterp;
134
0
    }
135
};
136
137
/************************************************************************/
138
/*                         GDALEEDAIDataset()                           */
139
/************************************************************************/
140
141
GDALEEDAIDataset::GDALEEDAIDataset()
142
0
    : m_nBlockSize(DEFAULT_BLOCK_SIZE), m_poParentDS(nullptr),
143
#ifdef DEBUG_VERBOSE
144
      m_iOvrLevel(0),
145
#endif
146
0
      m_bQueryMultipleBands(false)
147
0
{
148
0
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
149
0
    m_adfGeoTransform[0] = 0.0;
150
0
    m_adfGeoTransform[1] = 1.0;
151
0
    m_adfGeoTransform[2] = 0.0;
152
0
    m_adfGeoTransform[3] = 0.0;
153
0
    m_adfGeoTransform[4] = 0.0;
154
0
    m_adfGeoTransform[5] = 1.0;
155
0
}
156
157
/************************************************************************/
158
/*                         GDALEEDAIDataset()                           */
159
/************************************************************************/
160
161
GDALEEDAIDataset::GDALEEDAIDataset(GDALEEDAIDataset *poParentDS, int iOvrLevel)
162
0
    : m_nBlockSize(poParentDS->m_nBlockSize), m_osAsset(poParentDS->m_osAsset),
163
0
      m_osAssetName(poParentDS->m_osAssetName), m_poParentDS(poParentDS),
164
#ifdef DEBUG_VERBOSE
165
      m_iOvrLevel(iOvrLevel),
166
#endif
167
0
      m_osPixelEncoding(poParentDS->m_osPixelEncoding),
168
0
      m_bQueryMultipleBands(poParentDS->m_bQueryMultipleBands),
169
0
      m_oSRS(poParentDS->m_oSRS)
170
0
{
171
0
    m_osBaseURL = poParentDS->m_osBaseURL;
172
0
    nRasterXSize = m_poParentDS->nRasterXSize >> iOvrLevel;
173
0
    nRasterYSize = m_poParentDS->nRasterYSize >> iOvrLevel;
174
0
    m_adfGeoTransform[0] = m_poParentDS->m_adfGeoTransform[0];
175
0
    m_adfGeoTransform[1] = m_poParentDS->m_adfGeoTransform[1] *
176
0
                           m_poParentDS->nRasterXSize / nRasterXSize;
177
0
    m_adfGeoTransform[2] = m_poParentDS->m_adfGeoTransform[2];
178
0
    m_adfGeoTransform[3] = m_poParentDS->m_adfGeoTransform[3];
179
0
    m_adfGeoTransform[4] = m_poParentDS->m_adfGeoTransform[4];
180
0
    m_adfGeoTransform[5] = m_poParentDS->m_adfGeoTransform[5] *
181
0
                           m_poParentDS->nRasterYSize / nRasterYSize;
182
0
}
183
184
/************************************************************************/
185
/*                        ~GDALEEDAIDataset()                           */
186
/************************************************************************/
187
188
GDALEEDAIDataset::~GDALEEDAIDataset()
189
0
{
190
0
    for (size_t i = 0; i < m_apoOverviewDS.size(); i++)
191
0
    {
192
0
        delete m_apoOverviewDS[i];
193
0
    }
194
0
}
195
196
/************************************************************************/
197
/*                        GDALEEDAIRasterBand()                         */
198
/************************************************************************/
199
200
GDALEEDAIRasterBand::GDALEEDAIRasterBand(GDALEEDAIDataset *poDSIn,
201
                                         GDALDataType eDT)
202
0
    : m_eInterp(GCI_Undefined)
203
0
{
204
0
    eDataType = eDT;
205
0
    nBlockXSize = poDSIn->m_nBlockSize;
206
0
    nBlockYSize = poDSIn->m_nBlockSize;
207
0
}
208
209
/************************************************************************/
210
/*                       ~GDALEEDAIRasterBand()                         */
211
/************************************************************************/
212
213
GDALEEDAIRasterBand::~GDALEEDAIRasterBand()
214
0
{
215
0
}
216
217
/************************************************************************/
218
/*                           GetOverviewCount()                         */
219
/************************************************************************/
220
221
int GDALEEDAIRasterBand::GetOverviewCount()
222
0
{
223
0
    GDALEEDAIDataset *poGDS = reinterpret_cast<GDALEEDAIDataset *>(poDS);
224
0
    return static_cast<int>(poGDS->m_apoOverviewDS.size());
225
0
}
226
227
/************************************************************************/
228
/*                              GetOverview()                           */
229
/************************************************************************/
230
231
GDALRasterBand *GDALEEDAIRasterBand::GetOverview(int iIndex)
232
0
{
233
0
    GDALEEDAIDataset *poGDS = reinterpret_cast<GDALEEDAIDataset *>(poDS);
234
0
    if (iIndex >= 0 && iIndex < static_cast<int>(poGDS->m_apoOverviewDS.size()))
235
0
    {
236
0
        return poGDS->m_apoOverviewDS[iIndex]->GetRasterBand(nBand);
237
0
    }
238
0
    return nullptr;
239
0
}
240
241
/************************************************************************/
242
/*                            DecodeNPYArray()                          */
243
/************************************************************************/
244
245
bool GDALEEDAIRasterBand::DecodeNPYArray(const GByte *pabyData, int nDataLen,
246
                                         bool bQueryAllBands, void *pDstBuffer,
247
                                         int nBlockXOff, int nBlockYOff,
248
                                         int nXBlocks, int nYBlocks,
249
                                         int nReqXSize, int nReqYSize) const
250
0
{
251
0
    GDALEEDAIDataset *poGDS = reinterpret_cast<GDALEEDAIDataset *>(poDS);
252
253
    // See https://docs.scipy.org/doc/numpy-1.13.0/neps/npy-format.html
254
    // for description of NPY array serialization format
255
0
    if (nDataLen < 10)
256
0
    {
257
0
        CPLError(CE_Failure, CPLE_AppDefined, "Non NPY array returned");
258
0
        return false;
259
0
    }
260
261
0
    if (memcmp(pabyData, "\x93NUMPY", 6) != 0)
262
0
    {
263
0
        CPLError(CE_Failure, CPLE_AppDefined, "Non NPY array returned");
264
0
        return false;
265
0
    }
266
0
    const int nVersionMajor = pabyData[6];
267
0
    if (nVersionMajor != 1)
268
0
    {
269
0
        CPLError(CE_Failure, CPLE_AppDefined,
270
0
                 "Only version 1 of NPY array supported. Here found %d",
271
0
                 nVersionMajor);
272
0
        return false;
273
0
    }
274
    // Ignore version minor
275
0
    const int nHeaderLen = pabyData[8] | (pabyData[9] << 8);
276
0
    if (nDataLen < 10 + nHeaderLen)
277
0
    {
278
0
        CPLError(CE_Failure, CPLE_AppDefined,
279
0
                 "Corrupted NPY array returned: not enough bytes for header");
280
0
        return false;
281
0
    }
282
283
#ifdef DEBUG
284
    CPLString osDescr;
285
    osDescr.assign(reinterpret_cast<const char *>(pabyData) + 10, nHeaderLen);
286
    // Should be something like
287
    // {'descr': [('B2', '<u2'), ('B3', '<u2'), ('B4', '<u2'), ('B8', '<u2'),
288
    // ('QA10', '<u2')], 'fortran_order': False, 'shape': (256, 256), }
289
    CPLDebug("EEDAI", "NPY descr: %s", osDescr.c_str());
290
    // TODO: validate that the descr is the one expected
291
#endif
292
293
0
    int nTotalDataTypeSize = 0;
294
0
    for (int i = 1; i <= poGDS->GetRasterCount(); i++)
295
0
    {
296
0
        if (bQueryAllBands || i == nBand)
297
0
        {
298
0
            nTotalDataTypeSize += GDALGetDataTypeSizeBytes(
299
0
                poGDS->GetRasterBand(i)->GetRasterDataType());
300
0
        }
301
0
    }
302
0
    int nDataSize = nTotalDataTypeSize * nReqXSize * nReqYSize;
303
0
    if (nDataLen < 10 + nHeaderLen + nDataSize)
304
0
    {
305
0
        CPLError(CE_Failure, CPLE_AppDefined,
306
0
                 "Corrupted NPY array returned: not enough bytes for payload. "
307
0
                 "%d needed, only %d found",
308
0
                 10 + nHeaderLen + nDataSize, nDataLen);
309
0
        return false;
310
0
    }
311
0
    else if (nDataLen > 10 + nHeaderLen + nDataSize)
312
0
    {
313
0
        CPLError(CE_Warning, CPLE_AppDefined,
314
0
                 "Possibly corrupted NPY array returned: "
315
0
                 "expected bytes for payload. "
316
0
                 "%d needed, got %d found",
317
0
                 10 + nHeaderLen + nDataSize, nDataLen);
318
0
    }
319
320
0
    for (int iYBlock = 0; iYBlock < nYBlocks; iYBlock++)
321
0
    {
322
0
        int nBlockActualYSize = nBlockYSize;
323
0
        if ((iYBlock + nBlockYOff + 1) * nBlockYSize > nRasterYSize)
324
0
        {
325
0
            nBlockActualYSize =
326
0
                nRasterYSize - (iYBlock + nBlockYOff) * nBlockYSize;
327
0
        }
328
329
0
        for (int iXBlock = 0; iXBlock < nXBlocks; iXBlock++)
330
0
        {
331
0
            int nBlockActualXSize = nBlockXSize;
332
0
            if ((iXBlock + nBlockXOff + 1) * nBlockXSize > nRasterXSize)
333
0
            {
334
0
                nBlockActualXSize =
335
0
                    nRasterXSize - (iXBlock + nBlockXOff) * nBlockXSize;
336
0
            }
337
338
0
            int nOffsetBand =
339
0
                10 + nHeaderLen +
340
0
                (iYBlock * nBlockYSize * nReqXSize + iXBlock * nBlockXSize) *
341
0
                    nTotalDataTypeSize;
342
343
0
            for (int i = 1; i <= poGDS->GetRasterCount(); i++)
344
0
            {
345
0
                GDALRasterBlock *poBlock = nullptr;
346
0
                GByte *pabyDstBuffer;
347
0
                if (i == nBand && pDstBuffer != nullptr)
348
0
                    pabyDstBuffer = reinterpret_cast<GByte *>(pDstBuffer);
349
0
                else if (bQueryAllBands ||
350
0
                         (i == nBand && pDstBuffer == nullptr))
351
0
                {
352
0
                    GDALEEDAIRasterBand *poOtherBand =
353
0
                        reinterpret_cast<GDALEEDAIRasterBand *>(
354
0
                            poGDS->GetRasterBand(i));
355
0
                    poBlock = poOtherBand->TryGetLockedBlockRef(
356
0
                        nBlockXOff + iXBlock, nBlockYOff + iYBlock);
357
0
                    if (poBlock != nullptr)
358
0
                    {
359
0
                        poBlock->DropLock();
360
0
                        continue;
361
0
                    }
362
0
                    poBlock = poOtherBand->GetLockedBlockRef(
363
0
                        nBlockXOff + iXBlock, nBlockYOff + iYBlock, TRUE);
364
0
                    if (poBlock == nullptr)
365
0
                    {
366
0
                        continue;
367
0
                    }
368
0
                    pabyDstBuffer =
369
0
                        reinterpret_cast<GByte *>(poBlock->GetDataRef());
370
0
                }
371
0
                else
372
0
                {
373
0
                    continue;
374
0
                }
375
376
0
                GDALDataType eDT = poGDS->GetRasterBand(i)->GetRasterDataType();
377
0
                const int nDTSize = GDALGetDataTypeSizeBytes(eDT);
378
379
0
                for (int iLine = 0; iLine < nBlockActualYSize; iLine++)
380
0
                {
381
0
                    GByte *pabyLineDest =
382
0
                        pabyDstBuffer + iLine * nDTSize * nBlockXSize;
383
0
                    GDALCopyWords(const_cast<GByte *>(pabyData) + nOffsetBand +
384
0
                                      iLine * nTotalDataTypeSize * nReqXSize,
385
0
                                  eDT, nTotalDataTypeSize, pabyLineDest, eDT,
386
0
                                  nDTSize, nBlockActualXSize);
387
#ifdef CPL_MSB
388
                    if (nDTSize > 1)
389
                    {
390
                        GDALSwapWords(pabyLineDest, nDTSize, nBlockActualXSize,
391
                                      nDTSize);
392
                    }
393
#endif
394
0
                }
395
396
0
                nOffsetBand += nDTSize;
397
398
0
                if (poBlock)
399
0
                    poBlock->DropLock();
400
0
            }
401
0
        }
402
0
    }
403
0
    return true;
404
0
}
405
406
/************************************************************************/
407
/*                            DecodeGDALDataset()                         */
408
/************************************************************************/
409
410
bool GDALEEDAIRasterBand::DecodeGDALDataset(const GByte *pabyData, int nDataLen,
411
                                            bool bQueryAllBands,
412
                                            void *pDstBuffer, int nBlockXOff,
413
                                            int nBlockYOff, int nXBlocks,
414
                                            int nYBlocks, int nReqXSize,
415
                                            int nReqYSize)
416
0
{
417
0
    GDALEEDAIDataset *poGDS = reinterpret_cast<GDALEEDAIDataset *>(poDS);
418
419
0
    const CPLString osTmpFilename(VSIMemGenerateHiddenFilename("eedai"));
420
0
    VSIFCloseL(VSIFileFromMemBuffer(
421
0
        osTmpFilename, const_cast<GByte *>(pabyData), nDataLen, false));
422
0
    const char *const apszDrivers[] = {"PNG", "JPEG", "GTIFF", nullptr};
423
0
    GDALDataset *poTileDS = GDALDataset::FromHandle(GDALOpenEx(
424
0
        osTmpFilename, GDAL_OF_RASTER, apszDrivers, nullptr, nullptr));
425
0
    if (poTileDS == nullptr)
426
0
    {
427
0
        CPLError(CE_Failure, CPLE_AppDefined,
428
0
                 "Cannot decode buffer returned by the "
429
0
                 "server as a PNG, JPEG or GeoTIFF image");
430
0
        VSIUnlink(osTmpFilename);
431
0
        return false;
432
0
    }
433
0
    if (poTileDS->GetRasterXSize() != nReqXSize ||
434
0
        poTileDS->GetRasterYSize() != nReqYSize ||
435
        // The server might return a RGBA image even if only 3 bands are
436
        // requested
437
0
        poTileDS->GetRasterCount() <
438
0
            (bQueryAllBands ? poGDS->GetRasterCount() : 1))
439
0
    {
440
0
        CPLError(CE_Failure, CPLE_AppDefined,
441
0
                 "Bad dimensions/band count for image returned "
442
0
                 "by server: %dx%dx%d",
443
0
                 poTileDS->GetRasterXSize(), poTileDS->GetRasterYSize(),
444
0
                 poTileDS->GetRasterCount());
445
0
        delete poTileDS;
446
0
        VSIUnlink(osTmpFilename);
447
0
        return false;
448
0
    }
449
450
0
    for (int iYBlock = 0; iYBlock < nYBlocks; iYBlock++)
451
0
    {
452
0
        int nBlockActualYSize = nBlockYSize;
453
0
        if ((iYBlock + nBlockYOff + 1) * nBlockYSize > nRasterYSize)
454
0
        {
455
0
            nBlockActualYSize =
456
0
                nRasterYSize - (iYBlock + nBlockYOff) * nBlockYSize;
457
0
        }
458
459
0
        for (int iXBlock = 0; iXBlock < nXBlocks; iXBlock++)
460
0
        {
461
0
            int nBlockActualXSize = nBlockXSize;
462
0
            if ((iXBlock + nBlockXOff + 1) * nBlockXSize > nRasterXSize)
463
0
            {
464
0
                nBlockActualXSize =
465
0
                    nRasterXSize - (iXBlock + nBlockXOff) * nBlockXSize;
466
0
            }
467
468
0
            for (int i = 1; i <= poGDS->GetRasterCount(); i++)
469
0
            {
470
0
                GDALRasterBlock *poBlock = nullptr;
471
0
                GByte *pabyDstBuffer;
472
0
                if (i == nBand && pDstBuffer != nullptr)
473
0
                    pabyDstBuffer = reinterpret_cast<GByte *>(pDstBuffer);
474
0
                else if (bQueryAllBands ||
475
0
                         (i == nBand && pDstBuffer == nullptr))
476
0
                {
477
0
                    GDALEEDAIRasterBand *poOtherBand =
478
0
                        reinterpret_cast<GDALEEDAIRasterBand *>(
479
0
                            poGDS->GetRasterBand(i));
480
0
                    poBlock = poOtherBand->TryGetLockedBlockRef(
481
0
                        nBlockXOff + iXBlock, nBlockYOff + iYBlock);
482
0
                    if (poBlock != nullptr)
483
0
                    {
484
0
                        poBlock->DropLock();
485
0
                        continue;
486
0
                    }
487
0
                    poBlock = poOtherBand->GetLockedBlockRef(
488
0
                        nBlockXOff + iXBlock, nBlockYOff + iYBlock, TRUE);
489
0
                    if (poBlock == nullptr)
490
0
                    {
491
0
                        continue;
492
0
                    }
493
0
                    pabyDstBuffer =
494
0
                        reinterpret_cast<GByte *>(poBlock->GetDataRef());
495
0
                }
496
0
                else
497
0
                {
498
0
                    continue;
499
0
                }
500
501
0
                GDALDataType eDT = poGDS->GetRasterBand(i)->GetRasterDataType();
502
0
                const int nDTSize = GDALGetDataTypeSizeBytes(eDT);
503
0
                const int nTileBand = bQueryAllBands ? i : 1;
504
0
                CPLErr eErr = poTileDS->GetRasterBand(nTileBand)->RasterIO(
505
0
                    GF_Read, iXBlock * nBlockXSize, iYBlock * nBlockYSize,
506
0
                    nBlockActualXSize, nBlockActualYSize, pabyDstBuffer,
507
0
                    nBlockActualXSize, nBlockActualYSize, eDT, nDTSize,
508
0
                    static_cast<GSpacing>(nDTSize) * nBlockXSize, nullptr);
509
510
0
                if (poBlock)
511
0
                    poBlock->DropLock();
512
0
                if (eErr != CE_None)
513
0
                {
514
0
                    delete poTileDS;
515
0
                    VSIUnlink(osTmpFilename);
516
0
                    return false;
517
0
                }
518
0
            }
519
0
        }
520
0
    }
521
522
0
    delete poTileDS;
523
0
    VSIUnlink(osTmpFilename);
524
0
    return true;
525
0
}
526
527
CPLErr GDALEEDAIRasterBand::GetBlocks(int nBlockXOff, int nBlockYOff,
528
                                      int nXBlocks, int nYBlocks,
529
                                      bool bQueryAllBands, void *pBuffer)
530
0
{
531
0
    GDALEEDAIDataset *poGDS = reinterpret_cast<GDALEEDAIDataset *>(poDS);
532
533
    // Build request content
534
0
    json_object *poReq = json_object_new_object();
535
0
    json_object_object_add(poReq, "fileFormat",
536
0
                           json_object_new_string(poGDS->m_osPixelEncoding));
537
0
    json_object *poBands = json_object_new_array();
538
0
    for (int i = 1; i <= poGDS->GetRasterCount(); i++)
539
0
    {
540
0
        if (bQueryAllBands || i == nBand)
541
0
        {
542
0
            json_object_array_add(
543
0
                poBands, json_object_new_string(
544
0
                             poGDS->GetRasterBand(i)->GetDescription()));
545
0
        }
546
0
    }
547
0
    json_object_object_add(poReq, "bandIds", poBands);
548
549
0
    int nReqXSize = nBlockXSize * nXBlocks;
550
0
    if ((nBlockXOff + nXBlocks) * nBlockXSize > nRasterXSize)
551
0
        nReqXSize = nRasterXSize - nBlockXOff * nBlockXSize;
552
0
    int nReqYSize = nBlockYSize * nYBlocks;
553
0
    if ((nBlockYOff + nYBlocks) * nBlockYSize > nRasterYSize)
554
0
        nReqYSize = nRasterYSize - nBlockYOff * nBlockYSize;
555
0
    const double dfX0 = poGDS->m_adfGeoTransform[0] +
556
0
                        nBlockXOff * nBlockXSize * poGDS->m_adfGeoTransform[1];
557
0
    const double dfY0 = poGDS->m_adfGeoTransform[3] +
558
0
                        nBlockYOff * nBlockYSize * poGDS->m_adfGeoTransform[5];
559
#ifdef DEBUG_VERBOSE
560
    CPLDebug("EEDAI",
561
             "nBlockYOff=%d nBlockYOff=%d "
562
             "nXBlocks=%d nYBlocks=%d nReqXSize=%d nReqYSize=%d",
563
             nBlockYOff, nBlockYOff, nXBlocks, nYBlocks, nReqXSize, nReqYSize);
564
#endif
565
566
0
    json_object *poPixelGrid = json_object_new_object();
567
568
0
    json_object *poAffineTransform = json_object_new_object();
569
0
    json_object_object_add(
570
0
        poAffineTransform, "translateX",
571
0
        json_object_new_double_with_significant_figures(dfX0, 18));
572
0
    json_object_object_add(
573
0
        poAffineTransform, "translateY",
574
0
        json_object_new_double_with_significant_figures(dfY0, 18));
575
0
    json_object_object_add(poAffineTransform, "scaleX",
576
0
                           json_object_new_double_with_significant_figures(
577
0
                               poGDS->m_adfGeoTransform[1], 18));
578
0
    json_object_object_add(poAffineTransform, "scaleY",
579
0
                           json_object_new_double_with_significant_figures(
580
0
                               poGDS->m_adfGeoTransform[5], 18));
581
0
    json_object_object_add(
582
0
        poAffineTransform, "shearX",
583
0
        json_object_new_double_with_significant_figures(0.0, 18));
584
0
    json_object_object_add(
585
0
        poAffineTransform, "shearY",
586
0
        json_object_new_double_with_significant_figures(0.0, 18));
587
0
    json_object_object_add(poPixelGrid, "affineTransform", poAffineTransform);
588
589
0
    json_object *poDimensions = json_object_new_object();
590
0
    json_object_object_add(poDimensions, "width",
591
0
                           json_object_new_int(nReqXSize));
592
0
    json_object_object_add(poDimensions, "height",
593
0
                           json_object_new_int(nReqYSize));
594
0
    json_object_object_add(poPixelGrid, "dimensions", poDimensions);
595
0
    json_object_object_add(poReq, "grid", poPixelGrid);
596
597
0
    CPLString osPostContent = json_object_get_string(poReq);
598
0
    json_object_put(poReq);
599
600
    // Issue request
601
0
    char **papszOptions = (poGDS->m_poParentDS)
602
0
                              ? poGDS->m_poParentDS->GetBaseHTTPOptions()
603
0
                              : poGDS->GetBaseHTTPOptions();
604
0
    papszOptions = CSLSetNameValue(papszOptions, "CUSTOMREQUEST", "POST");
605
0
    CPLString osHeaders = CSLFetchNameValueDef(papszOptions, "HEADERS", "");
606
0
    if (!osHeaders.empty())
607
0
        osHeaders += "\r\n";
608
0
    osHeaders += "Content-Type: application/json";
609
0
    papszOptions = CSLSetNameValue(papszOptions, "HEADERS", osHeaders);
610
0
    papszOptions = CSLSetNameValue(papszOptions, "POSTFIELDS", osPostContent);
611
0
    CPLHTTPResult *psResult = EEDAHTTPFetch(
612
0
        (poGDS->m_osBaseURL + poGDS->m_osAssetName + ":getPixels").c_str(),
613
0
        papszOptions);
614
0
    CSLDestroy(papszOptions);
615
0
    if (psResult == nullptr)
616
0
        return CE_Failure;
617
618
0
    if (psResult->pszErrBuf != nullptr)
619
0
    {
620
0
        if (psResult->pabyData)
621
0
        {
622
0
            CPLError(CE_Failure, CPLE_AppDefined, "%s: %s", psResult->pszErrBuf,
623
0
                     reinterpret_cast<const char *>(psResult->pabyData));
624
0
        }
625
0
        else
626
0
        {
627
0
            CPLError(CE_Failure, CPLE_AppDefined, "%s", psResult->pszErrBuf);
628
0
        }
629
0
        CPLHTTPDestroyResult(psResult);
630
0
        return CE_Failure;
631
0
    }
632
633
0
    if (psResult->pabyData == nullptr)
634
0
    {
635
0
        CPLError(CE_Failure, CPLE_AppDefined,
636
0
                 "Empty content returned by server");
637
0
        CPLHTTPDestroyResult(psResult);
638
0
        return CE_Failure;
639
0
    }
640
#ifdef DEBUG_VERBOSE
641
    CPLDebug("EEADI", "Result: %s (%d bytes)",
642
             reinterpret_cast<const char *>(psResult->pabyData),
643
             psResult->nDataLen);
644
#endif
645
646
0
    if (EQUAL(poGDS->m_osPixelEncoding, "NPY"))
647
0
    {
648
0
        if (!DecodeNPYArray(psResult->pabyData, psResult->nDataLen,
649
0
                            bQueryAllBands, pBuffer, nBlockXOff, nBlockYOff,
650
0
                            nXBlocks, nYBlocks, nReqXSize, nReqYSize))
651
0
        {
652
0
            CPLHTTPDestroyResult(psResult);
653
0
            return CE_Failure;
654
0
        }
655
0
    }
656
0
    else
657
0
    {
658
0
        if (!DecodeGDALDataset(psResult->pabyData, psResult->nDataLen,
659
0
                               bQueryAllBands, pBuffer, nBlockXOff, nBlockYOff,
660
0
                               nXBlocks, nYBlocks, nReqXSize, nReqYSize))
661
0
        {
662
0
            CPLHTTPDestroyResult(psResult);
663
0
            return CE_Failure;
664
0
        }
665
0
    }
666
667
0
    CPLHTTPDestroyResult(psResult);
668
669
0
    return CE_None;
670
0
}
671
672
/************************************************************************/
673
/*                             IReadBlock()                             */
674
/************************************************************************/
675
676
CPLErr GDALEEDAIRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
677
                                       void *pBuffer)
678
0
{
679
0
    GDALEEDAIDataset *poGDS = reinterpret_cast<GDALEEDAIDataset *>(poDS);
680
#ifdef DEBUG_VERBOSE
681
    CPLDebug("EEDAI", "ReadBlock x=%d y=%d band=%d level=%d", nBlockXOff,
682
             nBlockYOff, nBand, poGDS->m_iOvrLevel);
683
#endif
684
685
0
    return GetBlocks(nBlockXOff, nBlockYOff, 1, 1, poGDS->m_bQueryMultipleBands,
686
0
                     pBuffer);
687
0
}
688
689
/************************************************************************/
690
/*                          PrefetchBlocks()                            */
691
/************************************************************************/
692
693
// Return or'ed flags among 0, RETRY_PER_BAND, RETRY_SPATIAL_SPLIT if the user
694
// should try to split the request in smaller chunks
695
696
GUInt32 GDALEEDAIRasterBand::PrefetchBlocks(int nXOff, int nYOff, int nXSize,
697
                                            int nYSize, int nBufXSize,
698
                                            int nBufYSize, bool bQueryAllBands)
699
0
{
700
0
    CPL_IGNORE_RET_VAL(nBufXSize);
701
0
    CPL_IGNORE_RET_VAL(nBufYSize);
702
703
0
    GDALEEDAIDataset *poGDS = reinterpret_cast<GDALEEDAIDataset *>(poDS);
704
0
    int nBlockXOff = nXOff / nBlockXSize;
705
0
    int nBlockYOff = nYOff / nBlockYSize;
706
0
    int nXBlocks = (nXOff + nXSize - 1) / nBlockXSize - nBlockXOff + 1;
707
0
    int nYBlocks = (nYOff + nYSize - 1) / nBlockYSize - nBlockYOff + 1;
708
709
0
    const int nThisDTSize = GDALGetDataTypeSizeBytes(GetRasterDataType());
710
0
    int nTotalDataTypeSize = 0;
711
0
    int nQueriedBands = 0;
712
0
    for (int i = 1; i <= poGDS->GetRasterCount(); i++)
713
0
    {
714
0
        if (bQueryAllBands || i == nBand)
715
0
        {
716
0
            nQueriedBands++;
717
0
            nTotalDataTypeSize += GDALGetDataTypeSizeBytes(
718
0
                poGDS->GetRasterBand(i)->GetRasterDataType());
719
0
        }
720
0
    }
721
722
    // Check the number of already cached blocks, and remove fully
723
    // cached lines at the top of the area of interest from the queried
724
    // blocks
725
0
    int nBlocksCached = 0;
726
0
    int nBlocksCachedForThisBand = 0;
727
0
    bool bAllLineCached = true;
728
0
    for (int iYBlock = 0; iYBlock < nYBlocks;)
729
0
    {
730
0
        for (int iXBlock = 0; iXBlock < nXBlocks; iXBlock++)
731
0
        {
732
0
            for (int i = 1; i <= poGDS->GetRasterCount(); i++)
733
0
            {
734
0
                GDALRasterBlock *poBlock = nullptr;
735
0
                if (bQueryAllBands || i == nBand)
736
0
                {
737
0
                    GDALEEDAIRasterBand *poOtherBand =
738
0
                        reinterpret_cast<GDALEEDAIRasterBand *>(
739
0
                            poGDS->GetRasterBand(i));
740
0
                    poBlock = poOtherBand->TryGetLockedBlockRef(
741
0
                        nBlockXOff + iXBlock, nBlockYOff + iYBlock);
742
0
                    if (poBlock != nullptr)
743
0
                    {
744
0
                        nBlocksCached++;
745
0
                        if (i == nBand)
746
0
                            nBlocksCachedForThisBand++;
747
0
                        poBlock->DropLock();
748
0
                        continue;
749
0
                    }
750
0
                    else
751
0
                    {
752
0
                        bAllLineCached = false;
753
0
                    }
754
0
                }
755
0
            }
756
0
        }
757
758
0
        if (bAllLineCached)
759
0
        {
760
0
            nBlocksCached -= nXBlocks * nQueriedBands;
761
0
            nBlocksCachedForThisBand -= nXBlocks;
762
0
            nBlockYOff++;
763
0
            nYBlocks--;
764
0
        }
765
0
        else
766
0
        {
767
0
            iYBlock++;
768
0
        }
769
0
    }
770
771
0
    if (nXBlocks > 0 && nYBlocks > 0)
772
0
    {
773
0
        bool bMustReturn = false;
774
0
        GUInt32 nRetryFlags = 0;
775
776
        // Get the blocks if the number of already cached blocks is lesser
777
        // than 25% of the to be queried blocks
778
0
        if (nBlocksCached > (nQueriedBands * nXBlocks * nYBlocks) / 4)
779
0
        {
780
0
            if (nBlocksCachedForThisBand <= (nXBlocks * nYBlocks) / 4)
781
0
            {
782
0
                nRetryFlags |= RETRY_PER_BAND;
783
0
            }
784
0
            else
785
0
            {
786
0
                bMustReturn = true;
787
0
            }
788
0
        }
789
790
        // Don't request too many pixels in one dimension
791
0
        if (nXBlocks * nBlockXSize > SERVER_DIMENSION_LIMIT ||
792
0
            nYBlocks * nBlockYSize > SERVER_DIMENSION_LIMIT)
793
0
        {
794
0
            bMustReturn = true;
795
0
            nRetryFlags |= RETRY_SPATIAL_SPLIT;
796
0
        }
797
798
        // Make sure that we have enough cache (with a margin of 50%)
799
        // and the number of queried pixels isn't too big w.r.t server
800
        // limit
801
0
        const GIntBig nUncompressedSize = static_cast<GIntBig>(nXBlocks) *
802
0
                                          nYBlocks * nBlockXSize * nBlockYSize *
803
0
                                          nTotalDataTypeSize;
804
0
        const GIntBig nCacheMax = GDALGetCacheMax64() / 2;
805
0
        if (nUncompressedSize > nCacheMax ||
806
0
            nUncompressedSize > SERVER_BYTE_LIMIT)
807
0
        {
808
0
            if (bQueryAllBands && poGDS->GetRasterCount() > 1)
809
0
            {
810
0
                const GIntBig nUncompressedSizeThisBand =
811
0
                    static_cast<GIntBig>(nXBlocks) * nYBlocks * nBlockXSize *
812
0
                    nBlockYSize * nThisDTSize;
813
0
                if (nUncompressedSizeThisBand <= SERVER_BYTE_LIMIT &&
814
0
                    nUncompressedSizeThisBand <= nCacheMax)
815
0
                {
816
0
                    nRetryFlags |= RETRY_PER_BAND;
817
0
                }
818
0
            }
819
0
            if (nXBlocks > 1 || nYBlocks > 1)
820
0
            {
821
0
                nRetryFlags |= RETRY_SPATIAL_SPLIT;
822
0
            }
823
0
            return nRetryFlags;
824
0
        }
825
0
        if (bMustReturn)
826
0
            return nRetryFlags;
827
828
0
        GetBlocks(nBlockXOff, nBlockYOff, nXBlocks, nYBlocks, bQueryAllBands,
829
0
                  nullptr);
830
0
    }
831
832
0
    return 0;
833
0
}
834
835
/************************************************************************/
836
/*                              IRasterIO()                             */
837
/************************************************************************/
838
839
CPLErr GDALEEDAIRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
840
                                      int nXSize, int nYSize, void *pData,
841
                                      int nBufXSize, int nBufYSize,
842
                                      GDALDataType eBufType,
843
                                      GSpacing nPixelSpace, GSpacing nLineSpace,
844
                                      GDALRasterIOExtraArg *psExtraArg)
845
846
0
{
847
848
    /* ==================================================================== */
849
    /*      Do we have overviews that would be appropriate to satisfy       */
850
    /*      this request?                                                   */
851
    /* ==================================================================== */
852
0
    if ((nBufXSize < nXSize || nBufYSize < nYSize) && GetOverviewCount() > 0 &&
853
0
        eRWFlag == GF_Read)
854
0
    {
855
0
        GDALRasterIOExtraArg sExtraArg;
856
0
        GDALCopyRasterIOExtraArg(&sExtraArg, psExtraArg);
857
858
0
        const int nOverview =
859
0
            GDALBandGetBestOverviewLevel2(this, nXOff, nYOff, nXSize, nYSize,
860
0
                                          nBufXSize, nBufYSize, &sExtraArg);
861
0
        if (nOverview >= 0)
862
0
        {
863
0
            GDALRasterBand *poOverviewBand = GetOverview(nOverview);
864
0
            if (poOverviewBand == nullptr)
865
0
                return CE_Failure;
866
867
0
            return poOverviewBand->RasterIO(
868
0
                eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize,
869
0
                nBufYSize, eBufType, nPixelSpace, nLineSpace, &sExtraArg);
870
0
        }
871
0
    }
872
873
0
    GDALEEDAIDataset *poGDS = reinterpret_cast<GDALEEDAIDataset *>(poDS);
874
0
    GUInt32 nRetryFlags =
875
0
        PrefetchBlocks(nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize,
876
0
                       poGDS->m_bQueryMultipleBands);
877
0
    if ((nRetryFlags & RETRY_SPATIAL_SPLIT) && nXSize == nBufXSize &&
878
0
        nYSize == nBufYSize && nYSize > nBlockYSize)
879
0
    {
880
0
        GDALRasterIOExtraArg sExtraArg;
881
0
        INIT_RASTERIO_EXTRA_ARG(sExtraArg);
882
883
0
        int nHalf =
884
0
            std::max(nBlockYSize, ((nYSize / 2) / nBlockYSize) * nBlockYSize);
885
0
        CPLErr eErr =
886
0
            IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nHalf, pData, nXSize,
887
0
                      nHalf, eBufType, nPixelSpace, nLineSpace, &sExtraArg);
888
0
        if (eErr == CE_None)
889
0
        {
890
0
            eErr = IRasterIO(
891
0
                eRWFlag, nXOff, nYOff + nHalf, nXSize, nYSize - nHalf,
892
0
                static_cast<GByte *>(pData) + nHalf * nLineSpace, nXSize,
893
0
                nYSize - nHalf, eBufType, nPixelSpace, nLineSpace, &sExtraArg);
894
0
        }
895
0
        return eErr;
896
0
    }
897
0
    else if ((nRetryFlags & RETRY_SPATIAL_SPLIT) && nXSize == nBufXSize &&
898
0
             nYSize == nBufYSize && nXSize > nBlockXSize)
899
0
    {
900
0
        GDALRasterIOExtraArg sExtraArg;
901
0
        INIT_RASTERIO_EXTRA_ARG(sExtraArg);
902
903
0
        int nHalf =
904
0
            std::max(nBlockXSize, ((nXSize / 2) / nBlockXSize) * nBlockXSize);
905
0
        CPLErr eErr =
906
0
            IRasterIO(eRWFlag, nXOff, nYOff, nHalf, nYSize, pData, nHalf,
907
0
                      nYSize, eBufType, nPixelSpace, nLineSpace, &sExtraArg);
908
0
        if (eErr == CE_None)
909
0
        {
910
0
            eErr =
911
0
                IRasterIO(eRWFlag, nXOff + nHalf, nYOff, nXSize - nHalf, nYSize,
912
0
                          static_cast<GByte *>(pData) + nHalf * nPixelSpace,
913
0
                          nXSize - nHalf, nYSize, eBufType, nPixelSpace,
914
0
                          nLineSpace, &sExtraArg);
915
0
        }
916
0
        return eErr;
917
0
    }
918
0
    else if ((nRetryFlags & RETRY_PER_BAND) && poGDS->m_bQueryMultipleBands &&
919
0
             poGDS->nBands > 1)
920
0
    {
921
0
        CPL_IGNORE_RET_VAL(PrefetchBlocks(nXOff, nYOff, nXSize, nYSize,
922
0
                                          nBufXSize, nBufYSize, false));
923
0
    }
924
925
0
    return GDALRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
926
0
                                     pData, nBufXSize, nBufYSize, eBufType,
927
0
                                     nPixelSpace, nLineSpace, psExtraArg);
928
0
}
929
930
/************************************************************************/
931
/*                              IRasterIO()                             */
932
/************************************************************************/
933
934
CPLErr GDALEEDAIDataset::IRasterIO(
935
    GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
936
    void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
937
    int nBandCount, BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
938
    GSpacing nLineSpace, GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
939
0
{
940
941
    /* ==================================================================== */
942
    /*      Do we have overviews that would be appropriate to satisfy       */
943
    /*      this request?                                                   */
944
    /* ==================================================================== */
945
0
    if ((nBufXSize < nXSize || nBufYSize < nYSize) &&
946
0
        GetRasterBand(1)->GetOverviewCount() > 0 && eRWFlag == GF_Read)
947
0
    {
948
0
        GDALRasterIOExtraArg sExtraArg;
949
0
        GDALCopyRasterIOExtraArg(&sExtraArg, psExtraArg);
950
951
0
        const int nOverview = GDALBandGetBestOverviewLevel2(
952
0
            GetRasterBand(1), nXOff, nYOff, nXSize, nYSize, nBufXSize,
953
0
            nBufYSize, &sExtraArg);
954
0
        if (nOverview >= 0)
955
0
        {
956
0
            GDALRasterBand *poOverviewBand =
957
0
                GetRasterBand(1)->GetOverview(nOverview);
958
0
            if (poOverviewBand == nullptr ||
959
0
                poOverviewBand->GetDataset() == nullptr)
960
0
            {
961
0
                return CE_Failure;
962
0
            }
963
964
0
            return poOverviewBand->GetDataset()->RasterIO(
965
0
                eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize,
966
0
                nBufYSize, eBufType, nBandCount, panBandMap, nPixelSpace,
967
0
                nLineSpace, nBandSpace, &sExtraArg);
968
0
        }
969
0
    }
970
971
0
    GDALEEDAIRasterBand *poBand =
972
0
        cpl::down_cast<GDALEEDAIRasterBand *>(GetRasterBand(1));
973
974
0
    GUInt32 nRetryFlags =
975
0
        poBand->PrefetchBlocks(nXOff, nYOff, nXSize, nYSize, nBufXSize,
976
0
                               nBufYSize, m_bQueryMultipleBands);
977
0
    int nBlockXSize, nBlockYSize;
978
0
    poBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
979
0
    if ((nRetryFlags & RETRY_SPATIAL_SPLIT) && nXSize == nBufXSize &&
980
0
        nYSize == nBufYSize && nYSize > nBlockYSize)
981
0
    {
982
0
        GDALRasterIOExtraArg sExtraArg;
983
0
        INIT_RASTERIO_EXTRA_ARG(sExtraArg);
984
985
0
        int nHalf =
986
0
            std::max(nBlockYSize, ((nYSize / 2) / nBlockYSize) * nBlockYSize);
987
0
        CPLErr eErr =
988
0
            IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nHalf, pData, nXSize,
989
0
                      nHalf, eBufType, nBandCount, panBandMap, nPixelSpace,
990
0
                      nLineSpace, nBandSpace, &sExtraArg);
991
0
        if (eErr == CE_None)
992
0
        {
993
0
            eErr = IRasterIO(
994
0
                eRWFlag, nXOff, nYOff + nHalf, nXSize, nYSize - nHalf,
995
0
                static_cast<GByte *>(pData) + nHalf * nLineSpace, nXSize,
996
0
                nYSize - nHalf, eBufType, nBandCount, panBandMap, nPixelSpace,
997
0
                nLineSpace, nBandSpace, &sExtraArg);
998
0
        }
999
0
        return eErr;
1000
0
    }
1001
0
    else if ((nRetryFlags & RETRY_SPATIAL_SPLIT) && nXSize == nBufXSize &&
1002
0
             nYSize == nBufYSize && nXSize > nBlockXSize)
1003
0
    {
1004
0
        GDALRasterIOExtraArg sExtraArg;
1005
0
        INIT_RASTERIO_EXTRA_ARG(sExtraArg);
1006
1007
0
        int nHalf =
1008
0
            std::max(nBlockXSize, ((nXSize / 2) / nBlockXSize) * nBlockXSize);
1009
0
        CPLErr eErr =
1010
0
            IRasterIO(eRWFlag, nXOff, nYOff, nHalf, nYSize, pData, nHalf,
1011
0
                      nYSize, eBufType, nBandCount, panBandMap, nPixelSpace,
1012
0
                      nLineSpace, nBandSpace, &sExtraArg);
1013
0
        if (eErr == CE_None)
1014
0
        {
1015
0
            eErr = IRasterIO(
1016
0
                eRWFlag, nXOff + nHalf, nYOff, nXSize - nHalf, nYSize,
1017
0
                static_cast<GByte *>(pData) + nHalf * nPixelSpace,
1018
0
                nXSize - nHalf, nYSize, eBufType, nBandCount, panBandMap,
1019
0
                nPixelSpace, nLineSpace, nBandSpace, &sExtraArg);
1020
0
        }
1021
0
        return eErr;
1022
0
    }
1023
0
    else if ((nRetryFlags & RETRY_PER_BAND) && m_bQueryMultipleBands &&
1024
0
             nBands > 1)
1025
0
    {
1026
0
        for (int iBand = 1; iBand <= nBands; iBand++)
1027
0
        {
1028
0
            poBand =
1029
0
                cpl::down_cast<GDALEEDAIRasterBand *>(GetRasterBand(iBand));
1030
0
            CPL_IGNORE_RET_VAL(poBand->PrefetchBlocks(
1031
0
                nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize, false));
1032
0
        }
1033
0
    }
1034
1035
0
    return GDALDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
1036
0
                                  nBufXSize, nBufYSize, eBufType, nBandCount,
1037
0
                                  panBandMap, nPixelSpace, nLineSpace,
1038
0
                                  nBandSpace, psExtraArg);
1039
0
}
1040
1041
/************************************************************************/
1042
/*                        ComputeQueryStrategy()                        */
1043
/************************************************************************/
1044
1045
bool GDALEEDAIDataset::ComputeQueryStrategy()
1046
0
{
1047
0
    m_bQueryMultipleBands = true;
1048
0
    m_osPixelEncoding.toupper();
1049
1050
0
    bool bHeterogeneousDataTypes = false;
1051
0
    if (nBands >= 2)
1052
0
    {
1053
0
        GDALDataType eDTFirstBand = GetRasterBand(1)->GetRasterDataType();
1054
0
        for (int i = 2; i <= nBands; i++)
1055
0
        {
1056
0
            if (GetRasterBand(i)->GetRasterDataType() != eDTFirstBand)
1057
0
            {
1058
0
                bHeterogeneousDataTypes = true;
1059
0
                break;
1060
0
            }
1061
0
        }
1062
0
    }
1063
1064
0
    if (EQUAL(m_osPixelEncoding, "AUTO"))
1065
0
    {
1066
0
        if (bHeterogeneousDataTypes)
1067
0
        {
1068
0
            m_osPixelEncoding = "NPY";
1069
0
        }
1070
0
        else
1071
0
        {
1072
0
            m_osPixelEncoding = "PNG";
1073
0
            for (int i = 1; i <= nBands; i++)
1074
0
            {
1075
0
                if (GetRasterBand(i)->GetRasterDataType() != GDT_Byte)
1076
0
                {
1077
0
                    m_osPixelEncoding = "GEO_TIFF";
1078
0
                }
1079
0
            }
1080
0
        }
1081
0
    }
1082
1083
0
    if (EQUAL(m_osPixelEncoding, "PNG") || EQUAL(m_osPixelEncoding, "JPEG") ||
1084
0
        EQUAL(m_osPixelEncoding, "AUTO_JPEG_PNG"))
1085
0
    {
1086
0
        if (nBands != 1 && nBands != 3)
1087
0
        {
1088
0
            m_bQueryMultipleBands = false;
1089
0
        }
1090
0
        for (int i = 1; i <= nBands; i++)
1091
0
        {
1092
0
            if (GetRasterBand(i)->GetRasterDataType() != GDT_Byte)
1093
0
            {
1094
0
                CPLError(
1095
0
                    CE_Failure, CPLE_NotSupported,
1096
0
                    "This dataset has non-Byte bands, which is incompatible "
1097
0
                    "with PIXEL_ENCODING=%s",
1098
0
                    m_osPixelEncoding.c_str());
1099
0
                return false;
1100
0
            }
1101
0
        }
1102
0
    }
1103
1104
0
    if (nBands > SERVER_SIMUTANEOUS_BAND_LIMIT)
1105
0
    {
1106
0
        m_bQueryMultipleBands = false;
1107
0
    }
1108
1109
0
    if (m_bQueryMultipleBands && m_osPixelEncoding != "NPY" &&
1110
0
        bHeterogeneousDataTypes)
1111
0
    {
1112
0
        CPLDebug("EEDAI",
1113
0
                 "%s PIXEL_ENCODING does not support heterogeneous data types. "
1114
0
                 "Falling back to querying band per band",
1115
0
                 m_osPixelEncoding.c_str());
1116
0
        m_bQueryMultipleBands = false;
1117
0
    }
1118
1119
0
    return true;
1120
0
}
1121
1122
/************************************************************************/
1123
/*                          GetSpatialRef()                             */
1124
/************************************************************************/
1125
1126
const OGRSpatialReference *GDALEEDAIDataset::GetSpatialRef() const
1127
0
{
1128
0
    return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
1129
0
}
1130
1131
/************************************************************************/
1132
/*                          GetGeoTransform()                           */
1133
/************************************************************************/
1134
1135
CPLErr GDALEEDAIDataset::GetGeoTransform(double *adfGeoTransform)
1136
0
{
1137
0
    memcpy(adfGeoTransform, m_adfGeoTransform, 6 * sizeof(double));
1138
0
    return CE_None;
1139
0
}
1140
1141
/************************************************************************/
1142
/*                               Open()                                 */
1143
/************************************************************************/
1144
1145
bool GDALEEDAIDataset::Open(GDALOpenInfo *poOpenInfo)
1146
0
{
1147
0
    m_osBaseURL = CPLGetConfigOption(
1148
0
        "EEDA_URL", "https://earthengine-highvolume.googleapis.com/v1alpha/");
1149
1150
0
    m_osAsset = CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "ASSET", "");
1151
0
    CPLString osBandList(
1152
0
        CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "BANDS", ""));
1153
0
    if (m_osAsset.empty())
1154
0
    {
1155
0
        char **papszTokens =
1156
0
            CSLTokenizeString2(poOpenInfo->pszFilename, ":", 0);
1157
0
        if (CSLCount(papszTokens) < 2)
1158
0
        {
1159
0
            CPLError(CE_Failure, CPLE_AppDefined,
1160
0
                     "No asset specified in connection string or "
1161
0
                     "ASSET open option");
1162
0
            CSLDestroy(papszTokens);
1163
0
            return false;
1164
0
        }
1165
0
        if (CSLCount(papszTokens) == 3)
1166
0
        {
1167
0
            osBandList = papszTokens[2];
1168
0
        }
1169
1170
0
        m_osAsset = papszTokens[1];
1171
0
        CSLDestroy(papszTokens);
1172
0
    }
1173
0
    m_osAssetName = ConvertPathToName(m_osAsset);
1174
1175
0
    m_osPixelEncoding = CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
1176
0
                                             "PIXEL_ENCODING", "AUTO");
1177
0
    m_nBlockSize =
1178
0
        atoi(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "BLOCK_SIZE",
1179
0
                                  CPLSPrintf("%d", DEFAULT_BLOCK_SIZE)));
1180
0
    if (m_nBlockSize < 128 &&
1181
0
        !CPLTestBool(CPLGetConfigOption("EEDA_FORCE_BLOCK_SIZE", "FALSE")))
1182
0
    {
1183
0
        CPLError(CE_Failure, CPLE_NotSupported, "Invalid BLOCK_SIZE");
1184
0
        return false;
1185
0
    }
1186
1187
0
    std::set<CPLString> oSetUserBandNames;
1188
0
    {
1189
0
        char **papszTokens = CSLTokenizeString2(osBandList, ",", 0);
1190
0
        for (int i = 0; papszTokens && papszTokens[i]; i++)
1191
0
            oSetUserBandNames.insert(papszTokens[i]);
1192
0
        CSLDestroy(papszTokens);
1193
0
    }
1194
1195
    // Issue request to get image metadata
1196
0
    char **papszOptions = GetBaseHTTPOptions();
1197
0
    if (papszOptions == nullptr)
1198
0
        return false;
1199
0
    CPLHTTPResult *psResult =
1200
0
        EEDAHTTPFetch((m_osBaseURL + m_osAssetName).c_str(), papszOptions);
1201
0
    CSLDestroy(papszOptions);
1202
0
    if (psResult == nullptr)
1203
0
        return false;
1204
0
    if (psResult->pszErrBuf != nullptr)
1205
0
    {
1206
0
        if (psResult->pabyData)
1207
0
        {
1208
0
            CPLError(CE_Failure, CPLE_AppDefined, "%s: %s", psResult->pszErrBuf,
1209
0
                     reinterpret_cast<const char *>(psResult->pabyData));
1210
0
        }
1211
0
        else
1212
0
        {
1213
0
            CPLError(CE_Failure, CPLE_AppDefined, "%s", psResult->pszErrBuf);
1214
0
        }
1215
0
        CPLHTTPDestroyResult(psResult);
1216
0
        return false;
1217
0
    }
1218
1219
0
    if (psResult->pabyData == nullptr)
1220
0
    {
1221
0
        CPLError(CE_Failure, CPLE_AppDefined,
1222
0
                 "Empty content returned by server");
1223
0
        CPLHTTPDestroyResult(psResult);
1224
0
        return false;
1225
0
    }
1226
1227
0
    const char *pszText = reinterpret_cast<const char *>(psResult->pabyData);
1228
#ifdef DEBUG_VERBOSE
1229
    CPLDebug("EEDAI", "%s", pszText);
1230
#endif
1231
1232
0
    json_object *poObj = nullptr;
1233
0
    if (!OGRJSonParse(pszText, &poObj, true))
1234
0
    {
1235
0
        CPLHTTPDestroyResult(psResult);
1236
0
        return false;
1237
0
    }
1238
1239
0
    CPLHTTPDestroyResult(psResult);
1240
1241
0
    if (json_object_get_type(poObj) != json_type_object)
1242
0
    {
1243
0
        CPLError(CE_Failure, CPLE_AppDefined,
1244
0
                 "Return is not a JSON dictionary");
1245
0
        json_object_put(poObj);
1246
0
        return false;
1247
0
    }
1248
1249
0
    json_object *poType = CPL_json_object_object_get(poObj, "type");
1250
0
    const char *pszType = json_object_get_string(poType);
1251
0
    if (pszType == nullptr || !EQUAL(pszType, "IMAGE"))
1252
0
    {
1253
0
        CPLError(CE_Failure, CPLE_AppDefined, "Asset is not an image, but %s",
1254
0
                 pszType ? pszType : "(null)");
1255
0
        json_object_put(poObj);
1256
0
        return false;
1257
0
    }
1258
1259
0
    json_object *poBands = CPL_json_object_object_get(poObj, "bands");
1260
0
    if (poBands == nullptr || json_object_get_type(poBands) != json_type_array)
1261
0
    {
1262
0
        CPLError(CE_Failure, CPLE_AppDefined, "No band found");
1263
0
        json_object_put(poObj);
1264
0
        return false;
1265
0
    }
1266
1267
0
    std::map<CPLString, CPLString> oMapCodeToWKT;
1268
0
    std::vector<EEDAIBandDesc> aoBandDesc =
1269
0
        BuildBandDescArray(poBands, oMapCodeToWKT);
1270
0
    std::map<CPLString, int> aoMapBandNames;
1271
1272
0
    if (aoBandDesc.empty())
1273
0
    {
1274
0
        CPLError(CE_Failure, CPLE_AppDefined, "No band found");
1275
0
        json_object_put(poObj);
1276
0
        return false;
1277
0
    }
1278
1279
    // Indices are aoBandDesc indices
1280
0
    std::map<int, std::vector<int>> oMapSimilarBands;
1281
1282
0
    size_t iIdxFirstBand = 0;
1283
0
    for (size_t i = 0; i < aoBandDesc.size(); i++)
1284
0
    {
1285
        // Instantiate bands if they are compatible between them, and
1286
        // if they are requested by the user (when user explicitly requested
1287
        // them)
1288
0
        if ((oSetUserBandNames.empty() ||
1289
0
             oSetUserBandNames.find(aoBandDesc[i].osName) !=
1290
0
                 oSetUserBandNames.end()) &&
1291
0
            (nBands == 0 || aoBandDesc[i].IsSimilar(aoBandDesc[iIdxFirstBand])))
1292
0
        {
1293
0
            if (nBands == 0)
1294
0
            {
1295
0
                iIdxFirstBand = i;
1296
0
                nRasterXSize = aoBandDesc[i].nWidth;
1297
0
                nRasterYSize = aoBandDesc[i].nHeight;
1298
0
                memcpy(m_adfGeoTransform, aoBandDesc[i].adfGeoTransform.data(),
1299
0
                       6 * sizeof(double));
1300
0
                m_oSRS.importFromWkt(aoBandDesc[i].osWKT);
1301
0
                int iOvr = 0;
1302
0
                while ((nRasterXSize >> iOvr) > 256 ||
1303
0
                       (nRasterYSize >> iOvr) > 256)
1304
0
                {
1305
0
                    iOvr++;
1306
0
                    m_apoOverviewDS.push_back(new GDALEEDAIDataset(this, iOvr));
1307
0
                }
1308
0
            }
1309
1310
0
            GDALRasterBand *poBand =
1311
0
                new GDALEEDAIRasterBand(this, aoBandDesc[i].eDT);
1312
0
            const int iBand = nBands + 1;
1313
0
            SetBand(iBand, poBand);
1314
0
            poBand->SetDescription(aoBandDesc[i].osName);
1315
1316
            // as images in USDA/NAIP/DOQQ catalog
1317
0
            if (EQUAL(aoBandDesc[i].osName, "R"))
1318
0
                poBand->SetColorInterpretation(GCI_RedBand);
1319
0
            else if (EQUAL(aoBandDesc[i].osName, "G"))
1320
0
                poBand->SetColorInterpretation(GCI_GreenBand);
1321
0
            else if (EQUAL(aoBandDesc[i].osName, "B"))
1322
0
                poBand->SetColorInterpretation(GCI_BlueBand);
1323
1324
0
            for (size_t iOvr = 0; iOvr < m_apoOverviewDS.size(); iOvr++)
1325
0
            {
1326
0
                GDALRasterBand *poOvrBand = new GDALEEDAIRasterBand(
1327
0
                    m_apoOverviewDS[iOvr], aoBandDesc[i].eDT);
1328
0
                m_apoOverviewDS[iOvr]->SetBand(iBand, poOvrBand);
1329
0
                poOvrBand->SetDescription(aoBandDesc[i].osName);
1330
0
            }
1331
1332
0
            aoMapBandNames[aoBandDesc[i].osName] = iBand;
1333
0
        }
1334
0
        else
1335
0
        {
1336
0
            if (oSetUserBandNames.find(aoBandDesc[i].osName) !=
1337
0
                oSetUserBandNames.end())
1338
0
            {
1339
0
                CPLError(CE_Warning, CPLE_AppDefined,
1340
0
                         "Band %s is not compatible of other bands",
1341
0
                         aoBandDesc[i].osName.c_str());
1342
0
            }
1343
0
            aoMapBandNames[aoBandDesc[i].osName] = -1;
1344
0
        }
1345
1346
        // Group similar bands to be able to build subdataset list
1347
0
        std::map<int, std::vector<int>>::iterator oIter =
1348
0
            oMapSimilarBands.begin();
1349
0
        for (; oIter != oMapSimilarBands.end(); ++oIter)
1350
0
        {
1351
0
            if (aoBandDesc[i].IsSimilar(aoBandDesc[oIter->first]))
1352
0
            {
1353
0
                oIter->second.push_back(static_cast<int>(i));
1354
0
                break;
1355
0
            }
1356
0
        }
1357
0
        if (oIter == oMapSimilarBands.end())
1358
0
        {
1359
0
            oMapSimilarBands[static_cast<int>(i)].push_back(
1360
0
                static_cast<int>(i));
1361
0
        }
1362
0
    }
1363
1364
0
    if (!ComputeQueryStrategy())
1365
0
    {
1366
0
        json_object_put(poObj);
1367
0
        return false;
1368
0
    }
1369
0
    for (size_t i = 0; i < m_apoOverviewDS.size(); i++)
1370
0
    {
1371
0
        m_apoOverviewDS[i]->ComputeQueryStrategy();
1372
0
    }
1373
1374
0
    if (nBands > 1)
1375
0
    {
1376
0
        SetMetadataItem("INTERLEAVE", m_bQueryMultipleBands ? "PIXEL" : "BAND",
1377
0
                        "IMAGE_STRUCTURE");
1378
0
    }
1379
1380
    // Build subdataset list
1381
0
    if (oSetUserBandNames.empty() && oMapSimilarBands.size() > 1)
1382
0
    {
1383
0
        CPLStringList aoSubDSList;
1384
0
        std::map<int, std::vector<int>>::iterator oIter =
1385
0
            oMapSimilarBands.begin();
1386
0
        for (; oIter != oMapSimilarBands.end(); ++oIter)
1387
0
        {
1388
0
            CPLString osSubDSSuffix;
1389
0
            for (size_t i = 0; i < oIter->second.size(); ++i)
1390
0
            {
1391
0
                if (!osSubDSSuffix.empty())
1392
0
                    osSubDSSuffix += ",";
1393
0
                osSubDSSuffix += aoBandDesc[oIter->second[i]].osName;
1394
0
            }
1395
0
            aoSubDSList.AddNameValue(
1396
0
                CPLSPrintf("SUBDATASET_%d_NAME", aoSubDSList.size() / 2 + 1),
1397
0
                CPLSPrintf("EEDAI:%s:%s", m_osAsset.c_str(),
1398
0
                           osSubDSSuffix.c_str()));
1399
0
            aoSubDSList.AddNameValue(
1400
0
                CPLSPrintf("SUBDATASET_%d_DESC", aoSubDSList.size() / 2 + 1),
1401
0
                CPLSPrintf("Band%s %s of %s",
1402
0
                           oIter->second.size() > 1 ? "s" : "",
1403
0
                           osSubDSSuffix.c_str(), m_osAsset.c_str()));
1404
0
        }
1405
0
        SetMetadata(aoSubDSList.List(), "SUBDATASETS");
1406
0
    }
1407
1408
    // Attach metadata to dataset or bands
1409
0
    json_object *poProperties = CPL_json_object_object_get(poObj, "properties");
1410
0
    if (poProperties && json_object_get_type(poProperties) == json_type_object)
1411
0
    {
1412
0
        SetMetadataFromProperties(poProperties, aoMapBandNames);
1413
0
    }
1414
0
    json_object_put(poObj);
1415
1416
0
    SetDescription(poOpenInfo->pszFilename);
1417
1418
0
    return true;
1419
0
}
1420
1421
/************************************************************************/
1422
/*                       SetMetadataFromProperties()                    */
1423
/************************************************************************/
1424
1425
void GDALEEDAIDataset::SetMetadataFromProperties(
1426
    json_object *poProperties, const std::map<CPLString, int> &aoMapBandNames)
1427
0
{
1428
0
    json_object_iter it;
1429
0
    it.key = nullptr;
1430
0
    it.val = nullptr;
1431
0
    it.entry = nullptr;
1432
0
    json_object_object_foreachC(poProperties, it)
1433
0
    {
1434
0
        if (it.val)
1435
0
        {
1436
0
            CPLString osKey(it.key);
1437
0
            int nBandForMD = 0;
1438
0
            std::map<CPLString, int>::const_iterator oIter =
1439
0
                aoMapBandNames.begin();
1440
0
            for (; oIter != aoMapBandNames.end(); ++oIter)
1441
0
            {
1442
0
                CPLString osBandName(oIter->first);
1443
0
                CPLString osNeedle("_" + osBandName);
1444
0
                size_t nPos = osKey.find(osNeedle);
1445
0
                if (nPos != std::string::npos &&
1446
0
                    nPos + osNeedle.size() == osKey.size())
1447
0
                {
1448
0
                    nBandForMD = oIter->second;
1449
0
                    osKey.resize(nPos);
1450
0
                    break;
1451
0
                }
1452
1453
                // Landsat bands are named Bxxx, must their metadata
1454
                // are _BAND_xxxx ...
1455
0
                if (osBandName.size() > 1 && osBandName[0] == 'B' &&
1456
0
                    atoi(osBandName.c_str() + 1) > 0)
1457
0
                {
1458
0
                    osNeedle = "_BAND_" + osBandName.substr(1);
1459
0
                    nPos = osKey.find(osNeedle);
1460
0
                    if (nPos != std::string::npos &&
1461
0
                        nPos + osNeedle.size() == osKey.size())
1462
0
                    {
1463
0
                        nBandForMD = oIter->second;
1464
0
                        osKey.resize(nPos);
1465
0
                        break;
1466
0
                    }
1467
0
                }
1468
0
            }
1469
1470
0
            if (nBandForMD > 0)
1471
0
            {
1472
0
                GetRasterBand(nBandForMD)
1473
0
                    ->SetMetadataItem(osKey, json_object_get_string(it.val));
1474
0
            }
1475
0
            else if (nBandForMD == 0)
1476
0
            {
1477
0
                SetMetadataItem(osKey, json_object_get_string(it.val));
1478
0
            }
1479
0
        }
1480
0
    }
1481
0
}
1482
1483
/************************************************************************/
1484
/*                          GDALEEDAIIdentify()                         */
1485
/************************************************************************/
1486
1487
static int GDALEEDAIIdentify(GDALOpenInfo *poOpenInfo)
1488
15.6k
{
1489
15.6k
    return STARTS_WITH_CI(poOpenInfo->pszFilename, "EEDAI:");
1490
15.6k
}
1491
1492
/************************************************************************/
1493
/*                            GDALEEDAIOpen()                           */
1494
/************************************************************************/
1495
1496
static GDALDataset *GDALEEDAIOpen(GDALOpenInfo *poOpenInfo)
1497
0
{
1498
0
    if (!GDALEEDAIIdentify(poOpenInfo))
1499
0
        return nullptr;
1500
1501
0
    GDALEEDAIDataset *poDS = new GDALEEDAIDataset();
1502
0
    if (!poDS->Open(poOpenInfo))
1503
0
    {
1504
0
        delete poDS;
1505
0
        return nullptr;
1506
0
    }
1507
0
    return poDS;
1508
0
}
1509
1510
/************************************************************************/
1511
/*                         GDALRegister_EEDAI()                         */
1512
/************************************************************************/
1513
1514
void GDALRegister_EEDAI()
1515
1516
2
{
1517
2
    if (GDALGetDriverByName("EEDAI") != nullptr)
1518
0
        return;
1519
1520
2
    GDALDriver *poDriver = new GDALDriver();
1521
1522
2
    poDriver->SetDescription("EEDAI");
1523
2
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1524
2
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Earth Engine Data API Image");
1525
2
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/eedai.html");
1526
2
    poDriver->SetMetadataItem(GDAL_DMD_CONNECTION_PREFIX, "EEDAI:");
1527
2
    poDriver->SetMetadataItem(
1528
2
        GDAL_DMD_OPENOPTIONLIST,
1529
2
        "<OpenOptionList>"
1530
2
        "  <Option name='ASSET' type='string' description='Asset name'/>"
1531
2
        "  <Option name='BANDS' type='string' "
1532
2
        "description='Comma separated list of band names'/>"
1533
2
        "  <Option name='PIXEL_ENCODING' type='string-select' "
1534
2
        "description='Format in which pixls are queried'>"
1535
2
        "       <Value>AUTO</Value>"
1536
2
        "       <Value>PNG</Value>"
1537
2
        "       <Value>JPEG</Value>"
1538
2
        "       <Value>GEO_TIFF</Value>"
1539
2
        "       <Value>AUTO_JPEG_PNG</Value>"
1540
2
        "       <Value>NPY</Value>"
1541
2
        "   </Option>"
1542
2
        "  <Option name='BLOCK_SIZE' type='integer' "
1543
2
        "description='Size of a block' default='256'/>"
1544
2
        "  <Option name='VSI_PATH_FOR_AUTH' type='string' "
1545
2
        "description='/vsigs/... path onto which a "
1546
2
        "GOOGLE_APPLICATION_CREDENTIALS path specific "
1547
2
        "option is set'/>"
1548
2
        "</OpenOptionList>");
1549
2
    poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
1550
1551
2
    poDriver->pfnOpen = GDALEEDAIOpen;
1552
2
    poDriver->pfnIdentify = GDALEEDAIIdentify;
1553
1554
2
    GetGDALDriverManager()->RegisterDriver(poDriver);
1555
2
}